kh
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
This commit is contained in:
170
cmdnat/matrix.py
170
cmdnat/matrix.py
@@ -18,6 +18,108 @@ _MATRIX_PENDING_ITEMS_KEY = "matrix_pending_items"
|
||||
_MATRIX_PENDING_TEXT_KEY = "matrix_pending_text"
|
||||
|
||||
|
||||
def _has_flag(args: Sequence[str], flag: str) -> bool:
|
||||
try:
|
||||
want = str(flag or "").strip().lower()
|
||||
if not want:
|
||||
return False
|
||||
return any(str(a).strip().lower() == want for a in (args or []))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _parse_config_room_filter_ids(config: Dict[str, Any]) -> List[str]:
|
||||
try:
|
||||
if not isinstance(config, dict):
|
||||
return []
|
||||
providers = config.get("provider")
|
||||
if not isinstance(providers, dict):
|
||||
return []
|
||||
matrix_conf = providers.get("matrix")
|
||||
if not isinstance(matrix_conf, dict):
|
||||
return []
|
||||
raw = None
|
||||
# Support a few common spellings; `room` is the documented key.
|
||||
for key in ("room", "room_id", "rooms", "room_ids"):
|
||||
if key in matrix_conf:
|
||||
raw = matrix_conf.get(key)
|
||||
break
|
||||
if raw is None:
|
||||
return []
|
||||
|
||||
# Allow either a string or a list-like value.
|
||||
if isinstance(raw, (list, tuple, set)):
|
||||
items = [str(v).strip() for v in raw if str(v).strip()]
|
||||
return items
|
||||
|
||||
text = str(raw or "").strip()
|
||||
if not text:
|
||||
return []
|
||||
# Comma-separated list of room IDs, but be tolerant of whitespace/newlines.
|
||||
items = [p.strip() for p in re.split(r"[,\s]+", text) if p and p.strip()]
|
||||
return items
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _get_matrix_size_limit_bytes(config: Dict[str, Any]) -> Optional[int]:
|
||||
"""Return max allowed per-file size in bytes for Matrix uploads.
|
||||
|
||||
Config: [provider=Matrix] size_limit=50 # MB
|
||||
"""
|
||||
try:
|
||||
if not isinstance(config, dict):
|
||||
return None
|
||||
providers = config.get("provider")
|
||||
if not isinstance(providers, dict):
|
||||
return None
|
||||
matrix_conf = providers.get("matrix")
|
||||
if not isinstance(matrix_conf, dict):
|
||||
return None
|
||||
|
||||
raw = None
|
||||
for key in ("size_limit", "size_limit_mb", "max_mb"):
|
||||
if key in matrix_conf:
|
||||
raw = matrix_conf.get(key)
|
||||
break
|
||||
if raw is None:
|
||||
return None
|
||||
|
||||
mb: Optional[float] = None
|
||||
if isinstance(raw, (int, float)):
|
||||
mb = float(raw)
|
||||
else:
|
||||
text = str(raw or "").strip().lower()
|
||||
if not text:
|
||||
return None
|
||||
m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(mb|mib|m)?", text)
|
||||
if not m:
|
||||
return None
|
||||
mb = float(m.group(1))
|
||||
|
||||
if mb is None or mb <= 0:
|
||||
return None
|
||||
|
||||
# Use MiB semantics for predictable limits.
|
||||
return int(mb * 1024 * 1024)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _room_id_matches_filter(room_id: str, allowed_ids_canon: set[str]) -> bool:
|
||||
rid = str(room_id or "").strip()
|
||||
if not rid or not allowed_ids_canon:
|
||||
return False
|
||||
|
||||
rid_canon = rid.casefold()
|
||||
if rid_canon in allowed_ids_canon:
|
||||
return True
|
||||
|
||||
# Allow matching when config omits the homeserver part: "!abc" matches "!abc:server".
|
||||
base = rid.split(":", 1)[0].strip().casefold()
|
||||
return bool(base) and base in allowed_ids_canon
|
||||
|
||||
|
||||
def _extract_text_arg(args: Sequence[str]) -> str:
|
||||
"""Extract a `-text <value>` argument from a cmdnat args list."""
|
||||
if not args:
|
||||
@@ -378,17 +480,50 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
except Exception:
|
||||
text_value = ""
|
||||
|
||||
size_limit_bytes = _get_matrix_size_limit_bytes(config)
|
||||
size_limit_mb = (size_limit_bytes / (1024 * 1024)) if size_limit_bytes else None
|
||||
|
||||
# Resolve upload paths once (also avoids repeated downloads when sending to multiple rooms).
|
||||
upload_jobs: List[Dict[str, Any]] = []
|
||||
any_failed = False
|
||||
for item in items:
|
||||
file_path = _resolve_upload_path(item, config)
|
||||
if not file_path:
|
||||
any_failed = True
|
||||
log("Matrix upload requires a local file (path) or a direct URL on the selected item", file=sys.stderr)
|
||||
continue
|
||||
|
||||
media_path = Path(file_path)
|
||||
if not media_path.exists():
|
||||
any_failed = True
|
||||
log(f"Matrix upload file missing: {file_path}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
if size_limit_bytes is not None:
|
||||
try:
|
||||
byte_size = int(media_path.stat().st_size)
|
||||
except Exception:
|
||||
byte_size = -1
|
||||
if byte_size >= 0 and byte_size > size_limit_bytes:
|
||||
any_failed = True
|
||||
actual_mb = byte_size / (1024 * 1024)
|
||||
lim = float(size_limit_mb or 0)
|
||||
log(
|
||||
f"ERROR: file is too big, skipping: {media_path.name} ({actual_mb:.1f} MB > {lim:.1f} MB)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
|
||||
upload_jobs.append({"path": str(media_path), "pipe_obj": item})
|
||||
|
||||
for rid in room_ids:
|
||||
sent_any_for_room = False
|
||||
for item in items:
|
||||
file_path = _resolve_upload_path(item, config)
|
||||
for job in upload_jobs:
|
||||
file_path = str(job.get("path") or "")
|
||||
if not file_path:
|
||||
any_failed = True
|
||||
log("Matrix upload requires a local file (path) or a direct URL on the selected item", file=sys.stderr)
|
||||
continue
|
||||
try:
|
||||
link = provider.upload_to_room(file_path, rid, pipe_obj=item)
|
||||
link = provider.upload_to_room(file_path, rid, pipe_obj=job.get("pipe_obj"))
|
||||
debug(f"✓ Sent {Path(file_path).name} -> {rid}")
|
||||
if link:
|
||||
log(link)
|
||||
@@ -433,13 +568,33 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
return 1
|
||||
|
||||
try:
|
||||
rooms = provider.list_rooms()
|
||||
configured_ids = None
|
||||
if not _has_flag(args, "-all"):
|
||||
ids = [str(v).strip() for v in _parse_config_room_filter_ids(config) if str(v).strip()]
|
||||
if ids:
|
||||
configured_ids = ids
|
||||
|
||||
rooms = provider.list_rooms(room_ids=configured_ids)
|
||||
except Exception as exc:
|
||||
log(f"Failed to list Matrix rooms: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Diagnostics if a configured filter yields no rows (provider filtered before name lookups for speed).
|
||||
if not rooms and not _has_flag(args, "-all"):
|
||||
configured_ids_dbg = [str(v).strip() for v in _parse_config_room_filter_ids(config) if str(v).strip()]
|
||||
if configured_ids_dbg:
|
||||
try:
|
||||
joined_ids = provider.list_joined_room_ids()
|
||||
debug(f"[matrix] Configured room filter IDs: {configured_ids_dbg}")
|
||||
debug(f"[matrix] Joined room IDs (from Matrix): {joined_ids}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not rooms:
|
||||
log("No joined rooms found.", file=sys.stderr)
|
||||
if _parse_config_room_filter_ids(config) and not _has_flag(args, "-all"):
|
||||
log("No joined rooms matched the configured Matrix room filter (use: .matrix -all)", file=sys.stderr)
|
||||
else:
|
||||
log("No joined rooms found.", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
table = ResultTable("Matrix Rooms (select with @N)")
|
||||
@@ -482,6 +637,7 @@ CMDLET = Cmdlet(
|
||||
usage="@N | .matrix",
|
||||
arg=[
|
||||
CmdletArg(name="send", type="bool", description="(internal) Send to selected room(s)", required=False),
|
||||
CmdletArg(name="all", type="bool", description="Ignore config room filter and show all joined rooms", required=False),
|
||||
CmdletArg(name="text", type="string", description="Send a follow-up text message after each upload (caption-like)", required=False),
|
||||
],
|
||||
exec=_run
|
||||
|
||||
Reference in New Issue
Block a user