diff --git a/README.md b/README.md index 5db79f1..0a81fa9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ A read-only Telegram client that keeps everything. A userbot on your own account logs every incoming message into Postgres, including the ones later deleted or edited, along with media, reactions, presence and stories. A web client reads that archive back, and an MCP server hands it to agents. -<<<<<<< before updating Useful on its own: a Telegram where nothing disappears. In the [beaver](https://git.kotikot.com/beaver) setup it is one of the agent's hands - the dispatcher reads chat history by the `chat_id` stored in a person's card. ## What it keeps @@ -32,7 +31,8 @@ Read-only: `list_watched_bots`, `get_bot_stats`, `get_bot_updates`, `search_mess ```sh make env # .env and docker-compose.override.yml from the examples make -C backend session # log the account in once; the session file stays in backend/sessions -make deploy # build what changed, migrate, start the containers +make -C backend migrate # the schema +make recreate # build and start the services in COMPOSE_PROFILES ``` `COMPOSE_PROFILES` in `.env` selects what runs on the machine, `docker-compose.override.yml` in each part holds the published ports and the `caddy` network aliases. The web client and the MCP endpoint are served by `api`; the bearer for both is `AUTH__TOKEN`. @@ -44,12 +44,3 @@ make fmt make check make -C frontend dev ``` -======= -```sh -make env # .env and docker-compose.override.yml from the examples -make recreate # the services in COMPOSE_PROFILES -make check -``` - -Parts: `backend/`, `frontend/`, each with its own Makefile. ->>>>>>> after updating diff --git a/backend/Makefile b/backend/Makefile index 99d7ec2..f5aef1f 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -1,13 +1,4 @@ COMPOSE := cd .. && docker compose -<<<<<<< before updating -IMAGE := beavergram/backend -IMAGE_INPUTS := pyproject.toml uv.lock Dockerfile -CODE := src scripts ../frontend -SERVICES := api userbot watcher -changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null -stamp = { git update-ref $(1) HEAD 2>/dev/null || true; } -======= ->>>>>>> after updating .PHONY: fmt check build migrate revision session diff --git a/backend/migrations/env.py b/backend/migrations/env.py index bfe3016..fdbaa2d 100644 --- a/backend/migrations/env.py +++ b/backend/migrations/env.py @@ -16,13 +16,12 @@ if config.config_file_name is not None: target_metadata = SQLModel.metadata -<<<<<<< before updating def _async_url() -> str: return env.db.connection_url.replace("postgresql://", "postgresql+asyncpg://", 1) -======= + + def include_name(name: str | None, type_: str, _parents: object) -> bool: return not (type_ == "table" and name is not None and name.startswith("taskiq_")) ->>>>>>> after updating def run_migrations_offline() -> None: @@ -37,17 +36,12 @@ def run_migrations_offline() -> None: context.run_migrations() -<<<<<<< before updating def _do_run_migrations(connection) -> None: # noqa: ANN001 - context.configure(connection=connection, target_metadata=target_metadata) -======= -def do_run_migrations(connection: Connection) -> None: context.configure( connection=connection, target_metadata=target_metadata, include_name=include_name, ) ->>>>>>> after updating with context.begin_transaction(): context.run_migrations() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 079ed12..1985075 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -39,6 +39,7 @@ unfixable = ["F401"] [tool.ruff.lint.per-file-ignores] "scripts/*" = ["INP"] +"scripts/check_imports.py" = ["T201"] "t/*" = ["ALL"] "*.lock" = ["ALL"] "migrations/*" = ["INP", "D", "E501"] diff --git a/backend/scripts/check_imports.py b/backend/scripts/check_imports.py index 9878fb3..c3831d9 100644 --- a/backend/scripts/check_imports.py +++ b/backend/scripts/check_imports.py @@ -4,8 +4,9 @@ import sys HEAVY = ["aiogram", "pyrogram", "fastapi", "google.genai"] SERVICES = { - "api": (["api.app"], {"fastapi"}), + "api": (["api.app"], {"fastapi", "pyrogram"}), "userbot": (["userbot.runner", "userbot.handlers"], {"pyrogram"}), + "watcher": (["watcher.runner"], {"pyrogram"}), } PROBE = """ import importlib, sys @@ -13,7 +14,7 @@ for module in sys.argv[1].split(","): importlib.import_module(module) print(" ".join(name for name in sys.argv[2:] if name in sys.modules)) """ -ENV = dict(os.environ) +ENV = {**os.environ, "AUTH__TOKEN": "check"} def loaded(modules: list[str]) -> tuple[set[str], str]: diff --git a/backend/scripts/session/create.py b/backend/scripts/session/create.py index 0847237..d2194e4 100644 --- a/backend/scripts/session/create.py +++ b/backend/scripts/session/create.py @@ -5,7 +5,6 @@ from userbot.modules.client import PyroClient from utils.env import env from utils.logging import logger, setup_logging -<<<<<<< before updating setup_logging() @@ -19,9 +18,6 @@ def _finalize_session(sessions_dir: Path, name: str) -> Path: target = sessions_dir / f"{name}.session" (sessions_dir / "generated.session").rename(target) return target -======= -from userbot.modules.client import PyroClient ->>>>>>> after updating async def main() -> None: diff --git a/backend/src/api/login.py b/backend/src/api/login.py index 52b5b7f..361f640 100644 --- a/backend/src/api/login.py +++ b/backend/src/api/login.py @@ -9,7 +9,7 @@ from pyrogram.errors import AuthTokenExpired, SessionPasswordNeeded from pyrogram.qrlogin import QRLogin from pyrogram.types import User -from userbot import PyroClient +from userbot.modules.client import PyroClient from utils.env import env LOGIN_TTL_SECONDS = 900 diff --git a/backend/src/api/routers/accounts.py b/backend/src/api/routers/accounts.py index 41fa8bf..5c69fd9 100644 --- a/backend/src/api/routers/accounts.py +++ b/backend/src/api/routers/accounts.py @@ -9,7 +9,7 @@ from pyrogram.errors import FloodWait, RPCError from pyrogram.types import User from api.login import LoginError, login_manager -from userbot import DEVICE_MODEL_LIMIT +from userbot.modules.client import DEVICE_MODEL_LIMIT from utils.read import accounts from utils.read.models import AccountView diff --git a/backend/src/dependencies/container.py b/backend/src/dependencies/container.py index 7bd29b6..f2cf878 100644 --- a/backend/src/dependencies/container.py +++ b/backend/src/dependencies/container.py @@ -3,8 +3,4 @@ from dishka import make_async_container from dependencies.providers.postgres import DbProvider from dependencies.providers.storage import StorageProvider -<<<<<<< before updating container = make_async_container(DbProvider(), StorageProvider()) -======= -container = make_async_container(DbProvider(), RepositoryProvider()) ->>>>>>> after updating diff --git a/backend/src/userbot/__init__.py b/backend/src/userbot/__init__.py index f322d9e..e69de29 100644 --- a/backend/src/userbot/__init__.py +++ b/backend/src/userbot/__init__.py @@ -1,6 +0,0 @@ -<<<<<<< before updating -from userbot.modules.client import DEVICE_MODEL, DEVICE_MODEL_LIMIT, PyroClient - -__all__ = ["DEVICE_MODEL", "DEVICE_MODEL_LIMIT", "PyroClient"] -======= ->>>>>>> after updating diff --git a/backend/src/userbot/handlers/deletes.py b/backend/src/userbot/handlers/deletes.py index 6927914..984835a 100644 --- a/backend/src/userbot/handlers/deletes.py +++ b/backend/src/userbot/handlers/deletes.py @@ -1,8 +1,8 @@ from pyrogram.types import Message -from userbot import PyroClient from userbot.modules.capture import repository from userbot.modules.capture.repository import CHANNEL_ID_THRESHOLD +from userbot.modules.client import PyroClient from utils.events import notify_bg_event diff --git a/backend/src/userbot/handlers/edits.py b/backend/src/userbot/handlers/edits.py index 4b7e192..1afe136 100644 --- a/backend/src/userbot/handlers/edits.py +++ b/backend/src/userbot/handlers/edits.py @@ -1,9 +1,9 @@ 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.capture.message import sender_id +from userbot.modules.client import PyroClient from userbot.modules.media import capture_media, media_unique_id, self_destruct_ttl from userbot.modules.raw_json import raw_json from utils.events import notify_bg_event diff --git a/backend/src/userbot/handlers/messages.py b/backend/src/userbot/handlers/messages.py index 03494a1..be9998e 100644 --- a/backend/src/userbot/handlers/messages.py +++ b/backend/src/userbot/handlers/messages.py @@ -1,8 +1,8 @@ from pyrogram.types import Message -from userbot import PyroClient from userbot.modules.capture import capture_message from userbot.modules.capture.chat_meta import meta_from_chat +from userbot.modules.client import PyroClient from userbot.modules.stt import is_transcribable from userbot.modules.stt.gate import safe_transcribe from utils.events import notify_bg_event diff --git a/backend/src/userbot/handlers/presence.py b/backend/src/userbot/handlers/presence.py index 7672e1f..1b29c48 100644 --- a/backend/src/userbot/handlers/presence.py +++ b/backend/src/userbot/handlers/presence.py @@ -2,7 +2,7 @@ from datetime import UTC, datetime from pyrogram.types import User -from userbot import PyroClient +from userbot.modules.client import PyroClient from userbot.modules.presence import repository from userbot.modules.raw_json import raw_json from utils.events import notify_bg_event diff --git a/backend/src/userbot/handlers/raw/contacts.py b/backend/src/userbot/handlers/raw/contacts.py index 6655705..8e86c61 100644 --- a/backend/src/userbot/handlers/raw/contacts.py +++ b/backend/src/userbot/handlers/raw/contacts.py @@ -1,6 +1,6 @@ from pyrogram import raw -from userbot import PyroClient +from userbot.modules.client import PyroClient HANDLES = (raw.types.UpdateContactsReset, raw.types.UpdateUser) diff --git a/backend/src/userbot/handlers/raw/dialog_filters.py b/backend/src/userbot/handlers/raw/dialog_filters.py index ff802b2..af47585 100644 --- a/backend/src/userbot/handlers/raw/dialog_filters.py +++ b/backend/src/userbot/handlers/raw/dialog_filters.py @@ -1,6 +1,6 @@ from pyrogram import raw -from userbot import PyroClient +from userbot.modules.client import PyroClient HANDLES = ( raw.types.UpdateDialogFilter, diff --git a/backend/src/userbot/handlers/raw/dispatcher.py b/backend/src/userbot/handlers/raw/dispatcher.py index 79c547d..c6e3e31 100644 --- a/backend/src/userbot/handlers/raw/dispatcher.py +++ b/backend/src/userbot/handlers/raw/dispatcher.py @@ -2,7 +2,6 @@ from collections.abc import Awaitable, Callable from pyrogram import raw -from userbot import PyroClient from userbot.handlers.raw import ( contacts, dialog_filters, @@ -12,6 +11,7 @@ from userbot.handlers.raw import ( read_receipts, transcribed, ) +from userbot.modules.client import PyroClient RawHandler = Callable[..., Awaitable[None]] diff --git a/backend/src/userbot/handlers/raw/profiles.py b/backend/src/userbot/handlers/raw/profiles.py index bc46f47..5e76d87 100644 --- a/backend/src/userbot/handlers/raw/profiles.py +++ b/backend/src/userbot/handlers/raw/profiles.py @@ -2,8 +2,8 @@ from dataclasses import replace from pyrogram import raw -from userbot import PyroClient from userbot.modules.avatars import capture_avatar +from userbot.modules.client import PyroClient 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 diff --git a/backend/src/userbot/handlers/raw/reactions.py b/backend/src/userbot/handlers/raw/reactions.py index d0a209c..3e944fe 100644 --- a/backend/src/userbot/handlers/raw/reactions.py +++ b/backend/src/userbot/handlers/raw/reactions.py @@ -1,8 +1,8 @@ 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 +from userbot.modules.client import PyroClient from utils.events import notify_bg_event HANDLES = (raw.types.UpdateMessageReactions,) diff --git a/backend/src/userbot/handlers/raw/read_contents.py b/backend/src/userbot/handlers/raw/read_contents.py index 9f85cbd..56dd922 100644 --- a/backend/src/userbot/handlers/raw/read_contents.py +++ b/backend/src/userbot/handlers/raw/read_contents.py @@ -2,8 +2,8 @@ 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.client import PyroClient from userbot.modules.raw_json import raw_json from userbot.modules.read_receipts import repository as receipts from userbot.modules.stt import repository diff --git a/backend/src/userbot/handlers/raw/read_receipts.py b/backend/src/userbot/handlers/raw/read_receipts.py index ec5ba6b..43e9ccf 100644 --- a/backend/src/userbot/handlers/raw/read_receipts.py +++ b/backend/src/userbot/handlers/raw/read_receipts.py @@ -2,7 +2,7 @@ from datetime import UTC, datetime from pyrogram import raw, utils -from userbot import PyroClient +from userbot.modules.client import PyroClient from userbot.modules.raw_json import raw_json from userbot.modules.read_receipts import repository from utils.events import notify_bg_event diff --git a/backend/src/userbot/handlers/raw/transcribed.py b/backend/src/userbot/handlers/raw/transcribed.py index 78460ae..335743b 100644 --- a/backend/src/userbot/handlers/raw/transcribed.py +++ b/backend/src/userbot/handlers/raw/transcribed.py @@ -1,7 +1,7 @@ from pyrogram import raw -from userbot import PyroClient from userbot.modules.capture.chat_meta import meta_from_peer +from userbot.modules.client import PyroClient from userbot.modules.stt import repository HANDLES = (raw.types.UpdateTranscribedAudio,) diff --git a/backend/src/userbot/handlers/stories.py b/backend/src/userbot/handlers/stories.py index c0d9ccf..93385de 100644 --- a/backend/src/userbot/handlers/stories.py +++ b/backend/src/userbot/handlers/stories.py @@ -1,6 +1,6 @@ from pyrogram.types import Story -from userbot import PyroClient +from userbot.modules.client import PyroClient from userbot.modules.stories.service import save_story diff --git a/backend/src/userbot/runner.py b/backend/src/userbot/runner.py index bf45952..36f8da7 100644 --- a/backend/src/userbot/runner.py +++ b/backend/src/userbot/runner.py @@ -10,8 +10,8 @@ import asyncpg import uvloop from dependencies.container import container -from userbot import PyroClient from userbot.modules.capture import CaptureContext, build_capture_context +from userbot.modules.client import PyroClient from userbot.modules.jobs import JobConsumer from utils.env import env from utils.jobs import enqueue diff --git a/backend/src/utils/env.py b/backend/src/utils/env.py index 15097c9..f945eb6 100644 --- a/backend/src/utils/env.py +++ b/backend/src/utils/env.py @@ -4,27 +4,7 @@ from pydantic import Field, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict -<<<<<<< before updating class DatabaseSettings(BaseSettings): -======= -def is_prod() -> bool: - return os.getenv("RUN_ENVIRONMENT") == "prod" - - -class Section(BaseSettings): - model_config = SettingsConfigDict(extra="ignore") - - -class LogSettings(Section): - level: str = "INFO" - level_external: str = "WARNING" - show_time: bool = False - show_locals: bool = False - console_width: int = 150 - - -class DatabaseSettings(Section): ->>>>>>> after updating host: str = "postgres" port: int = 5432 user: str = "beavergram" @@ -76,6 +56,7 @@ class LogSettings(BaseSettings): level: str = "INFO" level_external: str = "WARNING" show_time: bool = False + show_locals: bool = False console_width: int = 150 diff --git a/backend/src/watcher/drain.py b/backend/src/watcher/drain.py index 634a69a..17c12e0 100644 --- a/backend/src/watcher/drain.py +++ b/backend/src/watcher/drain.py @@ -13,10 +13,10 @@ from dataclasses import dataclass from pyrogram import raw, types from pyrogram.errors import RPCError -from userbot import PyroClient from userbot.modules.capture import capture_message from userbot.modules.capture.chat_meta import meta_from_chat from userbot.modules.capture.context import CaptureContext +from userbot.modules.client import PyroClient from utils.logging import logger EARLIEST_PTS = 1 diff --git a/backend/src/watcher/registry.py b/backend/src/watcher/registry.py index 039f21c..54094d6 100644 --- a/backend/src/watcher/registry.py +++ b/backend/src/watcher/registry.py @@ -11,9 +11,9 @@ from pyrogram.handlers import ( MessageHandler, ) -from userbot import PyroClient from userbot.modules.capture import capture_message from userbot.modules.capture.chat_meta import meta_from_chat +from userbot.modules.client import PyroClient from userbot.modules.jobs import JobConsumer from utils.events import notify_bg_event from utils.jobs import enqueue