feat: 1-to-1 message render + web data-lake backend
This commit is contained in:
@@ -1,13 +1,46 @@
|
||||
import asyncpg
|
||||
|
||||
from utils.read.models import ChatListItem, MessageVersionView, MessageView, Page
|
||||
from utils.read.message_view import build_message_view, load_raw, media_ref_from
|
||||
from utils.read.models import (
|
||||
ChatListItem,
|
||||
MediaRef,
|
||||
MessageVersionView,
|
||||
MessageView,
|
||||
Page,
|
||||
)
|
||||
|
||||
_MESSAGE_COLS = (
|
||||
"chat_id, message_id, date, sender_id, text, "
|
||||
"has_media, is_self_destruct, edited_at, deleted_at"
|
||||
"chat_id, message_id, date, sender_id, text, has_media, is_self_destruct, "
|
||||
"edited_at, deleted_at, raw, raw->>'media_group_id' AS media_group_id"
|
||||
)
|
||||
|
||||
|
||||
async def _media_map(
|
||||
pool: asyncpg.Pool, account_id: int, rows: list[asyncpg.Record]
|
||||
) -> dict[tuple[int, int], asyncpg.Record]:
|
||||
message_ids = list({row["message_id"] for row in rows})
|
||||
if not message_ids:
|
||||
return {}
|
||||
media_rows = await pool.fetch(
|
||||
"SELECT id, chat_id, message_id, kind, downloaded, mime, file_size, "
|
||||
"ttl_seconds FROM media "
|
||||
"WHERE account_id = $1 AND message_id = ANY($2::bigint[])",
|
||||
account_id,
|
||||
message_ids,
|
||||
)
|
||||
return {(row["chat_id"], row["message_id"]): row for row in media_rows}
|
||||
|
||||
|
||||
def _single_media(
|
||||
row: asyncpg.Record, raw: dict, media_by_key: dict[tuple[int, int], asyncpg.Record]
|
||||
) -> list[MediaRef]:
|
||||
media_row = media_by_key.get((row["chat_id"], row["message_id"]))
|
||||
if not (row["has_media"] or media_row):
|
||||
return []
|
||||
ref = media_ref_from(row["message_id"], raw, media_row)
|
||||
return [ref] if ref else []
|
||||
|
||||
|
||||
def _peer_title(
|
||||
first: str | None, last: str | None, username: str | None
|
||||
) -> str | None:
|
||||
@@ -28,7 +61,24 @@ async def list_chats(
|
||||
"WHERE p.account_id = $1 AND p.peer_id = m.chat_id) AS username, "
|
||||
"(SELECT ch.title FROM chat_history ch "
|
||||
"WHERE ch.account_id = $1 AND ch.chat_id = m.chat_id "
|
||||
"AND ch.title IS NOT NULL ORDER BY ch.ts DESC LIMIT 1) AS group_title "
|
||||
"AND ch.title IS NOT NULL ORDER BY ch.ts DESC LIMIT 1) AS group_title, "
|
||||
"EXISTS (SELECT 1 FROM avatars a "
|
||||
"WHERE a.account_id = $1 AND a.owner_id = m.chat_id) AS has_avatar, "
|
||||
"(SELECT COALESCE((p.raw->>'is_bot')::bool, (p.raw->>'bot')::bool, false) "
|
||||
"FROM peers p WHERE p.account_id = $1 AND p.peer_id = m.chat_id) AS is_bot, "
|
||||
"(SELECT COALESCE((p.raw->>'is_contact')::bool, (p.raw->>'contact')::bool, "
|
||||
"false) FROM peers p "
|
||||
"WHERE p.account_id = $1 AND p.peer_id = m.chat_id) AS is_contact, "
|
||||
"(SELECT ch.raw->'chat'->>'type' = 'ChatType.CHANNEL' FROM chat_history ch "
|
||||
"WHERE ch.account_id = $1 AND ch.chat_id = m.chat_id "
|
||||
"AND ch.raw->'chat'->>'type' IS NOT NULL "
|
||||
"ORDER BY ch.ts DESC LIMIT 1) AS is_broadcast, "
|
||||
"(SELECT lm.text FROM messages lm "
|
||||
"WHERE lm.account_id = $1 AND lm.chat_id = m.chat_id "
|
||||
"ORDER BY lm.date DESC, lm.message_id DESC LIMIT 1) AS last_text, "
|
||||
"(SELECT lm.sender_id FROM messages lm "
|
||||
"WHERE lm.account_id = $1 AND lm.chat_id = m.chat_id "
|
||||
"ORDER BY lm.date DESC, lm.message_id DESC LIMIT 1) AS last_sender_id "
|
||||
"FROM messages m WHERE m.account_id = $1 "
|
||||
"GROUP BY m.chat_id ORDER BY last_date DESC LIMIT $2 OFFSET $3",
|
||||
account_id,
|
||||
@@ -44,8 +94,15 @@ async def list_chats(
|
||||
ChatListItem(
|
||||
chat_id=row["chat_id"],
|
||||
title=title,
|
||||
kind="private" if row["chat_id"] > 0 else "group",
|
||||
has_avatar=row["has_avatar"],
|
||||
is_bot=bool(row["is_bot"]),
|
||||
is_contact=bool(row["is_contact"]),
|
||||
is_broadcast=bool(row["is_broadcast"]),
|
||||
message_count=row["message_count"],
|
||||
last_date=row["last_date"],
|
||||
last_text=row["last_text"],
|
||||
last_sender_id=row["last_sender_id"],
|
||||
)
|
||||
)
|
||||
return items
|
||||
@@ -70,7 +127,43 @@ async def get_chat_history(
|
||||
page.capped_limit,
|
||||
page.offset,
|
||||
)
|
||||
return [MessageView(**dict(row)) for row in rows]
|
||||
media_by_key = await _media_map(pool, account_id, rows)
|
||||
parsed = [(row, load_raw(row["raw"])) for row in rows]
|
||||
views: list[MessageView] = []
|
||||
index = 0
|
||||
while index < len(parsed):
|
||||
group_id = parsed[index][0]["media_group_id"]
|
||||
end = index + 1
|
||||
if group_id is not None:
|
||||
while end < len(parsed) and parsed[end][0]["media_group_id"] == group_id:
|
||||
end += 1
|
||||
members = parsed[index:end]
|
||||
if len(members) == 1:
|
||||
row, raw = members[0]
|
||||
views.append(
|
||||
build_message_view(row, raw, _single_media(row, raw, media_by_key))
|
||||
)
|
||||
else:
|
||||
views.append(_build_album(members, media_by_key))
|
||||
index = end
|
||||
return views
|
||||
|
||||
|
||||
def _build_album(
|
||||
members: list[tuple[asyncpg.Record, dict]],
|
||||
media_by_key: dict[tuple[int, int], asyncpg.Record],
|
||||
) -> MessageView:
|
||||
ordered = sorted(members, key=lambda m: m[0]["message_id"])
|
||||
media: list[MediaRef] = []
|
||||
for row, raw in ordered:
|
||||
media_row = media_by_key.get((row["chat_id"], row["message_id"]))
|
||||
ref = media_ref_from(row["message_id"], raw, media_row)
|
||||
if ref:
|
||||
media.append(ref)
|
||||
primary_row, primary_raw = next(
|
||||
((row, raw) for row, raw in ordered if row["text"]), ordered[0]
|
||||
)
|
||||
return build_message_view(primary_row, primary_raw, media)
|
||||
|
||||
|
||||
async def get_deleted_messages(
|
||||
@@ -88,7 +181,14 @@ async def get_deleted_messages(
|
||||
f"ORDER BY deleted_at DESC LIMIT ${len(params) - 1} OFFSET ${len(params)}",
|
||||
*params,
|
||||
)
|
||||
return [MessageView(**dict(row)) for row in rows]
|
||||
media_by_key = await _media_map(pool, account_id, rows)
|
||||
views: list[MessageView] = []
|
||||
for row in rows:
|
||||
raw = load_raw(row["raw"])
|
||||
views.append(
|
||||
build_message_view(row, raw, _single_media(row, raw, media_by_key))
|
||||
)
|
||||
return views
|
||||
|
||||
|
||||
async def get_message_versions(
|
||||
|
||||
Reference in New Issue
Block a user