diff --git a/bots/migrations/0009_auto_20191126_0058.py b/bots/migrations/0009_auto_20191126_0058.py new file mode 100644 index 0000000..c5ad099 --- /dev/null +++ b/bots/migrations/0009_auto_20191126_0058.py @@ -0,0 +1,63 @@ +# Generated by Django 3.0rc1 on 2019-11-25 21:58 + +from django.db import migrations, models +import django.db.models.deletion +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('bots', '0008_auto_20191124_1922'), + ] + + operations = [ + migrations.CreateModel( + name='CyberLinaBotModuleConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_part', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('second_part', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('third_part', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('emoji', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('already_ran', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('welcome_reactions', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ('inline_reactions', jsonfield.fields.JSONField(default=[], dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={})), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CyberLinaChat', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ('chat_id', models.BigIntegerField(db_index=True)), + ('last_run', models.DateField(blank=True, null=True)), + ('config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chats', to='bots.CyberLinaBotModuleConfig')), + ], + ), + migrations.CreateModel( + name='CyberLinaUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_id', models.BigIntegerField(db_index=True)), + ('name', models.TextField()), + ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to='bots.CyberLinaChat')), + ], + options={ + 'unique_together': {('chat', 'user_id')}, + }, + ), + migrations.AddField( + model_name='cyberlinachat', + name='last_choice', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='bots.CyberLinaUser'), + ), + migrations.AlterUniqueTogether( + name='cyberlinachat', + unique_together={('config', 'chat_id')}, + ), + ] diff --git a/bots/migrations/0010_auto_20191126_0112.py b/bots/migrations/0010_auto_20191126_0112.py new file mode 100644 index 0000000..cabbf1b --- /dev/null +++ b/bots/migrations/0010_auto_20191126_0112.py @@ -0,0 +1,50 @@ +# Generated by Django 3.0rc1 on 2019-11-25 22:12 + +from django.db import migrations +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('bots', '0009_auto_20191126_0058'), + ] + + operations = [ + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='already_ran', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='emoji', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='first_part', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='inline_reactions', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='second_part', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='third_part', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='welcome_reactions', + field=jsonfield.fields.JSONField(default={'items': []}, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + ] diff --git a/bots/migrations/0011_auto_20191127_2117.py b/bots/migrations/0011_auto_20191127_2117.py new file mode 100644 index 0000000..b135a2c --- /dev/null +++ b/bots/migrations/0011_auto_20191127_2117.py @@ -0,0 +1,50 @@ +# Generated by Django 3.0rc1 on 2019-11-27 18:17 + +from django.db import migrations +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('bots', '0010_auto_20191126_0112'), + ] + + operations = [ + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='already_ran', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='emoji', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='first_part', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='inline_reactions', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='second_part', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='third_part', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + migrations.AlterField( + model_name='cyberlinabotmoduleconfig', + name='welcome_reactions', + field=jsonfield.fields.JSONField(default='{"items": []}', dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}), + ), + ] diff --git a/bots/modules/__init__.py b/bots/modules/__init__.py index df6f707..9961707 100644 --- a/bots/modules/__init__.py +++ b/bots/modules/__init__.py @@ -1,5 +1,6 @@ from .overlay import OverlayBotModuleConfig from .channel_helper import ChannelHelperBotModuleConfig, QueuedItem from .echo import EchoBotModuleConfig +from .cyberlina import CyberLinaBotModuleConfig -BOT_MODULES = [EchoBotModuleConfig, ChannelHelperBotModuleConfig, OverlayBotModuleConfig] +BOT_MODULES = [EchoBotModuleConfig, ChannelHelperBotModuleConfig, OverlayBotModuleConfig, CyberLinaBotModuleConfig] diff --git a/bots/modules/cyberlina.py b/bots/modules/cyberlina.py new file mode 100644 index 0000000..b575c9e --- /dev/null +++ b/bots/modules/cyberlina.py @@ -0,0 +1,128 @@ +import os +from datetime import timedelta, datetime, time +from random import choice, seed +from uuid import uuid4 + +import humanize +from django.db import models +from django.utils.timezone import localdate, now, make_aware +from jsoneditor.forms import JSONEditor +from jsonfield import JSONField +from telegram import Update, Chat, User, InlineQueryResultArticle, InputTextMessageContent +from telegram.error import BadRequest +from telegram.ext import Dispatcher, CallbackContext, MessageHandler, Filters, CommandHandler, InlineQueryHandler +from telegram.utils.helpers import mention_html + +from bots.models import TelegramBotModuleConfig + + +class CyberLinaBotModuleConfig(TelegramBotModuleConfig): + first_part = JSONField(default='{"items": []}') + second_part = JSONField(default='{"items": []}') + third_part = JSONField(default='{"items": []}') + emoji = JSONField(default='{"items": []}') + already_ran = JSONField(default='{"items": []}') + welcome_reactions = JSONField(default='{"items": []}') + inline_reactions = JSONField(default='{"items": []}') + + MODULE_NAME = 'Киберлиночка' + CUSTOM_WIDGETS = { + 'first_part': JSONEditor(), + 'second_part': JSONEditor(), + 'third_part': JSONEditor(), + 'emoji': JSONEditor(), + 'already_ran': JSONEditor(), + 'welcome_reactions': JSONEditor(), + 'inline_reactions': JSONEditor(), + } + + def message_handler(self, update: Update, ctx: CallbackContext): + if not update.effective_chat or not update.effective_user: + return + CyberLinaUser.from_tg_obj(self, update.effective_chat, update.effective_user) + + def goodmorning_handler(self, update: Update, ctx: CallbackContext): + if not all([self.first_part['items'], self.second_part['items'], + self.third_part['items'], self.emoji['items']]): + return update.effective_message.reply_text('Я не настроена :c') + seed(os.urandom(128)) + self.message_handler(update, ctx) + chat = self.chats.get(chat_id=update.effective_chat.id) + if chat.last_run and (chat.last_run >= localdate() or + chat.last_run + timedelta(1) == localdate() and now().hour < 6): + humanize.i18n.activate('ru_RU') + time_left = make_aware(datetime.combine(chat.last_run + timedelta(1), time(6, 0))) - now() + return update.effective_message.reply_text( + choice(self.already_ran['items']).format( + name=chat.last_choice.name, + time=humanize.naturaldelta(time_left) + ) + ) + while True: + user = chat.users.order_by('?').first() # type: CyberLinaUser + if not user: + return update.effective_message.reply_text('Нет известных юзеров в чате') + try: + member = ctx.bot.get_chat_member(chat.chat_id, user.user_id) + CyberLinaUser.from_tg_obj(self, update.effective_chat, member.user) + break + except BadRequest: + user.delete() + msg = '{}, {}! {}, {} {}'.format( + choice(self.first_part['items']), + choice(self.second_part['items']), + mention_html(user.user_id, user.name), + choice(self.third_part['items']), + choice(self.emoji['items']), + ) + update.effective_chat.send_message(msg, parse_mode='html') + chat.last_run = localdate() + chat.last_choice = user + chat.save() + + def inline_query_handler(self, update: Update, ctx: CallbackContext): + if not self.inline_reactions: + return + seed(os.urandom(128)) + results = [ + InlineQueryResultArticle( + id=uuid4(), + title='Не нажимай >_<', + input_message_content=InputTextMessageContent(choice(self.inline_reactions['items'])) + ) + ] + update.inline_query.answer(results) + + def build_dispatcher(self, dispatcher: Dispatcher): + dispatcher.add_handler(CommandHandler('goodmorning', self.goodmorning_handler)) + dispatcher.add_handler(MessageHandler(Filters.all, self.message_handler)) + dispatcher.add_handler(InlineQueryHandler(self.inline_query_handler)) + return dispatcher + + +class CyberLinaChat(models.Model): + config = models.ForeignKey(CyberLinaBotModuleConfig, on_delete=models.CASCADE, related_name='chats') + name = models.TextField() + chat_id = models.BigIntegerField(db_index=True) + last_run = models.DateField(null=True, blank=True) + last_choice = models.ForeignKey('CyberLinaUser', on_delete=models.SET_NULL, null=True, blank=True, related_name='+') + + class Meta: + unique_together = 'config', 'chat_id', + + +class CyberLinaUser(models.Model): + chat = models.ForeignKey(CyberLinaChat, on_delete=models.CASCADE, related_name='users') + user_id = models.BigIntegerField(db_index=True) + name = models.TextField() + + @staticmethod + def from_tg_obj(config: CyberLinaBotModuleConfig, chat: Chat, user: User): + chat_title = chat.title or user.full_name + chat, _ = CyberLinaChat.objects.update_or_create(config=config, chat_id=chat.id, + defaults={'name': chat_title}) + CyberLinaUser.objects.update_or_create(chat=chat, user_id=user.id, + defaults={'name': user.full_name}) + + class Meta: + unique_together = 'chat', 'user_id', diff --git a/bots/templates/cabinet/bots/bot_form.html b/bots/templates/cabinet/bots/bot_form.html index 5b88831..327d15f 100644 --- a/bots/templates/cabinet/bots/bot_form.html +++ b/bots/templates/cabinet/bots/bot_form.html @@ -53,3 +53,8 @@ {% endif %} {% endblock %} + +{% block extra_body %} + {{ bot_form.media }} + {{ config_form.media }} +{% endblock %} diff --git a/cabinet/templates/cabinet/_base.html b/cabinet/templates/cabinet/_base.html index 53889da..ffe3c95 100644 --- a/cabinet/templates/cabinet/_base.html +++ b/cabinet/templates/cabinet/_base.html @@ -15,6 +15,7 @@ + @@ -74,5 +75,7 @@ }); {% endfor %} + + {% block extra_body %}{% endblock %} diff --git a/config/settings.py b/config/settings.py index 4cc2897..8954f8d 100644 --- a/config/settings.py +++ b/config/settings.py @@ -21,6 +21,7 @@ INSTALLED_APPS = [ 'bootstrap4', 'crispy_forms', 'djconfig', + 'jsoneditor', 'django.contrib.admin', 'django.contrib.auth', @@ -122,6 +123,9 @@ LOGOUT_REDIRECT_URL = LOGIN_URL CRISPY_TEMPLATE_PACK = 'bootstrap4' +JSON_EDITOR_JS = 'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/7.0.4/jsoneditor.min.js' +JSON_EDITOR_CSS = 'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/7.0.4/jsoneditor.min.css' + sentry_sdk.init( dsn=env.str('SENTRY_DSN', None), integrations=[DjangoIntegration()], diff --git a/config/utils.py b/config/utils.py index b9ac44c..0c6f934 100644 --- a/config/utils.py +++ b/config/utils.py @@ -64,6 +64,7 @@ def get_config_form(mdl): class Meta: model = mdl exclude = () + widgets = mdl.CUSTOM_WIDGETS if hasattr(mdl, 'CUSTOM_WIDGETS') else {} return ConfigForm diff --git a/requirements.txt b/requirements.txt index 8afa502..e73f11b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,6 +23,7 @@ django-djconfig==0.9.0 django-environ==0.4.5 django-extensions==2.1.4 django-jet==1.0.8 +django-jsoneditor==0.1.4 django-redis-cache==2.1.0 django-timezone-field==3.0 django-yamlfield==1.0.3 @@ -32,13 +33,19 @@ future==0.17.1 h2==3.1.1 hiredis==1.0.1 hpack==3.0.0 +humanize==0.5.1 hyperframe==5.2.0 hyperlink==19.0.0 idna==2.8 +importlib-metadata==0.23 incremental==17.5.0 json-rpc==1.12.1 +jsonfield2==3.0.3 +jsonschema==3.2.0 kombu==4.2.2.post1 +more-itertools==7.2.0 oauthlib==3.0.1 +packaging==19.2 Pillow==5.4.1 priority==1.3.0 psycopg2-binary==2.7.6.1 @@ -48,7 +55,9 @@ pyasn1-modules==0.2.7 pycparser==2.19 PyHamcrest==1.9.0 pyOpenSSL==19.1.0 +pyparsing==2.4.5 Pyrogram==0.11.0 +pyrsistent==0.15.6 PySocks==1.6.8 pyTelegramBotAPI==3.6.6 python-anticaptcha==0.3.1 @@ -74,4 +83,5 @@ urllib3==1.24.1 vine==1.2.0 vk-api==11.6.1 Werkzeug==0.14.1 +zipp==0.6.0 zope.interface==4.7.1 diff --git a/static/css/custom.css b/static/css/custom.css index 25054a1..aa2f979 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -64,3 +64,6 @@ html.dark .nav-subtitle { font-size: 12.8px; font-size: 0.8rem; } +html.dark .jsoneditor { + background: #fff; +} diff --git a/static/css/custom.styl b/static/css/custom.styl index f5b9bf7..75d30eb 100644 --- a/static/css/custom.styl +++ b/static/css/custom.styl @@ -67,3 +67,6 @@ html.dark padding 15px font-size 12.8px font-size 0.8rem + + .jsoneditor + background: #fff