#!/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 = ''
if text.startswith('!sign') or text.startswith('/sign'):
text = text[5:] + f'\n\n____________\n' \
f'by {escape(m.from_user.full_name)}'
return text[:limit]
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:
try:
name = conn.root.users[uid].name
del conn.root.users[uid]
commit()
bot.send_message(MANAGEMENT_CHAT, f'{name} был удален '
f'из-за блокировки бота', parse_mode='html')
except Exception:
sentry_sdk.capture_exception()
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()