diff --git a/backend/src/userbot/modules/profiles/parse.py b/backend/src/userbot/modules/profiles/parse.py index a52cd45..95eeed8 100644 --- a/backend/src/userbot/modules/profiles/parse.py +++ b/backend/src/userbot/modules/profiles/parse.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from pyrogram import Client, raw from pyrogram.types import Chat, User @@ -12,6 +12,11 @@ class ProfileFields: phone: str | None = None photo_unique_id: str | None = None is_deleted_account: bool = False + unknown: frozenset[str] = field(default=frozenset(), compare=False) + + +MIN_UNKNOWN = frozenset({"username", "phone"}) +CHAT_UNKNOWN = frozenset({"phone", "is_deleted_account"}) def active_username(usernames: list[raw.types.Username]) -> str | None: @@ -35,6 +40,7 @@ async def snapshot_from_user( phone=user.phone_number if user else None, photo_unique_id=photo_unique_id, is_deleted_account=bool(getattr(raw_user, "deleted", False)), + unknown=MIN_UNKNOWN if getattr(raw_user, "min", False) else frozenset(), ) return fields, photo_file_id, photo_unique_id @@ -52,6 +58,7 @@ def snapshot_from_high_level( phone=user.phone_number, photo_unique_id=photo_unique_id, is_deleted_account=bool(user.is_deleted), + unknown=MIN_UNKNOWN if user.is_min else frozenset(), ) return fields, photo_file_id, photo_unique_id @@ -64,8 +71,7 @@ def snapshot_from_chat(chat: Chat) -> tuple[ProfileFields, str | None, str | Non first_name=chat.first_name, last_name=chat.last_name, username=chat.username, - phone=None, photo_unique_id=photo_unique_id, - is_deleted_account=False, + unknown=CHAT_UNKNOWN, ) return fields, photo_file_id, photo_unique_id diff --git a/backend/src/userbot/modules/profiles/repository.py b/backend/src/userbot/modules/profiles/repository.py index 12eb073..3eec3b5 100644 --- a/backend/src/userbot/modules/profiles/repository.py +++ b/backend/src/userbot/modules/profiles/repository.py @@ -9,26 +9,46 @@ WHERE account_id = $1 AND peer_id = $2 """ _UPSERT_PEER = """ -INSERT INTO peers +INSERT INTO peers AS p (account_id, peer_id, first_name, last_name, username, phone, photo_unique_id, is_deleted_account, raw, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, now()) ON CONFLICT (account_id, peer_id) DO UPDATE SET first_name = EXCLUDED.first_name, last_name = EXCLUDED.last_name, - username = EXCLUDED.username, - phone = EXCLUDED.phone, + username = CASE WHEN 'username' = ANY($10::text[]) + THEN COALESCE(EXCLUDED.username, p.username) ELSE EXCLUDED.username END, + phone = CASE WHEN 'phone' = ANY($10::text[]) + THEN COALESCE(EXCLUDED.phone, p.phone) ELSE EXCLUDED.phone END, photo_unique_id = EXCLUDED.photo_unique_id, - is_deleted_account = EXCLUDED.is_deleted_account, + is_deleted_account = CASE WHEN 'is_deleted_account' = ANY($10::text[]) + THEN p.is_deleted_account ELSE EXCLUDED.is_deleted_account END, raw = EXCLUDED.raw, updated_at = now() """ -_INSERT_HISTORY = """ +_INSERT_CHANGE = """ INSERT INTO peer_history (account_id, peer_id, observed_at, first_name, last_name, username, phone, photo_unique_id, is_deleted_account, raw) -VALUES ($1, $2, now(), $3, $4, $5, $6, $7, $8, $9::jsonb) +SELECT account_id, peer_id, clock_timestamp(), first_name, last_name, username, + phone, photo_unique_id, is_deleted_account, raw +FROM peers p +WHERE account_id = $1 AND peer_id = $2 + AND NOT EXISTS ( + SELECT 1 FROM ( + SELECT first_name, last_name, username, phone, photo_unique_id, + is_deleted_account + FROM peer_history + WHERE account_id = $1 AND peer_id = $2 + ORDER BY observed_at DESC LIMIT 1 + ) latest + WHERE (latest.first_name, latest.last_name, latest.username, latest.phone, + latest.photo_unique_id, latest.is_deleted_account) + IS NOT DISTINCT FROM + (p.first_name, p.last_name, p.username, p.phone, + p.photo_unique_id, p.is_deleted_account) + ) ON CONFLICT DO NOTHING """ @@ -57,17 +77,22 @@ async def write_profile( # noqa: PLR0913 raw: str, *, history: bool, -) -> None: - args = ( - fields.first_name, - fields.last_name, - fields.username, - fields.phone, - fields.photo_unique_id, - fields.is_deleted_account, - raw, - ) +) -> bool: async with pool.acquire() as conn, conn.transaction(): - await conn.execute(_UPSERT_PEER, account_id, peer_id, *args) - if history: - await conn.execute(_INSERT_HISTORY, account_id, peer_id, *args) + await conn.execute( + _UPSERT_PEER, + account_id, + peer_id, + fields.first_name, + fields.last_name, + fields.username, + fields.phone, + fields.photo_unique_id, + fields.is_deleted_account, + raw, + sorted(fields.unknown), + ) + if not history: + return False + status = await conn.execute(_INSERT_CHANGE, account_id, peer_id) + return status == "INSERT 0 1"