From d673b995d3b5ba439a70b06b39684ea68192208c Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 7 Feb 2019 12:45:24 +0300 Subject: [PATCH] Add viber notifications --- chatbot/admin.py | 6 - chatbot/apps.py | 5 - chatbot/locale/ru/LC_MESSAGES/django.po | 112 ----------- chatbot/migrations/0001_initial.py | 64 ------ chatbot/migrations/0002_auto_20180808_1236.py | 27 --- chatbot/migrations/__init__.py | 0 chatbot/models.py | 73 ------- chatbot/telebot.py | 149 -------------- chatbot/views.py | 3 - devapp/locale/ru/LC_MESSAGES/django.po | 2 +- devapp/views.py | 16 +- djing/lib/decorators.py | 20 +- djing/local_settings.py.template | 3 + djing/settings.py | 7 +- djing/urls.py | 1 + messenger/__init__.py | 1 + messenger/admin.py | 7 + messenger/apps.py | 5 + messenger/forms.py | 28 +++ messenger/locale/ru/LC_MESSAGES/django.po | 190 ++++++++++++++++++ messenger/migrations/0001_initial.py | 88 ++++++++ {chatbot => messenger/migrations}/__init__.py | 0 messenger/models.py | 129 ++++++++++++ messenger/tasks.py | 56 ++++++ .../templates/messenger/add_messenger.html | 15 ++ .../templates/messenger/messenger_list.html | 57 ++++++ .../messenger/vibermessenger_form.html | 49 +++++ messenger/tests.py | 3 + messenger/urls.py | 17 ++ messenger/views.py | 161 +++++++++++++++ msg_app/models.py | 34 ++-- msg_app/urls.py | 3 +- msg_app/views.py | 22 -- requirements.txt | 7 +- static/js/my.js | 4 - systemd_units/djing_telebot.service | 15 -- taskapp/handle.py | 55 +++-- taskapp/urls.py | 3 +- taskapp/views.py | 21 +- telebot.py | 29 --- templates/base.html | 9 + 41 files changed, 886 insertions(+), 610 deletions(-) delete mode 100644 chatbot/admin.py delete mode 100644 chatbot/apps.py delete mode 100644 chatbot/locale/ru/LC_MESSAGES/django.po delete mode 100644 chatbot/migrations/0001_initial.py delete mode 100644 chatbot/migrations/0002_auto_20180808_1236.py delete mode 100644 chatbot/migrations/__init__.py delete mode 100644 chatbot/models.py delete mode 100755 chatbot/telebot.py delete mode 100644 chatbot/views.py create mode 100644 messenger/__init__.py create mode 100644 messenger/admin.py create mode 100644 messenger/apps.py create mode 100644 messenger/forms.py create mode 100644 messenger/locale/ru/LC_MESSAGES/django.po create mode 100644 messenger/migrations/0001_initial.py rename {chatbot => messenger/migrations}/__init__.py (100%) create mode 100644 messenger/models.py create mode 100644 messenger/tasks.py create mode 100644 messenger/templates/messenger/add_messenger.html create mode 100644 messenger/templates/messenger/messenger_list.html create mode 100644 messenger/templates/messenger/vibermessenger_form.html create mode 100644 messenger/tests.py create mode 100644 messenger/urls.py create mode 100644 messenger/views.py delete mode 100644 systemd_units/djing_telebot.service delete mode 100755 telebot.py diff --git a/chatbot/admin.py b/chatbot/admin.py deleted file mode 100644 index ef06580..0000000 --- a/chatbot/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from . import models - -admin.site.register(models.MessageHistory) -admin.site.register(models.TelegramBot) -admin.site.register(models.MessageQueue) diff --git a/chatbot/apps.py b/chatbot/apps.py deleted file mode 100644 index d440779..0000000 --- a/chatbot/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ChatbotConfig(AppConfig): - name = 'chatbot' diff --git a/chatbot/locale/ru/LC_MESSAGES/django.po b/chatbot/locale/ru/LC_MESSAGES/django.po deleted file mode 100644 index d557baa..0000000 --- a/chatbot/locale/ru/LC_MESSAGES/django.po +++ /dev/null @@ -1,112 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Dmitry Novikov , 2017. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-09 14:57+0300\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Dmitry Novikov \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" -"%100>=11 && n%100<=14)? 2 : 3);\n" - -#: models.py:13 -msgid "Employee" -msgstr "Сотрудник" - -#: models.py:14 -msgid "Telegram chat id" -msgstr "Номер чата из telegram" - -#: models.py:21 -msgid "Telegram bot" -msgstr "Telegram бот" - -#: models.py:22 -msgid "Telegram bots" -msgstr "Telegram боты" - -#: models.py:36 -msgid "Message history" -msgstr "История собщений" - -#: models.py:37 -msgid "Message histories" -msgstr "Истории собщений" - -#: models.py:54 -msgid "Target employee" -msgstr "Сотрудник" - -#: models.py:55 -msgid "Message" -msgstr "Сообщение" - -#: models.py:60 -msgid "Status of message" -msgstr "Статус сообщения" - -#: models.py:62 -msgid "App tag" -msgstr "Тэг приложения" - -#: models.py:71 models.py:72 -msgid "Message queue" -msgstr "Очередь оповещений" - -#: telebot.py:64 -msgid "Let's get acquainted, what is your name? Write your login from billing." -msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга." - -#: telebot.py:85 -msgid "I do not know the answer to this yet." -msgstr "Я пока не знаю ответа на это" - -#: telebot.py:106 -msgid "" -"You are not found in the database, check that it correctly pointed out its " -"LOGIN. Try again" -msgstr "" -"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. " -"Попробуй ещё" - -#: telebot.py:110 -#, python-format -msgid "" -"Yes, it's nice to meet %(username)s, I will notify you about events in " -"billing. Successful work ;)" -msgstr "" -"Да, приятно познакомиться %(username)s, я буду оповещать тебя о событиях в " -"биллинге. Удачной работы ;)" - -#: telebot.py:122 -msgid "Let's ping, write ip. It will be necessary to wait 10 seconds" -msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек" - -#: telebot.py:129 -msgid "It's not like ip address, try again" -msgstr "Это не похоже на ip адрес, попробуй ещё" - -#: telebot.py:132 -#, python-format -msgid "You're '%s', right?" -msgstr "Ты ведь %s ?" - -#: telebot.py:140 -msgid "Telegram bot token not found" -msgstr "Токен для бота Telegram не найден" - -#: telebot.py:145 -#, python-format -msgid "Recipient '%s' does not subscribed on notifications" -msgstr "%s не подписан на оповещения" diff --git a/chatbot/migrations/0001_initial.py b/chatbot/migrations/0001_initial.py deleted file mode 100644 index 5a6be22..0000000 --- a/chatbot/migrations/0001_initial.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-26 00:20 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='MessageHistory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('message', models.CharField(max_length=255)), - ('date_sent', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Message history', - 'verbose_name_plural': 'Message histories', - 'db_table': 'chat_message_history', - }, - ), - migrations.CreateModel( - name='MessageQueue', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('message', models.CharField(max_length=255, verbose_name='Message')), - ('status', models.CharField(choices=[('n', 'New'), ('r', 'Read')], default='n', max_length=1, - verbose_name='Status of message')), - ('tag', models.CharField(default='none', max_length=6, verbose_name='App tag')), - ('target_employee', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Target employee')), - ], - options={ - 'verbose_name': 'Message queue', - 'verbose_name_plural': 'Message queue', - 'db_table': 'chat_message_queue', - }, - ), - migrations.CreateModel( - name='TelegramBot', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('chat_id', models.PositiveIntegerField(default=0, verbose_name='Telegram chat id')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Employee')), - ], - options={ - 'verbose_name': 'Telegram bot', - 'verbose_name_plural': 'Telegram bots', - 'db_table': 'chat_telegram_bot', - }, - ), - ] diff --git a/chatbot/migrations/0002_auto_20180808_1236.py b/chatbot/migrations/0002_auto_20180808_1236.py deleted file mode 100644 index 52df0dc..0000000 --- a/chatbot/migrations/0002_auto_20180808_1236.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-08-08 12:36 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('chatbot', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='messagehistory', - options={'ordering': ('-date_sent',), 'verbose_name': 'Message history', 'verbose_name_plural': 'Message histories'}, - ), - migrations.AlterModelOptions( - name='messagequeue', - options={'ordering': ('target_employee__username',), 'verbose_name': 'Message queue', 'verbose_name_plural': 'Message queue'}, - ), - migrations.AlterModelOptions( - name='telegrambot', - options={'ordering': ('chat_id',), 'verbose_name': 'Telegram bot', 'verbose_name_plural': 'Telegram bots'}, - ), - ] diff --git a/chatbot/migrations/__init__.py b/chatbot/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chatbot/models.py b/chatbot/models.py deleted file mode 100644 index 200d72a..0000000 --- a/chatbot/models.py +++ /dev/null @@ -1,73 +0,0 @@ -from django.utils.translation import gettext_lazy as _ -from django.db import models -from django.conf import settings - -AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL') - - -class ChatException(Exception): - pass - - -class TelegramBot(models.Model): - user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('Employee')) - chat_id = models.PositiveIntegerField(_('Telegram chat id'), default=0) - - def __str__(self): - return "%s - %d" % (self.user.get_full_name(), self.chat_id) - - class Meta: - db_table = 'chat_telegram_bot' - verbose_name = _('Telegram bot') - verbose_name_plural = _('Telegram bots') - ordering = ('chat_id',) - - -class MessageHistory(models.Model): - user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE) - message = models.CharField(max_length=255) - date_sent = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return self.message - - class Meta: - db_table = 'chat_message_history' - verbose_name = _('Message history') - verbose_name_plural = _('Message histories') - ordering = ('-date_sent',) - - -class MessageQueueManager(models.Manager): - def pop(self, user, tag='none'): - msgs = self.filter(target_employee=user, status='n', tag=tag)[:1].only('message').values('id', 'message') - if len(msgs) > 0: - self.filter(id=msgs[0]['id']).delete() - return msgs[0]['message'] - - def push(self, msg, user, tag='none'): - msg = self.create(target_employee=user, message=msg, tag=tag) - return msg - - -class MessageQueue(models.Model): - target_employee = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('Target employee')) - message = models.CharField(_('Message'), max_length=255) - STATUSES = ( - ('n', 'New'), - ('r', 'Read') - ) - status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') - # tag: each application puts its own to separate messages between these applications - tag = models.CharField(_('App tag'), max_length=6, default='none') - - objects = MessageQueueManager() - - def __str__(self): - return self.message - - class Meta: - db_table = 'chat_message_queue' - verbose_name = _('Message queue') - verbose_name_plural = _('Message queue') - ordering = ('target_employee__username',) diff --git a/chatbot/telebot.py b/chatbot/telebot.py deleted file mode 100755 index 5f348a5..0000000 --- a/chatbot/telebot.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -from telepot import helper, glance, Bot -from telepot.exception import TelegramError -import os -import socket -import collections -from django.utils.translation import ugettext as _ -from urllib3.exceptions import ProtocolError -from .models import TelegramBot, ChatException, MessageQueue -from chatbot.models import MessageHistory -from accounts_app.models import UserProfile -from django.conf import settings - -token = getattr(settings, 'TELEGRAM_BOT_TOKEN') - - -class DjingTelebot(helper.ChatHandler): - _current_user = None - _dialog_fn = None - _chat_id = 0 - - def __init__(self, seed_tuple, **kwargs): - super(DjingTelebot, self).__init__(seed_tuple, **kwargs) - self.cmds = { - 'ping': self.ping, - 'iam': self.say_me - } - - # отвечаем пользователю - def _sent_reply(self, text): - self.sender.sendMessage(text) - - # задаём вопрос пользователю, и ожидаем ответ в fn - def _question(self, text, fn): - if not isinstance(fn, collections.Callable): - raise TypeError - self._dialog_fn = fn - if text is not None: - self._sent_reply(text) - - # сохраняем сообщение в базе - def _message_log(self, msg): - if self._current_user is None: - self._question(None, self.question_name) - return False - MessageHistory.objects.create( - user=self._current_user, - message=msg - ) - return True - - # Начинаем диалог - # @seed - chat_id - def open(self, initial_msg, seed): - content_type, chat_type, chat_id = glance(initial_msg) - if content_type != 'text': - return True - self._chat_id = chat_id - try: - tbot = TelegramBot.objects.get(chat_id=seed) - self._current_user = tbot.user - self._message_log(initial_msg['text']) - except TelegramBot.DoesNotExist: - self._question(_("Let's get acquainted, what is your name? Write your login from billing."), - self.question_name) - return True # prevent on_message() from being called on the initial message - - # получаем сообщение - def on_chat_message(self, msg): - content_type, chat_type, chat_id = glance(msg) - if content_type != 'text': - return - self._chat_id = chat_id - text = msg['text'].lower() - - # выполняем комманды если они есть - if text in self.cmds.keys(): - self.cmds[text]() - elif self._dialog_fn is not None: - if not callable(self._dialog_fn): - raise TypeError - self._dialog_fn(text) - self._dialog_fn = None - else: - self._sent_reply(_('I do not know the answer to this yet.')) - - if not self._message_log(text): - return - - # спрашиваем имя пользователя - def question_name(self, username): - try: - profile = UserProfile.objects.get(username=username) - self._current_user = profile - try: - TelegramBot.objects.get(user=profile) - except TelegramBot.DoesNotExist: - if self._chat_id == 0: - raise ChatException('telebot.py. def question_name: Chat id is empty') - TelegramBot.objects.create( - user=profile, - chat_id=self._chat_id - ) - except UserProfile.DoesNotExist: - self._question( - _("You are not found in the database, check that it correctly pointed out its LOGIN. Try again"), - self.question_name) - return - self._sent_reply( - _("Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)") - % {'username': profile.get_full_name()}) - - # заканчивается время диалога - # ex - время ожидания (timeout=ex в pave_event_space) - def on_close(self, ex): - self._current_user = None - self._dialog_fn = None - self._chat_id = 0 - - def ping(self, ip=None): - if ip is None: - self._question(_("Let's ping, write ip. It will be necessary to wait 10 seconds"), self.ping) - return - try: - socket.inet_aton(ip) - ret = os.popen('`which ping` -c 10 %s' % ip).read() - self._sent_reply(ret) - except socket.error: - self._question(_("It's not like ip address, try again"), self.ping) - - def say_me(self): - self._sent_reply(_("You're '%s', right?") % self._current_user.get_full_name()) - - -# Just sending text to specified account -def send_notify(msg_text, account, tag='none'): - try: - MessageQueue.objects.push(msg=msg_text, user=account, tag=tag) - if token is None: - raise ChatException(_('Telegram bot token not found')) - tb = TelegramBot.objects.get(user=account) - tbot = Bot(token) - tbot.sendMessage(tb.chat_id, msg_text) - except TelegramBot.DoesNotExist: - raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name()) - except ProtocolError as e: - raise ChatException('ProtocolError: %s' % e) - except TelegramError as e: - raise ChatException('Telegram error: %s' % e) diff --git a/chatbot/views.py b/chatbot/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/chatbot/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/devapp/locale/ru/LC_MESSAGES/django.po b/devapp/locale/ru/LC_MESSAGES/django.po index 9cfa2d7..513ca3d 100644 --- a/devapp/locale/ru/LC_MESSAGES/django.po +++ b/devapp/locale/ru/LC_MESSAGES/django.po @@ -324,7 +324,7 @@ msgstr "Название типа свича" #: templates/devapp/custom_dev_page/onu.html:22 #: templates/devapp/custom_dev_page/onu_for_zte.html:22 msgid "Attached user" -msgstr "Прикрепленный абонент" +msgstr "Прикреплённый абонент" #: templates/devapp/custom_dev_page/onu.html:48 #: templates/devapp/custom_dev_page/onu_for_zte.html:50 diff --git a/devapp/views.py b/devapp/views.py index 7f5c0d6..0ad2665 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -3,7 +3,6 @@ from ipaddress import ip_address from abonapp.models import Abon from accounts_app.models import UserProfile -from chatbot.models import ChatException from devapp.base_intr import DeviceImplementationError from django.conf import settings from django.contrib import messages @@ -27,8 +26,8 @@ from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError, \ from djing.tasks import multicast_email_notify from easysnmp import EasySNMPTimeoutError, EasySNMPError from group_app.models import Group -from guardian.decorators import \ - permission_required_or_403 as permission_required +from messenger.tasks import multicast_viber_notify +from guardian.decorators import permission_required_or_403 as permission_required from guardian.shortcuts import get_objects_for_user from .forms import DeviceForm, PortForm, DeviceExtraDataForm from .models import Device, Port, DeviceDBException, DeviceMonitoringException @@ -709,19 +708,20 @@ class OnDeviceMonitoringEvent(global_base_views.SecureApiView): recipients = UserProfile.objects.get_profiles_by_group( device_down.group.pk) - multicast_email_notify.delay(msg_text=gettext(notify_text) % { + user_ids = tuple(recipient.pk for recipient in recipients.only('pk').iterator() if recipient.flags.notify_mon) + text = gettext(notify_text) % { 'device_name': "%s(%s) %s" % ( device_down.ip_address, device_down.mac_addr, device_down.comment ) - }, account_ids=( - recipient.pk for recipient in recipients.only('pk').iterator() if recipient.flags.notify_mon - )) + } + multicast_email_notify.delay(msg_text=text, account_ids=user_ids) + multicast_viber_notify.delay(None, account_id_list=user_ids, message_text=text) return { 'text': 'notification successfully sent' } - except ChatException as e: + except ValueError as e: return { 'text': str(e) } diff --git a/djing/lib/decorators.py b/djing/lib/decorators.py index cbdf61f..fedfe9e 100644 --- a/djing/lib/decorators.py +++ b/djing/lib/decorators.py @@ -1,29 +1,11 @@ from functools import wraps from django.conf import settings -from django.http import HttpResponseRedirect, HttpResponseForbidden, JsonResponse +from django.http import HttpResponseForbidden, JsonResponse from django.shortcuts import redirect from djing.lib import check_sign -def require_ssl(view): - """ - Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of - the page. - from: https://gist.github.com/ckinsey/9709984 - """ - - @wraps(view) - def wrapper(request, *args, **kwargs): - debug = getattr(settings, 'DEBUG', False) - if not debug and not request.is_secure(): - target_url = "https://%s%s" % (request.META['HTTP_HOST'], request.path_info) - return HttpResponseRedirect(target_url) - return view(request, *args, **kwargs) - - return wrapper - - # Allow to view only admins def only_admins(fn): @wraps(fn) diff --git a/djing/local_settings.py.template b/djing/local_settings.py.template index a564159..4695a06 100644 --- a/djing/local_settings.py.template +++ b/djing/local_settings.py.template @@ -69,3 +69,6 @@ EMAIL_HOST = 'smtp.mailserver.com' EMAIL_PORT = 587 EMAIL_HOST_PASSWORD = 'password' EMAIL_USE_TLS = True + +# public url for Viber Bot +VIBER_BOT_PUBLIC_URL = 'https://your_domain.name' diff --git a/djing/settings.py b/djing/settings.py index 15698c8..349de4c 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -41,6 +41,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_cleanup.apps.CleanupConfig', 'ip_pool', 'accounts_app', 'gw_app', @@ -52,7 +53,7 @@ INSTALLED_APPS = [ 'statistics', 'taskapp', 'clientsideapp', - 'chatbot', + 'messenger', 'msg_app', 'dialing_app', 'group_app', @@ -234,3 +235,7 @@ REDIS_PORT = '6379' BROKER_URL = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600} CELERY_RESULT_BACKEND = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' + + +# public url for Viber Bot +VIBER_BOT_PUBLIC_URL = local_settings.VIBER_BOT_PUBLIC_URL diff --git a/djing/urls.py b/djing/urls.py index ad7e9da..b409ea5 100644 --- a/djing/urls.py +++ b/djing/urls.py @@ -18,6 +18,7 @@ urlpatterns = [ path('dialing/', include('dialing_app.urls', namespace='dialapp')), path('groups/', include('group_app.urls', namespace='group_app')), path('ip_pool/', include('ip_pool.urls', namespace='ip_pool')), + path('messenger/', include('messenger.urls', namespace='messenger')), path('gw/', include('gw_app.urls', namespace='gw_app')) # Switch language diff --git a/messenger/__init__.py b/messenger/__init__.py new file mode 100644 index 0000000..4407c25 --- /dev/null +++ b/messenger/__init__.py @@ -0,0 +1 @@ +default_app_config = 'messenger.apps.messengerConfig' \ No newline at end of file diff --git a/messenger/admin.py b/messenger/admin.py new file mode 100644 index 0000000..5e2afdc --- /dev/null +++ b/messenger/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from messenger import models + +admin.site.register(models.Messenger) +admin.site.register(models.ViberMessenger) +admin.site.register(models.ViberSubscriber) +admin.site.register(models.ViberMessage) diff --git a/messenger/apps.py b/messenger/apps.py new file mode 100644 index 0000000..786128b --- /dev/null +++ b/messenger/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class messengerConfig(AppConfig): + name = 'messenger' diff --git a/messenger/forms.py b/messenger/forms.py new file mode 100644 index 0000000..f85aabd --- /dev/null +++ b/messenger/forms.py @@ -0,0 +1,28 @@ +from django import forms +from messenger import models + + +class MessengerForm(forms.ModelForm): + class Meta: + model = models.Messenger + fields = ('bot_type',) + + +class MessengerViberForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + kwargs['initial']['bot_type'] = 1 + super().__init__(*args, **kwargs) + inst = getattr(self, 'instance') + if inst: + self.fields['bot_type'].disabled = True + #self.fields['bot_type'].widget.attrs['disabled'] = True + + class Meta: + model = models.ViberMessenger + fields = '__all__' + + +class MessengerViberMessageForm(forms.ModelForm): + class Meta: + model = models.ViberMessage + fields = '__all__' diff --git a/messenger/locale/ru/LC_MESSAGES/django.po b/messenger/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..65272d9 --- /dev/null +++ b/messenger/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,190 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Dmitry Novikov nerosketch@gmail.com, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-02-06 13:45+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" + +#: models.py:16 templates/messenger/messenger_list.html:20 +msgid "Title" +msgstr "Название" + +#: models.py:18 +msgid "Viber" +msgstr "Вайбер" + +#: models.py:20 +msgid "Bot type" +msgstr "Тип бота" + +#: models.py:21 templates/messenger/messenger_list.html:22 +msgid "Slug" +msgstr "Ссыль" + +#: models.py:28 models.py:92 +msgid "messenger" +msgstr "Мэссенджер" + +#: models.py:29 templates/messenger/messenger_list.html:7 +#: templates/messenger/messenger_list.html:12 +#: templates/messenger/vibermessenger_form.html:8 +msgid "messengers" +msgstr "Мэссенджэры" + +#: models.py:48 +msgid "Bot secret token" +msgstr "Секретный токен viber" + +#: models.py:49 models.py:108 +msgid "Avatar" +msgstr "Аватар" + +#: models.py:83 +msgid "Viber messenger" +msgstr "Viber мэссенджэр" + +#: models.py:84 +msgid "Viber messengers" +msgstr "Viber мэссенджэры" + +#: models.py:89 +msgid "Message" +msgstr "Сообщение" + +#: models.py:90 +msgid "Date" +msgstr "Дата" + +#: models.py:91 +msgid "Sender" +msgstr "Отправитель" + +#: models.py:93 +msgid "Subscriber" +msgstr "Подписчик" + +#: models.py:100 +msgid "Viber message" +msgstr "Сообщение viber" + +#: models.py:101 +msgid "Viber messages" +msgstr "Сообщения viber" + +#: models.py:106 +msgid "User unique id in viber" +msgstr "Уникальный id viber" + +#: models.py:107 +msgid "Name" +msgstr "Имя" + +#: models.py:109 +msgid "System account" +msgstr "Системная учётная запись" + +#: models.py:116 +msgid "Viber subscriber" +msgstr "Подписчик viber" + +#: models.py:117 +msgid "Viber subscribers" +msgstr "Подписчики viber" + +#: templates/messenger/add_messenger.html:5 +msgid "Select bot type" +msgstr "Выберите тип бота" + +#: templates/messenger/add_messenger.html:11 +msgid "Add" +msgstr "Добавить" + +#: templates/messenger/messenger_list.html:21 +msgid "Type" +msgstr "Тип" + +#: templates/messenger/messenger_list.html:33 +msgid "Edit" +msgstr "Изменить" + +#: templates/messenger/messenger_list.html:40 +msgid "messengers was not found" +msgstr "Мэссенджеры не найдены" + +#: templates/messenger/messenger_list.html:49 +msgid "New" +msgstr "Новый" + +#: templates/messenger/vibermessenger_form.html:10 +msgid "Update messenger" +msgstr "Обновить мэссенджэр" + +#: templates/messenger/vibermessenger_form.html:11 +msgid "Change viber" +msgstr "Изменить viber" + +#: templates/messenger/vibermessenger_form.html:13 +msgid "Add messenger" +msgstr "Добавить мэссенджэр" + +#: templates/messenger/vibermessenger_form.html:14 +msgid "Add viber" +msgstr "Добавить viber" + +#: templates/messenger/vibermessenger_form.html:25 +msgid "Change messenger" +msgstr "Изменить мэссенджэр" + +#: templates/messenger/vibermessenger_form.html:28 +msgid "Add new messenger" +msgstr "Добавить мэссенджэр" + +#: templates/messenger/vibermessenger_form.html:39 +msgid "Save" +msgstr "Сохранить" + +#: templates/messenger/vibermessenger_form.html:43 +msgid "Send webhook" +msgstr "Отправить webhook" + +#: views.py:38 +msgid "Unexpected bot type" +msgstr "Не известный тип бота" + +#: views.py:51 +msgid "New viber messenger successfully created" +msgstr "Новый viber мэссенджэр успешно создан" + +#: views.py:62 +msgid "Viber messenger successfully updated" +msgstr "viber мэссенджэр успешно обновлён" + +#: views.py:73 +msgid "Viber messenger successfully deleted" +msgstr "viber мэссенджэр успешно удалён" + +#: views.py:132 +msgid "My telephone number" +msgstr "Мой номер телефона" + +#: views.py:150 +msgid "" +"Telephone not found, please specify telephone number in account in billing" +msgstr "" +"Номер телефона не найден. Укажите свой номер телефона в учётке в биллинге" + +msgid "Your account is attached. Now you will be receive notifications from billing" +msgstr "Ваша учётка из биллинга привязана. Теперь вы будете получать оповещения из биллинга." diff --git a/messenger/migrations/0001_initial.py b/messenger/migrations/0001_initial.py new file mode 100644 index 0000000..31086f9 --- /dev/null +++ b/messenger/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 2.1.3 on 2019-02-07 12:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Messenger', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=64, verbose_name='Title')), + ('bot_type', models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Viber')], verbose_name='Bot type')), + ('slug', models.SlugField(verbose_name='Slug')), + ], + options={ + 'verbose_name': 'messenger', + 'verbose_name_plural': 'messengers', + 'db_table': 'messengers', + 'ordering': ('title',), + }, + ), + migrations.CreateModel( + name='ViberMessage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('msg', models.TextField(verbose_name='Message')), + ('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')), + ('sender', models.CharField(max_length=32, verbose_name='Sender')), + ], + options={ + 'verbose_name': 'Viber message', + 'verbose_name_plural': 'Viber messages', + 'db_table': 'viber_messages_notifications', + 'ordering': ('-date',), + }, + ), + migrations.CreateModel( + name='ViberSubscriber', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uid', models.CharField(max_length=32, verbose_name='User unique id in viber')), + ('name', models.CharField(blank=True, max_length=32, null=True, verbose_name='Name')), + ('avatar', models.URLField(blank=True, max_length=250, null=True, verbose_name='Avatar')), + ('account', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='System account')), + ], + options={ + 'verbose_name': 'Viber subscriber', + 'verbose_name_plural': 'Viber subscribers', + 'db_table': 'viber_subscriber', + 'ordering': ('name',), + }, + ), + migrations.CreateModel( + name='ViberMessenger', + fields=[ + ('messenger_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='messenger.Messenger')), + ('token', models.CharField(max_length=64, verbose_name='Bot secret token')), + ('avatar', models.ImageField(null=True, upload_to='viber_avatar', verbose_name='Avatar')), + ], + options={ + 'verbose_name': 'Viber messenger', + 'verbose_name_plural': 'Viber messengers', + 'db_table': 'viber_messenger_notifications', + 'ordering': ('title',), + }, + bases=('messenger.messenger',), + ), + migrations.AddField( + model_name='vibermessage', + name='subscriber', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='messenger.ViberSubscriber', verbose_name='Subscriber'), + ), + migrations.AddField( + model_name='vibermessage', + name='messenger', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='messenger.ViberMessenger', verbose_name='messenger'), + ), + ] diff --git a/chatbot/__init__.py b/messenger/migrations/__init__.py similarity index 100% rename from chatbot/__init__.py rename to messenger/migrations/__init__.py diff --git a/messenger/models.py b/messenger/models.py new file mode 100644 index 0000000..8976595 --- /dev/null +++ b/messenger/models.py @@ -0,0 +1,129 @@ +from typing import Iterable +from urllib.parse import urljoin + +from django.conf import settings +from django.shortcuts import resolve_url +from django.utils.translation import gettext_lazy as _ +from django.db import models +from viberbot import Api, BotConfiguration +from viberbot.api.messages import TextMessage +from viberbot.api.messages.message import Message + +from accounts_app.models import UserProfile + + +class Messenger(models.Model): + title = models.CharField(_('Title'), max_length=64) + CHAT_TYPES = ( + (1, _('Viber')), + ) + bot_type = models.PositiveSmallIntegerField(_('Bot type'), choices=CHAT_TYPES, blank=True) + slug = models.SlugField(_('Slug')) + + def __str__(self): + return self.title + + class Meta: + db_table = 'messengers' + verbose_name = _('messenger') + verbose_name_plural = _('messengers') + ordering = ('title',) + + def get_absolute_url(self): + if self.bot_type == 1: + return resolve_url('messenger:update_viber_messenger', self.slug) + + def get_next_url(self): + if self.bot_type == 1: # Viber + return resolve_url('messenger:update_viber_messenger', self.slug) + else: + return resolve_url('messenger:messengers_list') + + +class ViberMessenger(Messenger): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._viber_cache = None + + token = models.CharField(_('Bot secret token'), max_length=64) + avatar = models.ImageField(_('Avatar'), upload_to='viber_avatar', null=True) + + def get_viber(self): + if self._viber_cache is None: + self._viber_cache = Api(BotConfiguration( + name=str(self.slug), + avatar=self.avatar.url, + auth_token=str(self.token) + )) + return self._viber_cache + + def send_message(self, to: UserProfile, msg): + try: + viber = self.get_viber() + vs = to.vibersubscriber + if issubclass(msg.__class__, Message): + viber.send_messages(str(vs.uid), msg) + else: + viber.send_messages(str(vs.uid), TextMessage(text=msg)) + except ViberSubscriber.DoesNotExist: + pass + + def send_messages(self, receivers, msg_text: str): + """ + :param receivers: QuerySet of accounts_app.UserProfile + :param msg_text: text message + :return: nothing + """ + viber = self.get_viber() + msg = TextMessage(text=msg_text) + for vs in ViberSubscriber.objects.filter(account__in=receivers).iterator(): + viber.send_messages(str(vs.uid), msg) + + def send_webhook(self): + pub_url = getattr(settings, 'VIBER_BOT_PUBLIC_URL') + listen_url = resolve_url('messenger:listen_viber_bot', self.slug) + public_url = urljoin(pub_url, listen_url) + viber = self.get_viber() + viber.set_webhook(public_url, ['failed', 'subscribed', 'unsubscribed', 'conversation_started']) + + def __str__(self): + return self.title + + class Meta: + db_table = 'viber_messenger_notifications' + verbose_name = _('Viber messenger') + verbose_name_plural = _('Viber messengers') + ordering = ('title',) + + +class ViberMessage(models.Model): + msg = models.TextField(_('Message')) + date = models.DateTimeField(_('Date'), auto_now_add=True) + sender = models.CharField(_('Sender'), max_length=32) + messenger = models.ForeignKey(ViberMessenger, verbose_name=_('messenger'), on_delete=models.CASCADE) + subscriber = models.ForeignKey('ViberSubscriber', on_delete=models.SET_NULL, verbose_name=_('Subscriber'), null=True) + + def __str__(self): + return self.msg + + class Meta: + db_table = 'viber_messages_notifications' + verbose_name = _('Viber message') + verbose_name_plural = _('Viber messages') + ordering = ('-date',) + + +class ViberSubscriber(models.Model): + uid = models.CharField(_('User unique id in viber'), max_length=32) + name = models.CharField(_('Name'), max_length=32, null=True, blank=True) + avatar = models.URLField(_('Avatar'), max_length=250, null=True, blank=True) + account = models.OneToOneField(UserProfile, on_delete=models.CASCADE, verbose_name=_('System account'), blank=True, null=True) + + def __str__(self): + return self.name or 'no' + + class Meta: + db_table = 'viber_subscriber' + verbose_name = _('Viber subscriber') + verbose_name_plural = _('Viber subscribers') + ordering = ('name',) diff --git a/messenger/tasks.py b/messenger/tasks.py new file mode 100644 index 0000000..39f103f --- /dev/null +++ b/messenger/tasks.py @@ -0,0 +1,56 @@ +from typing import Optional, Iterable + +from celery import shared_task + +from accounts_app.models import UserProfile +from messenger.models import ViberMessenger + + +@shared_task +def send_viber_message(messenger_id: Optional[int], account_id: int, message_text: str) -> Optional[str]: + """ + Send text message via viber + :param messenger_id: Primary key UID for messanger.ViberMessenger + :param account_id: User id from accounts_app.UserProfile + :param message_text: + :return: Optional text for log + """ + if not message_text: + return 'ERROR: empty message text' + try: + sp = UserProfile.objects.get(pk=account_id) + if messenger_id is None: + for vm in ViberMessenger.objects.all().iterator(): + vm.send_message(sp, message_text) + else: + vm = ViberMessenger.objects.get(pk=messenger_id) + vm.send_message(sp, message_text) + except ViberMessenger.DoesNotExist: + return 'ERROR: Viber messanger with id=%d not found' % messenger_id + except UserProfile.DoesNotExist: + return 'ERROR: accounts_app.UserProfile with pk=%d does not exist' % account_id + + +@shared_task +def multicast_viber_notify(messenger_id: Optional[int], account_id_list: Iterable[int], message_text: str): + """ + Send multiple message via Viber to several addresses + :param messenger_id: Primary key UID for messanger.ViberMessenger + :param account_id_list: list of account ids from accounts_app.UserProfile + :param message_text: + :return: Optional text for log + """ + if not message_text: + return 'ERROR: empty message text' + account_id_list = tuple(account_id_list) + recipients = UserProfile.objects.filter(pk__in=account_id_list) + if not recipients.exists(): + return 'No recipients found from ids: %s' % ','.join(str(i) for i in account_id_list) + if messenger_id is None: + for vm in ViberMessenger.objects.all().iterator(): + vm.send_messages(recipients, message_text) + else: + vm = ViberMessenger.objects.filter(pk=messenger_id).first() + if vm is None: + return 'ERROR ViberMessenger with pk=%d does not exist' % messenger_id + vm.send_messages(recipients, message_text) diff --git a/messenger/templates/messenger/add_messenger.html b/messenger/templates/messenger/add_messenger.html new file mode 100644 index 0000000..b90e83d --- /dev/null +++ b/messenger/templates/messenger/add_messenger.html @@ -0,0 +1,15 @@ +{% load i18n bootstrap3 %} +
{% csrf_token %} + + + + +
diff --git a/messenger/templates/messenger/messenger_list.html b/messenger/templates/messenger/messenger_list.html new file mode 100644 index 0000000..3f70917 --- /dev/null +++ b/messenger/templates/messenger/messenger_list.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load dpagination i18n %} + +{% block breadcrumb %} + +{% endblock %} + +{% block page-header %} + {% trans 'messengers' %} +{% endblock %} + +{% block main %} +
+ + + + + + + + + + + {% for messenger in object_list %} + + + + + + + {% empty %} + + + + {% endfor %} + + {% if perms.messenger.add_messenger %} + + + + + + {% endif %} +
{% trans 'Title' %}{% trans 'Type' %}{% trans 'Slug' %}#
{{ messenger.title }}{{ messenger.get_bot_type_display }}{{ messenger.slug }} + + + +
{% trans 'messengers was not found' %}
+ + + +
+
+{% endblock %} diff --git a/messenger/templates/messenger/vibermessenger_form.html b/messenger/templates/messenger/vibermessenger_form.html new file mode 100644 index 0000000..a395fc6 --- /dev/null +++ b/messenger/templates/messenger/vibermessenger_form.html @@ -0,0 +1,49 @@ +{% extends request.is_ajax|yesno:'bajax.html,base.html' %} +{% load i18n bootstrap3 %} + + +{% block breadcrumb %} + +{% endblock %} + + +{% block main %} + + {% if object %} + {% url 'messenger:update_viber_messenger' object.slug as form_url %} + {% trans 'Change messenger' as panel_title %} + {% else %} + {% url 'messenger:add_viber_messenger' as form_url %} + {% trans 'Add new messenger' as panel_title %} + {% endif %} + +
+
+

{{ panel_title }}

+
+
+
{% csrf_token %} + {% bootstrap_form form %} + + {% if object %} + + {% trans 'Send webhook' %} + + {% endif %} +
+
+
+{% endblock %} diff --git a/messenger/tests.py b/messenger/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/messenger/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/messenger/urls.py b/messenger/urls.py new file mode 100644 index 0000000..46acab3 --- /dev/null +++ b/messenger/urls.py @@ -0,0 +1,17 @@ +from django.urls import path +from django.views.decorators.csrf import csrf_exempt + +from messenger import views + + +app_name = 'messenger' + +urlpatterns = [ + path('', views.messengerListView.as_view(), name='messengers_list'), + path('new/', views.AddmessengerCreateView.as_view(), name='add_messenger'), + path('viber/new/', views.AddmessengerViberCreateView.as_view(), name='add_viber_messenger'), + path('viber//update/', views.UpdateVibermessengerUpdateView.as_view(), name='update_viber_messenger'), + path('viber//delete/', views.RemoveVibermessengerDeleteView.as_view(), name='delete_viber_messenger'), + path('viber//listen/', csrf_exempt(views.ListenViberView.as_view()), name='listen_viber_bot'), + path('viber//set_webhook/', views.SetWebhook.as_view(), name='webhook_viber_bot'), +] diff --git a/messenger/views.py b/messenger/views.py new file mode 100644 index 0000000..c5f6cf6 --- /dev/null +++ b/messenger/views.py @@ -0,0 +1,161 @@ +from django.contrib import messages +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.http import HttpResponseForbidden, HttpResponse, HttpResponseNotFound +from django.shortcuts import resolve_url +from django.urls import reverse_lazy +from django.utils.decorators import method_decorator +from django.utils.translation import gettext_lazy as _, gettext +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import ListView, CreateView, UpdateView, DeleteView, FormView, View +from django.views.generic.detail import SingleObjectMixin +from viberbot.api.messages import KeyboardMessage, ContactMessage +from viberbot.api.user_profile import UserProfile as ViberUserProfile +from viberbot.api.viber_requests import ViberMessageRequest, ViberSubscribedRequest, ViberFailedRequest, \ + ViberUnsubscribedRequest + +from accounts_app.models import UserProfile +from djing.lib.mixins import LoginAdminPermissionMixin, LoginAdminMixin +from messenger import forms, models + +from messenger.models import ViberMessage, ViberSubscriber + + +class messengerListView(LoginAdminPermissionMixin, ListView): + model = models.Messenger + permission_required = 'messenger.view_messenger' + + +class AddmessengerCreateView(LoginAdminMixin, FormView): + template_name = 'messenger/add_messenger.html' + form_class = forms.MessengerForm + + def form_valid(self, form): + bot_type = form.cleaned_data.get('bot_type') + if isinstance(bot_type, int) and bot_type > 0: + if bot_type == 1: + self.success_url = resolve_url('messenger:add_viber_messenger') + return super().form_valid(form) + messages.info(self.request, _('Unexpected bot type')) + self.success_url = resolve_url('messenger:messengers_list') + return super().form_valid(form) + + +class AddmessengerViberCreateView(LoginAdminMixin, PermissionRequiredMixin, CreateView): + model = models.ViberMessenger + form_class = forms.MessengerViberForm + permission_required = 'messenger.add_vibermessenger' + success_url = reverse_lazy('messenger:messengers_list') + + def form_valid(self, form): + r = super().form_valid(form) + messages.success(self.request, _('New viber messenger successfully created')) + return r + + +class UpdateVibermessengerUpdateView(LoginAdminPermissionMixin, UpdateView): + model = models.ViberMessenger + form_class = forms.MessengerViberForm + permission_required = 'messenger.change_vibermessenger' + + def form_valid(self, form): + r = super().form_valid(form) + messages.success(self.request, _('Viber messenger successfully updated')) + return r + + +class RemoveVibermessengerDeleteView(LoginAdminPermissionMixin, DeleteView): + model = models.ViberMessenger + permission_required = 'messenger.delete_vibermessenger' + success_url = reverse_lazy('messenger:messengers_list') + + def delete(self, request, *args, **kwargs): + r = super().delete(request, *args, **kwargs) + messages.success(request, _('Viber messenger successfully deleted')) + return r + + +@method_decorator(csrf_exempt, name='post') +class ListenViberView(SingleObjectMixin, View): + http_method_names = 'post', + model = models.ViberMessenger + + def post(self, request, *args, **kwargs): + obj = self.get_object() + if not obj: + return HttpResponseNotFound() + self.object = obj + viber = obj.get_viber() + if not viber.verify_signature(request.body, request.META.get('HTTP_X_VIBER_CONTENT_SIGNATURE')): + return HttpResponseForbidden() + # this library supplies a simple way to receive a request object + vr = viber.parse_request(request.body) + if isinstance(vr, ViberMessageRequest): + in_msg = vr.message + if isinstance(in_msg, ContactMessage): + self.inbox_contact(in_msg, vr.sender) + subscriber, created = self.make_subscriber(vr.sender) + if not created: + ViberMessage.objects.create( + msg=vr.message, + sender=vr.sender.id, + messenger=obj, + subscriber=subscriber + ) + elif isinstance(vr, ViberSubscribedRequest): + self.make_subscriber(vr.user) + elif isinstance(vr, ViberFailedRequest): + print("client failed receiving message. failure: {0}".format(vr)) + elif isinstance(vr, ViberUnsubscribedRequest): + ViberSubscriber.objects.filter( + uid=vr.user_id + ).delete() + return HttpResponse(status=200) + + def make_subscriber(self, viber_user_profile: ViberUserProfile): + subscriber, created = ViberSubscriber.objects.get_or_create( + uid=viber_user_profile.id, + defaults={ + 'name': viber_user_profile.name, + 'avatar': viber_user_profile.avatar + } + ) + if created and hasattr(self, 'object'): + msg = KeyboardMessage(keyboard={ + 'Type': 'keyboard', + 'DefaultHeight': True, + 'Buttons': ({ + 'ActionType': 'share-phone', + 'ActionBody': 'reply to me', + "Text": gettext('My telephone number'), + "TextSize": "medium" + },) + }, min_api_version=3) + viber = self.object.get_viber() + viber.send_messages(viber_user_profile.id, msg) + return subscriber, created + + def inbox_contact(self, msg, sender: ViberUserProfile): + tel = msg.contact.phone_number + accs = UserProfile.objects.filter(telephone__icontains=tel) + viber = self.object.get_viber() + if accs.exists(): + subs = ViberSubscriber.objects.filter(uid=sender.id) + if subs.exists(): + subs.update(account=accs.first()) + viber.send_messages(sender.id, gettext( + 'Your account is attached. Now you will be receive notifications from billing' + )) + else: + viber.send_messages(sender.id, gettext('Telephone not found, please specify telephone number in account in billing')) + + +class SetWebhook(LoginAdminMixin, SingleObjectMixin, View): + http_method_names = 'get', + model = models.ViberMessenger + + def get(self, request, *args, **kwargs): + obj = self.get_object() + if not obj: + return HttpResponseNotFound + obj.send_webhook() + return HttpResponse(b'ok', status=200) diff --git a/msg_app/models.py b/msg_app/models.py index 136bebd..7a06148 100644 --- a/msg_app/models.py +++ b/msg_app/models.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from accounts_app.models import UserProfile from djing.tasks import send_email_notify -from chatbot.models import ChatException class MessageError(Exception): @@ -203,24 +202,21 @@ class Conversation(models.Model): return messages[0] def new_message(self, text, attachment, author, with_status=True): - try: - msg = Message.objects.create( - text=text, conversation=self, - attachment=attachment, author=author - ) - if with_status: - for participant in self.participants.filter(is_active=True): - if participant == author: - continue - MessageStatus.objects.create(msg=msg, user=participant) - if participant.flags.notify_msg: - send_email_notify.delay( - msg_text=text, - account_id=participant.pk - ) - return msg - except ChatException as e: - raise MessageError(e) + msg = Message.objects.create( + text=text, conversation=self, + attachment=attachment, author=author + ) + if with_status: + for participant in self.participants.filter(is_active=True): + if participant == author: + continue + MessageStatus.objects.create(msg=msg, user=participant) + if participant.flags.notify_msg: + send_email_notify.delay( + msg_text=text, + account_id=participant.pk + ) + return msg @staticmethod def remove_message(msg): diff --git a/msg_app/urls.py b/msg_app/urls.py index bfcd91f..eabb0d7 100644 --- a/msg_app/urls.py +++ b/msg_app/urls.py @@ -7,6 +7,5 @@ urlpatterns = [ path('', views.ConversationsListView.as_view(), name='home'), path('new/', views.new_conversation, name='new_conversation'), path('/', views.to_conversation, name='to_conversation'), - path('//del/', views.remove_msg, name='remove_msg'), - path('check_news/', views.check_news, name='check_news') + path('//del/', views.remove_msg, name='remove_msg') ] diff --git a/msg_app/views.py b/msg_app/views.py index 2310ab3..80d9573 100644 --- a/msg_app/views.py +++ b/msg_app/views.py @@ -1,15 +1,12 @@ -from json import dumps from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied -from django.http import HttpResponse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from django.contrib import messages from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ListView -from chatbot.models import MessageQueue from djing.lib.decorators import only_admins from guardian.decorators import permission_required_or_403 as permission_required @@ -87,22 +84,3 @@ def remove_msg(request, conv_id, msg_id): conversation_id = msg.conversation.pk msg.delete() return redirect('msg_app:to_conversation', conversation_id) - - -@login_required -@only_admins -def check_news(request): - if request.user.is_authenticated: - msg = MessageQueue.objects.pop(user=request.user, tag='msgapp') - if msg is None: - r = {'auth': True, 'exist': False} - else: - r = { - 'auth': True, - 'exist': True, - 'content': msg, - 'title': "%s" % _('Message') - } - else: - r = {'auth': False} - return HttpResponse(dumps(r)) diff --git a/requirements.txt b/requirements.txt index 8ac946f..0357e23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ urllib3 Django>=2 Pillow -telepot # for mac address field netaddr @@ -31,6 +30,12 @@ asterisk # django-bitfield -e git://github.com/disqus/django-bitfield.git#egg=django-bitfield +# django_cleanup for clean unused media +-e git://github.com/un1t/django-cleanup.git#egg=django_cleanup + +# viberbot +-e git://github.com/Viber/viber-bot-python.git#egg=viberbot + Celery redis==2.10.6 celery[redis] diff --git a/static/js/my.js b/static/js/my.js index 82a5def..c785ca9 100644 --- a/static/js/my.js +++ b/static/js/my.js @@ -305,8 +305,4 @@ $(document).ready(function () { $('[data-toggle="tooltip"]').tooltip({container:'body'}); $('.btn_ajloader').ajloader({'dst_block': '#id_block_devices'}); - - $(document).notifys({news_url: '/tasks/check_news', check_interval: 50}); - $(document).notifys({news_url: '/msg/check_news', check_interval: 55}); - }); diff --git a/systemd_units/djing_telebot.service b/systemd_units/djing_telebot.service deleted file mode 100644 index eb0de74..0000000 --- a/systemd_units/djing_telebot.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Djing telegram bot - -[Service] -Type=simple -ExecStart=/usr/bin/python3 ./telebot.py -PIDFile=/run/djing_telebot.pid -WorkingDirectory=/var/www/djing -TimeoutSec=9 -Restart=always -User=www-data -Group=www-data - -[Install] -WantedBy=multi-user.target diff --git a/taskapp/handle.py b/taskapp/handle.py index c001525..2285258 100644 --- a/taskapp/handle.py +++ b/taskapp/handle.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from djing.tasks import send_email_notify -from chatbot.models import ChatException -from djing.lib import MultipleException +from djing.tasks import send_email_notify, multicast_email_notify +from messenger.tasks import multicast_viber_notify, send_viber_message class TaskException(Exception): @@ -11,31 +10,31 @@ class TaskException(Exception): def handle(task, author, recipients): - errors = [] + profile_ids = [] for recipient in recipients: if not recipient.flags.notify_task: continue - try: - task_status = _('Task') - # If signal to myself then quietly - if author == recipient: - return - # If task completed or failed - elif task.state == 'F' or task.state == 'C': - task_status = _('Task completed') - - fulltext = render_to_string('taskapp/notification.html', { - 'task': task, - 'abon': task.abon, - 'task_status': task_status - }) - - if task.state == 'F' or task.state == 'C': - # If task completed or failed than send one message to author - send_email_notify.delay(fulltext, author.pk) - else: - send_email_notify.delay(fulltext, recipient.pk) - except ChatException as e: - errors.append(e) - if len(errors) > 0: - raise MultipleException(errors) + # If signal to myself then quietly + if author == recipient: + return + profile_ids.append(recipient.pk) + + task_status = _('Task') + + # If task completed or failed + if task.state == 'F' or task.state == 'C': + task_status = _('Task completed') + + fulltext = render_to_string('taskapp/notification.html', { + 'task': task, + 'abon': task.abon, + 'task_status': task_status + }) + + if task.state == 'F' or task.state == 'C': + # If task completed or failed than send one message to author + send_email_notify.delay(fulltext, author.pk) + send_viber_message.delay(None, author.pk, fulltext) + else: + multicast_email_notify.delay(fulltext, profile_ids) + multicast_viber_notify.delay(None, profile_ids, fulltext) diff --git a/taskapp/urls.py b/taskapp/urls.py index 0b38544..ad07312 100644 --- a/taskapp/urls.py +++ b/taskapp/urls.py @@ -21,6 +21,5 @@ urlpatterns = [ path('my/', views.MyTaskListView.as_view(), name='my_tasks'), path('all/', views.AllTasksListView.as_view(), name='all_tasks'), path('all_new/', views.AllNewTasksListView.as_view(), name='all_new_tasks'), - path('empty/', views.EmptyTasksListView.as_view(), name='empty_tasks'), - path('check_news/', views.check_news, name='check_news') + path('empty/', views.EmptyTasksListView.as_view(), name='empty_tasks') ] diff --git a/taskapp/views.py b/taskapp/views.py index c1731c0..4488332 100644 --- a/taskapp/views.py +++ b/taskapp/views.py @@ -11,11 +11,10 @@ from django.conf import settings from django.views.generic.edit import FormMixin, DeleteView, UpdateView from guardian.shortcuts import assign_perm -from chatbot.models import MessageQueue from abonapp.models import Abon from djing import httpresponse_to_referrer from djing.lib import safe_int, MultipleException, RuTimedelta -from djing.lib.decorators import only_admins, json_view +from djing.lib.decorators import only_admins from djing.lib.mixins import LoginAdminMixin, LoginAdminPermissionMixin from .handle import TaskException from .models import Task, ExtraComment @@ -272,24 +271,6 @@ def remind(request, task_id): return redirect('taskapp:home') -@json_view -def check_news(request): - if request.user.is_authenticated and request.user.is_admin: - msg = MessageQueue.objects.pop(user=request.user, tag='taskap') - if msg is not None: - r = { - 'auth': True, - 'exist': True, - 'content': msg, - 'title': _('Task') - } - else: - r = {'auth': True, 'exist': False} - else: - r = {'auth': False} - return r - - class NewCommentView(LoginAdminMixin, LoginRequiredMixin, CreateView): form_class = ExtraCommentForm model = ExtraComment diff --git a/telebot.py b/telebot.py deleted file mode 100755 index 0111c53..0000000 --- a/telebot.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -import os -from pid.decorator import pidfile -import django -from telepot import DelegatorBot -from telepot.exception import BadHTTPResponse -from telepot.delegate import per_chat_id, create_open, pave_event_space - - -@pidfile(pidname='djing_telebot.pid', piddir='/run') -def main(): - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") - django.setup() - from chatbot.telebot import token, DjingTelebot - while True: - try: - bot = DelegatorBot(token, [ - pave_event_space()( - per_chat_id(), create_open, DjingTelebot, timeout=300 - ), - ]) - bot.message_loop(run_forever='Listening ...') - except BadHTTPResponse as e: - print(e) - - -if __name__ == '__main__': - main() diff --git a/templates/base.html b/templates/base.html index cbf12ad..4ee2130 100644 --- a/templates/base.html +++ b/templates/base.html @@ -113,6 +113,15 @@ {% endif %} + {% if perms.gw_app.view_nasmodel %} + {% url 'messenger:messengers_list' as mesngrhome %} + + + {% trans 'Messengers' %} + + + {% endif %} +