GraziePrego commited on
Commit
4f9872d
·
verified ·
1 Parent(s): 12749d0

Upload plugins/_telegram_integration/helpers/bot_manager.py with huggingface_hub

Browse files
plugins/_telegram_integration/helpers/bot_manager.py CHANGED
@@ -1,3 +1,223 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:e9a5d34fabdd28eb9d645e9da30e89fbeb74f809f7028f164a71dc416fdc66e5
3
- size 7185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ from typing import Callable, Awaitable
4
+
5
+ from aiogram import Bot, Dispatcher, Router, F
6
+ from aiogram.client.default import DefaultBotProperties
7
+ from aiogram.enums import ParseMode, ChatType, ContentType
8
+ from aiogram.filters import Command, CommandStart
9
+ from aiogram.types import Message
10
+
11
+ from helpers.errors import format_error
12
+ from helpers.print_style import PrintStyle
13
+
14
+ # Data models
15
+
16
+ @dataclass
17
+ class BotInstance:
18
+ name: str
19
+ bot: Bot
20
+ dispatcher: Dispatcher
21
+ router: Router
22
+ task: asyncio.Task | None = None # polling task
23
+ webhook_active: bool = False # True when webhook mode is registered
24
+ webhook_secret: str = "" # secret for webhook verification
25
+ group_mode: str = "mention" # current group_mode setting
26
+ bot_info: object | None = None # cached result of bot.get_me()
27
+
28
+ # Bot registry (singleton, persists across module reloads)
29
+
30
+ _bots: dict[str, BotInstance] = {}
31
+
32
+
33
+ def get_bot(name: str) -> BotInstance | None:
34
+ return _bots.get(name)
35
+
36
+
37
+ def get_all_bots() -> dict[str, BotInstance]:
38
+ return _bots
39
+
40
+ # Bot creation
41
+
42
+ def create_bot(
43
+ name: str,
44
+ token: str,
45
+ on_message: Callable[..., Awaitable],
46
+ on_command_start: Callable[..., Awaitable],
47
+ on_command_clear: Callable[..., Awaitable],
48
+ on_callback_query: Callable[..., Awaitable] | None = None,
49
+ on_new_members: Callable[..., Awaitable] | None = None,
50
+ group_mode: str = "mention",
51
+ ) -> BotInstance:
52
+ bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
53
+ dp = Dispatcher()
54
+ router = Router()
55
+
56
+ # Register command handlers
57
+ router.message.register(on_command_start, CommandStart())
58
+ router.message.register(on_command_clear, Command("clear"))
59
+
60
+ if on_callback_query:
61
+ router.callback_query.register(on_callback_query)
62
+
63
+ if on_new_members:
64
+ router.message.register(on_new_members, F.content_type == ContentType.NEW_CHAT_MEMBERS)
65
+
66
+ # Register message handler with group filtering
67
+ if group_mode == "off":
68
+ # Private chats only
69
+ router.message.register(
70
+ on_message, F.chat.type == ChatType.PRIVATE,
71
+ )
72
+ elif group_mode == "mention":
73
+ # Private chats: all messages; Groups: only when mentioned/replied
74
+ router.message.register(
75
+ on_message, F.chat.type == ChatType.PRIVATE,
76
+ )
77
+ router.message.register(
78
+ _make_group_mention_filter(on_message, bot),
79
+ )
80
+ else:
81
+ # All messages in all chats
82
+ router.message.register(on_message)
83
+
84
+ dp.include_router(router)
85
+ instance = BotInstance(name=name, bot=bot, dispatcher=dp, router=router, group_mode=group_mode)
86
+ _bots[name] = instance
87
+ return instance
88
+
89
+
90
+ async def cache_bot_info(instance: BotInstance):
91
+ """Fetch and cache bot info. Call after create_bot."""
92
+ if not instance.bot_info:
93
+ instance.bot_info = await instance.bot.get_me()
94
+ return instance.bot_info
95
+
96
+
97
+ def _make_group_mention_filter(handler: Callable, bot: Bot):
98
+ """Create a group message handler that only responds to mentions and replies."""
99
+ async def _group_handler(message: Message):
100
+ if message.chat.type == ChatType.PRIVATE:
101
+ return
102
+ # Use cached bot_info from the instance
103
+ bot_info = None
104
+ for b in _bots.values():
105
+ if b.bot is bot:
106
+ bot_info = b.bot_info
107
+ break
108
+ if not bot_info:
109
+ bot_info = await bot.get_me()
110
+ bot_username = bot_info.username or ""
111
+
112
+ # Check for reply to bot
113
+ if message.reply_to_message and message.reply_to_message.from_user:
114
+ if message.reply_to_message.from_user.id == bot_info.id:
115
+ await handler(message)
116
+ return
117
+
118
+ # Check for @mention in text or caption (media messages use caption)
119
+ text = message.text or message.caption or ""
120
+ entities = message.entities or message.caption_entities or []
121
+
122
+ if text and f"@{bot_username}" in text:
123
+ await handler(message)
124
+ return
125
+
126
+ # Check entities for mention
127
+ for entity in entities:
128
+ if entity.type == "mention":
129
+ mention_text = text[entity.offset:entity.offset + entity.length]
130
+ if mention_text.lower() == f"@{bot_username.lower()}":
131
+ await handler(message)
132
+ return
133
+
134
+ _group_handler.__name__ = f"_group_handler_{id(handler)}"
135
+ return _group_handler
136
+
137
+ # Polling
138
+
139
+ async def start_polling(instance: BotInstance) -> asyncio.Task:
140
+ # Ensure any leftover webhook is removed before polling
141
+ try:
142
+ await instance.bot.delete_webhook()
143
+ except Exception:
144
+ pass
145
+
146
+ async def _poll():
147
+ try:
148
+ PrintStyle.info(f"Telegram ({instance.name}): starting polling")
149
+ await instance.dispatcher.start_polling(
150
+ instance.bot,
151
+ handle_signals=False,
152
+ )
153
+ except asyncio.CancelledError:
154
+ PrintStyle.info(f"Telegram ({instance.name}): polling cancelled")
155
+ except Exception as e:
156
+ PrintStyle.error(f"Telegram ({instance.name}): polling error: {format_error(e)}")
157
+
158
+ task = asyncio.create_task(_poll())
159
+ instance.task = task
160
+ return task
161
+
162
+
163
+ async def stop_polling(instance: BotInstance):
164
+ if instance.task and not instance.task.done():
165
+ await instance.dispatcher.stop_polling()
166
+ instance.task.cancel()
167
+ try:
168
+ await instance.task
169
+ except asyncio.CancelledError:
170
+ pass
171
+ instance.task = None
172
+
173
+ # Webhook
174
+
175
+ async def setup_webhook(instance: BotInstance, webhook_url: str, secret: str = ""):
176
+ """Register webhook with Telegram. Updates are received via the API handler."""
177
+ full_url = f"{webhook_url.rstrip('/')}/api/plugins/_telegram_integration/webhook?bot={instance.name}"
178
+
179
+ await instance.bot.set_webhook(
180
+ url=full_url,
181
+ secret_token=secret or None,
182
+ )
183
+
184
+ instance.webhook_active = True
185
+ instance.webhook_secret = secret
186
+ PrintStyle.info(f"Telegram ({instance.name}): webhook active via {webhook_url.rstrip('/')}")
187
+
188
+
189
+ async def remove_webhook(instance: BotInstance):
190
+ try:
191
+ await instance.bot.delete_webhook()
192
+ except Exception as e:
193
+ PrintStyle.error(f"Telegram ({instance.name}): remove webhook error: {format_error(e)}")
194
+ instance.webhook_active = False
195
+ instance.webhook_secret = ""
196
+
197
+ # Cleanup
198
+
199
+ async def stop_bot(name: str):
200
+ instance = _bots.pop(name, None)
201
+ if not instance:
202
+ return
203
+ if instance.task and not instance.task.done():
204
+ await stop_polling(instance)
205
+ else:
206
+ await remove_webhook(instance)
207
+ try:
208
+ await instance.bot.session.close()
209
+ except Exception:
210
+ pass
211
+ PrintStyle.info(f"Telegram ({name}): stopped")
212
+
213
+
214
+ # Test connection
215
+
216
+ async def test_token(token: str) -> tuple[bool, str]:
217
+ try:
218
+ bot = Bot(token=token)
219
+ info = await bot.get_me()
220
+ await bot.session.close()
221
+ return True, f"Connected as @{info.username} ({info.first_name})"
222
+ except Exception as e:
223
+ return False, format_error(e)