This commit is contained in:
2026-01-09 01:22:06 -08:00
parent 89ac3bb7e8
commit 1deddfda5c
10 changed files with 1004 additions and 179 deletions

View File

@@ -1,15 +1,18 @@
from __future__ import annotations
import mimetypes
import sys
import time
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, Iterable, List, Optional, Tuple
from urllib.parse import quote
import requests
from ProviderCore.base import Provider
from ProviderCore.base import Provider, SearchResult
from SYS.provider_helpers import TableProviderMixin
from SYS.logger import log
_MATRIX_INIT_CHECK_CACHE: Dict[str,
Tuple[bool,
@@ -207,8 +210,29 @@ def _matrix_health_check(*,
return False, str(exc)
class Matrix(Provider):
"""File provider for Matrix (Element) chat rooms."""
class Matrix(TableProviderMixin, Provider):
"""Matrix (Element) room provider with file uploads and selection.
This provider uses the new table system (strict ResultTable adapter pattern) for
consistent room listing and selection. It exposes Matrix joined rooms as selectable
rows with metadata enrichment for:
- room_id: Unique Matrix room identifier
- room_name: Human-readable room display name
- _selection_args: For @N expansion control and upload routing
KEY FEATURES:
- Table system: Using ResultTable adapter for strict column/metadata handling
- Room discovery: search() or list_rooms() to enumerate joined rooms
- Selection integration: @N selection triggers upload_to_room() via selector()
- Deferred uploads: Files can be queued for upload to multiple rooms
- MIME detection: Automatic content type classification for Matrix msgtype
SELECTION FLOW:
1. User runs: search-file -provider matrix "room" (or .matrix -list-rooms)
2. Results show available joined rooms
3. User selects rooms: @1 @2 (or @1,2)
4. Selection triggers upload of pending files to selected rooms
"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
super().__init__(config)
@@ -261,6 +285,70 @@ class Matrix(Provider):
and (matrix_conf.get("access_token") or matrix_conf.get("password"))
)
def search(
self,
query: str,
limit: int = 50,
filters: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> List[SearchResult]:
"""Search/list joined Matrix rooms.
If query is empty or "*", returns all joined rooms.
Otherwise, filters rooms by name/ID matching the query.
"""
try:
rooms = self.list_rooms()
except Exception as exc:
log(f"[matrix] Failed to list rooms: {exc}", file=sys.stderr)
return []
q = (query or "").strip().lower()
needle = "" if q in {"*", "all", "list"} else q
results: List[SearchResult] = []
for room in rooms:
if len(results) >= limit:
break
room_id = room.get("room_id") or ""
room_name = room.get("name") or ""
# Filter by query if provided
if needle:
match_text = f"{room_name} {room_id}".lower()
if needle not in match_text:
continue
if not room_id:
continue
display_name = room_name or room_id
results.append(
SearchResult(
table="matrix",
title=display_name,
path=f"matrix:room:{room_id}",
detail=room_id if room_name else "",
annotations=["room"],
media_kind="folder",
columns=[
("Room", display_name),
("ID", room_id),
],
full_metadata={
"room_id": room_id,
"room_name": room_name,
"provider": "matrix",
# Selection metadata for table system and @N expansion
"_selection_args": ["-room-id", room_id],
},
)
)
return results
def _get_homeserver_and_token(self) -> Tuple[str, str]:
matrix_conf = self.config.get("provider",
{}).get("matrix",
@@ -595,3 +683,100 @@ class Matrix(Provider):
if any_failed:
print("\nOne or more Matrix uploads failed\n")
return True
# Minimal provider registration for the new table system
try:
from SYS.result_table_adapters import register_provider
from SYS.result_table_api import ResultModel, ColumnSpec, metadata_column, title_column
def _convert_search_result_to_model(sr: Any) -> ResultModel:
"""Convert Matrix SearchResult to ResultModel for strict table display."""
d = sr.to_dict() if hasattr(sr, "to_dict") else (sr if isinstance(sr, dict) else {"title": getattr(sr, "title", str(sr))})
title = d.get("title") or ""
path = d.get("path") or None
columns = d.get("columns") or getattr(sr, "columns", None) or []
# Extract metadata from columns and full_metadata
metadata: Dict[str, Any] = {}
for name, value in columns:
key = str(name or "").strip().lower()
if key in ("room_id", "room_name", "id", "name"):
metadata[key] = value
try:
fm = d.get("full_metadata") or {}
if isinstance(fm, dict):
for k, v in fm.items():
metadata[str(k).strip().lower()] = v
except Exception:
pass
return ResultModel(
title=str(title),
path=str(path) if path else None,
ext=None,
size_bytes=None,
metadata=metadata,
source="matrix"
)
def _adapter(items: Iterable[Any]) -> Iterable[ResultModel]:
"""Adapter to convert SearchResults to ResultModels."""
for it in items:
try:
yield _convert_search_result_to_model(it)
except Exception:
continue
def _has_metadata(rows: List[ResultModel], key: str) -> bool:
"""Check if any row has a given metadata key with a non-empty value."""
for row in rows:
md = row.metadata or {}
if key in md:
val = md[key]
if val is None:
continue
if isinstance(val, str) and not val.strip():
continue
return True
return False
def _columns_factory(rows: List[ResultModel]) -> List[ColumnSpec]:
"""Build column specifications from available metadata in rows."""
cols = [title_column()]
if _has_metadata(rows, "room_id"):
cols.append(metadata_column("room_id", "Room ID"))
if _has_metadata(rows, "room_name"):
cols.append(metadata_column("room_name", "Name"))
return cols
def _selection_fn(row: ResultModel) -> List[str]:
"""Return selection args for @N expansion and room selection.
Uses explicit -room-id flag to identify the selected room for file uploads.
"""
metadata = row.metadata or {}
# Check for explicit selection args first
args = metadata.get("_selection_args") or metadata.get("selection_args")
if isinstance(args, (list, tuple)) and args:
return [str(x) for x in args if x is not None]
# Fallback to room_id
room_id = metadata.get("room_id")
if room_id:
return ["-room-id", str(room_id)]
return ["-title", row.title or ""]
register_provider(
"matrix",
_adapter,
columns=_columns_factory,
selection_fn=_selection_fn,
metadata={"description": "Matrix room provider with file uploads"},
)
except Exception:
# best-effort registration
pass