diff --git a/CenturyGothicBold.ttf b/CenturyGothicBold.ttf deleted file mode 100644 index d3577b9..0000000 Binary files a/CenturyGothicBold.ttf and /dev/null differ diff --git a/MORJ.png b/MORJ.png deleted file mode 100644 index bec9f82..0000000 Binary files a/MORJ.png and /dev/null differ diff --git a/lobster.ttf b/lobster.ttf deleted file mode 100644 index e1d7eff..0000000 Binary files a/lobster.ttf and /dev/null differ diff --git a/main.py b/main.py index b176607..1cbe697 100755 --- a/main.py +++ b/main.py @@ -5,43 +5,55 @@ import os import re import traceback from datetime import datetime, timedelta +from hashlib import sha1 from html import escape from queue import Queue, Empty from time import sleep -from threading import Thread +from threading import Thread, Event from typing import Dict, List -from uuid import uuid4 import sentry_sdk +from redis import Redis from telegram.error import Unauthorized, TelegramError -from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler, CallbackContext from telegram import Message, Update, Bot, InlineKeyboardMarkup, InlineKeyboardButton, User, InputMediaPhoto, \ InputMediaVideo, InputMediaAnimation, InputMediaAudio, InputMediaDocument from config import BOT_TOKEN, SENTRY_DSN, MANAGEMENT_CHAT, DEBUG -from db import get_conn, Subscriber, PersistentMapping, commit -from morj import draw_morj +from db import get_conn, Subscriber, commit from send_users_list import send_users_list -from shepherd import draw_shepherd logging.basicConfig(level=logging.WARNING) queue = Queue() sentry_sdk.init(dsn=SENTRY_DSN) conn = get_conn() +redis = Redis() MAX_MESSAGE_LENGTH = 4096 MAX_CAPTION_LENGTH = 1024 +def _antispam(args): + if not args: + return True + args = '|'.join(map(str, args)) + digest = sha1(args.encode()).digest() + key = 'lono-' + digest.hex() + if redis.get(key): + return False + redis.set(key, '1', ex=30) + return True + + 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): +def welcome(update: Update, ctx: CallbackContext): if DEBUG: - _add_user(bot, update.effective_user.id) + _add_user(ctx.bot, update.effective_user.id) update.message.reply_text('Добро пожаловать (debug)') return @@ -49,14 +61,14 @@ def welcome(bot: Bot, update: Update): update.message.reply_text('Вы уже являетесь участником ЛОНО') else: update.message.reply_text('Пожалуйста, обратитесь к @lono_contactbot') - _notify_access_request(bot, update.message.from_user) + _notify_access_request(ctx.bot, update.message.from_user) -def unsubscribe(bot: Bot, update: Update): +def unsubscribe(update: Update, ctx: CallbackContext): user = _remove_user(update.message.chat_id) update.message.reply_text('Вы были отписаны от бота. ' 'Обратитесь к @lono_contactbot если вы хотите подписаться снова.') - bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} отписался') + ctx.bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} отписался') def _add_user(bot, uid): @@ -65,7 +77,7 @@ def _add_user(bot, uid): return user -def add_user(bot: Bot, update: Update, groups=(), args=()): +def add_user(update: Update, ctx: CallbackContext, groups=(), args=()): if update.callback_query: update.callback_query.answer() @@ -78,7 +90,7 @@ def add_user(bot: Bot, update: Update, groups=(), args=()): 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 пользователя или ответьте на его сообщение') + return ctx.bot.send_message(MANAGEMENT_CHAT, 'Укажите ID пользователя или ответьте на его сообщение') try: uid = int(uid) @@ -86,14 +98,14 @@ def add_user(bot: Bot, update: Update, groups=(), args=()): pass try: - user = _add_user(bot, uid) + user = _add_user(ctx.bot, uid) if update.callback_query: update.callback_query.message.edit_reply_markup() - bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} был добавлен', + ctx.bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} был добавлен', parse_mode='html') - bot.send_message(uid, 'Добро пожаловать. Снова.') + ctx.bot.send_message(uid, 'Добро пожаловать. Снова.') except TelegramError as e: - bot.send_message(MANAGEMENT_CHAT, str(e)) + ctx.bot.send_message(MANAGEMENT_CHAT, str(e)) def _remove_user(uid): @@ -103,7 +115,7 @@ def _remove_user(uid): return user -def remove_user(bot: Bot, update: Update, groups=(), args=()): +def remove_user(update: Update, ctx: CallbackContext, groups=(), args=()): if update.callback_query: update.callback_query.answer() @@ -116,7 +128,7 @@ def remove_user(bot: Bot, update: Update, groups=(), args=()): 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 пользователя или ответьте на его сообщение') + return ctx.bot.send_message(MANAGEMENT_CHAT, 'Укажите ID пользователя или ответьте на его сообщение') try: uid = int(uid) @@ -125,19 +137,19 @@ def remove_user(bot: Bot, update: Update, groups=(), args=()): try: user = _remove_user(uid) - bot.send_message(MANAGEMENT_CHAT, f'{escape(user.name)} был удален', + ctx.bot.send_message(MANAGEMENT_CHAT, f'{escape(user.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} не был найден') + ctx.bot.send_message(MANAGEMENT_CHAT, f'Пользователь id={uid} не был найден') -def users(bot: Bot, update: Update): +def users(update: Update, ctx: CallbackContext): send_users_list() -def msg(bot: Bot, update: Update): +def msg(update: Update, ctx: CallbackContext): queue.put(update.message) @@ -219,7 +231,7 @@ def _process_media_group(bot: Bot, messages: List[Message]): _remove_user(uid) -def users_list(bot: Bot, update: Update): +def users_list(update: Update, ctx: CallbackContext): current_chat = update.effective_chat.id subs = conn.root.subscribers # type: Dict[int, Subscriber] if current_chat not in subs: @@ -238,22 +250,6 @@ def users_list(bot: Bot, update: Update): update.effective_message.reply_text('\n'.join(messages[i:i+40]), parse_mode='html') -def morj(bot: Bot, update: Update): - text = update.effective_message.text[6:] - fname = '/tmp/morj{}.png'.format(uuid4()) - draw_morj(text, fname) - update.effective_message.reply_photo(open(fname, 'rb')) - os.unlink(fname) - - -def shepherd(bot: Bot, update: Update): - text = update.effective_message.text[10:] - fname = '/tmp/shepherd{}.png'.format(uuid4()) - draw_shepherd(text, fname) - update.effective_message.reply_photo(open(fname, 'rb')) - os.unlink(fname) - - def _process_message(bot: Bot, m: Message): if m.sticker or m.animation: delta = datetime.now() - conn.root.last_media @@ -282,80 +278,96 @@ def _process_message(bot: Bot, m: Message): if m.reply_to_message and m.reply_to_message.message_id in users[current_chat].messages_forward: reply_to_message_internal_id = users[current_chat].messages_forward[m.reply_to_message.message_id] + func = None + args = [] + kwargs = {} + if m.forward_date: + func = m.forward + elif hasattr(m, 'audio') and m.audio: + a = m.audio + func = bot.send_audio + args = [a.file_id, a.duration, a.performer, a.title, caption] + kwargs = dict(parse_mode='html') + elif hasattr(m, 'document') and m.document: + d = m.document + func = bot.send_document + args = [d.file_id, d.file_name, caption] + kwargs = dict(parse_mode='html') + elif hasattr(m, 'photo') and m.photo: + p = m.photo + func = bot.send_photo + args = [p[-1].file_id, caption] + kwargs = dict(parse_mode='html') + elif hasattr(m, 'sticker') and m.sticker: + s = m.sticker + func = bot.send_sticker + args = [s.file_id] + elif hasattr(m, 'video') and m.video: + v = m.video + func = bot.send_video + args = [v.file_id, v.duration, caption] + kwargs = dict(parse_mode='html') + elif hasattr(m, 'voice') and m.voice: + v = m.voice + func = bot.send_voice + args = [v.file_id, v.duration, caption] + kwargs = dict(parse_mode='html') + elif hasattr(m, 'video_note') and m.video_note: + vn = m.video_note + func = bot.send_video_note + args = [vn.file_id, vn.duration, vn.length] + elif hasattr(m, 'contact') and m.contact: + c = m.contact + func = bot.send_contact + args = [c.phone_number, c.first_name, c.last_name] + elif hasattr(m, 'location') and m.location: + l = m.location + func = bot.send_location + args = [l.latitude, l.longitude] + elif hasattr(m, 'venue') and m.venue: + v = m.venue + l = v.location + func = bot.send_venue + args = [l.latitude, l.longitude, v.title, v.address, v.foursquare_id] + elif hasattr(m, 'text') and m.text: + func = bot.send_message + args = [text, 'html'] + + if not _antispam(args): + return m.reply_text('Не вайпи', quote=True) + remove_uids = [] - for uid, user in users.items(): - sleep(.02) + if func: + for uid, user in users.items(): + sleep(.02) - reply_to_message_id = None - if reply_to_message_internal_id: - reply_to_message_id = user.messages_reverse.get(reply_to_message_internal_id, None) + reply_to_message_id = None + if reply_to_message_internal_id: + reply_to_message_id = user.messages_reverse.get(reply_to_message_internal_id, None) - 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, - reply_to_message_id=reply_to_message_id, 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, reply_to_message_id=reply_to_message_id, - parse_mode='html') - elif hasattr(m, 'photo') and m.photo: - p = m.photo - r = bot.send_photo(uid, p[-1].file_id, caption, reply_to_message_id=reply_to_message_id, - parse_mode='html') - elif hasattr(m, 'sticker') and m.sticker: - s = m.sticker - r = bot.send_sticker(uid, s.file_id, reply_to_message_id=reply_to_message_id) - elif hasattr(m, 'video') and m.video: - v = m.video - r = bot.send_video(uid, v.file_id, v.duration, caption, reply_to_message_id=reply_to_message_id, - parse_mode='html') - elif hasattr(m, 'voice') and m.voice: - v = m.voice - r = bot.send_voice(uid, v.file_id, v.duration, caption, reply_to_message_id=reply_to_message_id, - 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, - reply_to_message_id=reply_to_message_id) - elif hasattr(m, 'contact') and m.contact: - c = m.contact - r = bot.send_contact(uid, c.phone_number, c.first_name, c.last_name, - reply_to_message_id=reply_to_message_id) - elif hasattr(m, 'location') and m.location: - l = m.location - r = bot.send_location(uid, l.latitude, l.longitude, reply_to_message_id=reply_to_message_id) - 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, - reply_to_message_id=reply_to_message_id) - elif hasattr(m, 'text') and m.text: - r = bot.send_message(uid, text, 'html', reply_to_message_id=reply_to_message_id) - if r: - user.update_from_message(r) - user.messages_forward[r.message_id] = conn.root.counter - user.messages_reverse[conn.root.counter] = r.message_id - except Unauthorized: - remove_uids.append(uid) - bot.send_message(MANAGEMENT_CHAT, f'{user.name} был удален ' - f'из-за блокировки бота', parse_mode='html') - except Exception: - traceback.print_exc() - sentry_sdk.capture_exception() + try: + r = func(*([uid] + args), **kwargs, reply_to_message_id=reply_to_message_id) + if r: + user.update_from_message(r) + user.messages_forward[r.message_id] = conn.root.counter + user.messages_reverse[conn.root.counter] = r.message_id + except Unauthorized: + remove_uids.append(uid) + bot.send_message(MANAGEMENT_CHAT, f'{user.name} был удален ' + f'из-за блокировки бота', parse_mode='html') + except Exception: + traceback.print_exc() + sentry_sdk.capture_exception() conn.root.counter += 1 commit() for uid in remove_uids: _remove_user(uid) -def task_queue(u: Updater): +def task_queue(u: Updater, stop_signal: Event): while True: - if not u.running: + if not u.running or stop_signal.is_set(): return try: @@ -375,8 +387,8 @@ def task_queue(u: Updater): sentry_sdk.capture_exception() -if __name__ == '__main__': - updater = Updater(BOT_TOKEN, workers=4) +def main(): + updater = Updater(BOT_TOKEN, workers=4, use_context=True) updater.dispatcher.add_handler(CommandHandler('start', welcome, Filters.private)) updater.dispatcher.add_handler(CommandHandler('stop', unsubscribe, Filters.private)) @@ -385,17 +397,21 @@ if __name__ == '__main__': updater.dispatcher.add_handler(CommandHandler('remove', remove_user, Filters.chat(MANAGEMENT_CHAT), pass_args=True)) updater.dispatcher.add_handler(CallbackQueryHandler(remove_user, pattern=r'^remove (\d+)$', pass_groups=True)) updater.dispatcher.add_handler(CommandHandler('users', users_list, Filters.private)) - updater.dispatcher.add_handler(CommandHandler('morj', morj)) - updater.dispatcher.add_handler(CommandHandler('shepherd', shepherd)) updater.dispatcher.add_handler(MessageHandler(Filters.private, msg)) updater.start_polling() - tq = Thread(target=task_queue, args=(updater,)) + stop_signal = Event() + tq = Thread(target=task_queue, args=(updater, stop_signal)) tq.start() logging.warning('LONO has started') updater.idle() + stop_signal.set() logging.warning('LONO is stopping...') commit() conn.close() + + +if __name__ == '__main__': + main() diff --git a/morj.py b/morj.py deleted file mode 100644 index f5c9ea9..0000000 --- a/morj.py +++ /dev/null @@ -1,10 +0,0 @@ -from image_text import ImageText - -FONT_FILE = 'CenturyGothicBold.ttf' -BG = 'MORJ.png' - - -def draw_morj(text, filename): - img = ImageText(BG) - img.write_text_box((67, 70), text, box_width=800, font_filename=FONT_FILE, font_size=40, color=(255, 255, 255)) - img.save(filename) diff --git a/shepherd.jpg b/shepherd.jpg deleted file mode 100644 index aa009be..0000000 Binary files a/shepherd.jpg and /dev/null differ diff --git a/shepherd.py b/shepherd.py deleted file mode 100644 index 2c6fbb5..0000000 --- a/shepherd.py +++ /dev/null @@ -1,11 +0,0 @@ -from image_text import ImageText - -FONT_FILE = 'lobster.ttf' -BG = 'shepherd.jpg' - - -def draw_shepherd(text, filename): - img = ImageText(BG) - img.write_text_box((74, 310), text, box_width=600, font_filename=FONT_FILE, font_size=40, color=(0, 0, 0), - place='center') - img.save(filename)