feat: log presence, read receipts, group leaves, links, profiles, stories
This commit is contained in:
1 parent
bfd16ab02c
commit
bcb94b6474
31 files changed
+1298
-19
No files matched your search
@@ -1,3 +1,10 @@
|
||||
from userbot.handlers import deletes, edits, messages, raw
|
||||
from userbot.handlers import deletes, edits, messages, presence, raw, stories
|
||||
|
||||
handlers = messages.handlers + edits.handlers + deletes.handlers + raw.handlers
|
||||
handlers = (
|
||||
messages.handlers
|
||||
+ edits.handlers
|
||||
+ deletes.handlers
|
||||
+ presence.handlers
|
||||
+ stories.handlers
|
||||
+ raw.handlers
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from pyrogram.types import User
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.presence import repository
|
||||
|
||||
|
||||
@PyroClient.on_user_status()
|
||||
async def on_user_status(client: PyroClient, user: User) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None or user.status is None:
|
||||
return
|
||||
await repository.insert_status(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
user.id,
|
||||
datetime.now(UTC),
|
||||
user.status.name.lower(),
|
||||
user.last_online_date,
|
||||
user.next_offline_date,
|
||||
str(user.raw),
|
||||
)
|
||||
|
||||
|
||||
handlers = on_user_status.handlers
|
||||
@@ -6,14 +6,24 @@ from userbot import PyroClient
|
||||
from userbot.handlers.raw import (
|
||||
contacts,
|
||||
dialog_filters,
|
||||
profiles,
|
||||
reactions,
|
||||
read_contents,
|
||||
read_receipts,
|
||||
transcribed,
|
||||
)
|
||||
|
||||
RawHandler = Callable[..., Awaitable[None]]
|
||||
|
||||
_MODULES = (contacts, dialog_filters, reactions, read_contents, transcribed)
|
||||
_MODULES = (
|
||||
contacts,
|
||||
dialog_filters,
|
||||
profiles,
|
||||
reactions,
|
||||
read_contents,
|
||||
read_receipts,
|
||||
transcribed,
|
||||
)
|
||||
_REGISTRY: dict[type, RawHandler] = {
|
||||
update_type: module.handle for module in _MODULES for update_type in module.HANDLES
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
from dataclasses import replace
|
||||
|
||||
from pyrogram import raw
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.avatars import capture_avatar
|
||||
from userbot.modules.profiles import active_username, snapshot_from_user
|
||||
from userbot.modules.profiles.parse import ProfileFields
|
||||
from userbot.modules.profiles.repository import get_peer, write_profile
|
||||
|
||||
HANDLES = (raw.types.UpdateUserName, raw.types.UpdateUser, raw.types.UpdateUserPhone)
|
||||
|
||||
|
||||
async def _handle_user(
|
||||
client: PyroClient, update: raw.types.UpdateUser, users: dict
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
raw_user = users.get(update.user_id)
|
||||
if raw_user is None:
|
||||
return
|
||||
fields, photo_file_id, photo_unique_id = snapshot_from_user(client, raw_user)
|
||||
current = await get_peer(ctx.pool, ctx.account_id, update.user_id)
|
||||
if current == fields:
|
||||
return
|
||||
await write_profile(ctx.pool, ctx.account_id, update.user_id, fields, str(raw_user))
|
||||
changed_photo = current is None or current.photo_unique_id != photo_unique_id
|
||||
if photo_file_id and photo_unique_id and changed_photo:
|
||||
await capture_avatar(
|
||||
client,
|
||||
ctx,
|
||||
update.user_id,
|
||||
"peer",
|
||||
photo_file_id,
|
||||
photo_unique_id,
|
||||
str(raw_user),
|
||||
)
|
||||
|
||||
|
||||
async def _merge_partial(
|
||||
client: PyroClient, peer_id: int, overrides: dict, raw_str: str
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
current = await get_peer(ctx.pool, ctx.account_id, peer_id)
|
||||
merged = replace(current or ProfileFields(), **overrides)
|
||||
if merged == current:
|
||||
return
|
||||
await write_profile(ctx.pool, ctx.account_id, peer_id, merged, raw_str)
|
||||
|
||||
|
||||
async def handle(
|
||||
client: PyroClient, update: raw.base.Update, users: dict, _chats: dict
|
||||
) -> None:
|
||||
if isinstance(update, raw.types.UpdateUser):
|
||||
await _handle_user(client, update, users)
|
||||
elif isinstance(update, raw.types.UpdateUserName):
|
||||
await _merge_partial(
|
||||
client,
|
||||
update.user_id,
|
||||
{
|
||||
"first_name": update.first_name or None,
|
||||
"last_name": update.last_name or None,
|
||||
"username": active_username(update.usernames),
|
||||
},
|
||||
str(update),
|
||||
)
|
||||
elif isinstance(update, raw.types.UpdateUserPhone):
|
||||
await _merge_partial(
|
||||
client, update.user_id, {"phone": update.phone or None}, str(update)
|
||||
)
|
||||
@@ -1,7 +1,10 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from pyrogram import raw, utils
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.capture.chat_meta import meta_from_chat_id
|
||||
from userbot.modules.read_receipts import repository as receipts
|
||||
from userbot.modules.stt import repository
|
||||
from userbot.modules.stt.gate import safe_transcribe
|
||||
|
||||
@@ -19,16 +22,30 @@ async def handle(
|
||||
return
|
||||
if isinstance(update, raw.types.UpdateChannelReadMessagesContents):
|
||||
chat_id = utils.get_peer_id(raw.types.PeerChannel(channel_id=update.channel_id))
|
||||
candidates = await repository.pending_voice_reads(
|
||||
candidates = await repository.voice_reads(
|
||||
ctx.pool, ctx.account_id, update.messages, chat_id=chat_id
|
||||
)
|
||||
elif isinstance(update, raw.types.UpdateReadMessagesContents):
|
||||
candidates = await repository.pending_voice_reads(
|
||||
candidates = await repository.voice_reads(
|
||||
ctx.pool, ctx.account_id, update.messages
|
||||
)
|
||||
else:
|
||||
return
|
||||
for cand_chat_id, message_id in candidates:
|
||||
self_id = client.me.id if client.me else None
|
||||
for cand_chat_id, message_id, sender_id, untranscribed in candidates:
|
||||
if sender_id is not None and sender_id == self_id:
|
||||
await receipts.insert_listened(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
cand_chat_id,
|
||||
cand_chat_id,
|
||||
datetime.now(UTC),
|
||||
message_id,
|
||||
str(update),
|
||||
)
|
||||
continue
|
||||
if not untranscribed:
|
||||
continue
|
||||
meta = meta_from_chat_id(cand_chat_id, ctx.contacts.ids)
|
||||
if ctx.resolve(meta).stt:
|
||||
await safe_transcribe(client, ctx, cand_chat_id, message_id)
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from pyrogram import raw, utils
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.read_receipts import repository
|
||||
|
||||
HANDLES = (raw.types.UpdateReadHistoryOutbox,)
|
||||
|
||||
|
||||
async def handle(
|
||||
client: PyroClient, update: raw.base.Update, _users: dict, _chats: dict
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None or not isinstance(update, raw.types.UpdateReadHistoryOutbox):
|
||||
return
|
||||
chat_id = utils.get_peer_id(update.peer)
|
||||
await repository.insert_read(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
chat_id,
|
||||
chat_id,
|
||||
datetime.now(UTC),
|
||||
update.max_id,
|
||||
str(update),
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
from io import BytesIO
|
||||
|
||||
from pyrogram.types import Story
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.stories import repository
|
||||
|
||||
|
||||
def _peer_id(story: Story) -> int:
|
||||
if story.chat is not None:
|
||||
return story.chat.id or 0
|
||||
if story.from_user is not None:
|
||||
return story.from_user.id or 0
|
||||
return 0
|
||||
|
||||
|
||||
@PyroClient.on_story()
|
||||
async def on_story(client: PyroClient, story: Story) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
media_kind = story.media.name.lower() if story.media else None
|
||||
storage_key: str | None = None
|
||||
file_size: int | None = None
|
||||
downloaded = False
|
||||
if not story.deleted and story.media is not None:
|
||||
buffer = await client.download_media(story, in_memory=True)
|
||||
if isinstance(buffer, BytesIO):
|
||||
data = buffer.getvalue()
|
||||
storage_key = ctx.storage.put(data)
|
||||
file_size = len(data)
|
||||
downloaded = True
|
||||
await repository.upsert_story(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
_peer_id(story),
|
||||
story.id,
|
||||
story.date,
|
||||
story.expire_date,
|
||||
story.caption,
|
||||
media_kind,
|
||||
storage_key,
|
||||
file_size,
|
||||
story.views,
|
||||
str(story.raw),
|
||||
pinned=bool(story.pinned),
|
||||
deleted=bool(story.deleted),
|
||||
downloaded=downloaded,
|
||||
)
|
||||
|
||||
|
||||
handlers = on_story.handlers
|
||||
Reference in new issue
Block a user