# features.py import json import re import time from datetime import timedelta from pathlib import Path from typing import Optional from flask import abort, jsonify, send_from_directory import markdown from sanitizer import sanitize_html_fragment IMAGE_MIME_PREFIX = "image/" AUDIO_MIME_PREFIX = "audio/" VIDEO_MIME_PREFIX = "video/" IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "webp"} AUDIO_EXTENSIONS = {"mp3", "wav", "ogg", "m4a", "aac", "flac"} VIDEO_EXTENSIONS = {"mp4", "webm", "mov", "m4v", "ogv"} DEFAULT_ROLE_NAME_COLORS = { "admin": "#ff7b7b", "moderator": "#8ab4ff", "user": "#f5f5f7", } PROFILE_BIO_MODES = {"txt", "md", "html"} THEME_MODES = {"dark", "light"} PAGE_VISIBILITIES = {"team", "public"} PAGE_TYPES = {"project", "document", "update", "resource"} TEAM_MEMBER_ROLES = {"owner", "admin", "member"} PROFILE_VISIBILITIES = {"public", "members", "private"} LAST_ACTIVE_VISIBILITIES = {"everyone", "members", "nobody"} PRESENCE_MODES = {"online", "silent", "focus", "dnd"} DM_LAYOUT_MODES = {"cards", "compact"} REACTION_EMOJI_RE = re.compile(r"^.{1,16}$") HEX_COLOR_RE = re.compile(r"^#[0-9a-fA-F]{6}$") PAGE_LINK_RE = re.compile(r"\]\((?:/teams/[^/]+/pages/)?([a-z0-9-]{1,48})\)", re.IGNORECASE) def valid_hex_color(value: object) -> bool: return bool(isinstance(value, str) and HEX_COLOR_RE.fullmatch(value.strip())) def normalized_hex_color(value: object) -> Optional[str]: if not valid_hex_color(value): return None return str(value).strip().lower() def render_markdown_to_safe_html(text: str) -> str: rendered = markdown.markdown( text or "", extensions=[ "extra", "sane_lists", "nl2br", "tables", "fenced_code", ], output_format="html5", ) return sanitize_html_fragment(rendered) def safe_profile_bio_html(html_module, bio: str, bio_mode: str) -> str: mode = bio_mode if bio_mode in PROFILE_BIO_MODES else "txt" if mode == "html": return sanitize_html_fragment(bio or "") if mode == "md": return render_markdown_to_safe_html(bio or "") return "

" + html_module.escape(bio or "").replace("\n", "
") + "

" def extract_page_reference_slugs(markdown_text: str) -> list[str]: text = str(markdown_text or "") matches = PAGE_LINK_RE.findall(text) seen = set() slugs = [] for slug in matches: normalized = slug.lower().strip() if not normalized or normalized in seen: continue seen.add(normalized) slugs.append(normalized) return slugs def detect_website_brand(website: str) -> dict: value = str(website or "").strip().lower() if not value: return {"host": "", "brand": "web", "label": "website"} host = value host = host.replace("https://", "").replace("http://", "") host = host.split("/", 1)[0] host = host.removeprefix("www.") mappings = [ ("github.com", "github", "GitHub"), ("youtube.com", "youtube", "YouTube"), ("youtu.be", "youtube", "YouTube"), ("spotify.com", "spotify", "Spotify"), ("soundcloud.com", "soundcloud", "SoundCloud"), ("twitter.com", "x", "X"), ("x.com", "x", "X"), ] for suffix, brand, label in mappings: if host.endswith(suffix): return {"host": host, "brand": brand, "label": label} return {"host": host, "brand": "web", "label": "website"} def sqlite_table_exists(get_db, table_name: str) -> bool: with get_db() as db: row = db.execute( """ SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? """, (table_name,), ).fetchone() return bool(row) def sqlite_column_exists(get_db, table_name: str, column_name: str) -> bool: with get_db() as db: rows = db.execute(f"PRAGMA table_info({table_name})").fetchall() return any(row["name"] == column_name for row in rows) def attachment_kind_from_name_and_mime( original_name: Optional[str], mime_type: Optional[str], ) -> str: mime_type = str(mime_type or "").lower() name = str(original_name or "") ext = name.rsplit(".", 1)[-1].lower() if "." in name else "" if mime_type.startswith(IMAGE_MIME_PREFIX) or ext in IMAGE_EXTENSIONS: return "image" if mime_type.startswith(AUDIO_MIME_PREFIX) or ext in AUDIO_EXTENSIONS: return "audio" if mime_type.startswith(VIDEO_MIME_PREFIX) or ext in VIDEO_EXTENSIONS: return "video" return "file" def load_page_children(get_db, page_id: int) -> list[dict]: if not sqlite_column_exists(get_db, "pages", "parent_page_id"): return [] with get_db() as db: rows = db.execute( """ SELECT id, slug, title, updated_at FROM pages WHERE parent_page_id = ? AND is_active = 1 ORDER BY title COLLATE NOCASE, id ASC """, (page_id,), ).fetchall() return [dict(row) for row in rows] def load_page_backlinks(get_db, page_id: int, page_slug: str, team_id: int) -> list[dict]: pattern_1 = f"%]({page_slug})%" pattern_2 = f"%/pages/{page_slug})%" with get_db() as db: rows = db.execute( """ SELECT id, slug, title, updated_at FROM pages WHERE team_id = ? AND is_active = 1 AND id != ? AND (body LIKE ? OR body LIKE ?) ORDER BY updated_at DESC, id DESC """, (team_id, page_id, pattern_1, pattern_2), ).fetchall() return [dict(row) for row in rows] def load_page_comments(get_db, page_id: int) -> list[dict]: if not sqlite_table_exists(get_db, "page_comments"): return [] with get_db() as db: rows = db.execute( """ SELECT pc.id, pc.page_id, pc.user_id, pc.username, pc.body, pc.created_at, pc.updated_at, u.role AS user_role, us.name_color AS user_name_color FROM page_comments AS pc JOIN users AS u ON u.id = pc.user_id LEFT JOIN user_settings AS us ON us.user_id = u.id WHERE pc.page_id = ? AND pc.deleted_at IS NULL ORDER BY pc.created_at ASC, pc.id ASC """, (page_id,), ).fetchall() return [ { "id": row["id"], "page_id": row["page_id"], "user_id": row["user_id"], "username": row["username"], "body": row["body"], "created_at": row["created_at"], "updated_at": row["updated_at"], "user_role": row["user_role"], "user_name_color": resolve_name_color(row["user_role"], row["user_name_color"]), } for row in rows ] def next_page_version_number(get_db, page_id: int) -> int: if not sqlite_table_exists(get_db, "page_versions"): return 1 with get_db() as db: row = db.execute( """ SELECT COALESCE(MAX(version_number), 0) AS value FROM page_versions WHERE page_id = ? """, (page_id,), ).fetchone() return int(row["value"] or 0) + 1 def snapshot_page_version( get_db, page_id: int, title: str, body: str, visibility: str, page_type: str, channel_id: Optional[int], parent_page_id: Optional[int], created_by_user_id: int, created_at: str, ) -> int: if not sqlite_table_exists(get_db, "page_versions"): return 0 version_number = next_page_version_number(get_db, page_id) with get_db() as db: db.execute("BEGIN IMMEDIATE") cursor = db.execute( """ INSERT INTO page_versions ( page_id, version_number, title, body, visibility, page_type, channel_id, parent_page_id, created_by_user_id, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( page_id, version_number, title, body, visibility, page_type, channel_id, parent_page_id, created_by_user_id, created_at, ), ) db.commit() return cursor.lastrowid def load_page_versions(get_db, page_id: int) -> list[dict]: if not sqlite_table_exists(get_db, "page_versions"): return [] with get_db() as db: rows = db.execute( """ SELECT pv.id, pv.page_id, pv.version_number, pv.title, pv.visibility, pv.page_type, pv.channel_id, pv.parent_page_id, pv.created_by_user_id, pv.created_at, u.username AS created_by_username FROM page_versions AS pv JOIN users AS u ON u.id = pv.created_by_user_id WHERE pv.page_id = ? ORDER BY pv.version_number DESC """, (page_id,), ).fetchall() return [dict(row) for row in rows] def load_page_version_detail(get_db, version_id: int): if not sqlite_table_exists(get_db, "page_versions"): return None with get_db() as db: row = db.execute( """ SELECT pv.id, pv.page_id, pv.version_number, pv.title, pv.body, pv.visibility, pv.page_type, pv.channel_id, pv.parent_page_id, pv.created_by_user_id, pv.created_at, u.username AS created_by_username FROM page_versions AS pv JOIN users AS u ON u.id = pv.created_by_user_id WHERE pv.id = ? """, (version_id,), ).fetchone() return row def user_can_see_last_active(settings_row, viewer: Optional[dict], target_user_id: int, is_team_member_func=None) -> bool: visibility = (settings_row["last_active_visibility"] if settings_row and "last_active_visibility" in settings_row.keys() else "everyone") or "everyone" if visibility == "nobody": return False if visibility == "everyone": return True if visibility == "members": if not viewer: return False if viewer["id"] == target_user_id: return True if viewer["role"] in {"moderator", "admin"}: return True if is_team_member_func: return True return False return False def page_payload(app_module, page, user: Optional[dict]) -> dict: can_read = bool( page["visibility"] == "public" or ( user and ( user["role"] in {"moderator", "admin"} or app_module["is_team_member"](page["team_id"], user["id"]) ) ) ) rendered_html = render_markdown_to_safe_html(page["body"] or "") parent_page_id = page["parent_page_id"] if "parent_page_id" in page.keys() else None parent_page_slug = page["parent_page_slug"] if "parent_page_slug" in page.keys() else None parent_page_title = page["parent_page_title"] if "parent_page_title" in page.keys() else None return { "id": page["id"], "team_id": page["team_id"], "team_slug": page["team_slug"], "team_name": page["team_name"], "channel_id": page["channel_id"], "channel_slug": page["channel_slug"], "channel_name": page["channel_name"], "parent_page_id": parent_page_id, "parent_page_slug": parent_page_slug, "parent_page_title": parent_page_title, "slug": page["slug"], "title": page["title"], "body": page["body"], "body_html": rendered_html, "visibility": page["visibility"], "page_type": page["page_type"], "created_by_user_id": page["created_by_user_id"], "updated_by_user_id": page["updated_by_user_id"], "created_at": page["created_at"], "updated_at": page["updated_at"], "can_read": can_read, "can_edit": bool( user and ( user["role"] in {"moderator", "admin"} or app_module["is_team_admin"](page["team_id"], user) ) ), "children": load_page_children(app_module["get_db"], page["id"]), "backlinks": load_page_backlinks( app_module["get_db"], page["id"], page["slug"], page["team_id"], ), "references": extract_page_reference_slugs(page["body"] or ""), "comments": load_page_comments(app_module["get_db"], page["id"]), } def attachment_payload_from_row(row) -> Optional[dict]: if not row or not row["id"]: return None kind = attachment_kind_from_name_and_mime( row["original_name"], row["mime_type"], ) return { "id": row["id"], "original_name": row["original_name"], "mime_type": row["mime_type"], "size_bytes": row["size_bytes"], "kind": kind, "url": f"/api/attachments/{row['id']}/download", } def attachment_payload_legacy_from_message_select_row(row) -> Optional[dict]: if not row["attachment_id"]: return None kind = attachment_kind_from_name_and_mime( row["attachment_original_name"], row["attachment_mime_type"], ) return { "id": row["attachment_id"], "original_name": row["attachment_original_name"], "mime_type": row["attachment_mime_type"], "size_bytes": row["attachment_size_bytes"], "kind": kind, "url": f"/api/attachments/{row['attachment_id']}/download", } def attachment_list_for_message(get_db, message_id: int) -> list[dict]: with get_db() as db: rows = db.execute( """ SELECT a.id, a.original_name, a.mime_type, a.size_bytes FROM message_attachments AS ma JOIN attachments AS a ON a.id = ma.attachment_id WHERE ma.message_id = ? ORDER BY ma.position ASC, a.id ASC """, (message_id,), ).fetchall() return [attachment_payload_from_row(row) for row in rows] def cleanup_orphaned_attachments( get_db, upload_dir: Path, utc_now, orphan_ttl_hours: int, app_logger, ) -> None: cutoff = (utc_now() - timedelta(hours=orphan_ttl_hours)).isoformat() with get_db() as db: orphaned = db.execute( """ SELECT a.id, a.stored_name FROM attachments AS a WHERE a.created_at < ? AND NOT EXISTS ( SELECT 1 FROM messages AS m WHERE m.attachment_id = a.id ) AND NOT EXISTS ( SELECT 1 FROM message_attachments AS ma WHERE ma.attachment_id = a.id ) """, (cutoff,), ).fetchall() for attachment in orphaned: db.execute( """ DELETE FROM attachments WHERE id = ? """, (attachment["id"],), ) db.commit() for attachment in orphaned: try: (upload_dir / attachment["stored_name"]).unlink(missing_ok=True) except OSError: app_logger.warning( "Could not remove orphaned attachment file: %s", attachment["stored_name"], ) def maybe_cleanup_orphaned_attachments( state: dict, cleanup_interval_seconds: int, cleanup_func, app_logger, ) -> None: now = time.time() last = state.get("last_attachment_cleanup", 0.0) if now - last < cleanup_interval_seconds: return state["last_attachment_cleanup"] = now try: cleanup_func() except Exception: app_logger.exception("Could not clean orphaned attachments.") def resolve_owned_attachment_ids( get_db, attachment_ids: object, user_id: int, ) -> tuple[Optional[list[int]], Optional[tuple]]: if attachment_ids is None: return [], None if not isinstance(attachment_ids, list): return None, (jsonify({"error": "attachment_ids must be an array."}), 400) parsed = [] seen = set() for item in attachment_ids[:10]: try: attachment_id = int(item) except (TypeError, ValueError): return None, (jsonify({"error": "Invalid attachment."}), 400) if attachment_id in seen: continue seen.add(attachment_id) with get_db() as db: row = db.execute( """ SELECT id, uploader_user_id FROM attachments WHERE id = ? """, (attachment_id,), ).fetchone() if not row: return None, (jsonify({"error": "Attachment not found."}), 404) if row["uploader_user_id"] != user_id: return None, ( jsonify({"error": "You can only attach files you uploaded."}), 403, ) parsed.append(attachment_id) return parsed, None def resolve_owned_attachment( get_db, attachment_id: object, user_id: int, ) -> tuple[Optional[int], Optional[tuple]]: if attachment_id is None: return None, None try: parsed_id = int(attachment_id) except (TypeError, ValueError): return None, (jsonify({"error": "Invalid attachment."}), 400) with get_db() as db: attachment = db.execute( """ SELECT id, uploader_user_id FROM attachments WHERE id = ? """, (parsed_id,), ).fetchone() if not attachment: return None, (jsonify({"error": "Attachment not found."}), 404) if attachment["uploader_user_id"] != user_id: return None, ( jsonify({"error": "You can only attach files you uploaded."}), 403, ) return parsed_id, None def can_download_attachment( get_db, attachment_id: int, user: Optional[dict], can_access_room, can_access_channel, get_channel_by_id, ) -> bool: if not user: return False with get_db() as db: room_rows = db.execute( """ SELECT DISTINCT r.id, r.slug, r.name, r.description, r.visibility, r.created_by_user_id, r.is_active FROM message_attachments AS ma JOIN messages AS m ON m.id = ma.message_id JOIN rooms AS r ON r.id = m.room_id WHERE ma.attachment_id = ? AND r.is_active = 1 """, (attachment_id,), ).fetchall() channel_rows = db.execute( """ SELECT DISTINCT m.channel_id FROM message_attachments AS ma JOIN messages AS m ON m.id = ma.message_id WHERE ma.attachment_id = ? AND m.channel_id IS NOT NULL """, (attachment_id,), ).fetchall() dm_rows = db.execute( """ SELECT DISTINCT dc.id FROM message_attachments AS ma JOIN messages AS m ON m.id = ma.message_id JOIN direct_conversations AS dc ON dc.id = m.direct_conversation_id JOIN direct_conversation_members AS dcm ON dcm.direct_conversation_id = dc.id WHERE ma.attachment_id = ? AND dcm.user_id = ? """, (attachment_id, user["id"]), ).fetchall() if dm_rows: return True if any(can_access_room(room, user) for room in room_rows): return True for item in channel_rows: channel = get_channel_by_id(item["channel_id"]) if channel and can_access_channel(channel, user): return True return False def send_attachment_file( get_db, upload_dir: Path, attachment_id: int, user: Optional[dict], can_access_room, can_access_channel, get_channel_by_id, ): with get_db() as db: attachment = db.execute( """ SELECT id, stored_name, original_name, mime_type FROM attachments WHERE id = ? """, (attachment_id,), ).fetchone() if not attachment: abort(404) if not can_download_attachment( get_db, attachment_id, user, can_access_room, can_access_channel, get_channel_by_id, ): abort(403) return send_from_directory( upload_dir, attachment["stored_name"], as_attachment=True, download_name=attachment["original_name"], mimetype=attachment["mime_type"], ) def role_default_name_color(role: Optional[str]) -> str: return DEFAULT_ROLE_NAME_COLORS.get(str(role or "user"), DEFAULT_ROLE_NAME_COLORS["user"]) def resolve_name_color(user_role: Optional[str], explicit_color: Optional[str]) -> str: return normalized_hex_color(explicit_color) or role_default_name_color(user_role) def reaction_summary_for_message(get_db, message_id: int, user: Optional[dict]) -> list[dict]: with get_db() as db: rows = db.execute( """ SELECT emoji, COUNT(*) AS count, MAX(CASE WHEN user_id = ? THEN 1 ELSE 0 END) AS reacted FROM message_reactions WHERE message_id = ? GROUP BY emoji ORDER BY count DESC, emoji ASC """, (user["id"] if user else -1, message_id), ).fetchall() return [ { "emoji": row["emoji"], "count": row["count"], "reacted": bool(row["reacted"]), } for row in rows ] def richer_message_payload( get_db, row, user: Optional[dict], current_user_id: Optional[int], ) -> dict: with get_db() as db: edited = db.execute( """ SELECT created_at FROM message_edits WHERE message_id = ? ORDER BY id DESC LIMIT 1 """, (row["id"],), ).fetchone() receipt_count = db.execute( """ SELECT COUNT(*) AS count FROM message_receipts WHERE message_id = ? """, (row["id"],), ).fetchone()["count"] seen_by_user = False is_bookmarked = False is_pinned = False pin_row = None bookmark_table_exists = bool( db.execute( """ SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'message_bookmarks' """ ).fetchone() ) pin_table_exists = bool( db.execute( """ SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'message_pins' """ ).fetchone() ) if current_user_id: seen_by_user = bool( db.execute( """ SELECT 1 FROM message_receipts WHERE message_id = ? AND user_id = ? """, (row["id"], current_user_id), ).fetchone() ) if bookmark_table_exists: is_bookmarked = bool( db.execute( """ SELECT 1 FROM message_bookmarks WHERE message_id = ? AND user_id = ? """, (row["id"], current_user_id), ).fetchone() ) if pin_table_exists: pin_row = db.execute( """ SELECT pinned_at, pinned_by_user_id FROM message_pins WHERE message_id = ? """, (row["id"],), ).fetchone() is_pinned = bool(pin_row) primary_attachment = attachment_payload_legacy_from_message_select_row(row) attachments = attachment_list_for_message(get_db, row["id"]) if primary_attachment and not any(item["id"] == primary_attachment["id"] for item in attachments): attachments.insert(0, primary_attachment) user_role = row["user_role"] if "user_role" in row.keys() else "user" user_name_color = row["user_name_color"] if "user_name_color" in row.keys() else None resolved_color = resolve_name_color(user_role, user_name_color) return { "id": row["id"], "room_id": row["room_id"], "channel_id": row["channel_id"], "direct_conversation_id": row["direct_conversation_id"] if "direct_conversation_id" in row.keys() else None, "user_id": row["user_id"], "username": row["username"], "user_role": user_role, "user_name_color": resolved_color, "text": row["text"], "created_at": row["created_at"], "updated_at": edited["created_at"] if edited else None, "is_edited": bool(edited), "parent_message_id": row["parent_message_id"], "thread_reply_count": row["thread_reply_count"], "attachment": primary_attachment, "attachments": attachments, "read_receipt_count": receipt_count, "seen_by_current_user": seen_by_user, "reactions": reaction_summary_for_message(get_db, row["id"], user), "is_dm": bool(("direct_conversation_id" in row.keys()) and row["direct_conversation_id"]), "is_pinned": is_pinned, "pinned_at": pin_row["pinned_at"] if pin_row else None, "pinned_by_user_id": pin_row["pinned_by_user_id"] if pin_row else None, "is_bookmarked": is_bookmarked, } def feed_message_payload( get_db, row, user: Optional[dict], ) -> dict: payload = richer_message_payload( get_db, row, user, user["id"] if user else None, ) payload.update( { "room_slug": row["room_slug"] if "room_slug" in row.keys() else None, "room_name": row["room_name"] if "room_name" in row.keys() else None, "room_visibility": row["room_visibility"] if "room_visibility" in row.keys() else None, "team_slug": row["team_slug"] if "team_slug" in row.keys() else None, "team_name": row["team_name"] if "team_name" in row.keys() else None, "channel_slug": row["channel_slug"] if "channel_slug" in row.keys() else None, "channel_name": row["channel_name"] if "channel_name" in row.keys() else None, "dm_id": row["direct_conversation_id"] if "direct_conversation_id" in row.keys() else None, "is_mention": bool( user and f"@{user['username'].lower()}" in payload["text"].lower() ), } ) return payload def profile_payload( html_module, user_payload_func, user_row, profile_row, settings_row, viewer: Optional[dict], ) -> dict: is_self = bool(viewer and viewer["id"] == user_row["id"]) status_payload = user_payload_func(user_row, settings_row) bio = profile_row["bio"] or "" bio_mode = profile_row["bio_mode"] or "txt" explicit_name_color = settings_row["name_color"] if settings_row else None resolved = resolve_name_color(user_row["role"], explicit_name_color) profile_visibility = settings_row["profile_visibility"] if settings_row and "profile_visibility" in settings_row.keys() else "public" if profile_visibility not in PROFILE_VISIBILITIES: profile_visibility = "public" last_active_visibility = settings_row["last_active_visibility"] if settings_row and "last_active_visibility" in settings_row.keys() else "everyone" if last_active_visibility not in LAST_ACTIVE_VISIBILITIES: last_active_visibility = "everyone" presence_mode = settings_row["presence_mode"] if settings_row and "presence_mode" in settings_row.keys() else "online" if presence_mode not in PRESENCE_MODES: presence_mode = "online" dm_layout_mode = settings_row["dm_layout_mode"] if settings_row and "dm_layout_mode" in settings_row.keys() else "cards" if dm_layout_mode not in DM_LAYOUT_MODES: dm_layout_mode = "cards" show_presence_badge = bool( settings_row["show_presence_badge"] if settings_row and "show_presence_badge" in settings_row.keys() else 1 ) last_active_at = user_row["last_login_at"] if "last_login_at" in user_row.keys() else None can_show_last_active = False if last_active_visibility == "everyone": can_show_last_active = True elif last_active_visibility == "members": can_show_last_active = bool(viewer and (viewer["id"] == user_row["id"] or viewer["role"] in {"moderator", "admin"})) elif last_active_visibility == "nobody": can_show_last_active = bool(viewer and viewer["id"] == user_row["id"]) website = profile_row["website"] or "" website_meta = detect_website_brand(website) if website else {"host": "", "brand": "web", "label": "website"} return { "id": user_row["id"], "username": user_row["username"], "role": user_row["role"], "status": status_payload["status"], "presence_mode": presence_mode, "created_at": user_row["created_at"], "last_active_at": last_active_at if can_show_last_active else None, "display_name": profile_row["display_name"] if profile_row["display_name"] else user_row["username"], "badge_text": (profile_row["badge_text"] or "").strip() if "badge_text" in profile_row.keys() else "", "bio": bio, "bio_mode": bio_mode, "bio_html": safe_profile_bio_html(html_module, bio, bio_mode), "website": website, "website_meta": website_meta, "location": profile_row["location"] or "", "status_message": profile_row["status_message"] if "status_message" in profile_row.keys() else "", "banner_attachment_id": profile_row["banner_attachment_id"] if "banner_attachment_id" in profile_row.keys() else None, "banner_image_url": f"/api/attachments/{profile_row['banner_attachment_id']}/download" if ("banner_attachment_id" in profile_row.keys() and profile_row["banner_attachment_id"]) else "", "updated_at": profile_row["updated_at"], "is_self": is_self, "name_color": explicit_name_color, "resolved_name_color": resolved, "profile_background_color": settings_row["profile_background_color"] if settings_row and "profile_background_color" in settings_row.keys() else None, "profile_banner_color": settings_row["profile_banner_color"] if settings_row and "profile_banner_color" in settings_row.keys() else None, "profile_visibility": profile_visibility, "last_active_visibility": last_active_visibility, "show_presence_badge": show_presence_badge, "dm_layout_mode": dm_layout_mode, } def list_dms_for_user(get_db, user_id: int) -> list[dict]: with get_db() as db: rows = db.execute( """ SELECT dc.id, dc.created_at, other.id AS other_user_id, other.username AS other_username, other.role AS other_role, us.name_color AS other_name_color, up.display_name AS other_display_name, up.status_message AS other_status_message, us.presence_mode AS other_presence_mode, ( SELECT m.text FROM messages AS m WHERE m.direct_conversation_id = dc.id ORDER BY m.id DESC LIMIT 1 ) AS last_message_text, ( SELECT m.created_at FROM messages AS m WHERE m.direct_conversation_id = dc.id ORDER BY m.id DESC LIMIT 1 ) AS last_message_created_at, ( SELECT m.id FROM messages AS m WHERE m.direct_conversation_id = dc.id ORDER BY m.id DESC LIMIT 1 ) AS last_message_id FROM direct_conversations AS dc JOIN direct_conversation_members AS self_member ON self_member.direct_conversation_id = dc.id JOIN direct_conversation_members AS other_member ON other_member.direct_conversation_id = dc.id AND other_member.user_id != self_member.user_id JOIN users AS other ON other.id = other_member.user_id LEFT JOIN user_profiles AS up ON up.user_id = other.id LEFT JOIN user_settings AS us ON us.user_id = other.id WHERE self_member.user_id = ? ORDER BY COALESCE(last_message_id, 0) DESC, dc.id DESC """, (user_id,), ).fetchall() items = [] for row in rows: items.append( { "id": row["id"], "created_at": row["created_at"], "other_user": { "id": row["other_user_id"], "username": row["other_username"], "display_name": row["other_display_name"] or row["other_username"], "role": row["other_role"], "name_color": row["other_name_color"], "resolved_name_color": resolve_name_color(row["other_role"], row["other_name_color"]), "status_message": row["other_status_message"] or "", "presence_mode": row["other_presence_mode"] or "online", }, "last_message_text": row["last_message_text"], "last_message_created_at": row["last_message_created_at"], "last_message_id": row["last_message_id"], "unread_count": 0, } ) return items def find_or_create_dm(get_db, db_write, now_iso, user_a_id: int, user_b_id: int) -> int: low_id, high_id = sorted([int(user_a_id), int(user_b_id)]) with get_db() as db: existing = db.execute( """ SELECT dc.id FROM direct_conversations AS dc JOIN direct_conversation_members AS m1 ON m1.direct_conversation_id = dc.id AND m1.user_id = ? JOIN direct_conversation_members AS m2 ON m2.direct_conversation_id = dc.id AND m2.user_id = ? WHERE dc.member_pair_key = ? LIMIT 1 """, (low_id, high_id, f"{low_id}:{high_id}"), ).fetchone() if existing: return existing["id"] with get_db() as db: db.execute("BEGIN IMMEDIATE") existing = db.execute( """ SELECT id FROM direct_conversations WHERE member_pair_key = ? LIMIT 1 """, (f"{low_id}:{high_id}",), ).fetchone() if existing: db.commit() return existing["id"] cursor = db.execute( """ INSERT INTO direct_conversations ( member_pair_key, created_at ) VALUES (?, ?) """, (f"{low_id}:{high_id}", now_iso()), ) dm_id = cursor.lastrowid db.execute( """ INSERT INTO direct_conversation_members ( direct_conversation_id, user_id, added_at ) VALUES (?, ?, ?) """, (dm_id, low_id, now_iso()), ) db.execute( """ INSERT INTO direct_conversation_members ( direct_conversation_id, user_id, added_at ) VALUES (?, ?, ?) """, (dm_id, high_id, now_iso()), ) db.commit() return dm_id def get_dm_for_user(get_db, dm_id: int, user_id: int): with get_db() as db: row = db.execute( """ SELECT dc.id, dc.created_at FROM direct_conversations AS dc JOIN direct_conversation_members AS dcm ON dcm.direct_conversation_id = dc.id WHERE dc.id = ? AND dcm.user_id = ? """, (dm_id, user_id), ).fetchone() return row def get_dm_other_user(get_db, dm_id: int, user_id: int): with get_db() as db: row = db.execute( """ SELECT u.id, u.username, u.role, up.display_name, up.status_message, us.name_color, us.presence_mode FROM direct_conversation_members AS dcm JOIN users AS u ON u.id = dcm.user_id LEFT JOIN user_profiles AS up ON up.user_id = u.id LEFT JOIN user_settings AS us ON us.user_id = u.id WHERE dcm.direct_conversation_id = ? AND dcm.user_id != ? LIMIT 1 """, (dm_id, user_id), ).fetchone() return row def validate_dm_access(get_db, dm_id: int, user: Optional[dict]) -> bool: if not user: return False return bool(get_dm_for_user(get_db, dm_id, user["id"])) def dm_payload(get_db, dm_id: int, user_id: int) -> dict: dm = get_dm_for_user(get_db, dm_id, user_id) other = get_dm_other_user(get_db, dm_id, user_id) return { "id": dm["id"], "created_at": dm["created_at"], "other_user": { "id": other["id"], "username": other["username"], "display_name": other["display_name"] or other["username"], "role": other["role"], "name_color": other["name_color"], "resolved_name_color": resolve_name_color(other["role"], other["name_color"]), "status_message": other["status_message"] or "", "presence_mode": other["presence_mode"] or "online", }, } def update_presence( db_write, user_id: int, view_name: Optional[str] = None, room_id: Optional[int] = None, channel_id: Optional[int] = None, direct_conversation_id: Optional[int] = None, ) -> None: db_write( """ INSERT INTO presence_events ( user_id, updated_at, view_name, room_id, channel_id, direct_conversation_id ) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET updated_at = excluded.updated_at, view_name = excluded.view_name, room_id = excluded.room_id, channel_id = excluded.channel_id, direct_conversation_id = excluded.direct_conversation_id """, ( user_id, time.time(), view_name, room_id, channel_id, direct_conversation_id, ), ) def room_presence_count(get_db, ttl_seconds: int, room_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE room_id = ? AND updated_at >= ? """, (room_id, cutoff), ).fetchone() return row["count"] if row else 0 def channel_presence_count(get_db, ttl_seconds: int, channel_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE channel_id = ? AND updated_at >= ? """, (channel_id, cutoff), ).fetchone() return row["count"] if row else 0 def dm_presence_count(get_db, ttl_seconds: int, dm_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE direct_conversation_id = ? AND updated_at >= ? """, (dm_id, cutoff), ).fetchone() return row["count"] if row else 0 def create_call_session(db_write, now_iso, dm_id: int, started_by_user_id: int) -> int: cursor = db_write( """ INSERT INTO call_sessions ( direct_conversation_id, started_by_user_id, status, created_at, ended_at ) VALUES (?, ?, 'active', ?, NULL) """, (dm_id, started_by_user_id, now_iso()), ) return cursor.lastrowid def append_call_signal( db_write, now_iso, call_id: int, from_user_id: int, signal_type: str, payload: dict, ) -> int: cursor = db_write( """ INSERT INTO call_signals ( call_session_id, from_user_id, signal_type, payload_json, created_at ) VALUES (?, ?, ?, ?, ?) """, ( call_id, from_user_id, signal_type, json.dumps(payload or {}), now_iso(), ), ) return cursor.lastrowid def list_call_signals(get_db, call_id: int, after_id: int = 0) -> list[dict]: with get_db() as db: rows = db.execute( """ SELECT cs.id, cs.call_session_id, cs.from_user_id, u.username, cs.signal_type, cs.payload_json, cs.created_at FROM call_signals AS cs JOIN users AS u ON u.id = cs.from_user_id WHERE cs.call_session_id = ? AND cs.id > ? ORDER BY cs.id ASC LIMIT 200 """, (call_id, after_id), ).fetchall() items = [] for row in rows: try: payload = json.loads(row["payload_json"] or "{}") except json.JSONDecodeError: payload = {} items.append( { "id": row["id"], "call_session_id": row["call_session_id"], "from_user_id": row["from_user_id"], "username": row["username"], "signal_type": row["signal_type"], "payload": payload, "created_at": row["created_at"], } ) return items def end_call(db_write, now_iso, call_id: int) -> None: db_write( """ UPDATE call_sessions SET status = 'ended', ended_at = ? WHERE id = ? """, (now_iso(), call_id), ) def get_call_with_dm(get_db, call_id: int): with get_db() as db: row = db.execute( """ SELECT cs.id, cs.direct_conversation_id, cs.started_by_user_id, cs.status, cs.created_at, cs.ended_at FROM call_sessions AS cs WHERE cs.id = ? """, (call_id,), ).fetchone() return row def call_payload(get_db, call_id: int) -> Optional[dict]: row = get_call_with_dm(get_db, call_id) if not row: return None return { "id": row["id"], "direct_conversation_id": row["direct_conversation_id"], "started_by_user_id": row["started_by_user_id"], "status": row["status"], "created_at": row["created_at"], "ended_at": row["ended_at"], } def dispatch_scheduled_messages(get_db, now_iso_func, app_logger) -> None: if not sqlite_table_exists(get_db, "scheduled_messages"): return now_value = now_iso_func() with get_db() as db: rows = db.execute( """ SELECT id, room_id, channel_id, direct_conversation_id, user_id, username, text, attachment_ids_json FROM scheduled_messages WHERE sent_at IS NULL AND cancelled_at IS NULL AND deliver_at <= ? ORDER BY deliver_at ASC, id ASC LIMIT 100 """, (now_value,), ).fetchall() if not rows: return db.execute("BEGIN IMMEDIATE") for row in rows: try: attachment_ids = json.loads(row["attachment_ids_json"] or "[]") except json.JSONDecodeError: attachment_ids = [] attachment_id = attachment_ids[0] if attachment_ids else None cursor = db.execute( """ INSERT INTO messages ( room_id, channel_id, direct_conversation_id, user_id, username, text, attachment_id, parent_message_id, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?) """, ( row["room_id"], row["channel_id"], row["direct_conversation_id"], row["user_id"], row["username"], row["text"], attachment_id, now_value, ), ) message_id = cursor.lastrowid for position, extra_attachment_id in enumerate(attachment_ids): db.execute( """ INSERT OR IGNORE INTO message_attachments ( message_id, attachment_id, position ) VALUES (?, ?, ?) """, (message_id, extra_attachment_id, position), ) db.execute( """ UPDATE scheduled_messages SET sent_at = ? WHERE id = ? """, (now_value, row["id"]), ) db.commit()