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)