feat(api,userbot,frontend): search peers without chats and start tracking them
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Annotated
|
||||
|
||||
import asyncpg
|
||||
from dishka.integrations.fastapi import DishkaRoute, FromDishka
|
||||
from fastapi import APIRouter, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api.routers.policy import POLICY_CHANGED_CHANNEL
|
||||
from utils.jobs import enqueue
|
||||
from utils.policy import repository as policy_repository
|
||||
from utils.policy.defaults import TRACKING
|
||||
from utils.policy.models import ScopeType
|
||||
from utils.read import discover
|
||||
from utils.read.models import DiscoverItem
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["discover"], route_class=DishkaRoute)
|
||||
|
||||
DEFAULT_LIMIT = 30
|
||||
REMOTE_TIMEOUT_SECONDS = 20.0
|
||||
POLL_INTERVAL_SECONDS = 0.2
|
||||
FINISHED = ("done", "failed", "canceled")
|
||||
|
||||
_CHAT_POLICY_ID = """
|
||||
SELECT id FROM capture_policy
|
||||
WHERE account_id = $1 AND scope_type = 'chat' AND scope_id = $2
|
||||
"""
|
||||
|
||||
AccountId = Annotated[int, Query()]
|
||||
|
||||
|
||||
class TrackRequest(BaseModel):
|
||||
account_id: int
|
||||
backfill: bool = True
|
||||
|
||||
|
||||
class SyncContactsRequest(BaseModel):
|
||||
account_id: int
|
||||
|
||||
|
||||
async def _remote_ids(
|
||||
pool: asyncpg.Pool, account_id: int, query: str, limit: int
|
||||
) -> list[int]:
|
||||
job_id = await enqueue(
|
||||
pool, account_id, "search_peers", {"query": query, "limit": limit}
|
||||
)
|
||||
loop = asyncio.get_running_loop()
|
||||
deadline = loop.time() + REMOTE_TIMEOUT_SECONDS
|
||||
while loop.time() < deadline:
|
||||
await asyncio.sleep(POLL_INTERVAL_SECONDS)
|
||||
row = await pool.fetchrow(
|
||||
"SELECT status, progress FROM jobs WHERE id = $1", job_id
|
||||
)
|
||||
if row is not None and row["status"] in FINISHED:
|
||||
await pool.execute("DELETE FROM jobs WHERE id = $1", job_id)
|
||||
return json.loads(row["progress"]).get("ids", [])
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/discover")
|
||||
async def discover_peers(
|
||||
pool: FromDishka[asyncpg.Pool],
|
||||
account_id: AccountId,
|
||||
query: Annotated[str, Query()] = "",
|
||||
remote: Annotated[bool, Query()] = False,
|
||||
limit: Annotated[int, Query()] = DEFAULT_LIMIT,
|
||||
) -> list[DiscoverItem]:
|
||||
if not query.strip():
|
||||
return []
|
||||
items = await discover.search(pool, account_id, query, limit)
|
||||
if not remote:
|
||||
return items
|
||||
known = {item.chat_id for item in items}
|
||||
ids = await _remote_ids(pool, account_id, query, limit)
|
||||
extra = await discover.by_ids(
|
||||
pool, account_id, [chat_id for chat_id in ids if chat_id not in known]
|
||||
)
|
||||
return [*items, *extra]
|
||||
|
||||
|
||||
@router.get("/discover/{chat_id}")
|
||||
async def discover_chat(
|
||||
pool: FromDishka[asyncpg.Pool], chat_id: int, account_id: AccountId
|
||||
) -> DiscoverItem:
|
||||
return await discover.get_item(pool, account_id, chat_id)
|
||||
|
||||
|
||||
@router.post("/chats/{chat_id}/track", status_code=201)
|
||||
async def track_chat(
|
||||
pool: FromDishka[asyncpg.Pool], chat_id: int, body: TrackRequest
|
||||
) -> DiscoverItem:
|
||||
kind = await discover.chat_kind(pool, body.account_id, chat_id)
|
||||
toggles = TRACKING[kind]
|
||||
policy_id = await pool.fetchval(_CHAT_POLICY_ID, body.account_id, chat_id)
|
||||
if policy_id is None:
|
||||
await policy_repository.create_policy(
|
||||
pool, body.account_id, ScopeType.CHAT, chat_id, toggles
|
||||
)
|
||||
else:
|
||||
await policy_repository.update_policy(pool, policy_id, toggles)
|
||||
await pool.execute(f"NOTIFY {POLICY_CHANGED_CHANNEL}")
|
||||
await enqueue(pool, body.account_id, "enrich_chat", {"chat_id": chat_id})
|
||||
if body.backfill:
|
||||
await enqueue(
|
||||
pool, body.account_id, "backfill", {"chat_id": chat_id, "media": True}
|
||||
)
|
||||
return await discover.get_item(pool, body.account_id, chat_id)
|
||||
|
||||
|
||||
@router.post("/contacts/sync", status_code=201)
|
||||
async def sync_contacts(
|
||||
pool: FromDishka[asyncpg.Pool], body: SyncContactsRequest
|
||||
) -> dict[str, int]:
|
||||
job_id = await enqueue(pool, body.account_id, "sync_contacts", {})
|
||||
return {"job_id": job_id}
|
||||
Reference in New Issue
Block a user