feat: enhance Mattermost mirror with direct-message and group DM labeling for improved clarity

This commit is contained in:
2026-05-19 16:35:24 -06:00
parent d318701899
commit e081360a84
2 changed files with 50 additions and 1 deletions

View File

@@ -69,6 +69,12 @@ and `threads/...` when a single discussion thread is the relevant evidence.
This mirrors Slack's export pattern of one folder per conversation with one file This mirrors Slack's export pattern of one folder per conversation with one file
per date, while adding Mattermost-specific thread views. per date, while adding Mattermost-specific thread views.
Direct-message channels are labeled as `dm-<user-a>--<user-b>` when the mirror
has seen enough user metadata to resolve the Mattermost channel ID. Group DMs
use `group-...`. If a DM was first captured before the relevant user metadata
arrived, the folder can temporarily use raw IDs; later captures use the readable
label and `refs/channels.json` remains the source for resolving channel IDs.
The mirror writes any post payload it sees, including older messages returned The mirror writes any post payload it sees, including older messages returned
when the desktop app loads channel history or a thread. It dedupes by `post_id`, when the desktop app loads channel history or a thread. It dedupes by `post_id`,
so scrolling back through useful history is a safe way to backfill missing local so scrolling back through useful history is a safe way to backfill missing local

View File

@@ -61,6 +61,7 @@ class MattermostMirror:
self.seen_by_file: dict[Path, set[str]] = {} self.seen_by_file: dict[Path, set[str]] = {}
self.users: dict[str, str] = {} self.users: dict[str, str] = {}
self.channels: dict[str, str] = {} self.channels: dict[str, str] = {}
self.channel_meta: dict[str, dict[str, Any]] = {}
self.state: dict[str, Any] = {"channels": {}, "users": {}, "updated_at": None} self.state: dict[str, Any] = {"channels": {}, "users": {}, "updated_at": None}
self._ensure_dirs() self._ensure_dirs()
@@ -80,6 +81,7 @@ class MattermostMirror:
try: try:
self.state = json.loads(self.state_path.read_text(encoding="utf-8")) self.state = json.loads(self.state_path.read_text(encoding="utf-8"))
self.users = dict(self.state.get("users") or {}) self.users = dict(self.state.get("users") or {})
self.channel_meta = dict(self.state.get("channel_meta") or {})
for channel_id, value in (self.state.get("channels") or {}).items(): for channel_id, value in (self.state.get("channels") or {}).items():
if isinstance(value, dict): if isinstance(value, dict):
name = value.get("channel_name") or value.get("name") name = value.get("channel_name") or value.get("name")
@@ -205,10 +207,48 @@ class MattermostMirror:
channel_id = channel.get("id") channel_id = channel.get("id")
if not channel_id: if not channel_id:
return return
name = channel.get("name") or channel.get("display_name") or channel_id self.channel_meta[channel_id] = channel
name = self._channel_label(channel)
self.channels[channel_id] = name self.channels[channel_id] = name
self._write_refs() self._write_refs()
def _user_label(self, user_id: str | None) -> str | None:
if not user_id:
return None
return self.users.get(user_id) or user_id
def _channel_label(self, channel: dict[str, Any]) -> str:
channel_id = channel.get("id") or "unknown-channel"
channel_type = channel.get("type")
display_name = (channel.get("display_name") or "").strip()
name = (channel.get("name") or "").strip()
if channel_type == "D":
user_ids = [item for item in name.split("__") if item]
labels = [self._user_label(user_id) or user_id for user_id in user_ids]
if labels:
return "dm-" + "--".join(labels)
if channel_type == "G":
if display_name:
return "group-" + display_name
user_ids = [item for item in name.split("__") if item]
labels = [self._user_label(user_id) or user_id for user_id in user_ids]
if labels:
return "group-" + "--".join(labels)
return display_name or name or channel_id
def _refresh_channel_labels(self) -> None:
changed = False
for channel_id, meta in self.channel_meta.items():
label = self._channel_label(meta)
if label and self.channels.get(channel_id) != label:
self.channels[channel_id] = label
changed = True
if changed:
self._write_refs()
def _write_refs(self) -> None: def _write_refs(self) -> None:
users_path = self.refs_dir / "users.json" users_path = self.refs_dir / "users.json"
channels_path = self.refs_dir / "channels.json" channels_path = self.refs_dir / "channels.json"
@@ -244,6 +284,8 @@ class MattermostMirror:
if isinstance(channel, dict): if isinstance(channel, dict):
self._remember_channel(channel) self._remember_channel(channel)
self._refresh_channel_labels()
def _normalize_post(self, post: dict[str, Any], source: str, raw_event: str | None = None) -> dict[str, Any] | None: def _normalize_post(self, post: dict[str, Any], source: str, raw_event: str | None = None) -> dict[str, Any] | None:
post_id = post.get("id") post_id = post.get("id")
channel_id = post.get("channel_id") channel_id = post.get("channel_id")
@@ -311,6 +353,7 @@ class MattermostMirror:
entry["last_seen_create_at"] = max(int(entry.get("last_seen_create_at") or 0), int(msg.get("created_at_ms") or 0)) entry["last_seen_create_at"] = max(int(entry.get("last_seen_create_at") or 0), int(msg.get("created_at_ms") or 0))
entry["last_seen_post_id"] = msg.get("post_id") entry["last_seen_post_id"] = msg.get("post_id")
self.state["users"] = self.users self.state["users"] = self.users
self.state["channel_meta"] = self.channel_meta
self.state["updated_at"] = datetime.now(timezone.utc).isoformat() self.state["updated_at"] = datetime.now(timezone.utc).isoformat()
self._atomic_write_text(self.state_path, json.dumps(self.state, ensure_ascii=False, indent=2, sort_keys=True) + "\n") self._atomic_write_text(self.state_path, json.dumps(self.state, ensure_ascii=False, indent=2, sort_keys=True) + "\n")
self._write_refs() self._write_refs()