pooling update handlers, add overlay bot module

This commit is contained in:
bakatrouble 2019-11-16 03:38:12 +03:00
parent 06d79850ac
commit 752256be7b
8 changed files with 117 additions and 14 deletions

View File

@ -1,17 +1,25 @@
import logging import logging
import traceback import traceback
from multiprocessing.pool import ThreadPool
import sentry_sdk import sentry_sdk
from django.core.cache import cache from django.core.cache import cache
from django.core.management import BaseCommand from django.core.management import BaseCommand
from telegram import TelegramError from telegram import TelegramError, Update
from telegram.error import TimedOut from telegram.error import TimedOut
from telegram.ext import CallbackContext
from bots.models import TelegramBot from bots.models import TelegramBot
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
pool = ThreadPool(8)
def error_handler(update: Update, ctx: CallbackContext):
sentry_sdk.capture_exception(ctx.error)
logging.exception('Exception while processing update', exc_info=ctx.error)
dispatchers = [] dispatchers = []
while True: while True:
try: try:
@ -20,7 +28,7 @@ class Command(BaseCommand):
dispatchers = [] dispatchers = []
for bot in TelegramBot.objects.filter(active=True): for bot in TelegramBot.objects.filter(active=True):
try: try:
dispatcher = bot.build_dispatcher() dispatcher = bot.build_dispatcher(error_handler)
dispatcher.last_update_id = 0 dispatcher.last_update_id = 0
dispatchers.append(dispatcher) dispatchers.append(dispatcher)
except TelegramError: except TelegramError:
@ -38,15 +46,11 @@ class Command(BaseCommand):
updates = [] updates = []
for update in updates: for update in updates:
try: pool.apply_async(dispatcher.process_update, (update,))
dispatcher.process_update(update)
except KeyboardInterrupt:
return
except Exception as e:
sentry_sdk.capture_exception(e)
traceback.print_exc()
dispatcher.last_update_id = update.update_id + 1 dispatcher.last_update_id = update.update_id + 1
except KeyboardInterrupt: except KeyboardInterrupt:
pool.terminate()
pool.join()
return return
except Exception as e: except Exception as e:
sentry_sdk.capture_exception(e) sentry_sdk.capture_exception(e)

View File

@ -0,0 +1,24 @@
# Generated by Django 2.1.5 on 2019-11-16 00:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bots', '0005_channelhelperbotmoduleconfig'),
]
operations = [
migrations.CreateModel(
name='OverlayBotModuleConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comment', models.TextField(blank=True, null=True)),
('image', models.ImageField(upload_to='')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.1.5 on 2019-11-16 00:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bots', '0006_overlaybotmoduleconfig'),
]
operations = [
migrations.AddField(
model_name='overlaybotmoduleconfig',
name='start_text',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='overlaybotmoduleconfig',
name='type_error_text',
field=models.TextField(blank=True, null=True),
),
]

View File

@ -23,10 +23,11 @@ class TelegramBot(models.Model):
def get_bot(self): def get_bot(self):
return Bot(self.bot_token) return Bot(self.bot_token)
def build_dispatcher(self): def build_dispatcher(self, error_handler):
bot = self.get_bot() bot = self.get_bot()
bot.get_me() bot.get_me()
dispatcher = Dispatcher(bot, None, workers=0, use_context=True) dispatcher = Dispatcher(bot, None, workers=0, use_context=True)
dispatcher.add_error_handler(error_handler)
self.config.build_dispatcher(dispatcher) self.config.build_dispatcher(dispatcher)
return dispatcher return dispatcher

View File

@ -1,4 +1,5 @@
from bots.modules.overlay import OverlayBotModuleConfig
from .channel_helper import ChannelHelperBotModuleConfig from .channel_helper import ChannelHelperBotModuleConfig
from .echo import EchoBotModuleConfig from .echo import EchoBotModuleConfig
BOT_MODULES = [EchoBotModuleConfig, ChannelHelperBotModuleConfig] BOT_MODULES = [EchoBotModuleConfig, ChannelHelperBotModuleConfig, OverlayBotModuleConfig]

48
bots/modules/overlay.py Normal file
View File

@ -0,0 +1,48 @@
import os
from tempfile import TemporaryDirectory
from time import sleep
from PIL import Image
from django.db import models
from telegram import Update
from telegram.ext import Dispatcher, CallbackContext, MessageHandler, Filters, CommandHandler
from bots.models import TelegramBotModuleConfig
class OverlayBotModuleConfig(TelegramBotModuleConfig):
start_text = models.TextField(null=True, blank=True)
type_error_text = models.TextField(null=True, blank=True)
comment = models.TextField(blank=True, null=True)
image = models.ImageField()
MODULE_NAME = 'Overlay'
def start_handler(self, update: Update, ctx: CallbackContext):
if self.start_text:
update.effective_message.reply_text(self.start_text)
def message_handler(self, update: Update, ctx: CallbackContext):
if self.type_error_text:
update.effective_message.reply_text(self.type_error_text)
def photo_handler(self, update: Update, ctx: CallbackContext):
with TemporaryDirectory() as d:
src = os.path.join(d, 'src.jpg')
out = os.path.join(d, 'out.png')
q = update.effective_message.photo[-1].get_file().download(src)
im = Image.open(q).convert('RGBA') # type: Image.Image
overlay = Image.open(self.image.path).convert('RGBA') # type: Image.Image
w, h = im.size
min_side = min(w, h)
overlay = overlay.resize((min_side, min_side), Image.LANCZOS)
im.paste(overlay, ((w - min_side) // 2, (h - min_side) // 2), overlay)
im.save(out)
update.effective_message.reply_photo(open(out, 'rb'), caption=self.comment)
raise Exception('hi')
def build_dispatcher(self, dispatcher: Dispatcher):
dispatcher.add_handler(CommandHandler('start', self.start_handler))
dispatcher.add_handler(MessageHandler(Filters.photo, self.photo_handler))
dispatcher.add_handler(MessageHandler(Filters.all, self.message_handler))
return dispatcher

View File

@ -7,7 +7,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<form action="" method="post" class="card"> <form action="" method="post" class="card" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<header class="card-header"> <header class="card-header">
<h2 class="card-title">{% if feed %}Bot "{{ feed.title }}" configuration{% else %}New bot{% endif %}</h2> <h2 class="card-title">{% if feed %}Bot "{{ feed.title }}" configuration{% else %}New bot{% endif %}</h2>

View File

@ -29,8 +29,10 @@ class BaseBotConfigView(CabinetViewMixin, TemplateView):
def get_forms(self): def get_forms(self):
bot = self.get_object() bot = self.get_object()
data = self.request.POST if self.request.method == 'POST' else None data = self.request.POST if self.request.method == 'POST' else None
return BotForm(data=data, instance=bot, module=self.get_content_type().model_class()), \ files = self.request.FILES if self.request.method == 'POST' else None
get_config_form(self.get_content_type().model_class())(data=data, instance=bot.config if bot else None) return BotForm(data=data, files=files, instance=bot, module=self.get_content_type().model_class()), \
get_config_form(self.get_content_type().model_class())(data=data, files=files,
instance=bot.config if bot else None)
def get_context_data(self, forms=None, **kwargs): def get_context_data(self, forms=None, **kwargs):
ctx = super(BaseBotConfigView, self).get_context_data(**kwargs) ctx = super(BaseBotConfigView, self).get_context_data(**kwargs)