fix(backend): group title changes found by enrichment are recorded, enrichment and syncs skip unchanged profiles

This commit is contained in:
hh committed 2026-10-19 02:57:49 +00:00
1 parent efc431c969
commit aa41bbbc74
6 files changed
+77 -110

No files matched your search

@@ -89,17 +89,15 @@ async def _capture_chat(message: Message, ctx: CaptureContext) -> None:
meta = (chat.title, photo_unique_id) meta = (chat.title, photo_unique_id)
if not await ctx.chat_meta.changed(ctx.pool, ctx.account_id, chat.id, meta): if not await ctx.chat_meta.changed(ctx.pool, ctx.account_id, chat.id, meta):
return return
from userbot.modules.groups.repository import insert_chat_history # noqa: PLC0415 from userbot.modules.groups.repository import record_chat_meta # noqa: PLC0415
await insert_chat_history( await record_chat_meta(
ctx.pool, ctx.pool,
ctx.account_id, ctx.account_id,
chat.id, chat.id,
message.id, message.id,
"meta",
chat.title, chat.title,
photo_unique_id, photo_unique_id,
None,
message.date, message.date,
raw_json(message), raw_json(message),
) )
@@ -7,7 +7,23 @@ INSERT INTO chat_history
(account_id, chat_id, message_id, event, title, photo_unique_id, actor_id, (account_id, chat_id, message_id, event, title, photo_unique_id, actor_id,
ts, raw) ts, raw)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb)
ON CONFLICT (account_id, chat_id, message_id) DO NOTHING ON CONFLICT DO NOTHING
"""
_INSERT_CHAT_META = """
INSERT INTO chat_history
(account_id, chat_id, message_id, event, title, photo_unique_id, ts, raw)
SELECT $1, $2, $3, 'meta', $4::text, $5::text, $6, $7::jsonb
WHERE ($4::text, $5::text) IS DISTINCT FROM (
(SELECT title FROM chat_history
WHERE account_id = $1 AND chat_id = $2 AND event IN ('meta', 'title')
ORDER BY ts DESC LIMIT 1),
(SELECT photo_unique_id FROM chat_history
WHERE account_id = $1 AND chat_id = $2
AND event IN ('meta', 'photo', 'photo_deleted')
ORDER BY ts DESC LIMIT 1)
)
ON CONFLICT DO NOTHING
""" """
_INSERT_MEMBERSHIP = """ _INSERT_MEMBERSHIP = """
@@ -19,13 +35,14 @@ ON CONFLICT (account_id, chat_id, message_id, user_id) DO NOTHING
_LATEST_TITLE = """ _LATEST_TITLE = """
SELECT title FROM chat_history SELECT title FROM chat_history
WHERE account_id = $1 AND chat_id = $2 AND title IS NOT NULL WHERE account_id = $1 AND chat_id = $2 AND event IN ('meta', 'title')
ORDER BY ts DESC LIMIT 1 ORDER BY ts DESC LIMIT 1
""" """
_LATEST_PHOTO = """ _LATEST_PHOTO = """
SELECT photo_unique_id FROM chat_history SELECT photo_unique_id FROM chat_history
WHERE account_id = $1 AND chat_id = $2 AND photo_unique_id IS NOT NULL WHERE account_id = $1 AND chat_id = $2
AND event IN ('meta', 'photo', 'photo_deleted')
ORDER BY ts DESC LIMIT 1 ORDER BY ts DESC LIMIT 1
""" """
@@ -64,6 +81,29 @@ async def insert_chat_history( # noqa: PLR0913
) )
async def record_chat_meta( # noqa: PLR0913
pool: asyncpg.Pool,
account_id: int,
chat_id: int,
message_id: int,
title: str | None,
photo_unique_id: str | None,
ts: datetime,
raw: str,
) -> bool:
status = await pool.execute(
_INSERT_CHAT_META,
account_id,
chat_id,
message_id,
title,
photo_unique_id,
ts,
raw,
)
return status == "INSERT 0 1"
async def insert_membership( # noqa: PLR0913 async def insert_membership( # noqa: PLR0913
pool: asyncpg.Pool, pool: asyncpg.Pool,
account_id: int, account_id: int,
@@ -1,17 +1,11 @@
from datetime import UTC, datetime
from pyrogram import Client from pyrogram import Client
from pyrogram.errors import BadRequest, Forbidden from pyrogram.errors import BadRequest, Forbidden
from pyrogram.types import User from pyrogram.types import User
from userbot.modules.avatars import note_avatar
from userbot.modules.capture.context import CaptureContext from userbot.modules.capture.context import CaptureContext
from userbot.modules.groups.repository import insert_chat_history
from userbot.modules.jobs.context import JobContext from userbot.modules.jobs.context import JobContext
from userbot.modules.jobs.registry import register from userbot.modules.jobs.registry import register
from userbot.modules.profiles.parse import snapshot_from_high_level from userbot.modules.profiles.snapshots import save_group, save_user
from userbot.modules.profiles.repository import write_profile
from userbot.modules.raw_json import raw_json
MEMBER_CAP = 200 MEMBER_CAP = 200
@@ -25,52 +19,17 @@ LIMIT 100
""" """
async def _save_user(ctx: CaptureContext, user: User) -> None:
fields, photo_file_id, photo_unique_id = snapshot_from_high_level(user)
await write_profile(
ctx.pool,
ctx.account_id,
user.id,
fields,
raw_json(user),
history=ctx.resolve_peer(user.id, is_bot=bool(user.is_bot)).profile_history,
)
if photo_file_id and photo_unique_id:
await note_avatar(
ctx.pool, ctx.account_id, user.id, "peer", photo_unique_id, photo_file_id
)
async def _enrich_chat_meta(client: Client, ctx: CaptureContext, chat_id: int) -> None: async def _enrich_chat_meta(client: Client, ctx: CaptureContext, chat_id: int) -> None:
chat = await client.get_chat(chat_id) chat = await client.get_chat(chat_id)
if chat is None: if chat is not None:
return await save_group(ctx, chat)
photo = chat.photo
photo_unique_id = photo.big_photo_unique_id if photo else None
photo_file_id = photo.big_file_id if photo else None
await insert_chat_history(
ctx.pool,
ctx.account_id,
chat_id,
0,
"meta",
chat.title,
photo_unique_id,
None,
datetime.now(UTC),
raw_json(chat),
)
if photo_file_id and photo_unique_id:
await note_avatar(
ctx.pool, ctx.account_id, chat_id, "chat", photo_unique_id, photo_file_id
)
async def _enrich_members(client: Client, ctx: CaptureContext, chat_id: int) -> None: async def _enrich_members(client: Client, ctx: CaptureContext, chat_id: int) -> None:
try: try:
async for member in client.get_chat_members(chat_id, limit=MEMBER_CAP): async for member in client.get_chat_members(chat_id, limit=MEMBER_CAP):
if isinstance(member.user, User): if isinstance(member.user, User):
await _save_user(ctx, member.user) await save_user(ctx, member.user)
except (BadRequest, Forbidden): except (BadRequest, Forbidden):
return return
@@ -96,7 +55,7 @@ async def _enrich_senders(client: Client, ctx: CaptureContext, chat_id: int) ->
if not ids: if not ids:
return return
for user in await _fetch_users(client, ids): for user in await _fetch_users(client, ids):
await _save_user(ctx, user) await save_user(ctx, user)
@register("enrich_chat") @register("enrich_chat")
@@ -1,11 +1,8 @@
from pyrogram.types import User from pyrogram.types import User
from userbot.modules.avatars import note_avatar
from userbot.modules.jobs.context import JobContext from userbot.modules.jobs.context import JobContext
from userbot.modules.jobs.registry import register from userbot.modules.jobs.registry import register
from userbot.modules.profiles.parse import snapshot_from_high_level from userbot.modules.profiles.snapshots import save_user
from userbot.modules.profiles.repository import write_profile
from userbot.modules.raw_json import raw_json
@register("sync_contacts") @register("sync_contacts")
@@ -21,26 +18,7 @@ async def sync_contacts(ctx: JobContext) -> None:
for user in contacts: for user in contacts:
if not isinstance(user, User): if not isinstance(user, User):
continue continue
fields, photo_file_id, photo_unique_id = snapshot_from_high_level(user) await save_user(capture, user)
await write_profile(
ctx.pool,
ctx.account_id,
user.id,
fields,
raw_json(user),
history=capture.resolve_peer(
user.id, is_bot=bool(user.is_bot)
).profile_history,
)
if photo_file_id and photo_unique_id:
await note_avatar(
ctx.pool,
ctx.account_id,
user.id,
"peer",
photo_unique_id,
photo_file_id,
)
processed += 1 processed += 1
await capture.contacts.refresh() await capture.contacts.refresh()
await ctx.report_progress({"processed": processed, "done": True}) await ctx.report_progress({"processed": processed, "done": True})
@@ -2,14 +2,10 @@ from pyrogram import Client
from pyrogram.errors import BadRequest, Forbidden from pyrogram.errors import BadRequest, Forbidden
from pyrogram.types import User from pyrogram.types import User
from userbot.modules.avatars import note_avatar
from userbot.modules.capture.context import CaptureContext from userbot.modules.capture.context import CaptureContext
from userbot.modules.jobs.context import JobContext from userbot.modules.jobs.context import JobContext
from userbot.modules.jobs.registry import register from userbot.modules.jobs.registry import register
from userbot.modules.profiles.parse import snapshot_from_high_level from userbot.modules.profiles.snapshots import save_group, save_private, save_user
from userbot.modules.profiles.repository import write_profile
from userbot.modules.profiles.snapshots import save_group, save_private
from userbot.modules.raw_json import raw_json
from utils.logging import logger from utils.logging import logger
SAVE_EVERY = 100 SAVE_EVERY = 100
@@ -30,28 +26,8 @@ async def _enrich_users(client: Client, ctx: CaptureContext, ids: list[int]) ->
continue continue
users = result if isinstance(result, list) else [result] users = result if isinstance(result, list) else [result]
for user in users: for user in users:
if not isinstance(user, User): if isinstance(user, User):
continue await save_user(ctx, user)
fields, photo_file_id, photo_unique_id = snapshot_from_high_level(user)
await write_profile(
ctx.pool,
ctx.account_id,
user.id,
fields,
raw_json(user),
history=ctx.resolve_peer(
user.id, is_bot=bool(user.is_bot)
).profile_history,
)
if photo_file_id and photo_unique_id:
await note_avatar(
ctx.pool,
ctx.account_id,
user.id,
"peer",
photo_unique_id,
photo_file_id,
)
@register("sync_dialogs") @register("sync_dialogs")
@@ -1,16 +1,34 @@
from datetime import UTC, datetime from datetime import UTC, datetime
from pyrogram.enums import ChatType from pyrogram.enums import ChatType
from pyrogram.types import Chat from pyrogram.types import Chat, User
from userbot.modules.avatars import note_avatar from userbot.modules.avatars import note_avatar
from userbot.modules.capture.context import CaptureContext from userbot.modules.capture.context import CaptureContext
from userbot.modules.groups.repository import insert_chat_history from userbot.modules.groups.repository import record_chat_meta
from userbot.modules.profiles.parse import snapshot_from_chat from userbot.modules.profiles.parse import snapshot_from_chat, snapshot_from_high_level
from userbot.modules.profiles.repository import write_profile from userbot.modules.profiles.repository import write_profile
from userbot.modules.raw_json import raw_json from userbot.modules.raw_json import raw_json
async def save_user(ctx: CaptureContext, user: User) -> None:
fields, photo_file_id, photo_unique_id = snapshot_from_high_level(user)
if not await ctx.peer_identity.changed(ctx.pool, ctx.account_id, user.id, fields):
return
await write_profile(
ctx.pool,
ctx.account_id,
user.id,
fields,
raw_json(user),
history=ctx.resolve_peer(user.id, is_bot=bool(user.is_bot)).profile_history,
)
if photo_file_id and photo_unique_id:
await note_avatar(
ctx.pool, ctx.account_id, user.id, "peer", photo_unique_id, photo_file_id
)
async def save_private(ctx: CaptureContext, chat: Chat) -> bool: async def save_private(ctx: CaptureContext, chat: Chat) -> bool:
chat_id = chat.id or 0 chat_id = chat.id or 0
fields, photo_file_id, photo_unique_id = snapshot_from_chat(chat) fields, photo_file_id, photo_unique_id = snapshot_from_chat(chat)
@@ -35,15 +53,13 @@ async def save_group(ctx: CaptureContext, chat: Chat) -> None:
photo = chat.photo photo = chat.photo
photo_unique_id = photo.big_photo_unique_id if photo else None photo_unique_id = photo.big_photo_unique_id if photo else None
photo_file_id = photo.big_file_id if photo else None photo_file_id = photo.big_file_id if photo else None
await insert_chat_history( await record_chat_meta(
ctx.pool, ctx.pool,
ctx.account_id, ctx.account_id,
chat_id, chat_id,
0, 0,
"meta",
chat.title, chat.title,
photo_unique_id, photo_unique_id,
None,
datetime.now(UTC), datetime.now(UTC),
raw_json(chat), raw_json(chat),
) )