feat: add event watcher

This commit is contained in:
h
2026-05-30 01:54:49 +02:00
parent c40e720163
commit f0afb7ec5b
11 changed files with 176 additions and 14 deletions
@@ -3,6 +3,7 @@ from pyrogram import Client
from userbot.modules.contacts import ContactCache
from userbot.modules.folders import FolderCache
from userbot.modules.watches import WatchCache
from utils.policy.models import CaptureToggles, ChatMeta, PolicySet
from utils.policy.repository import load_policy_set
from utils.policy.resolver import resolve
@@ -23,6 +24,7 @@ class CaptureContext:
self.storage = storage
self.folders = folders
self.contacts = contacts
self.watches = WatchCache(pool, account_id)
self.policies = PolicySet()
async def reload_policies(self) -> None:
@@ -44,4 +46,5 @@ async def build_capture_context(
await contacts.refresh()
ctx = CaptureContext(account_id, pool, storage, folders, contacts)
await ctx.reload_policies()
await ctx.watches.refresh()
return ctx
@@ -0,0 +1,3 @@
from userbot.modules.watches.cache import WatchCache
__all__ = ["WatchCache"]
@@ -0,0 +1,60 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from userbot.modules.watches.evaluator import (
KIND_KEYWORD,
KIND_PEER_ONLINE,
match_keyword,
match_peer_online,
)
from utils.logging import logger
from utils.read import watches as watches_read
if TYPE_CHECKING:
import asyncpg
from utils.read.models import WatchView
class WatchCache:
def __init__(self, pool: asyncpg.Pool, account_id: int) -> None:
self._pool = pool
self._account_id = account_id
self.watches: list[WatchView] = []
self._online: set[int] = set()
async def refresh(self) -> None:
rows = await watches_read.list_watches(self._pool, self._account_id)
self.watches = [watch for watch in rows if watch.enabled]
logger.info(f"[green]Watches cached:[/] {len(self.watches)}")
async def on_text(self, chat_id: int, message_id: int, text: str | None) -> None:
if not text:
return
for watch in self.watches:
if watch.kind == KIND_KEYWORD and match_keyword(
text, chat_id, watch.params
):
await watches_read.insert_alert(
self._pool,
self._account_id,
watch.id,
{"chat_id": chat_id, "message_id": message_id},
dedup_key=f"{chat_id}:{message_id}",
)
async def on_status(self, peer_id: int, *, is_online: bool) -> None:
if not is_online:
self._online.discard(peer_id)
return
if peer_id in self._online:
return
self._online.add(peer_id)
for watch in self.watches:
if watch.kind == KIND_PEER_ONLINE and match_peer_online(
peer_id, watch.params
):
await watches_read.insert_alert(
self._pool, self._account_id, watch.id, {"peer_id": peer_id}
)
@@ -0,0 +1,24 @@
import re
from typing import Any
KIND_KEYWORD = "keyword"
KIND_PEER_ONLINE = "peer_online"
def match_keyword(text: str, chat_id: int, params: dict[str, Any]) -> bool:
target_chat = params.get("chat_id")
if target_chat is not None and target_chat != chat_id:
return False
pattern = params.get("pattern")
if not pattern:
return False
if params.get("regex"):
try:
return re.search(pattern, text) is not None
except re.error:
return False
return pattern.casefold() in text.casefold()
def match_peer_online(peer_id: int, params: dict[str, Any]) -> bool:
return params.get("peer_id") == peer_id