feat: add realtime capturing
This commit is contained in:
1 parent
920a0235e2
commit
3c1a12750c
29 files changed
+967
-47
No files matched your search
@@ -1,3 +1,3 @@
|
||||
from .dialog_filters import dialog_filter_handler
|
||||
from userbot.handlers import deletes, edits, messages, raw
|
||||
|
||||
__all__ = ["dialog_filter_handler"]
|
||||
handlers = messages.handlers + edits.handlers + deletes.handlers + raw.handlers
|
||||
@@ -0,0 +1,28 @@
|
||||
from pyrogram.types import Message
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.capture import repository
|
||||
from userbot.modules.capture.repository import CHANNEL_ID_THRESHOLD
|
||||
|
||||
|
||||
@PyroClient.on_deleted_messages()
|
||||
async def on_deleted_messages(client: PyroClient, messages: list[Message]) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
box: list[int] = []
|
||||
channels: dict[int, list[int]] = {}
|
||||
for message in messages:
|
||||
chat = message.chat
|
||||
chat_id = chat.id if chat is not None else None
|
||||
if chat_id is None or chat_id > CHANNEL_ID_THRESHOLD:
|
||||
box.append(message.id)
|
||||
else:
|
||||
channels.setdefault(chat_id, []).append(message.id)
|
||||
if box:
|
||||
await repository.mark_deleted_box(ctx.pool, ctx.account_id, box)
|
||||
for chat_id, ids in channels.items():
|
||||
await repository.mark_deleted_channel(ctx.pool, ctx.account_id, chat_id, ids)
|
||||
|
||||
|
||||
handlers = on_deleted_messages.handlers
|
||||
@@ -1,20 +0,0 @@
|
||||
from pyrogram import Client, raw
|
||||
from pyrogram.handlers import RawUpdateHandler
|
||||
|
||||
from userbot.folders import FolderCache
|
||||
|
||||
_FILTER_UPDATES = (
|
||||
raw.types.UpdateDialogFilter,
|
||||
raw.types.UpdateDialogFilters,
|
||||
raw.types.UpdateDialogFilterOrder,
|
||||
)
|
||||
|
||||
|
||||
def dialog_filter_handler(cache: FolderCache) -> RawUpdateHandler:
|
||||
async def on_update(
|
||||
_client: Client, update: raw.base.Update, _users: dict, _chats: dict
|
||||
) -> None:
|
||||
if isinstance(update, _FILTER_UPDATES):
|
||||
await cache.refresh()
|
||||
|
||||
return RawUpdateHandler(on_update)
|
||||
@@ -0,0 +1,36 @@
|
||||
from pyrogram.types import Message
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.handlers.messages import sender_id
|
||||
from userbot.modules.capture import repository
|
||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||
from userbot.modules.media import self_destruct_ttl
|
||||
|
||||
|
||||
@PyroClient.on_edited_message()
|
||||
async def on_edited_message(client: PyroClient, message: Message) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None or message.empty or message.chat is None or message.date is None:
|
||||
return
|
||||
chat = message.chat
|
||||
chat_id = chat.id or 0
|
||||
meta = meta_from_chat(chat, ctx.contacts.ids)
|
||||
toggles = ctx.resolve(meta)
|
||||
if not toggles.track_edits_deletes:
|
||||
return
|
||||
await repository.add_version(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
chat_id,
|
||||
message.id,
|
||||
message.date,
|
||||
sender_id(message),
|
||||
message.text or message.caption,
|
||||
str(message),
|
||||
message.edit_date,
|
||||
has_media=message.media is not None,
|
||||
is_self_destruct=self_destruct_ttl(message) is not None,
|
||||
)
|
||||
|
||||
|
||||
handlers = on_edited_message.handlers
|
||||
@@ -0,0 +1,64 @@
|
||||
from pyrogram.types import Message
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.capture import repository
|
||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||
from userbot.modules.media import capture_media, self_destruct_ttl
|
||||
|
||||
|
||||
def sender_id(message: Message) -> int | None:
|
||||
if message.from_user is not None:
|
||||
return message.from_user.id
|
||||
if message.sender_chat is not None:
|
||||
return message.sender_chat.id
|
||||
return None
|
||||
|
||||
|
||||
def _callbacks(message: Message) -> list[tuple[int, str | None, bytes | None]]:
|
||||
rows = getattr(message.reply_markup, "inline_keyboard", None)
|
||||
if not rows:
|
||||
return []
|
||||
buttons: list[tuple[int, str | None, bytes | None]] = []
|
||||
position = 0
|
||||
for row in rows:
|
||||
for button in row:
|
||||
data = button.callback_data
|
||||
if data is not None:
|
||||
encoded = data.encode() if isinstance(data, str) else data
|
||||
buttons.append((position, button.text, encoded))
|
||||
position += 1
|
||||
return buttons
|
||||
|
||||
|
||||
@PyroClient.on_message()
|
||||
async def on_message(client: PyroClient, message: Message) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None or message.empty or message.chat is None or message.date is None:
|
||||
return
|
||||
chat = message.chat
|
||||
chat_id = chat.id or 0
|
||||
meta = meta_from_chat(chat, ctx.contacts.ids)
|
||||
toggles = ctx.resolve(meta)
|
||||
if not toggles.messages:
|
||||
return
|
||||
await repository.upsert_message(
|
||||
ctx.pool,
|
||||
ctx.account_id,
|
||||
chat_id,
|
||||
message.id,
|
||||
message.date,
|
||||
sender_id(message),
|
||||
message.text or message.caption,
|
||||
str(message),
|
||||
has_media=message.media is not None,
|
||||
is_self_destruct=self_destruct_ttl(message) is not None,
|
||||
)
|
||||
await capture_media(client, message, ctx, chat_id, message.id, toggles)
|
||||
buttons = _callbacks(message)
|
||||
if buttons:
|
||||
await repository.insert_callbacks(
|
||||
ctx.pool, ctx.account_id, chat_id, message.id, buttons
|
||||
)
|
||||
|
||||
|
||||
handlers = on_message.handlers
|
||||
@@ -0,0 +1,3 @@
|
||||
from userbot.handlers.raw.dispatcher import handlers
|
||||
|
||||
__all__ = ["handlers"]
|
||||
@@ -0,0 +1,22 @@
|
||||
from pyrogram import raw
|
||||
|
||||
from userbot import PyroClient
|
||||
|
||||
HANDLES = (raw.types.UpdateContactsReset, raw.types.UpdateUser)
|
||||
|
||||
|
||||
async def handle(
|
||||
client: PyroClient, update: raw.base.Update, users: dict, _chats: dict
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
if isinstance(update, raw.types.UpdateContactsReset):
|
||||
await ctx.contacts.refresh()
|
||||
return
|
||||
if not isinstance(update, raw.types.UpdateUser):
|
||||
return
|
||||
user = users.get(update.user_id)
|
||||
if not isinstance(user, raw.types.User) or user.min:
|
||||
return
|
||||
ctx.contacts.mark(user.id, is_contact=bool(user.contact))
|
||||
@@ -0,0 +1,19 @@
|
||||
from pyrogram import raw
|
||||
|
||||
from userbot import PyroClient
|
||||
|
||||
HANDLES = (
|
||||
raw.types.UpdateDialogFilter,
|
||||
raw.types.UpdateDialogFilters,
|
||||
raw.types.UpdateDialogFilterOrder,
|
||||
)
|
||||
|
||||
|
||||
async def handle(
|
||||
client: PyroClient, _update: raw.base.Update, _users: dict, _chats: dict
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
await ctx.folders.refresh()
|
||||
await ctx.reload_policies()
|
||||
@@ -0,0 +1,25 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from pyrogram import raw
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.handlers.raw import contacts, dialog_filters, reactions
|
||||
|
||||
RawHandler = Callable[..., Awaitable[None]]
|
||||
|
||||
_MODULES = (contacts, dialog_filters, reactions)
|
||||
_REGISTRY: dict[type, RawHandler] = {
|
||||
update_type: module.handle for module in _MODULES for update_type in module.HANDLES
|
||||
}
|
||||
|
||||
|
||||
@PyroClient.on_raw_update()
|
||||
async def dispatch(
|
||||
client: PyroClient, update: raw.base.Update, users: dict, chats: dict
|
||||
) -> None:
|
||||
handler = _REGISTRY.get(type(update))
|
||||
if handler is not None:
|
||||
await handler(client, update, users, chats)
|
||||
|
||||
|
||||
handlers = dispatch.handlers
|
||||
@@ -0,0 +1,49 @@
|
||||
from pyrogram import raw, utils
|
||||
|
||||
from userbot import PyroClient
|
||||
from userbot.modules.capture import repository
|
||||
from userbot.modules.capture.chat_meta import meta_from_peer
|
||||
|
||||
HANDLES = (raw.types.UpdateMessageReactions,)
|
||||
|
||||
|
||||
def _reaction_key(reaction: raw.base.Reaction) -> str | None:
|
||||
if isinstance(reaction, raw.types.ReactionEmoji):
|
||||
return reaction.emoticon
|
||||
if isinstance(reaction, raw.types.ReactionCustomEmoji):
|
||||
return f"custom:{reaction.document_id}"
|
||||
return None
|
||||
|
||||
|
||||
def _parse_recent(reactions: raw.base.MessageReactions) -> list[tuple[int, str]]:
|
||||
recent = getattr(reactions, "recent_reactions", None) or []
|
||||
parsed: list[tuple[int, str]] = []
|
||||
for peer_reaction in recent:
|
||||
key = _reaction_key(peer_reaction.reaction)
|
||||
if key is None:
|
||||
continue
|
||||
try:
|
||||
peer_id = utils.get_peer_id(peer_reaction.peer_id)
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
parsed.append((peer_id, key))
|
||||
return parsed
|
||||
|
||||
|
||||
async def handle(
|
||||
client: PyroClient,
|
||||
update: raw.types.UpdateMessageReactions,
|
||||
users: dict,
|
||||
chats: dict,
|
||||
) -> None:
|
||||
ctx = client.capture
|
||||
if ctx is None:
|
||||
return
|
||||
meta = meta_from_peer(update.peer, chats, users, ctx.contacts.ids)
|
||||
toggles = ctx.resolve(meta)
|
||||
if not toggles.reactions:
|
||||
return
|
||||
current = _parse_recent(update.reactions)
|
||||
await repository.sync_reactions(
|
||||
ctx.pool, ctx.account_id, meta.chat_id, update.msg_id, current
|
||||
)
|
||||
Reference in new issue
Block a user