ai-mini-box-telegram 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ # TAUSIK
2
+ .tausik/
3
+
4
+ __pycache__/
5
+ *.pyc
6
+ *.pyo
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .env
11
+ *.db
12
+ migrations/versions/
13
+ !migrations/versions/.gitkeep
14
+ data/
15
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kibertum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-mini-box-telegram
3
+ Version: 0.1.0
4
+ Summary: Telegram integration for ai-mini-box — poll/daemon commands via Bot API
5
+ Project-URL: Homepage, https://github.com/Kibertum/ai-mini-box
6
+ Project-URL: Repository, https://github.com/Kibertum/ai-mini-box
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Kibertum
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: 3.13
34
+ Classifier: Topic :: Communications :: Chat
35
+ Requires-Python: >=3.12
36
+ Requires-Dist: ai-mini-box-core>=5.0.0
37
+ Requires-Dist: requests>=2.31
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest-mock>=3; extra == 'dev'
40
+ Requires-Dist: pytest>=8; extra == 'dev'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # ai-mini-box-telegram
44
+
45
+ Telegram Bot API integration for ai-mini-box. Receives messages via long polling and saves them to the database.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install ai-mini-box-telegram
51
+ ```
52
+
53
+ Requires `ai-mini-box-core>=5.0.0`.
54
+
55
+ ## Setup
56
+
57
+ Set your Telegram bot token (get one from [@BotFather](https://t.me/botfather)):
58
+
59
+ ```bash
60
+ ai-mini-box config set telegram_token "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
61
+ ```
62
+
63
+ Optionally restrict which chats the bot processes:
64
+
65
+ ```bash
66
+ ai-mini-box config set telegram_allowed_chat_ids "[123456789, 987654321]"
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### Poll once
72
+
73
+ Fetch new messages and save them:
74
+
75
+ ```bash
76
+ ai-mini-box telegram poll
77
+ # → Processed 3 new messages
78
+ ```
79
+
80
+ ### Run daemon
81
+
82
+ Continuous polling loop (Ctrl+C to stop):
83
+
84
+ ```bash
85
+ ai-mini-box telegram daemon
86
+ ```
87
+
88
+ Configuration fields used:
89
+
90
+ | Field | Default | Description |
91
+ |---|---|---|
92
+ | `telegram_token` | — | Bot token (required, encrypted) |
93
+ | `telegram_allowed_chat_ids` | `[]` | Restrict to specific chats (empty = all) |
94
+ | `poll_interval` | `30` | Seconds between polls in daemon mode |
@@ -0,0 +1,52 @@
1
+ # ai-mini-box-telegram
2
+
3
+ Telegram Bot API integration for ai-mini-box. Receives messages via long polling and saves them to the database.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install ai-mini-box-telegram
9
+ ```
10
+
11
+ Requires `ai-mini-box-core>=5.0.0`.
12
+
13
+ ## Setup
14
+
15
+ Set your Telegram bot token (get one from [@BotFather](https://t.me/botfather)):
16
+
17
+ ```bash
18
+ ai-mini-box config set telegram_token "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
19
+ ```
20
+
21
+ Optionally restrict which chats the bot processes:
22
+
23
+ ```bash
24
+ ai-mini-box config set telegram_allowed_chat_ids "[123456789, 987654321]"
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Poll once
30
+
31
+ Fetch new messages and save them:
32
+
33
+ ```bash
34
+ ai-mini-box telegram poll
35
+ # → Processed 3 new messages
36
+ ```
37
+
38
+ ### Run daemon
39
+
40
+ Continuous polling loop (Ctrl+C to stop):
41
+
42
+ ```bash
43
+ ai-mini-box telegram daemon
44
+ ```
45
+
46
+ Configuration fields used:
47
+
48
+ | Field | Default | Description |
49
+ |---|---|---|
50
+ | `telegram_token` | — | Bot token (required, encrypted) |
51
+ | `telegram_allowed_chat_ids` | `[]` | Restrict to specific chats (empty = all) |
52
+ | `poll_interval` | `30` | Seconds between polls in daemon mode |
@@ -0,0 +1,36 @@
1
+ import requests
2
+
3
+ from .exceptions import TelegramAPIError
4
+
5
+
6
+ class TelegramBot:
7
+ BASE = "https://api.telegram.org/bot"
8
+
9
+ def __init__(self, token: str):
10
+ self.token = token
11
+
12
+ def _url(self, method: str) -> str:
13
+ return f"{self.BASE}{self.token}/{method}"
14
+
15
+ def get_updates(self, offset: int | None = None, timeout: int = 10) -> list[dict]:
16
+ url = self._url("getUpdates")
17
+ params: dict = {"timeout": timeout}
18
+ if offset is not None:
19
+ params["offset"] = offset
20
+ try:
21
+ resp = requests.get(url, params=params, timeout=timeout + 5)
22
+ resp.raise_for_status()
23
+ data = resp.json()
24
+ except requests.exceptions.RequestException as e:
25
+ raise TelegramAPIError(str(e), status_code=getattr(e.response, "status_code", 0)) from e
26
+ if not data.get("ok"):
27
+ raise TelegramAPIError(data.get("description", "Unknown error"), status_code=0)
28
+ return data["result"]
29
+
30
+ def send_message(self, chat_id: int, text: str) -> bool:
31
+ url = self._url("sendMessage")
32
+ try:
33
+ resp = requests.post(url, json={"chat_id": chat_id, "text": text}, timeout=10)
34
+ return resp.ok
35
+ except requests.exceptions.RequestException:
36
+ return False
@@ -0,0 +1,92 @@
1
+ import signal
2
+ import sys
3
+ import time
4
+
5
+ import typer
6
+ from loguru import logger
7
+
8
+ from ai_mini_box.infrastructure.config import JsonConfigManager
9
+ from ai_mini_box.infrastructure.database import get_db
10
+
11
+ from .bot import TelegramBot
12
+ from .exceptions import TelegramAPIError
13
+ from .handlers import process_update
14
+ from .state import MemoryTelegramStateRepo
15
+
16
+ logger.add("logs/plugin_telegram.log", rotation="1 MB", retention=3)
17
+
18
+
19
+ def _resolve_config() -> tuple[str, list[int], int]:
20
+ config = JsonConfigManager().load()
21
+ token = config.telegram_token
22
+ if not token:
23
+ typer.echo("Error: telegram_token not set. Run: ai-mini-box config set telegram_token <token>")
24
+ raise typer.Exit(1)
25
+ return token, config.telegram_allowed_chat_ids, config.poll_interval
26
+
27
+
28
+ def _do_poll(bot: TelegramBot, state: MemoryTelegramStateRepo, allowed_chat_ids: list[int]) -> int:
29
+ offset = state.get_offset()
30
+ updates = bot.get_updates(offset=offset)
31
+ count = 0
32
+ for update in updates:
33
+ with get_db() as session:
34
+ if process_update(update, session, allowed_chat_ids=allowed_chat_ids):
35
+ count += 1
36
+ state.save_offset(update["update_id"] + 1)
37
+ return count
38
+
39
+
40
+ def register(app: typer.Typer):
41
+ tg = typer.Typer(help="Telegram bot integration")
42
+ app.add_typer(tg, name="telegram")
43
+
44
+ @tg.command()
45
+ def poll():
46
+ """Poll Telegram for new messages once and save them."""
47
+ token, allowed_chat_ids, _ = _resolve_config()
48
+ bot = TelegramBot(token)
49
+ state = MemoryTelegramStateRepo()
50
+ try:
51
+ count = _do_poll(bot, state, allowed_chat_ids)
52
+ except TelegramAPIError as e:
53
+ typer.echo(f"Error: {e}")
54
+ raise typer.Exit(1)
55
+ typer.echo(f"Processed {count} new messages")
56
+
57
+ @tg.command()
58
+ def daemon():
59
+ """Run continuous polling loop until interrupted."""
60
+ token, allowed_chat_ids, interval = _resolve_config()
61
+ bot = TelegramBot(token)
62
+ state = MemoryTelegramStateRepo()
63
+ logger.info(f"Starting telegram daemon (poll interval={interval}s)")
64
+
65
+ stop = False
66
+
67
+ def _signal_handler(signum, frame):
68
+ nonlocal stop
69
+ stop = True
70
+ logger.info("Shutdown requested, finishing current cycle...")
71
+
72
+ signal.signal(signal.SIGINT, _signal_handler)
73
+ signal.signal(signal.SIGTERM, _signal_handler)
74
+
75
+ while not stop:
76
+ logger.info(f"Polling... (interval={interval}s)")
77
+ try:
78
+ count = _do_poll(bot, state, allowed_chat_ids)
79
+ if count:
80
+ logger.info(f"Processed {count} new messages")
81
+ except TelegramAPIError as e:
82
+ logger.error(f"Polling error: {e}")
83
+ except Exception as e:
84
+ logger.error(f"Unexpected polling error: {e}")
85
+ if stop:
86
+ break
87
+ try:
88
+ time.sleep(interval)
89
+ except KeyboardInterrupt:
90
+ stop = True
91
+
92
+ logger.info("Telegram daemon stopped")
@@ -0,0 +1,5 @@
1
+ class TelegramAPIError(Exception):
2
+ def __init__(self, description: str, status_code: int = 0):
3
+ self.description = description
4
+ self.status_code = status_code
5
+ super().__init__(f"Telegram API error ({status_code}): {description}")
@@ -0,0 +1,48 @@
1
+ from sqlalchemy.orm import Session
2
+
3
+ from ai_mini_box.core.container import RepoContainer
4
+ from ai_mini_box.core.models import Contact, Message, MessageSource, Topic
5
+
6
+
7
+ def process_update(
8
+ update: dict,
9
+ session: Session,
10
+ allowed_chat_ids: list[int] | None = None,
11
+ ) -> bool:
12
+ if "message" not in update:
13
+ return False
14
+
15
+ message_data = update["message"]
16
+ chat_id = message_data["chat"]["id"]
17
+
18
+ if allowed_chat_ids and chat_id not in allowed_chat_ids:
19
+ return False
20
+
21
+ update_id = update["update_id"]
22
+ text = message_data.get("text") or message_data.get("caption", "")
23
+ repos = RepoContainer(session)
24
+
25
+ contacts = repos.contacts.list(telegram=str(chat_id), limit=1)
26
+ if contacts:
27
+ contact = contacts[0]
28
+ else:
29
+ contact = repos.contacts.add(
30
+ Contact(
31
+ name=str(chat_id),
32
+ telegram=str(chat_id),
33
+ source=MessageSource.TELEGRAM,
34
+ )
35
+ )
36
+
37
+ repos.messages.add(
38
+ Message(
39
+ source=MessageSource.TELEGRAM,
40
+ external_id=str(update_id),
41
+ chat_id=str(chat_id),
42
+ contact_id=contact.id,
43
+ text=text,
44
+ topic=Topic.OTHER,
45
+ )
46
+ )
47
+
48
+ return True
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class TelegramStateRepo(ABC):
5
+ @abstractmethod
6
+ def get_offset(self) -> int | None:
7
+ ...
8
+
9
+ @abstractmethod
10
+ def save_offset(self, offset: int) -> None:
11
+ ...
12
+
13
+
14
+ class MemoryTelegramStateRepo(TelegramStateRepo):
15
+ def __init__(self):
16
+ self._offset: int | None = None
17
+
18
+ def get_offset(self) -> int | None:
19
+ return self._offset
20
+
21
+ def save_offset(self, offset: int) -> None:
22
+ self._offset = offset
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ai-mini-box-telegram"
7
+ version = "0.1.0"
8
+ description = "Telegram integration for ai-mini-box — poll/daemon commands via Bot API"
9
+ requires-python = ">=3.12"
10
+ readme = "README.md"
11
+ license = { file = "LICENSE" }
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Communications :: Chat",
19
+ ]
20
+ dependencies = [
21
+ "ai-mini-box-core>=5.0.0",
22
+ "requests>=2.31",
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ dev = ["pytest>=8", "pytest-mock>=3"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/Kibertum/ai-mini-box"
30
+ Repository = "https://github.com/Kibertum/ai-mini-box"
31
+
32
+ [project.entry-points."ai_mini_box.tools"]
33
+ telegram = "ai_mini_box_telegram.commands:register"
34
+
35
+ [tool.hatch.build]
36
+ include = ["ai_mini_box_telegram/**"]