diff --git a/django_telegrambot/__init__.py b/django_telegrambot/__init__.py index 3c91b5f..e4a581e 100644 --- a/django_telegrambot/__init__.py +++ b/django_telegrambot/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.2.6' +__version__ = '0.2.7' default_app_config = 'django_telegrambot.apps.DjangoTelegramBot' diff --git a/django_telegrambot/apps.py b/django_telegrambot/apps.py index 5890e83..aeb6d44 100644 --- a/django_telegrambot/apps.py +++ b/django_telegrambot/apps.py @@ -7,6 +7,7 @@ import telegram from django.utils.module_loading import module_has_submodule from telegram.ext import Dispatcher +from telegram.ext import Updater import os.path import logging @@ -14,21 +15,45 @@ logger = logging.getLogger(__name__) TELEGRAM_BOT_MODULE_NAME = 'telegrambot' +WEBHOOK_MODE, POLLING_MODE = range(2) +class classproperty(property): + def __get__(self, obj, objtype=None): + return super(classproperty, self).__get__(objtype) + def __set__(self, obj, value): + super(classproperty, self).__set__(type(obj), value) + def __delete__(self, obj): + super(classproperty, self).__delete__(type(obj)) class DjangoTelegramBot(AppConfig): name = 'django_telegrambot' verbose_name = 'Django TelegramBot' ready_run = False - dispatcher = None + #_dispatcher = None + #_updater = None bot_tokens = [] bot_usernames = [] dispatchers = [] bots = [] + updaters = [] + __used_tokens = set() + + @classproperty + def dispatcher(cls): + #print("Getting value default dispatcher") + cls.__used_tokens.add(cls.bot_tokens[0]) + return cls.dispatchers[0] + + @classproperty + def updater(cls): + #print("Getting value default updater") + cls.__used_tokens.add(cls.bot_tokens[0]) + return cls.updaters[0] @classmethod - def getDispatcher(cls, bot_id=None, safe=True): + def get_dispatcher(cls, bot_id=None, safe=True): if bot_id is None: + cls.__used_tokens.add(cls.bot_tokens[0]) return cls.dispatchers[0] else: try: @@ -40,12 +65,22 @@ def getDispatcher(cls, bot_id=None, safe=True): index = cls.bot_usernames.index(bot_id) except ValueError: return None + cls.__used_tokens.add(cls.bot_tokens[index]) return cls.dispatchers[index] + @classmethod - def getBot(cls, bot_id=None, safe=True): + def getDispatcher(cls, bot_id=None, safe=True): + return cls.get_dispatcher(bot_id, safe) + + + @classmethod + def get_bot(cls, bot_id=None, safe=True): if bot_id is None: - return cls.bots[0] + if safe: + return cls.bots[0] + else: + return None else: try: index = cls.bot_tokens.index(bot_id) @@ -58,63 +93,113 @@ def getBot(cls, bot_id=None, safe=True): return None return cls.bots[index] + + @classmethod + def getBot(cls, bot_id=None, safe=True): + return cls.get_bot(bot_id, safe) + + + @classmethod + def get_updater(cls, bot_id=None, safe=True): + if bot_id is None: + return cls.updaters[0] + else: + try: + index = cls.bot_tokens.index(bot_id) + except ValueError: + if not safe: + return None + try: + index = cls.bot_usernames.index(bot_id) + except ValueError: + return None + return cls.updaters[index] + + + @classmethod + def getUpdater(cls, id=None, safe=True): + return cls.get_updater(id, safe) + + def ready(self): if DjangoTelegramBot.ready_run: return DjangoTelegramBot.ready_run = True + self.mode = WEBHOOK_MODE + if hasattr(settings, 'TELEGRAM_BOT_MODE'): + if settings.TELEGRAM_BOT_MODE == 'POLLING': + self.mode = POLLING_MODE + modes = ['WEBHOOK','POLLING'] + logger.info('Django Telegram Bot <{} mode>'.format(modes[self.mode])) + if not hasattr(settings, 'TELEGRAM_BOT_TOKENS'): logger.warn('Required TELEGRAM_BOT_TOKENS missing in settings') return tokens = settings.TELEGRAM_BOT_TOKENS - if not hasattr(settings, 'TELEGRAM_WEBHOOK_SITE'): - logger.warn('Required TELEGRAM_WEBHOOK_SITE missing in settings') - return - webhook_site = settings.TELEGRAM_WEBHOOK_SITE - - if not hasattr(settings, 'TELEGRAM_WEBHOOK_BASE'): - logger.warn('Required TELEGRAM_WEBHOOK_BASE missing in settings') - return - webhook_base = settings.TELEGRAM_WEBHOOK_BASE - - use_certificate = False - if hasattr(settings, 'TELEGRAM_WEBHOOK_CERTIFICATE'): - cert = settings.TELEGRAM_WEBHOOK_CERTIFICATE - if os.path.exists(cert): - use_certificate = True - logger.info('TELEGRAM_WEBHOOK_CERTIFICATE found in {}'.format(cert)) + if self.mode == WEBHOOK_MODE: + if not hasattr(settings, 'TELEGRAM_WEBHOOK_SITE'): + logger.warn('Required TELEGRAM_WEBHOOK_SITE missing in settings') + return + webhook_site = settings.TELEGRAM_WEBHOOK_SITE + + webhook_base = "" + if hasattr(settings, 'TELEGRAM_WEBHOOK_BASE'): + logger.warn('Required TELEGRAM_WEBHOOK_BASE missing in settings') + webhook_base = settings.TELEGRAM_WEBHOOK_BASE + + certificate = None + if hasattr(settings, 'TELEGRAM_WEBHOOK_CERTIFICATE'): + cert = settings.TELEGRAM_WEBHOOK_CERTIFICATE + if os.path.exists(cert): + logger.info('TELEGRAM_WEBHOOK_CERTIFICATE found in {}'.format(cert)) + certificate=open(cert, 'rb') + else: + logger.error('TELEGRAM_WEBHOOK_CERTIFICATE not found in {} '.format(cert)) + + timeout = None + if hasattr(settings, 'TELEGRAM_WEBHOOK_TIMEOUT'): + timeout = settings.TELEGRAM_WEBHOOK_TIMEOUT + + max_connections = 40 + if hasattr(settings, 'TELEGRAM_WEBHOOK_MAX_CONNECTIONS'): + max_connections = settings.TELEGRAM_WEBHOOK_MAX_CONNECTIONS + + allowed_updates = None + if hasattr(settings, 'TELEGRAM_WEBHOOK_ALLOWED_UPDATES'): + allowed_updates = settings.TELEGRAM_WEBHOOK_ALLOWED_UPDATES + + for token in tokens: + if self.mode == WEBHOOK_MODE: + bot = telegram.Bot(token=token) + DjangoTelegramBot.dispatchers.append(Dispatcher(bot, None, workers=0)) + hookurl = '{}{}/{}/'.format(webhook_site, webhook_base, token) + setted = bot.setWebhook(hookurl, certificate=certificate, timeout=timeout, max_connections=max_connections, allowed_updates=allowed_updates) + webhook_info = bot.getWebhookInfo() + real_allowed = webhook_info.allowed_updates if webhook_info.allowed_updates else ["ALL"] + bot.more_info = webhook_info + logger.info('Telegram Bot <{}> setting webhook [ {} ] max connections:{} allowed updates:{} pending updates:{} : {}'.format(bot.username, webhook_info.url, webhook_info.max_connections, real_allowed, webhook_info.pending_update_count, setted)) else: - logger.error('TELEGRAM_WEBHOOK_CERTIFICATE not found in {} '.format(cert)) - - for index, token in enumerate(tokens): + updater = Updater(token=token) + bot = updater.bot + bot.delete_webhook() + DjangoTelegramBot.updaters.append(updater) + DjangoTelegramBot.dispatchers.append(updater.dispatcher) + DjangoTelegramBot.__used_tokens.add(token) - bot = telegram.Bot(token=token) - - DjangoTelegramBot.dispatchers.append(Dispatcher(bot, None, workers=0)) DjangoTelegramBot.bots.append(bot) - DjangoTelegramBot.bot_tokens.append(bot.token) + DjangoTelegramBot.bot_tokens.append(token) DjangoTelegramBot.bot_usernames.append(bot.username) - hookurl = '{}{}/{}/'.format(webhook_site, webhook_base, token) - if use_certificate: - setted = bot.setWebhook(hookurl, certificate=open(cert, 'rb')) - else: - setted = bot.setWebhook(hookurl, certificate=None) - - logger.info('Telegram Bot <{}> setting webhook [ {} ] : {}'.format(bot.username, hookurl, setted)) - # per compatibilità salvo il primo bot nella proprietà DjangoTelegramBot.dispatcher - if index == 0: - DjangoTelegramBot.dispatcher = DjangoTelegramBot.dispatchers[0] - logger.debug('Telegram Bot <{}> set as default bot'.format(bot.username)) + logger.debug('Telegram Bot <{}> set as default bot'.format(DjangoTelegramBot.bots[0].username)) def module_exists(module_name, method_name, execute): try: - # m = __import__(module_name).telegrambot m = importlib.import_module(module_name) if execute and hasattr(m, method_name): - logger.debug('Run {}.main()'.format(module_name)) + logger.debug('Run {}.{}()'.format(module_name,method_name)) getattr(m, method_name)() else: logger.debug('Run {}'.format(module_name)) @@ -131,3 +216,14 @@ def module_exists(module_name, method_name, execute): module_name = '%s.%s' % (app_config.name, TELEGRAM_BOT_MODULE_NAME) if module_exists(module_name, 'main', True): logger.info('Loaded {}'.format(module_name)) + + num_bots=len(DjangoTelegramBot.__used_tokens) + if self.mode == POLLING_MODE and num_bots>0: + logger.info('Please manually start polling update for {0} bot{1}. Run command{1}:'.format(num_bots, 's' if num_bots>1 else '')) + for token in DjangoTelegramBot.__used_tokens: + updater = DjangoTelegramBot.get_updater(bot_id=token) + logger.info('python manage.py botpolling --username={}'.format(updater.bot.username)) + + + + diff --git a/django_telegrambot/management/__init__.py b/django_telegrambot/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_telegrambot/management/commands/__init__.py b/django_telegrambot/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_telegrambot/management/commands/botpolling.py b/django_telegrambot/management/commands/botpolling.py new file mode 100644 index 0000000..ba6744d --- /dev/null +++ b/django_telegrambot/management/commands/botpolling.py @@ -0,0 +1,57 @@ +import logging + +from django.core.management.base import BaseCommand +#from telegram.ext import Updater + +from django_telegrambot.apps import DjangoTelegramBot + + +class Command(BaseCommand): + help = "Run telegram bot in polling mode" + can_import_settings = True + + def add_arguments(self, parser): + parser.add_argument('--username', '-i', help="Bot username", default=None) + parser.add_argument('--token', '-t', help="Bot token", default=None) + pass + + def get_updater(self, username=None, token=None): + updater = None + if username is not None: + updater = DjangoTelegramBot.get_updater(bot_id=username) + if not updater: + self.stderr.write("Cannot find default bot with username {}".format(username)) + elif token: + updater = DjangoTelegramBot.get_updater(bot_id=token) + if not updater: + self.stderr.write("Cannot find bot with token {}".format(token)) + return updater + + def handle(self, *args, **options): + from django.conf import settings + if not settings.TELEGRAM_BOT_MODE == 'POLLING': + self.stderr.write("Webhook mode active, change it in your settings if you want use polling update") + return + + updater = self.get_updater(username=options.get('username'), token=options.get('token')) + if not updater: + self.stderr.write("Bot not found") + return + # Enable Logging + logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + logger = logging.getLogger("telegrambot") + logger.setLevel(logging.INFO) + console = logging.StreamHandler() + console.setLevel(logging.INFO) + console.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s')) + logger.addHandler(console) + + #wbinfo = updater.bot.getWebhookInfo() + #logger.warn(wbinfo) + #updater.bot.deleteWebhook() #not yet present in python-telegram-bot 5.3.1 + self.stdout.write("Run polling...") + updater.start_polling() + self.stdout.write("the bot is started and runs until we press Ctrl-C on the command line.") + updater.idle() \ No newline at end of file diff --git a/django_telegrambot/templates/django_telegrambot/index.html b/django_telegrambot/templates/django_telegrambot/index.html new file mode 100644 index 0000000..84c149b --- /dev/null +++ b/django_telegrambot/templates/django_telegrambot/index.html @@ -0,0 +1,88 @@ +{% extends "admin/index.html" %} +{% block extrastyle %} +{{ block.super }} + + +{% endblock %} +{% block extrahead %} + + + + + + +{% endblock %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} + + + {{ block.super }} +
+

Django-TelegramBot

+

+ The full documentation is at https://django-telegrambot.readthedocs.org. +

+

Bot update mode: {{update_mode}}

+ {% if update_mode == 'POLLING' %}

Please remember to start polling mode with commands: +

+

+ {% endif %} + + {% if bot_list %} +

Bot List:

+ + {% else %} +

No bots are available. Please configure it in settings.py

+ {% endif %} +
+ +{% endblock %} diff --git a/django_telegrambot/urls.py b/django_telegrambot/urls.py index 12111ee..64549b5 100644 --- a/django_telegrambot/urls.py +++ b/django_telegrambot/urls.py @@ -1,7 +1,18 @@ from django.conf.urls import url from . import views +from django.conf import settings + +if hasattr(settings, 'TELEGRAM_WEBHOOK_BASE'): + webhook_base = settings.TELEGRAM_WEBHOOK_BASE + if webhook_base.startswith("/"): + webhook_base = webhook_base[1:] + if not webhook_base.endswith("/"): + webhook_base += "/" +else: + webhook_base = "" urlpatterns = [ - url(r'(?P.+?)/$', views.webhook, name='webhook'), + url(r'admin/django-telegrambot/$', views.home, name='django-telegrambot'), + url(r'{}(?P.+?)/$'.format(webhook_base), views.webhook, name='webhook'), ] diff --git a/django_telegrambot/views.py b/django_telegrambot/views.py index d49f168..8412939 100644 --- a/django_telegrambot/views.py +++ b/django_telegrambot/views.py @@ -1,11 +1,15 @@ # coding=utf-8 from django.shortcuts import render +from django.contrib.admin.views.decorators import staff_member_required +from django.conf import settings from django.http import JsonResponse from django_telegrambot.apps import DjangoTelegramBot from django.views.decorators.csrf import csrf_exempt import sys import json import telegram +from telegram.error import (TelegramError, Unauthorized, BadRequest, + TimedOut, ChatMigrated, NetworkError) # import the logging library import logging @@ -13,17 +17,22 @@ logger = logging.getLogger(__name__) -# Create your views here. +@staff_member_required +def home(request): + bot_list = DjangoTelegramBot.bots + context = {'bot_list': bot_list, 'update_mode':settings.TELEGRAM_BOT_MODE} + return render(request, 'django_telegrambot/index.html', context) + @csrf_exempt def webhook (request, bot_token): - + #verifico la validità del token - bot = DjangoTelegramBot.getBot(bot_token, safe=False) + bot = DjangoTelegramBot.getBot(bot_id=bot_token, safe=False) if bot is None: logger.warn('Request for not found token : {}'.format(bot_token)) return JsonResponse({}) - + try: data = json.loads(request.body.decode("utf-8")) @@ -35,7 +44,7 @@ def webhook (request, bot_token): if dispatcher is None: logger.error('Dispatcher for bot <{}> not found : {}'.format(bot.username, bot_token)) return JsonResponse({}) - + try: update = telegram.Update.de_json(data, bot) dispatcher.process_update(update) @@ -48,6 +57,6 @@ def webhook (request, bot_token): # All other errors should not stop the thread, just print them except: logger.error("Bot <{}> : An uncaught error was raised while processing an update\n{}".format(bot.username, sys.exc_info()[0])) - + finally: return JsonResponse({}) diff --git a/requirements.txt b/requirements.txt index 6bd885f..e9949bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ django>=1.8.18 # Additional requirements go here -python-telegram-bot>=5.1 +python-telegram-bot>=6.0.1