#!/usr/bin/env python3 import logging from html import escape from queue import Queue, Empty from time import sleep from threading import Thread import sentry_sdk from telegram.error import Unauthorized, TelegramError from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler from telegram import Message, Update, Bot, InlineKeyboardMarkup, InlineKeyboardButton, User from config import BOT_TOKEN, SENTRY_DSN, MANAGEMENT_CHAT from db import get_conn, Subscriber, PersistentMapping, commit from send_users_list import send_users_list logging.basicConfig(level=logging.WARNING) queue = Queue() sentry_sdk.init(dsn=SENTRY_DSN) conn = get_conn() MAX_MESSAGE_LENGTH = 4096 MAX_CAPTION_LENGTH = 1024 def _notify_access_request(bot: Bot, user: User): markup = InlineKeyboardMarkup([[InlineKeyboardButton('Добавить', callback_data=f'add {user.id}')]]) bot.send_message(MANAGEMENT_CHAT, f'{escape(user.full_name)} запросил доступ', parse_mode='html', reply_markup=markup) def welcome(bot: Bot, update: Update): if update.effective_user.id in conn.root.subscribers: update.message.reply_text('Вы уже являетесь участником ЛОНО') else: update.message.reply_text('Пожалуйста, обратитесь к @lono_contactbot') _notify_access_request(bot, update.message.from_user) def unsubscribe(bot: Bot, update: Update): del conn.root.subscribers[update.message.chat_id] commit() update.message.reply_text('Вы были отписаны от бота. ' 'Обратитесь к @lono_contactbot если вы хотите подписаться снова.') user = update.message.from_user bot.send_message(MANAGEMENT_CHAT, f'{escape(user.full_name)} отписался') def add_user(bot: Bot, update: Update, groups=(), args=()): if update.callback_query: update.callback_query.answer() if groups: if update.callback_query.message.chat.id != MANAGEMENT_CHAT: return uid = groups[0] elif args: uid = args[0] elif update.message and update.message.reply_to_message and update.message.reply_to_message.forward_from: uid = update.message.reply_to_message.forward_from.id else: return bot.send_message(MANAGEMENT_CHAT, 'Укажите ID пользователя или ответьте на его сообщение') try: uid = int(uid) except (ValueError, TypeError): pass try: user = conn.root.subscribers[uid] = Subscriber.from_chat(bot.get_chat(uid)) commit() if update.callback_query: update.callback_query.message.edit_reply_markup() bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} был добавлен', parse_mode='html') bot.send_message(uid, 'Добро пожаловать. Снова.') except TelegramError as e: bot.send_message(MANAGEMENT_CHAT, str(e)) def remove_user(bot: Bot, update: Update, groups=(), args=()): if update.callback_query: update.callback_query.answer() if groups: if update.callback_query.message.chat.id != MANAGEMENT_CHAT: return uid = groups[0] elif args: uid = args[0] elif update.message and update.message.reply_to_message and update.message.reply_to_message.forward_from: uid = update.message.reply_to_message.forward_from.id else: return bot.send_message(MANAGEMENT_CHAT, 'Укажите ID пользователя или ответьте на его сообщение') try: uid = int(uid) except (ValueError, TypeError): pass try: name = conn.root.subscribers[uid].name del conn.root.subscribers[uid] commit() bot.send_message(MANAGEMENT_CHAT, f'{escape(name)} был удален', parse_mode='html') if update.callback_query: update.callback_query.message.edit_reply_markup() except KeyError: bot.send_message(MANAGEMENT_CHAT, f'Пользователь id={uid} не был найден') def users(bot: Bot, update: Update): send_users_list() def msg(bot: Bot, update: Update): queue.put(update.message) def _sign_text(text, m: Message, limit): if not text: text = '' sign = '' if text.startswith('!sign') or text.startswith('/sign'): sign = f'\n\n____________\n' \ f'by {escape(m.from_user.full_name)}' return text[:limit - len(sign)] + sign def _process_message(bot: Bot, m: Message): current_chat = m.chat_id users = conn.root.subscribers # type: PersistentMapping if current_chat not in users: _notify_access_request(bot, m.from_user) return m.reply_text('Пожалуйста, обратитесь к @lono_contactbot') text = _sign_text(m.text_html, m, MAX_MESSAGE_LENGTH) caption = _sign_text(m.caption_html, m, MAX_CAPTION_LENGTH) for uid, user in users.items(): if uid == current_chat: continue sleep(.02) try: r = None if m.forward_date: r = m.forward(uid) elif hasattr(m, 'audio') and m.audio: a = m.audio r = bot.send_audio(uid, a.file_id, a.duration, a.performer, a.title, caption, parse_mode='html') elif hasattr(m, 'document') and m.document: d = m.document r = bot.send_document(uid, d.file_id, d.file_name, caption, parse_mode='html') elif hasattr(m, 'photo') and m.photo: p = m.photo r = bot.send_photo(uid, p[-1].file_id, caption, parse_mode='html') elif hasattr(m, 'sticker') and m.sticker: s = m.sticker r = bot.send_sticker(uid, s.file_id) elif hasattr(m, 'video') and m.video: v = m.video r = bot.send_video(uid, v.file_id, v.duration, caption, parse_mode='html') elif hasattr(m, 'voice') and m.voice: v = m.voice r = bot.send_voice(uid, v.file_id, v.duration, caption, parse_mode='html') elif hasattr(m, 'video_note') and m.video_note: vn = m.video_note r = bot.send_video_note(uid, vn.file_id, vn.duration, vn.length) elif hasattr(m, 'contact') and m.contact: c = m.contact r = bot.send_contact(uid, c.phone_number, c.first_name, c.last_name) elif hasattr(m, 'location') and m.location: l = m.location r = bot.send_location(uid, l.latitude, l.longitude) elif hasattr(m, 'venue') and m.venue: v = m.venue l = v.location r = bot.send_venue(uid, l.latitude, l.longitude, v.title, v.address, v.foursquare_id) elif hasattr(m, 'text') and m.text: r = bot.send_message(uid, text, 'html') if r: user.update_from_message(r) except Unauthorized: name = conn.root.subscribers[uid].name del conn.root.subscribers[uid] commit() bot.send_message(MANAGEMENT_CHAT, f'{name} был удален ' f'из-за блокировки бота', parse_mode='html') except TelegramError: sentry_sdk.capture_exception() commit() def task_queue(u: Updater): while True: if not u.running: return try: m = queue.get(timeout=1) # type: Message _process_message(u.bot, m) except Empty: pass except: sentry_sdk.capture_exception() if __name__ == '__main__': updater = Updater(BOT_TOKEN, workers=4) updater.dispatcher.add_handler(CommandHandler('start', welcome, Filters.private)) updater.dispatcher.add_handler(CommandHandler('stop', unsubscribe, Filters.private)) updater.dispatcher.add_handler(CommandHandler('add', add_user, Filters.chat(MANAGEMENT_CHAT))) updater.dispatcher.add_handler(CallbackQueryHandler(add_user, pattern=r'^add (\d+)$', pass_groups=True)) updater.dispatcher.add_handler(CommandHandler('remove', remove_user, Filters.chat(MANAGEMENT_CHAT))) updater.dispatcher.add_handler(CallbackQueryHandler(remove_user, pattern=r'^remove (\d+)$', pass_groups=True)) updater.dispatcher.add_handler(CommandHandler('users', users, Filters.chat(MANAGEMENT_CHAT))) updater.dispatcher.add_handler(MessageHandler(Filters.private, msg)) updater.start_polling() tq = Thread(target=task_queue, args=(updater,)) tq.start() logging.warning('LONO has started') updater.idle() logging.warning('LONO is stopping...') commit() conn.close()