125 lines
4.0 KiB
Python
125 lines
4.0 KiB
Python
from datetime import UTC, datetime, timedelta
|
|
|
|
from pyrogram import Client, raw
|
|
from pyrogram.errors import (
|
|
ChannelInvalid,
|
|
ChannelPrivate,
|
|
ChatTooBig,
|
|
FloodPremiumWait,
|
|
FloodWait,
|
|
MessageIdInvalid,
|
|
MessageNotReadYet,
|
|
MessageTooOld,
|
|
MsgIdInvalid,
|
|
MsgTooOld,
|
|
PeerIdInvalid,
|
|
RPCError,
|
|
UserPrivacyRestricted,
|
|
YourPrivacyRestricted,
|
|
)
|
|
|
|
from userbot.modules.jobs.context import JobContext
|
|
from userbot.modules.jobs.registry import register
|
|
from userbot.modules.read_receipts.repository import save_fetch, save_reads
|
|
from utils.jobs import enqueue
|
|
from utils.logging import logger
|
|
from utils.policy.models import ChatKind
|
|
from utils.read.peers import get_peers
|
|
from utils.read.read_receipts import read_info
|
|
|
|
RETRY = timedelta(seconds=60)
|
|
LATER = timedelta(days=1)
|
|
|
|
Refusal = tuple[tuple[type[Exception], ...], str, timedelta]
|
|
|
|
REFUSALS: tuple[Refusal, ...] = (
|
|
((MessageNotReadYet,), "not_read_yet", RETRY),
|
|
((UserPrivacyRestricted,), "user_privacy", RETRY),
|
|
((YourPrivacyRestricted,), "your_privacy", LATER),
|
|
((ChatTooBig,), "chat_too_big", LATER),
|
|
((MsgTooOld, MessageTooOld), "too_old", LATER),
|
|
(
|
|
(
|
|
PeerIdInvalid,
|
|
ChannelInvalid,
|
|
ChannelPrivate,
|
|
MessageIdInvalid,
|
|
MsgIdInvalid,
|
|
KeyError,
|
|
ValueError,
|
|
),
|
|
"unavailable",
|
|
RETRY,
|
|
),
|
|
)
|
|
|
|
|
|
def _refusal(exc: Exception) -> tuple[str, timedelta]:
|
|
for kinds, outcome, retry in REFUSALS:
|
|
if isinstance(exc, kinds):
|
|
return outcome, retry
|
|
return "error", RETRY
|
|
|
|
|
|
def _at(timestamp: int) -> datetime:
|
|
return datetime.fromtimestamp(timestamp, UTC)
|
|
|
|
|
|
async def ask_telegram(
|
|
client: Client, chat_id: int, message_id: int, *, group: bool
|
|
) -> list[tuple[int, datetime]]:
|
|
peer = await client.resolve_peer(chat_id)
|
|
if peer is None:
|
|
msg = f"unknown peer {chat_id}"
|
|
raise ValueError(msg)
|
|
if group:
|
|
participants = await client.invoke(
|
|
raw.functions.messages.GetMessageReadParticipants(
|
|
peer=peer, msg_id=message_id
|
|
)
|
|
)
|
|
return [(item.user_id, _at(item.date)) for item in participants]
|
|
outbox = await client.invoke(
|
|
raw.functions.messages.GetOutboxReadDate(peer=peer, msg_id=message_id)
|
|
)
|
|
return [(chat_id, _at(outbox.date))]
|
|
|
|
|
|
async def _enrich_unknown(ctx: JobContext, chat_id: int, reader_ids: list[int]) -> None:
|
|
known = {
|
|
peer.peer_id for peer in await get_peers(ctx.pool, ctx.account_id, reader_ids)
|
|
}
|
|
if set(reader_ids) - known:
|
|
await enqueue(ctx.pool, ctx.account_id, "enrich_chat", {"chat_id": chat_id})
|
|
|
|
|
|
@register("fetch_read_info")
|
|
async def fetch_read_info(ctx: JobContext) -> None:
|
|
client = ctx.client
|
|
if client is None:
|
|
return
|
|
chat_id = ctx.job.params["chat_id"]
|
|
message_id = ctx.job.params["message_id"]
|
|
info = await read_info(ctx.pool, ctx.account_id, chat_id, message_id)
|
|
if info is None or not info.can_fetch:
|
|
return
|
|
group = info.chat_kind == ChatKind.GROUP
|
|
try:
|
|
reads = await ask_telegram(client, chat_id, message_id, group=group)
|
|
except (FloodWait, FloodPremiumWait) as exc:
|
|
wait = exc.value if isinstance(exc.value, int) else 0
|
|
retry = max(RETRY, timedelta(seconds=wait))
|
|
await save_fetch(ctx.pool, ctx.account_id, chat_id, message_id, "flood", retry)
|
|
return
|
|
except (RPCError, KeyError, ValueError) as exc:
|
|
outcome, retry = _refusal(exc)
|
|
if outcome == "error":
|
|
logger.warning(f"[yellow]Read info {chat_id}/{message_id}: {exc!r}[/]")
|
|
await save_fetch(ctx.pool, ctx.account_id, chat_id, message_id, outcome, retry)
|
|
return
|
|
source = "participants" if group else "outbox_date"
|
|
await save_reads(ctx.pool, ctx.account_id, chat_id, message_id, source, reads)
|
|
await save_fetch(ctx.pool, ctx.account_id, chat_id, message_id, "ok", RETRY)
|
|
if group and reads:
|
|
await _enrich_unknown(ctx, chat_id, [reader_id for reader_id, _ in reads])
|