jj
This commit is contained in:
2
CLI.py
2
CLI.py
@@ -1207,6 +1207,8 @@ class CmdletExecutor:
|
|||||||
"get_url",
|
"get_url",
|
||||||
"search-file",
|
"search-file",
|
||||||
"search_file",
|
"search_file",
|
||||||
|
"add-file",
|
||||||
|
"add_file",
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd_name in self_managing_commands:
|
if cmd_name in self_managing_commands:
|
||||||
|
|||||||
@@ -249,12 +249,6 @@ class Matrix(TableProviderMixin, Provider):
|
|||||||
"default": "",
|
"default": "",
|
||||||
"secret": True
|
"secret": True
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "password",
|
|
||||||
"label": "Password (fallback)",
|
|
||||||
"default": "",
|
|
||||||
"secret": True
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
@@ -270,12 +264,10 @@ class Matrix(TableProviderMixin, Provider):
|
|||||||
)
|
)
|
||||||
homeserver = matrix_conf.get("homeserver")
|
homeserver = matrix_conf.get("homeserver")
|
||||||
access_token = matrix_conf.get("access_token")
|
access_token = matrix_conf.get("access_token")
|
||||||
password = matrix_conf.get("password")
|
|
||||||
|
|
||||||
# Not configured: keep instance but mark invalid via validate().
|
# Not configured: keep instance but mark invalid via validate().
|
||||||
# Note: `room_id` is intentionally NOT required, since the CLI can prompt
|
# Note: `room_id` is intentionally NOT required, since the CLI can prompt
|
||||||
# the user to select a room dynamically.
|
# the user to select a room dynamically.
|
||||||
if not (homeserver and (access_token or password)):
|
if not (homeserver and access_token):
|
||||||
self._init_ok = None
|
self._init_ok = None
|
||||||
self._init_reason = None
|
self._init_reason = None
|
||||||
return
|
return
|
||||||
@@ -305,7 +297,7 @@ class Matrix(TableProviderMixin, Provider):
|
|||||||
{})
|
{})
|
||||||
return bool(
|
return bool(
|
||||||
matrix_conf.get("homeserver")
|
matrix_conf.get("homeserver")
|
||||||
and (matrix_conf.get("access_token") or matrix_conf.get("password"))
|
and matrix_conf.get("access_token")
|
||||||
)
|
)
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
from textual.app import ComposeResult
|
import re
|
||||||
from textual.screen import ModalScreen
|
from typing import Any, Dict, List, Optional
|
||||||
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
|
||||||
from textual.widgets import Static, Button, Input, Label, ListView, ListItem, Rule, Select
|
|
||||||
from textual import on, work
|
from textual import on, work
|
||||||
from typing import Any
|
from textual.app import ComposeResult
|
||||||
|
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
||||||
|
from textual.screen import ModalScreen
|
||||||
|
from textual.widgets import Static, Button, Input, Label, ListView, ListItem, Rule, Select
|
||||||
|
|
||||||
from SYS.config import load_config, save_config, reload_config, global_config
|
from SYS.config import load_config, save_config, reload_config, global_config
|
||||||
from SYS.logger import log
|
from SYS.logger import log
|
||||||
from Store.registry import _discover_store_classes, _required_keys_for
|
from Store.registry import _discover_store_classes, _required_keys_for
|
||||||
from ProviderCore.registry import list_providers
|
from ProviderCore.registry import list_providers
|
||||||
|
from TUI.modalscreen.matrix_room_picker import MatrixRoomPicker
|
||||||
from TUI.modalscreen.selection_modal import SelectionModal
|
from TUI.modalscreen.selection_modal import SelectionModal
|
||||||
|
|
||||||
class ConfigModal(ModalScreen):
|
class ConfigModal(ModalScreen):
|
||||||
@@ -117,6 +120,8 @@ class ConfigModal(ModalScreen):
|
|||||||
self.editing_item_name = None
|
self.editing_item_name = None
|
||||||
self._button_id_map = {}
|
self._button_id_map = {}
|
||||||
self._input_id_map = {}
|
self._input_id_map = {}
|
||||||
|
self._matrix_status: Optional[Static] = None
|
||||||
|
self._matrix_test_running = False
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Container(id="config-container"):
|
with Container(id="config-container"):
|
||||||
@@ -491,13 +496,28 @@ class ConfigModal(ModalScreen):
|
|||||||
inp_id = f"item-{idx}"
|
inp_id = f"item-{idx}"
|
||||||
self._input_id_map[inp_id] = rk
|
self._input_id_map[inp_id] = rk
|
||||||
row = Horizontal(classes="field-row")
|
row = Horizontal(classes="field-row")
|
||||||
|
container.mount(row)
|
||||||
row.mount(Input(value="", id=inp_id, classes="config-input"))
|
row.mount(Input(value="", id=inp_id, classes="config-input"))
|
||||||
row.mount(Button("Paste", id=f"paste-{inp_id}", classes="paste-btn"))
|
row.mount(Button("Paste", id=f"paste-{inp_id}", classes="paste-btn"))
|
||||||
container.mount(row)
|
|
||||||
idx += 1
|
idx += 1
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if (
|
||||||
|
item_type == "provider"
|
||||||
|
and isinstance(item_name, str)
|
||||||
|
and item_name.strip().lower() == "matrix"
|
||||||
|
):
|
||||||
|
container.mount(Rule())
|
||||||
|
container.mount(Label("Matrix helpers", classes="config-label"))
|
||||||
|
status = Static("Set homeserver + token, then test before saving", id="matrix-status")
|
||||||
|
container.mount(status)
|
||||||
|
row = Horizontal(classes="field-row")
|
||||||
|
container.mount(row)
|
||||||
|
row.mount(Button("Test connection", id="matrix-test-btn"))
|
||||||
|
row.mount(Button("Choose default rooms", variant="primary", id="matrix-rooms-btn"))
|
||||||
|
self._matrix_status = status
|
||||||
|
|
||||||
def create_field(self, name: str, value: Any, id: str) -> Vertical:
|
def create_field(self, name: str, value: Any, id: str) -> Vertical:
|
||||||
# This method is now unused - we mount labels and inputs directly
|
# This method is now unused - we mount labels and inputs directly
|
||||||
v = Vertical(classes="config-field")
|
v = Vertical(classes="config-field")
|
||||||
@@ -594,6 +614,10 @@ class ConfigModal(ModalScreen):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.app.push_screen(SelectionModal("Select Provider Type", options), callback=self.on_provider_type_selected)
|
self.app.push_screen(SelectionModal("Select Provider Type", options), callback=self.on_provider_type_selected)
|
||||||
|
elif bid == "matrix-test-btn":
|
||||||
|
self._request_matrix_test()
|
||||||
|
elif bid == "matrix-rooms-btn":
|
||||||
|
self._open_matrix_room_picker()
|
||||||
elif bid.startswith("paste-"):
|
elif bid.startswith("paste-"):
|
||||||
# Programmatic paste button
|
# Programmatic paste button
|
||||||
target_id = bid.replace("paste-", "")
|
target_id = bid.replace("paste-", "")
|
||||||
@@ -777,6 +801,94 @@ class ConfigModal(ModalScreen):
|
|||||||
|
|
||||||
self._update_config_value(widget_id, value)
|
self._update_config_value(widget_id, value)
|
||||||
|
|
||||||
|
def _get_matrix_provider_block(self) -> Dict[str, Any]:
|
||||||
|
providers = self.config_data.get("provider")
|
||||||
|
if not isinstance(providers, dict):
|
||||||
|
return {}
|
||||||
|
block = providers.get("matrix")
|
||||||
|
return block if isinstance(block, dict) else {}
|
||||||
|
|
||||||
|
def _parse_matrix_rooms_value(self) -> List[str]:
|
||||||
|
block = self._get_matrix_provider_block()
|
||||||
|
raw = block.get("rooms")
|
||||||
|
if isinstance(raw, (list, tuple, set)):
|
||||||
|
return [str(item).strip() for item in raw if str(item).strip()]
|
||||||
|
text = str(raw or "").strip()
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
return [segment for segment in re.split(r"[\s,]+", text) if segment]
|
||||||
|
|
||||||
|
def _request_matrix_test(self) -> None:
|
||||||
|
if self._matrix_test_running:
|
||||||
|
return
|
||||||
|
self._synchronize_inputs_to_config()
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update("Testing Matrix connection…")
|
||||||
|
self._matrix_test_running = True
|
||||||
|
self._matrix_test_background()
|
||||||
|
|
||||||
|
@work(thread=True)
|
||||||
|
def _matrix_test_background(self) -> None:
|
||||||
|
try:
|
||||||
|
from Provider.matrix import Matrix
|
||||||
|
|
||||||
|
provider = Matrix(self.config_data)
|
||||||
|
rooms = provider.list_rooms()
|
||||||
|
self.app.call_from_thread(self._matrix_test_result, True, rooms, None)
|
||||||
|
except Exception as exc:
|
||||||
|
self.app.call_from_thread(self._matrix_test_result, False, [], str(exc))
|
||||||
|
|
||||||
|
def _matrix_test_result(
|
||||||
|
self,
|
||||||
|
success: bool,
|
||||||
|
rooms: List[Dict[str, Any]],
|
||||||
|
error: Optional[str],
|
||||||
|
) -> None:
|
||||||
|
self._matrix_test_running = False
|
||||||
|
if success:
|
||||||
|
msg = f"Matrix test succeeded ({len(rooms)} room(s))."
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update(msg)
|
||||||
|
self._open_matrix_room_picker(prefetched_rooms=rooms)
|
||||||
|
else:
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update(f"Matrix test failed: {error}")
|
||||||
|
|
||||||
|
def _open_matrix_room_picker(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
prefetched_rooms: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
) -> None:
|
||||||
|
existing = self._parse_matrix_rooms_value()
|
||||||
|
self.app.push_screen(
|
||||||
|
MatrixRoomPicker(
|
||||||
|
self.config_data,
|
||||||
|
existing=existing,
|
||||||
|
rooms=prefetched_rooms,
|
||||||
|
),
|
||||||
|
callback=self.on_matrix_rooms_selected,
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_matrix_rooms_selected(self, result: Any = None) -> None:
|
||||||
|
if not isinstance(result, list):
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update("Room selection cancelled.")
|
||||||
|
return
|
||||||
|
cleaned: List[str] = []
|
||||||
|
for item in result:
|
||||||
|
candidate = str(item or "").strip()
|
||||||
|
if candidate and candidate not in cleaned:
|
||||||
|
cleaned.append(candidate)
|
||||||
|
if not cleaned:
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update("No default rooms were saved.")
|
||||||
|
return
|
||||||
|
matrix_block = self.config_data.setdefault("provider", {}).setdefault("matrix", {})
|
||||||
|
matrix_block["rooms"] = ", ".join(cleaned)
|
||||||
|
if self._matrix_status:
|
||||||
|
self._matrix_status.update(f"Saved {len(cleaned)} default room(s).")
|
||||||
|
self.refresh_view()
|
||||||
|
|
||||||
@on(Input.Changed)
|
@on(Input.Changed)
|
||||||
def on_input_changed(self, event: Input.Changed) -> None:
|
def on_input_changed(self, event: Input.Changed) -> None:
|
||||||
if event.input.id:
|
if event.input.id:
|
||||||
|
|||||||
197
TUI/modalscreen/matrix_room_picker.py
Normal file
197
TUI/modalscreen/matrix_room_picker.py
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from textual.app import ComposeResult
|
||||||
|
from textual.containers import Container, Horizontal, ScrollableContainer, Vertical
|
||||||
|
from textual.screen import ModalScreen
|
||||||
|
from textual.widgets import Static, Button, Checkbox
|
||||||
|
from textual import work
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixRoomPicker(ModalScreen[List[str]]):
|
||||||
|
"""Modal that lists Matrix rooms and returns selected defaults."""
|
||||||
|
|
||||||
|
CSS = """
|
||||||
|
MatrixRoomPicker {
|
||||||
|
align: center middle;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-picker {
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
background: $panel;
|
||||||
|
border: thick $primary;
|
||||||
|
padding: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-picker-hint {
|
||||||
|
text-style: italic;
|
||||||
|
color: $text-muted;
|
||||||
|
margin-bottom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-scroll {
|
||||||
|
height: 1fr;
|
||||||
|
border: solid $surface;
|
||||||
|
padding: 1;
|
||||||
|
margin-bottom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-checklist {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-room-row {
|
||||||
|
border-bottom: solid $surface;
|
||||||
|
padding: 1 0;
|
||||||
|
align: left middle;
|
||||||
|
background: $surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-room-checkbox {
|
||||||
|
width: 3;
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-room-meta {
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-room-name {
|
||||||
|
content-align: left middle;
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-room-id {
|
||||||
|
content-align: left middle;
|
||||||
|
text-style: dim;
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-actions {
|
||||||
|
height: 3;
|
||||||
|
align: right middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-room-status {
|
||||||
|
height: 3;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: Dict[str, Any],
|
||||||
|
*,
|
||||||
|
existing: Optional[List[str]] = None,
|
||||||
|
rooms: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.config = config
|
||||||
|
self._prefetched_rooms = rooms
|
||||||
|
self._existing_ids = {str(r).strip() for r in (existing or []) if str(r).strip()}
|
||||||
|
self._checkbox_map: Dict[str, str] = {}
|
||||||
|
self._rooms: List[Dict[str, Any]] = []
|
||||||
|
self._status_widget: Optional[Static] = None
|
||||||
|
self._checklist: Optional[Vertical] = None
|
||||||
|
self._save_button: Optional[Button] = None
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Container(id="matrix-room-picker"):
|
||||||
|
yield Static("Matrix Default Rooms", classes="section-title")
|
||||||
|
yield Static(
|
||||||
|
"Choose rooms to keep in the sharing defaults and autocomplete.",
|
||||||
|
id="matrix-room-picker-hint",
|
||||||
|
)
|
||||||
|
with ScrollableContainer(id="matrix-room-scroll"):
|
||||||
|
yield Vertical(id="matrix-room-checklist")
|
||||||
|
with Horizontal(id="matrix-room-actions"):
|
||||||
|
yield Button("Cancel", variant="error", id="matrix-room-cancel")
|
||||||
|
yield Button("Save defaults", variant="success", id="matrix-room-save")
|
||||||
|
yield Static("Loading rooms...", id="matrix-room-status")
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
self._status_widget = self.query_one("#matrix-room-status", Static)
|
||||||
|
self._checklist = self.query_one("#matrix-room-checklist", Vertical)
|
||||||
|
self._save_button = self.query_one("#matrix-room-save", Button)
|
||||||
|
if self._save_button:
|
||||||
|
self._save_button.disabled = True
|
||||||
|
if self._prefetched_rooms is not None:
|
||||||
|
self._apply_room_results(self._prefetched_rooms, None)
|
||||||
|
else:
|
||||||
|
if self._status_widget:
|
||||||
|
self._set_status("Loading rooms...")
|
||||||
|
self._load_rooms_background()
|
||||||
|
|
||||||
|
def _set_status(self, text: str) -> None:
|
||||||
|
if self._status_widget:
|
||||||
|
self._status_widget.update(text)
|
||||||
|
|
||||||
|
def _render_rooms(self, rooms: List[Dict[str, Any]]) -> None:
|
||||||
|
if not self._checklist:
|
||||||
|
return
|
||||||
|
for child in list(self._checklist.children):
|
||||||
|
child.remove()
|
||||||
|
self._checkbox_map.clear()
|
||||||
|
self._rooms = rooms
|
||||||
|
if not rooms:
|
||||||
|
self._set_status("Matrix returned no rooms.")
|
||||||
|
if self._save_button:
|
||||||
|
self._save_button.disabled = True
|
||||||
|
return
|
||||||
|
for idx, room in enumerate(rooms):
|
||||||
|
room_id = str(room.get("room_id") or "").strip()
|
||||||
|
name = str(room.get("name") or "").strip()
|
||||||
|
checkbox_id = f"matrix-room-{idx}"
|
||||||
|
checkbox = Checkbox(
|
||||||
|
"",
|
||||||
|
id=checkbox_id,
|
||||||
|
classes="matrix-room-checkbox",
|
||||||
|
value=bool(room_id and room_id in self._existing_ids),
|
||||||
|
)
|
||||||
|
self._checkbox_map[checkbox_id] = room_id
|
||||||
|
info = Vertical(classes="matrix-room-meta")
|
||||||
|
info.mount(Static(name or room_id or "Matrix Room", classes="matrix-room-name"))
|
||||||
|
info.mount(Static(room_id or "(no id)", classes="matrix-room-id"))
|
||||||
|
row = Horizontal(classes="matrix-room-row")
|
||||||
|
self._checklist.mount(row)
|
||||||
|
row.mount(checkbox)
|
||||||
|
row.mount(info)
|
||||||
|
self._set_status("Loaded rooms. Select one or more and save.")
|
||||||
|
if self._save_button:
|
||||||
|
self._save_button.disabled = False
|
||||||
|
|
||||||
|
@work(thread=True)
|
||||||
|
def _load_rooms_background(self) -> None:
|
||||||
|
try:
|
||||||
|
from Provider.matrix import Matrix
|
||||||
|
provider = Matrix(self.config)
|
||||||
|
rooms = provider.list_rooms()
|
||||||
|
self.app.call_from_thread(self._apply_room_results, rooms, None)
|
||||||
|
except Exception as exc:
|
||||||
|
self.app.call_from_thread(self._apply_room_results, [], str(exc))
|
||||||
|
|
||||||
|
def _apply_room_results(self, rooms: List[Dict[str, Any]], error: Optional[str]) -> None:
|
||||||
|
if error:
|
||||||
|
self._set_status(f"Failed to load Matrix rooms: {error}")
|
||||||
|
if self._save_button:
|
||||||
|
self._save_button.disabled = True
|
||||||
|
return
|
||||||
|
self._render_rooms(rooms)
|
||||||
|
|
||||||
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
if event.button.id == "matrix-room-cancel":
|
||||||
|
self.dismiss([])
|
||||||
|
elif event.button.id == "matrix-room-save":
|
||||||
|
selected: List[str] = []
|
||||||
|
for checkbox_id, room_id in self._checkbox_map.items():
|
||||||
|
try:
|
||||||
|
cb = self.query_one(f"#{checkbox_id}", Checkbox)
|
||||||
|
if cb.value and room_id:
|
||||||
|
selected.append(room_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.dismiss(selected)
|
||||||
240
cmdnat/matrix.py
240
cmdnat/matrix.py
@@ -9,6 +9,7 @@ import uuid
|
|||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
from cmdlet._shared import Cmdlet, CmdletArg
|
from cmdlet._shared import Cmdlet, CmdletArg
|
||||||
|
from SYS.config import load_config, save_config
|
||||||
from SYS.logger import log, debug
|
from SYS.logger import log, debug
|
||||||
from SYS.result_table import Table
|
from SYS.result_table import Table
|
||||||
from SYS import pipeline as ctx
|
from SYS import pipeline as ctx
|
||||||
@@ -78,55 +79,43 @@ def _extract_set_value_arg(args: Sequence[str]) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _update_matrix_config(config: Dict[str, Any], key: str, value: Any) -> bool:
|
def _update_matrix_config(config: Dict[str, Any], key: str, value: Any) -> bool:
|
||||||
"""Update a Matrix config value and write to config file.
|
"""Update the Matrix provider block in the shared config.
|
||||||
|
|
||||||
Returns True if successful, False otherwise.
|
This method writes to the unified config store so changes persist between
|
||||||
|
sessions.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from SYS.config import get_config_path
|
|
||||||
from configparser import ConfigParser
|
|
||||||
|
|
||||||
if not isinstance(config, dict):
|
if not isinstance(config, dict):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ensure provider.matrix section exists
|
value_str = str(value)
|
||||||
providers = config.get("provider", {})
|
|
||||||
|
current_cfg = load_config() or {}
|
||||||
|
providers = current_cfg.setdefault("provider", {})
|
||||||
if not isinstance(providers, dict):
|
if not isinstance(providers, dict):
|
||||||
providers = {}
|
providers = {}
|
||||||
config["provider"] = providers
|
current_cfg["provider"] = providers
|
||||||
|
|
||||||
matrix_conf = providers.get("matrix", {})
|
matrix_cfg = providers.setdefault("matrix", {})
|
||||||
if not isinstance(matrix_conf, dict):
|
if not isinstance(matrix_cfg, dict):
|
||||||
matrix_conf = {}
|
matrix_cfg = {}
|
||||||
providers["matrix"] = matrix_conf
|
providers["matrix"] = matrix_cfg
|
||||||
|
|
||||||
# Update the in-memory config
|
matrix_cfg[key] = value_str
|
||||||
matrix_conf[key] = value
|
|
||||||
|
save_config(current_cfg)
|
||||||
# Try to write to config file using configparser
|
|
||||||
try:
|
# Keep the supplied config dict in sync for the running CLI
|
||||||
config_path = get_config_path()
|
target_providers = config.setdefault("provider", {})
|
||||||
if not config_path:
|
if not isinstance(target_providers, dict):
|
||||||
return False
|
target_providers = {}
|
||||||
|
config["provider"] = target_providers
|
||||||
parser = ConfigParser()
|
target_matrix = target_providers.setdefault("matrix", {})
|
||||||
if Path(config_path).exists():
|
if not isinstance(target_matrix, dict):
|
||||||
parser.read(config_path)
|
target_matrix = {}
|
||||||
|
target_providers["matrix"] = target_matrix
|
||||||
section_name = "provider=matrix"
|
target_matrix[key] = value_str
|
||||||
if not parser.has_section(section_name):
|
return True
|
||||||
parser.add_section(section_name)
|
|
||||||
|
|
||||||
parser.set(section_name, key, str(value))
|
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
|
||||||
parser.write(f)
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as exc:
|
|
||||||
debug(f"[matrix] Failed to write config file: {exc}")
|
|
||||||
# Config was updated in memory at least
|
|
||||||
return True
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug(f"[matrix] Failed to update Matrix config: {exc}")
|
debug(f"[matrix] Failed to update Matrix config: {exc}")
|
||||||
return False
|
return False
|
||||||
@@ -628,14 +617,14 @@ def _show_settings_table(config: Dict[str, Any]) -> int:
|
|||||||
|
|
||||||
settings_items = []
|
settings_items = []
|
||||||
if isinstance(matrix_conf, dict):
|
if isinstance(matrix_conf, dict):
|
||||||
|
sensitive_keys = {"access_token", "password"}
|
||||||
for key in sorted(matrix_conf.keys()):
|
for key in sorted(matrix_conf.keys()):
|
||||||
value = matrix_conf[key]
|
value = matrix_conf[key]
|
||||||
# Skip sensitive/complex values
|
display_value = "***" if key in sensitive_keys else str(value)
|
||||||
if key in ("password",):
|
|
||||||
value = "***"
|
|
||||||
settings_items.append({
|
settings_items.append({
|
||||||
"key": key,
|
"key": key,
|
||||||
"value": str(value),
|
"label": key,
|
||||||
|
"value": display_value,
|
||||||
"original_value": value,
|
"original_value": value,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -643,10 +632,19 @@ def _show_settings_table(config: Dict[str, Any]) -> int:
|
|||||||
log("No Matrix settings configured. Edit config.conf manually.", file=sys.stderr)
|
log("No Matrix settings configured. Edit config.conf manually.", file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
settings_items.append({
|
||||||
|
"action": "test",
|
||||||
|
"label": "Test connection",
|
||||||
|
"value": "Verify the homeserver and token before picking rooms",
|
||||||
|
"description": "Runs a health check and then prompts for default rooms",
|
||||||
|
})
|
||||||
|
|
||||||
for item in settings_items:
|
for item in settings_items:
|
||||||
row = table.add_row()
|
row = table.add_row()
|
||||||
row.add_column("Key", item["key"])
|
label = item.get("label") or item.get("key") or "Setting"
|
||||||
row.add_column("Value", item["value"])
|
value_text = item.get("value") or item.get("description") or ""
|
||||||
|
row.add_column("Key", label)
|
||||||
|
row.add_column("Value", value_text)
|
||||||
|
|
||||||
ctx.set_last_result_table_overlay(table, settings_items)
|
ctx.set_last_result_table_overlay(table, settings_items)
|
||||||
ctx.set_current_stage_table(table)
|
ctx.set_current_stage_table(table)
|
||||||
@@ -712,6 +710,15 @@ def _handle_settings_edit(result: Any, args: Sequence[str], config: Dict[str, An
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
selected_item = last_items[idx]
|
selected_item = last_items[idx]
|
||||||
|
selected_action = None
|
||||||
|
if isinstance(selected_item, dict):
|
||||||
|
selected_action = selected_item.get("action")
|
||||||
|
else:
|
||||||
|
selected_action = getattr(selected_item, "action", None)
|
||||||
|
|
||||||
|
if selected_action == "test":
|
||||||
|
return _handle_settings_test(config)
|
||||||
|
|
||||||
key = None
|
key = None
|
||||||
if isinstance(selected_item, dict):
|
if isinstance(selected_item, dict):
|
||||||
key = selected_item.get("key")
|
key = selected_item.get("key")
|
||||||
@@ -742,6 +749,132 @@ def _handle_settings_edit(result: Any, args: Sequence[str], config: Dict[str, An
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_settings_test(config: Dict[str, Any]) -> int:
|
||||||
|
"""Test Matrix credentials and prompt for default rooms upon success."""
|
||||||
|
from Provider.matrix import Matrix
|
||||||
|
|
||||||
|
try:
|
||||||
|
provider = Matrix(config)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"Matrix test failed: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
log("Matrix configuration validated. Select default rooms to share.")
|
||||||
|
return _show_default_room_picker(config, provider=provider)
|
||||||
|
|
||||||
|
|
||||||
|
def _show_default_room_picker(config: Dict[str, Any], *, provider: Optional["Matrix"] = None) -> int:
|
||||||
|
"""Display joined rooms so the user can select defaults for sharing."""
|
||||||
|
from Provider.matrix import Matrix
|
||||||
|
|
||||||
|
try:
|
||||||
|
if provider is None:
|
||||||
|
provider = Matrix(config)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"Matrix not available: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
rooms = provider.list_rooms()
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"Failed to list Matrix rooms: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not rooms:
|
||||||
|
log("No joined rooms found.", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
default_ids = {
|
||||||
|
str(v).strip()
|
||||||
|
for v in _parse_config_room_filter_ids(config)
|
||||||
|
if str(v).strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
table = Table("Matrix Rooms (select defaults with @N)")
|
||||||
|
table.set_table("matrix")
|
||||||
|
table.set_source_command(".matrix", ["-settings"])
|
||||||
|
|
||||||
|
room_items: List[Dict[str, Any]] = []
|
||||||
|
for room in rooms:
|
||||||
|
if isinstance(room, dict):
|
||||||
|
room_id = str(room.get("room_id") or "").strip()
|
||||||
|
name = str(room.get("name") or "").strip()
|
||||||
|
else:
|
||||||
|
room_id = ""
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
row = table.add_row()
|
||||||
|
row.add_column("Name", name)
|
||||||
|
row.add_column("Room", room_id)
|
||||||
|
row.add_column("Default", "✓" if room_id and room_id in default_ids else "")
|
||||||
|
|
||||||
|
room_items.append({
|
||||||
|
**(room if isinstance(room, dict) else {}),
|
||||||
|
"room_id": room_id,
|
||||||
|
"name": name,
|
||||||
|
"store": "matrix",
|
||||||
|
"title": name or room_id or "Matrix Room",
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.set_last_result_table_overlay(table, room_items)
|
||||||
|
ctx.set_current_stage_table(table)
|
||||||
|
ctx.set_pending_pipeline_tail([[".matrix", "-settings-rooms"]], ".matrix")
|
||||||
|
log("Select default rooms to share (used by @N and autocomplete).")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_settings_rooms(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||||
|
"""Store the selected rooms list as the default sharing target."""
|
||||||
|
selection_indices = []
|
||||||
|
try:
|
||||||
|
selection_indices = ctx.get_last_selection() or []
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
last_items = []
|
||||||
|
try:
|
||||||
|
last_items = ctx.get_last_result_items() or []
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not selection_indices or not last_items:
|
||||||
|
log("No Matrix room selected. Use @N on the rooms table.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
room_ids: List[str] = []
|
||||||
|
for idx in selection_indices:
|
||||||
|
if not isinstance(idx, int):
|
||||||
|
continue
|
||||||
|
if idx < 0 or idx >= len(last_items):
|
||||||
|
continue
|
||||||
|
item = last_items[idx]
|
||||||
|
candidate = None
|
||||||
|
if isinstance(item, dict):
|
||||||
|
candidate = item.get("room_id") or item.get("id")
|
||||||
|
else:
|
||||||
|
candidate = getattr(item, "room_id", None) or getattr(item, "id", None)
|
||||||
|
if candidate:
|
||||||
|
room_ids.append(str(candidate).strip())
|
||||||
|
|
||||||
|
cleaned: List[str] = []
|
||||||
|
for rid in room_ids:
|
||||||
|
clean = str(rid or "").strip()
|
||||||
|
if clean and clean not in cleaned:
|
||||||
|
cleaned.append(clean)
|
||||||
|
|
||||||
|
if not cleaned:
|
||||||
|
log("No valid Matrix room selected.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
value = ", ".join(cleaned)
|
||||||
|
if not _update_matrix_config(config, "rooms", value):
|
||||||
|
log("✗ Failed to save default rooms", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
log(f"✓ Default rooms saved: {value}")
|
||||||
|
return _show_settings_table(config)
|
||||||
|
|
||||||
|
|
||||||
def _show_rooms_table(config: Dict[str, Any]) -> int:
|
def _show_rooms_table(config: Dict[str, Any]) -> int:
|
||||||
"""Display rooms (refactored original behavior)."""
|
"""Display rooms (refactored original behavior)."""
|
||||||
from Provider.matrix import Matrix
|
from Provider.matrix import Matrix
|
||||||
@@ -855,6 +988,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
if _has_flag(args, "-settings-edit"):
|
if _has_flag(args, "-settings-edit"):
|
||||||
return _handle_settings_edit(result, args, config)
|
return _handle_settings_edit(result, args, config)
|
||||||
|
|
||||||
|
if _has_flag(args, "-settings-rooms"):
|
||||||
|
return _handle_settings_rooms(result, args, config)
|
||||||
|
|
||||||
# Internal stage: send previously selected items to selected rooms.
|
# Internal stage: send previously selected items to selected rooms.
|
||||||
if _has_flag(args, "-send"):
|
if _has_flag(args, "-send"):
|
||||||
# Ensure we don't re-print the rooms picker table on the send stage.
|
# Ensure we don't re-print the rooms picker table on the send stage.
|
||||||
@@ -1031,6 +1167,12 @@ CMDLET = Cmdlet(
|
|||||||
description="(internal) Handle settings modification",
|
description="(internal) Handle settings modification",
|
||||||
required=False,
|
required=False,
|
||||||
),
|
),
|
||||||
|
CmdletArg(
|
||||||
|
name="settings-rooms",
|
||||||
|
type="bool",
|
||||||
|
description="(internal) Save selected default rooms",
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
CmdletArg(
|
CmdletArg(
|
||||||
name="set-value",
|
name="set-value",
|
||||||
type="string",
|
type="string",
|
||||||
|
|||||||
Reference in New Issue
Block a user