From 87cb337a34b9cf719d5cf53fb28f10ca2519a3f7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 22:58:10 +0000 Subject: [PATCH] fix: receive updates via Bot API when Telethon MTProto is blocked Cloud Agent and similar networks allow HTTPS Bot API but reset raw MTProto, so importing bot_telethon used to crash before the bot could start. Connect Telethon lazily, keep send/update workers alive without it, and long-poll getUpdates as a receive fallback. Co-authored-by: Kovalenko K. --- agent/bot_telethon.py | 66 +++++--- app/controller/general/notify.py | 7 +- app/core/balancers/recordSender.py | 33 +++- app/core/balancers/telebotAnswerer.py | 7 + app/core/controller.py | 8 +- app/jobs/podcastsUpdater.py | 19 ++- app/routes/botapi_receiver.py | 211 ++++++++++++++++++++++++++ app/routes/initialize_routes.py | 132 +++++++++------- app/routes/test_botapi_receiver.py | 51 +++++++ main.py | 19 ++- 10 files changed, 454 insertions(+), 99 deletions(-) create mode 100644 app/routes/botapi_receiver.py create mode 100644 app/routes/test_botapi_receiver.py diff --git a/agent/bot_telethon.py b/agent/bot_telethon.py index 4487fb0e..2df4de5e 100644 --- a/agent/bot_telethon.py +++ b/agent/bot_telethon.py @@ -18,22 +18,54 @@ api_hash = config.app_api_hash bot_token = config.token -thonbot = TelegramClient( - session_handler, app_id, api_hash).start(bot_token=bot_token) +# Do not connect at import time: MTProto is blocked in some environments +# (Cloud Agent, filtered networks) while Bot API HTTPS still works. +thonbot = TelegramClient(session_handler, app_id, api_hash) +thonbot_uploader: TelegramClient | None = None +thobot_session_handler = "" +telethon_available = False -if os.path.exists(uploader_session): - with open(uploader_session, 'r') as f: - string_session = StringSession(f.readline()) -else: - string_session = StringSession() +logger = Logger(file="sender") -thonbot_uploader = TelegramClient(string_session, app_id, api_hash).start(bot_token=bot_token) -thobot_session_handler = thonbot_uploader.session.save() -logger = Logger(file="sender") +def try_start_telethon() -> bool: + """Connect Telethon clients. Returns False when MTProto is unavailable.""" + global thonbot_uploader, thobot_session_handler, telethon_available + + if telethon_available: + return True + + if os.environ.get('TELEGRAM_FORCE_BOTAPI', '').lower() in ('1', 'true', 'yes'): + logger.log("TELEGRAM_FORCE_BOTAPI is set, skipping MTProto") + return False -with open(uploader_session, 'w+') as f: - f.write(thobot_session_handler) + if not bot_token or not app_id or not api_hash: + logger.log("Telethon credentials missing, skipping MTProto") + return False + + try: + thonbot.start(bot_token=bot_token) + + if os.path.exists(uploader_session): + with open(uploader_session, 'r') as f: + string_session = StringSession(f.readline()) + else: + string_session = StringSession() + + uploader = TelegramClient(string_session, app_id, api_hash).start( + bot_token=bot_token) + session_string = uploader.session.save() + with open(uploader_session, 'w+') as f: + f.write(session_string) + + thonbot_uploader = uploader + thobot_session_handler = session_string + telethon_available = True + logger.log("Telethon MTProto connected") + return True + except Exception as e: + logger.err("Telethon MTProto connect failed:", e) + return False async def __uploader(local_thonbot, fname, callback=None): @@ -131,13 +163,3 @@ def get_next_ep_button(argv): else: return None - -# def get_or_create_event_loop(): -# try: -# loop = asyncio.get_event_loop() -# except RuntimeError: -# loop = asyncio.new_event_loop() -# asyncio.set_event_loop(loop) -# print("LOOOOP IS ", loop, flush=True) -# return loop - diff --git a/app/controller/general/notify.py b/app/controller/general/notify.py index 81a9fd28..5aff920a 100644 --- a/app/controller/general/notify.py +++ b/app/controller/general/notify.py @@ -19,8 +19,11 @@ def notify( disable_web_page_preview: bool = True ): if call is not None: - bot.answer_callback_query( - callback_query_id=call.id, show_alert=alert, text=text) + try: + bot.answer_callback_query( + callback_query_id=call.id, show_alert=alert, text=text) + except Exception: + pass return if message is not None: diff --git a/app/core/balancers/recordSender.py b/app/core/balancers/recordSender.py index 4af1fd7d..1db23177 100644 --- a/app/core/balancers/recordSender.py +++ b/app/core/balancers/recordSender.py @@ -6,7 +6,7 @@ from telethon import TelegramClient from telethon.sessions import StringSession -from agent.bot_telethon import thobot_session_handler +from agent import bot_telethon from app.controller.builders import recsModule from app.jobs import podcastsUpdater from config import app_api_id, app_api_hash, token, threads_config @@ -155,12 +155,9 @@ def run(self): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop = asyncio.get_event_loop() - thonbot = TelegramClient( - StringSession(thobot_session_handler), app_api_id, app_api_hash, loop=loop - ).start(bot_token=token) - thonbot.disconnect() + thonbot = None + logger.log(f"{self.thread_num} waiting for send jobs") while True: try: @@ -171,7 +168,14 @@ def run(self): try: logger.log(f"Sending in thread #{self.thread_num}") - self.process_input(input_data, thonbot) + if thonbot is None: + thonbot = self._connect_telethon(loop) + if thonbot is None: + logger.warn( + f"{self.thread_num} Telethon unavailable, " + f"cannot {input_data.get('action')}") + else: + self.process_input(input_data, thonbot) except Exception as e: logger.err(f"{self.thread_num} failed sending, continuing:", e) finally: @@ -179,6 +183,21 @@ def run(self): if self.thread_queue.empty(): self.pause() + def _connect_telethon(self, loop): + if not bot_telethon.telethon_available or not bot_telethon.thobot_session_handler: + logger.log(f"{self.thread_num} running without Telethon") + return None + try: + client = TelegramClient( + StringSession(bot_telethon.thobot_session_handler), + app_api_id, app_api_hash, loop=loop + ).start(bot_token=token) + client.disconnect() + return client + except Exception as e: + logger.err(f"{self.thread_num} Telethon connect failed:", e) + return None + def process_input(self, input_data, thonbot): if input_data['action'] == 'rec': diff --git a/app/core/balancers/telebotAnswerer.py b/app/core/balancers/telebotAnswerer.py index 1eb65910..4aad756e 100644 --- a/app/core/balancers/telebotAnswerer.py +++ b/app/core/balancers/telebotAnswerer.py @@ -132,6 +132,13 @@ def _serve(self, input_data: HandleInThreadParams): controller_params, input_data['data']['user'], start_related_params['is_new_user'], start_related_params['is_by_refer'], start_related_params['action']) + callback = controller_params.get('callback') + if callback is not None: + try: + from agent.bot_telebot import bot + bot.answer_callback_query(callback.id) + except Exception: + pass # Inline query elif 'inline' in input_data['data']: diff --git a/app/core/controller.py b/app/core/controller.py index 054c033e..dc9ebe23 100644 --- a/app/core/controller.py +++ b/app/core/controller.py @@ -1,5 +1,3 @@ -import telethon - from app.repository.storage import storage from app.routes import router_tools from app.routes.routes import RouteMap @@ -10,8 +8,8 @@ from lib.python import dict_tools -def empty_state_input_state_corrector(event: telethon.events.NewMessage.Event): - prev, curr = storage.get_user_prev_curr_states(event.chat_id) +def empty_state_input_state_corrector(chat_id: int): + prev, curr = storage.get_user_prev_curr_states(chat_id) if prev is None: return @@ -31,7 +29,7 @@ def empty_state_input_state_corrector(event: telethon.events.NewMessage.Event): (dict_tools.deep_get(RouteMap.ROUTES, prev, 'waits_for_input', default=False) or someone_have_state_for_input) and curr == empty): - storage.del_user_curr_state(event.chat_id) + storage.del_user_curr_state(chat_id) def construct_params( diff --git a/app/jobs/podcastsUpdater.py b/app/jobs/podcastsUpdater.py index 9d24c499..e7c33898 100644 --- a/app/jobs/podcastsUpdater.py +++ b/app/jobs/podcastsUpdater.py @@ -15,7 +15,7 @@ import app.service.user.language import lib.markup.cleaner from agent.bot_telebot import bot -from agent.bot_telethon import thobot_session_handler +from agent import bot_telethon from app.controller.builders.helpModule import get_promo_messages from app.controller.general.notify import notify from app.core.sender.send_record_helper import ChatParamsType, DescriptionModeOptions @@ -51,13 +51,18 @@ def main(interval=120): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop = asyncio.get_event_loop() - asyncio.set_event_loop(loop) - thonbot = TelegramClient( - StringSession(thobot_session_handler), app_api_id, app_api_hash, loop=loop - ).start(bot_token=token) - thonbot.disconnect() + if bot_telethon.telethon_available and bot_telethon.thobot_session_handler: + try: + thonbot = TelegramClient( + StringSession(bot_telethon.thobot_session_handler), + app_api_id, app_api_hash, loop=loop + ).start(bot_token=token) + thonbot.disconnect() + except Exception as e: + logger.err("Telethon unavailable for updater:", e) + else: + logger.log("Updater running without Telethon session warmup") while True: if not server: diff --git a/app/routes/botapi_receiver.py b/app/routes/botapi_receiver.py new file mode 100644 index 00000000..53aaae13 --- /dev/null +++ b/app/routes/botapi_receiver.py @@ -0,0 +1,211 @@ +"""Bot API long-polling receive path. + +Telegram MTProto (Telethon) is blocked on some networks while HTTPS Bot API +still works. Incoming updates are converted to the same ptypes used by the +Telethon handlers and dispatched through the existing worker queue. +""" +from typing import Any, Iterator + +from agent.bot_telebot import bot +import config +from app.core.controller import empty_state_input_state_corrector +from app.repository.storage import storage +from app.routes.initialize_routes import ( + dispatch_action, dispatch_go_back, dispatch_inline, dispatch_maintenance, + dispatch_route, get_tp) +from app.routes.ptypes import Callback, Chat, ForwardedFrom, Inline, Message, User +from app.routes.routes import RouteMap +from app.routes.routes_list import AvailableRoutes +from app.service.payment import starsPaymentModule +from lib.python import dict_tools +from lib.tools.logger import logger + + +def command_name(text: str) -> str | None: + if not text or not text.startswith('/'): + return None + token = text[1:].split()[0] + if '@' in token: + token = token.split('@', 1)[0] + return token or None + + +def is_channel_chat(chat: Any) -> bool: + return getattr(chat, 'type', None) == 'channel' + + +def iter_command_routes(command: str) -> Iterator[AvailableRoutes]: + for route, params in RouteMap.ROUTES.items(): + if params is None: + continue + if 'command' not in params.get('available_from', []): + continue + names = list(params.get('commands') or [route]) + if command in names: + yield route + + +def message_from_telebot(msg: Any) -> Message: + lang = '' + if getattr(msg, 'from_user', None) is not None and msg.from_user.language_code: + lang = msg.from_user.language_code + user = User(lang) + chat = Chat(msg.chat.id) + fwd = None + fwd_chat = getattr(msg, 'forward_from_chat', None) + if fwd_chat is not None and getattr(fwd_chat, 'id', None) is not None: + fwd = ForwardedFrom(str(fwd_chat.id)) + return Message(msg.message_id, chat, user, msg.text or '', fwd) + + +def callback_from_telebot(call: Any) -> tuple[Callback, Message | None]: + lang = '' + if getattr(call, 'from_user', None) is not None and call.from_user.language_code: + lang = call.from_user.language_code + user = User(lang) + if call.message is not None: + chat = Chat(call.message.chat.id) + message = message_from_telebot(call.message) + else: + chat = Chat(call.from_user.id) + message = None + data = call.data if call.data is not None else '' + return Callback(call.id, data, user, chat, message), message + + +def inline_from_telebot(query: Any) -> Inline: + offset = 0 + try: + offset = int(query.offset or 0) + except Exception: + offset = 0 + return Inline(query.id, query.from_user.id, query.query or '', offset) + + +def _action_matches(route: str, action: str, chat_id: int, tp: str) -> bool: + if tp != action: + return False + return ( + storage.get_user_curr_state(chat_id) == route + or dict_tools.deep_get( + RouteMap.ROUTES, route, 'actions', action, 'state_independent') is True + ) + + +def on_botapi_message(message: Any) -> None: + if is_channel_chat(message.chat): + return + + empty_state_input_state_corrector(message.chat.id) + pmsg = message_from_telebot(message) + text = pmsg.text or '' + cmd = command_name(text) + if cmd is not None: + for route in iter_command_routes(cmd): + dispatch_route(route, None, pmsg) + return + + for route, params in RouteMap.ROUTES.items(): + if params is None: + continue + if 'message' not in params.get('available_from', []): + continue + states = params.get('states_for_input', [route]) + if storage.get_user_curr_state(message.chat.id) in states: + dispatch_route(route, None, pmsg) + + +def on_botapi_callback(call: Any) -> None: + if call.message is not None and is_channel_chat(call.message.chat): + return + + callback, message = callback_from_telebot(call) + tp = get_tp(callback.data) + if tp == 'bck': + dispatch_go_back(callback, message) + return + + for route, params in RouteMap.ROUTES.items(): + if params is None: + continue + if 'call' in params.get('available_from', []) and tp == route: + dispatch_route(route, callback, message) + actions = params.get('actions') + if actions: + for action in actions: + if _action_matches(route, action, callback.chat.id, tp): + dispatch_action( + route, action, callback, message) + + +def _on_precheckout(query: Any) -> None: + try: + success, error = starsPaymentModule.validate_precheckout_payload( + query.invoice_payload, query.from_user.id, query.currency, query.total_amount) + except Exception as e: + logger.err(e) + success, error = False, "Invalid payment payload" + try: + bot.answer_pre_checkout_query( + query.id, ok=success, error_message=error if not success else None) + except Exception as e: + logger.err(e) + + +def _on_successful_payment(message: Any) -> None: + payment = message.successful_payment + if payment is None: + return + starsPaymentModule.process_successful_payment( + message.chat.id, + payment.currency, + payment.total_amount, + payment.invoice_payload, + payment.telegram_payment_charge_id, + payment.provider_payment_charge_id) + + +def initialize_botapi_routes() -> None: + if config.maintenance: + @bot.message_handler(func=lambda _m: True) + def maintenance_messages(message: Any) -> None: + dispatch_maintenance(None, message_from_telebot(message)) + + @bot.callback_query_handler(func=lambda _c: True) + def maintenance_callbacks(call: Any) -> None: + callback, message = callback_from_telebot(call) + dispatch_maintenance(callback, message) + return + + @bot.pre_checkout_query_handler(func=lambda _q: True) + def precheckout(query: Any) -> None: + _on_precheckout(query) + + @bot.message_handler(content_types=['successful_payment']) + def successful_payment(message: Any) -> None: + _on_successful_payment(message) + + @bot.inline_handler(func=lambda _q: True) + def inline_queries(query: Any) -> None: + dispatch_inline(inline_from_telebot(query)) + + @bot.callback_query_handler(func=lambda _c: True) + def callbacks(call: Any) -> None: + on_botapi_callback(call) + + @bot.message_handler(content_types=['text']) + def texts(message: Any) -> None: + on_botapi_message(message) + + +def run_botapi_polling() -> None: + bot.remove_webhook() + logger.log("Receiving via Bot API long polling") + bot.infinity_polling( + skip_pending=True, + timeout=20, + long_polling_timeout=20, + allowed_updates=[ + 'message', 'callback_query', 'inline_query', 'pre_checkout_query', + ], + restart_on_change=False) diff --git a/app/routes/initialize_routes.py b/app/routes/initialize_routes.py index ef0543a2..12801569 100644 --- a/app/routes/initialize_routes.py +++ b/app/routes/initialize_routes.py @@ -2,7 +2,7 @@ import json import threading from functools import partial -from typing import Callable, Tuple +from typing import Any, Callable, Tuple import telethon.events from telethon import events @@ -30,11 +30,11 @@ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -def get_tp(data): +def get_tp(data: Any) -> str: try: return json.loads(data)['tp'] except Exception as e: - logger.err(e, flush=True) + logger.err(e) return "" @@ -119,19 +119,79 @@ def get_payment_chat_id(message) -> int | None: t_answer_sender: TelebotBalancer | None = None +_answer_sender_queue: Any = None +_threads_to_watch: Any = None -def initialize_routes(t_answer_sender_outer: TelebotBalancer, answer_sender_queue, threads_to_watch): +def handle_event_in_thread(params: HandleInThreadParams): global t_answer_sender + if isinstance(t_answer_sender, threading.Thread): + if not t_answer_sender.is_alive() and _answer_sender_queue is not None: + t_answer_sender = telebotAnswerer.TelebotBalancer( + _answer_sender_queue, _threads_to_watch or []) + t_answer_sender.main_queue.put(params) + + +def dispatch_route( + route_name: AvailableRoutes, call: Callback | None, message: Message | None): + menu_route: AvailableRoutes = 'menu' + method_params = construct_params(call, message, route_name) + + validator = dict_tools.deep_get(RouteMap.ROUTES, route_name, 'validator') + if validator is not None: + valid = validator(method_params) + if not valid: + return + + if call is None and message is not None: + if router_tools.is_command(message.text): + storage.clear_user_storage(message.chat.id) + storage.add_user_state(method_params['chat_id'], menu_route) + storage.set_user_resend_flag(message.chat.id) + + method: Callable = dict_tools.deep_get(RouteMap.ROUTES, route_name, 'method') + handle_event_in_thread( + {'action': method, 'data': method_params} + ) + + +def dispatch_action( + route_name: AvailableRoutes, action_name: AvailableActions, + call: Callback, message: Message | None): + method_params = construct_params(call, message, route_name, action_name) + method: Callable = dict_tools.deep_get( + RouteMap.ROUTES, route_name, 'actions', action_name, 'method') + handle_event_in_thread({'action': method, 'data': method_params}) + + +def dispatch_go_back(call: Callback | None, message: Message | None): + method_params = construct_params(call, message, None) + handle_event_in_thread( + {'action': goBackModule.go_back, 'data': method_params}) + + +def dispatch_inline(inline: Inline): + handle_event_in_thread({ + 'action': searchModule.inline_podcast_searcher, + 'data': {'inline': inline}, 'special': 'inline'}) + + +def dispatch_maintenance(call: Callback | None, message: Message | None): + method_params = construct_params(call, message, None) + handle_event_in_thread( + {'action': welcomeModule.maintenance, 'data': method_params}) + + +def initialize_routes( + t_answer_sender_outer: TelebotBalancer, answer_sender_queue, threads_to_watch, + register_telethon: bool = True): + global t_answer_sender, _answer_sender_queue, _threads_to_watch t_answer_sender = t_answer_sender_outer + _answer_sender_queue = answer_sender_queue + _threads_to_watch = threads_to_watch - def handle_event_in_thread(params: HandleInThreadParams): - global t_answer_sender - if isinstance(t_answer_sender, threading.Thread): - if not t_answer_sender.is_alive(): - t_answer_sender = telebotAnswerer.TelebotBalancer( - answer_sender_queue, threads_to_watch) - t_answer_sender.main_queue.put(params) + if not register_telethon: + return @thonbot.on(events.Raw(tl_types.UpdateBotPrecheckoutQuery)) async def telegram_stars_precheckout(update: tl_types.UpdateBotPrecheckoutQuery): @@ -182,9 +242,7 @@ async def telegram_stars_successful_payment(update): if config.maintenance: async def maintenance_catcher(event: telethon.events.NewMessage.Event | telethon.events.CallbackQuery.Event): callback, message = await get_call_and_message(event) - method_params = construct_params(callback, message, None) - handle_event_in_thread( - {'action': welcomeModule.maintenance, 'data': method_params}) + dispatch_maintenance(callback, message) thonbot.add_event_handler(maintenance_catcher, events.NewMessage(incoming=True)) thonbot.add_event_handler(maintenance_catcher, events.CallbackQuery()) @@ -192,7 +250,7 @@ async def maintenance_catcher(event: telethon.events.NewMessage.Event | telethon # If status is 'empty' and previous waits for text, goback to previously and process async def process_empty_state_input(event: telethon.events.NewMessage.Event): - empty_state_input_state_corrector(event) + empty_state_input_state_corrector(event.chat_id) thonbot.add_event_handler(process_empty_state_input, events.NewMessage(incoming=True)) # /Outer middlewares @@ -211,48 +269,20 @@ async def processor(route_name: AvailableRoutes, # --- call, message = await get_call_and_message(event) - - menu_route: AvailableRoutes = 'menu' - method_params = construct_params(call, message, route_name) - - # Validate access - validator = dict_tools.deep_get(RouteMap.ROUTES, route_name, 'validator') - if validator is not None: - valid = validator(method_params) - if not valid: - return - - if call is None and message is not None: - # Clear state on commands - if router_tools.is_command(message.text): - storage.clear_user_storage(event.chat.id) - storage.add_user_state(method_params['chat_id'], menu_route) - # Set resend on message - storage.set_user_resend_flag(message.chat.id) - - method: Callable = dict_tools.deep_get(RouteMap.ROUTES, route_name, 'method') - - # succeed = await method(method_params) - handle_event_in_thread( - {'action': method, 'data': method_params} - ) - # if succeed is not False: # returns None by default - # storage.add_user_state(method_params['chat_id'], route_name) + dispatch_route(route_name, call, message) async def action_processor( route_name: AvailableRoutes, action_name: AvailableActions, call: telethon.events.CallbackQuery.Event): - call, message = await get_call_and_message(call) - method_params = construct_params(call, message, route_name, action_name) - method: Callable = dict_tools.deep_get(RouteMap.ROUTES, route_name, 'actions', action_name, 'method') - handle_event_in_thread({'action': method, 'data': method_params}) + callback, message = await get_call_and_message(call) + if callback is None: + return + dispatch_action(route_name, action_name, callback, message) # Goback module @thonbot.on(events.CallbackQuery(func=lambda call: get_tp(call.data) == 'bck')) async def go_back(event: events.CallbackQuery.Event): callback, message = await get_call_and_message(event) - method_params = construct_params(callback, message, None) - handle_event_in_thread( - {'action': goBackModule.go_back, 'data': method_params}) + dispatch_go_back(callback, message) def commands_list_validator(commands: list[str], incoming_command: str): return incoming_command[1:] in commands @@ -354,6 +384,4 @@ def action_validator(tested_route: str, tested_action: str, call: telethon.event @thonbot.on(events.InlineQuery) async def inline_queries_handler(event): inline = await get_inline(event) - handle_event_in_thread({ - 'action': searchModule.inline_podcast_searcher, - 'data': {'inline': inline}, 'special': 'inline'}) + dispatch_inline(inline) diff --git a/app/routes/test_botapi_receiver.py b/app/routes/test_botapi_receiver.py new file mode 100644 index 00000000..c5c26a79 --- /dev/null +++ b/app/routes/test_botapi_receiver.py @@ -0,0 +1,51 @@ +"""Smoke tests for Bot API command/callback routing helpers. + +Run from the repo root: python app/routes/test_botapi_receiver.py +Does not talk to Telegram. +""" +import os +import sys + +_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +if _ROOT not in sys.path: + sys.path.insert(0, _ROOT) + +from app.routes.botapi_receiver import command_name, iter_command_routes, is_channel_chat +from app.routes.initialize_routes import get_tp + + +class _Chat: + def __init__(self, chat_type: str): + self.type = chat_type + + +def _assert(cond: bool, message: str) -> None: + if not cond: + raise AssertionError(message) + + +def main() -> None: + _assert(command_name('/start') == 'start', 'parse /start') + _assert(command_name('/start@YourInstaFeedDevelopingBot extra') == 'start', 'parse /start@bot') + _assert(command_name('/subscriptions') == 'subscriptions', 'parse /subscriptions') + _assert(command_name('hello') is None, 'plain text is not a command') + _assert(command_name('') is None, 'empty text is not a command') + + _assert(list(iter_command_routes('start')) == ['start'], 'start route') + _assert(list(iter_command_routes('menu')) == ['menu'], 'menu route') + _assert(list(iter_command_routes('subscriptions')) == ['subs'], 'subscriptions alias') + _assert(list(iter_command_routes('nope')) == [], 'unknown command') + + _assert(get_tp('{"tp":"bck"}') == 'bck', 'callback back') + _assert(get_tp('{"tp":"podcast","id":1}') == 'podcast', 'callback podcast') + _assert(get_tp('not-json') == '', 'invalid callback data') + + _assert(is_channel_chat(_Chat('channel')) is True, 'skip channels') + _assert(is_channel_chat(_Chat('private')) is False, 'allow private') + _assert(is_channel_chat(_Chat('supergroup')) is False, 'allow groups') + + print('botapi_receiver helpers: ok') + + +if __name__ == '__main__': + main() diff --git a/main.py b/main.py index ae8cb9d5..fedd7d07 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ from lib.analytics import analytics from app.core.balancers import recordSender, telebotAnswerer -from agent.bot_telethon import thonbot +from agent.bot_telethon import thonbot, try_start_telethon from lib.tools.logger import logger logger.log("The bot is starting", '---\n\n') @@ -45,6 +45,8 @@ threads_to_watch = [] +telethon_ok = try_start_telethon() + t_podcast_sender = recordSender.RecordBalancer(None) # already initialized in builders/recsModule @@ -121,6 +123,15 @@ def shutdown(signum, frame): answer_sender_queue, threads_to_watch) t_answer_sender.start() - initialize_routes(t_answer_sender, answer_sender_queue, threads_to_watch) - - thonbot.run_until_disconnected() + initialize_routes( + t_answer_sender, answer_sender_queue, threads_to_watch, + register_telethon=telethon_ok) + + if telethon_ok: + logger.log("Receiving via Telethon MTProto") + thonbot.run_until_disconnected() + else: + from app.routes.botapi_receiver import initialize_botapi_routes, run_botapi_polling + logger.warn("Telethon MTProto unavailable, receiving via Bot API long polling") + initialize_botapi_routes() + run_botapi_polling()