From 7b26b3cac6896a60eec50b8e5ad05515c510fc98 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Wed, 15 May 2019 17:27:24 +0100 Subject: [PATCH 01/11] =?UTF-8?q?A=C3=B1ade=20comando=20/status=20para=20s?= =?UTF-8?q?aber=20si=20el=20bot=20esta=20activo=20y=20desde=20cuando?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/bot.py b/bot.py index e936696..ad3c9ce 100644 --- a/bot.py +++ b/bot.py @@ -1,5 +1,8 @@ +import logging +import datetime + import telegram -from telegram.ext import Updater, Filters, MessageHandler +from telegram.ext import Updater, Filters, MessageHandler, CommandHandler from telegram import Bot, Update import config @@ -44,11 +47,43 @@ def reply(bot, update): ) -updater = Updater(config.TELEGRAM_BOT_TOKEN) -dp = updater.dispatcher +def since(reference=datetime.datetime.now()): + now = datetime.datetime.now() + delta = now - reference + buff = [] + if delta.days: + buff.append('{} días'.format(delta.days)) + hours = delta.seconds // 3600 + if hours > 0: + buff.append('{} horas'.format(hours)) + minutes = delta.seconds // 60 + if minutes > 0: + buff.append('{} minutos'.format(minutes)) + seconds = delta.seconds % 60 + buff.append('{} segundos'.format(seconds)) + return ' '.join(buff) + + +def status(bot, update): + bot.send_message( + chat_id=update.message.chat_id, + text='Status is OK, running since {}'.format(since()) + ) + + +def main(): + logging.basicConfig(level=logging.INFO) + logging.info('Starting bot...') + updater = Updater(config.TELEGRAM_BOT_TOKEN) + dp = updater.dispatcher + + dp.add_handler(MessageHandler(Filters.status_update.new_chat_members, welcome)) + dp.add_handler(MessageHandler(Filters.group, reply)) + dp.add_handler(CommandHandler('status', status)) + + updater.start_polling() + updater.idle() -dp.add_handler(MessageHandler(Filters.status_update.new_chat_members, welcome)) -dp.add_handler(MessageHandler(Filters.group, reply)) -updater.start_polling() -updater.idle() +if __name__ == "__main__": + main() From 893f2d3856293e19fe1f368f0267c667e456eadb Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Wed, 15 May 2019 17:30:12 +0100 Subject: [PATCH 02/11] Los mensajes del bot sobre /status van en ingles --- bot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index ad3c9ce..7faa278 100644 --- a/bot.py +++ b/bot.py @@ -52,15 +52,15 @@ def since(reference=datetime.datetime.now()): delta = now - reference buff = [] if delta.days: - buff.append('{} días'.format(delta.days)) + buff.append('{} days'.format(delta.days)) hours = delta.seconds // 3600 if hours > 0: - buff.append('{} horas'.format(hours)) + buff.append('{} hours'.format(hours)) minutes = delta.seconds // 60 if minutes > 0: - buff.append('{} minutos'.format(minutes)) + buff.append('{} minutes'.format(minutes)) seconds = delta.seconds % 60 - buff.append('{} segundos'.format(seconds)) + buff.append('{} seconds'.format(seconds)) return ' '.join(buff) From 43fe09ffea159b067d84a3c2b520da176e93d266 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 09:42:45 +0100 Subject: [PATCH 03/11] Added a logger --- bot.py | 23 +++++++++++++++++++++-- config.py | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/bot.py b/bot.py index 7faa278..098147e 100644 --- a/bot.py +++ b/bot.py @@ -9,8 +9,22 @@ import utils +def get_logger(name=__name__): + if get_logger.logger is None: + logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=config.LOG_LEVEL, + ) + get_logger.logger = logging.getLogger(name) + return get_logger.logger + +get_logger.logger = None + + def welcome(bot: Bot, update: Update): + logger = get_logger() new_member = update.message.new_chat_members[0] + logger.info(f'send welcome message for {new_member.name}') msg = None if new_member.is_bot: @@ -35,12 +49,14 @@ def welcome(bot: Bot, update: Update): def reply(bot, update): + logger = get_logger() if not config.bot_replies_enabled(): return msg = update.message.text reply_spec = utils.triggers_reply(msg) if reply_spec is not None: + logger.info(f'bot sends reply {reply_spec.reply}') bot.send_message( chat_id=update.message.chat_id, text=reply_spec.reply @@ -65,6 +81,8 @@ def since(reference=datetime.datetime.now()): def status(bot, update): + logger = get_logger() + logger.info('bot asked to execute status commamd') bot.send_message( chat_id=update.message.chat_id, text='Status is OK, running since {}'.format(since()) @@ -72,8 +90,8 @@ def status(bot, update): def main(): - logging.basicConfig(level=logging.INFO) - logging.info('Starting bot...') + logger = get_logger() + logger.info('Starting bot...') updater = Updater(config.TELEGRAM_BOT_TOKEN) dp = updater.dispatcher @@ -81,6 +99,7 @@ def main(): dp.add_handler(MessageHandler(Filters.group, reply)) dp.add_handler(CommandHandler('status', status)) + logger.info('Bot is ready') updater.start_polling() updater.idle() diff --git a/config.py b/config.py index 42e6c1a..742c15e 100644 --- a/config.py +++ b/config.py @@ -5,6 +5,7 @@ default="put here the token of your bot" ) +LOG_LEVEL = config('LOG_LEVEL', default='WARNING') # How likely is the bot to be triggered by one of the patterns it recognises. # - Allowed values: A float from 0 to 1 (0 will disable bot replies) From 239b18501a2e73334c0eed8064e9da9a4d0d7831 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:12:56 +0100 Subject: [PATCH 04/11] Add function since to utils This gives a description of time since the program started, for the /status command. --- test/test_utils.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ utils.py | 34 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 test/test_utils.py diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 0000000..027ab54 --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import datetime + +import pytest +import utils + + +def test_since_second(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 13, 35, 16) + assert utils.since(now, ref) == '1 second' + + +def test_since_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 13, 35, 21) + assert utils.since(now, ref) == '6 seconds' + + +def test_since_minute_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 13, 36, 21) + assert utils.since(now, ref) == '1 minute 6 seconds' + + +def test_since_minutes_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 13, 37, 21) + assert utils.since(now, ref) == '2 minutes 6 seconds' + + +def test_since_hour_and_minutes_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 14, 37, 21) + assert utils.since(now, ref) == '1 hour 2 minutes 6 seconds' + + +def test_since_hours_and_minutes_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 16, 15, 37, 21) + assert utils.since(now, ref) == '2 hours 2 minutes 6 seconds' + + +def test_since_day_hours_and_minutes_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 17, 15, 37, 21) + assert utils.since(now, ref) == '1 day 2 hours 2 minutes 6 seconds' + + +def test_since_days_hours_and_minutes_and_seconds(): + ref = datetime.datetime(2019, 5, 16, 13, 35, 15) + now = datetime.datetime(2019, 5, 19, 15, 37, 21) + assert utils.since(now, ref) == '3 days 2 hours 2 minutes 6 seconds' + + +if __name__ == "__main__": + pytest.main() diff --git a/utils.py b/utils.py index 7ed05f3..cf1c75f 100644 --- a/utils.py +++ b/utils.py @@ -1,4 +1,5 @@ import functools +import datetime import random import re import typing @@ -89,3 +90,36 @@ def triggers_reply(message: str) -> typing.Optional[BotReplySpec]: bot_reply = random.choice(bot_reply) return BotReplySpec(message, match.group(0), bot_reply) return None + + +def since(dt=None, reference=datetime.datetime.now()) -> str: + """Returns a textual description of time passed. + + Parameters: + + - dt: datetime is the date to calculate the difference from + reference. If not used, take the value from the current + datetime. + + - reference: datetime is the datetime used to get the difference + ir delta. If not defined, default value is since the definition + of the function, this is,since the moment the current run of the + program started. + """ + dt = dt or datetime.datetime.now() + delta = dt - reference + buff = [] + days = delta.days + if days: + buff.append(f'{days} day' if days == 1 else f'{days} days') + seconds = delta.seconds + if seconds > 3600: + hours = seconds // 3600 + buff.append(f'{hours} hour' if hours == 1 else f'{hours} hours') + seconds = seconds % 3600 + minutes = seconds // 60 + if minutes > 0: + buff.append(f'{minutes} minute' if minutes == 1 else f'{minutes} minutes') + seconds = seconds % 60 + buff.append(f'{seconds} second' if seconds == 1 else f'{seconds} seconds') + return ' '.join(buff) From 29953c24aacafa1a5bc47d84b3f9f62f10636631 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:14:16 +0100 Subject: [PATCH 05/11] Added values BOT_GREETING and LOG_LEVEL First one is used in the /start command, second one defines default log level for the modules using logging (default is WARNING) --- config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.py b/config.py index 742c15e..1b0e783 100644 --- a/config.py +++ b/config.py @@ -72,3 +72,5 @@ def bot_replies_enabled() -> bool: MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 CHINESE_CHARS_MAX_PERCENT = 0.15 + +BOT_GREETING = "Hi! I'm a friendly, ligthty psychopath robot." From 4e14d533e9e1e577b0c904d27e37512fac0d18ac Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:25:38 +0100 Subject: [PATCH 06/11] New config variable POLL_INTERVAL (default value is 3) --- config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 1b0e783..f191bd8 100644 --- a/config.py +++ b/config.py @@ -5,13 +5,20 @@ default="put here the token of your bot" ) -LOG_LEVEL = config('LOG_LEVEL', default='WARNING') - # How likely is the bot to be triggered by one of the patterns it recognises. # - Allowed values: A float from 0 to 1 (0 will disable bot replies) VERBOSITY = config("BOT_VERBOSITY", float, default=0.33) +# Log level, default is WARNING +LOG_LEVEL = config('LOG_LEVEL', default='WARNING') + +# Poll interval for telegram API request, default is 3 seconds +POLL_INTERVAL = config('POLL_INTERVAL', int, default=3) + +# Bot message for start command +BOT_GREETING = "Hi! I'm a friendly, ligthty psychopath robot." + # A username longer than this will be considered non-human # - Allowed values: An integer larger than 1 MAX_HUMAN_USERNAME_LENGTH = config('MAX_HUMAN_USERNAME_LENGTH', @@ -72,5 +79,3 @@ def bot_replies_enabled() -> bool: MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 CHINESE_CHARS_MAX_PERCENT = 0.15 - -BOT_GREETING = "Hi! I'm a friendly, ligthty psychopath robot." From 787c97f757c0f38b68977a940b7a318cdb7b277f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:45:21 +0100 Subject: [PATCH 07/11] Add commands /status, /start and /help --- bot.py | 64 +++++++++++++++++++++++++++------------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/bot.py b/bot.py index 098147e..78ff261 100644 --- a/bot.py +++ b/bot.py @@ -9,20 +9,27 @@ import utils -def get_logger(name=__name__): - if get_logger.logger is None: - logging.basicConfig( - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=config.LOG_LEVEL, - ) - get_logger.logger = logging.getLogger(name) - return get_logger.logger +logger = logging.getLogger('bot') -get_logger.logger = None + +def start(bot, update): + logger.info('Received command /start') + bot.send_message(chat_id=update.message.chat_id, text=config.BOT_GREETING) + + +def help(bot, update): + logger.info('Received command /help') + bot.send_message( + chat_id=update.message.chat_id, + text="Available commands:\n" + " - /start - start intereaction with the bot\n" + " - /help - Show commands\n" + " - /status - Show status and alive time\n" + ) def welcome(bot: Bot, update: Update): - logger = get_logger() + logger.info('Received new user event') new_member = update.message.new_chat_members[0] logger.info(f'send welcome message for {new_member.name}') msg = None @@ -49,7 +56,6 @@ def welcome(bot: Bot, update: Update): def reply(bot, update): - logger = get_logger() if not config.bot_replies_enabled(): return @@ -63,46 +69,36 @@ def reply(bot, update): ) -def since(reference=datetime.datetime.now()): - now = datetime.datetime.now() - delta = now - reference - buff = [] - if delta.days: - buff.append('{} days'.format(delta.days)) - hours = delta.seconds // 3600 - if hours > 0: - buff.append('{} hours'.format(hours)) - minutes = delta.seconds // 60 - if minutes > 0: - buff.append('{} minutes'.format(minutes)) - seconds = delta.seconds % 60 - buff.append('{} seconds'.format(seconds)) - return ' '.join(buff) - - def status(bot, update): - logger = get_logger() - logger.info('bot asked to execute status commamd') + logger.info('bot asked to execute /status commamd') bot.send_message( chat_id=update.message.chat_id, - text='Status is OK, running since {}'.format(since()) + text='Status is OK, running since {}'.format(utils.since()) ) def main(): - logger = get_logger() + logging.basicConfig( + level=config.LOG_LEVEL, + format='%(asctime)s [%(name)s] %(levelname)s: %(message)s', + ) logger.info('Starting bot...') + logger.info(f'- Log level is {config.LOG_LEVEL}') + logger.info(f'- Poll interval is {config.POLL_INTERVAL}') updater = Updater(config.TELEGRAM_BOT_TOKEN) dp = updater.dispatcher + dp.add_handler(CommandHandler('start', start)) + dp.add_handler(CommandHandler('help', help)) + dp.add_handler(CommandHandler('status', status)) dp.add_handler(MessageHandler(Filters.status_update.new_chat_members, welcome)) dp.add_handler(MessageHandler(Filters.group, reply)) - dp.add_handler(CommandHandler('status', status)) logger.info('Bot is ready') - updater.start_polling() + updater.start_polling(poll_interval=config.POLL_INTERVAL) updater.idle() if __name__ == "__main__": main() + From d0a0b691ab3928b68213355983fc67e197e5be56 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:46:52 +0100 Subject: [PATCH 08/11] Fixed typo --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index f191bd8..3784f7b 100644 --- a/config.py +++ b/config.py @@ -17,7 +17,7 @@ POLL_INTERVAL = config('POLL_INTERVAL', int, default=3) # Bot message for start command -BOT_GREETING = "Hi! I'm a friendly, ligthty psychopath robot." +BOT_GREETING = "Hi! I'm a friendly, ligthly psychopath robot" # A username longer than this will be considered non-human # - Allowed values: An integer larger than 1 From f35a333e605770feed0849c5234a04d9d42f5626 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 14:58:03 +0100 Subject: [PATCH 09/11] Fixed error, call to main was deleted in merge --- bot.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 1b4accc..356b062 100644 --- a/bot.py +++ b/bot.py @@ -96,4 +96,8 @@ def main(): logger.info('Bot is ready') updater.start_polling(poll_interval=config.POLL_INTERVAL) - updater.idle() \ No newline at end of file + updater.idle() + + +if __name__ == "__main__": + main() From e2e9e55bb79ebb3abea55659b172212af21a2f25 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 15:26:02 +0100 Subject: [PATCH 10/11] utils.py formatted with black --- utils.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/utils.py b/utils.py index cf1c75f..4165337 100644 --- a/utils.py +++ b/utils.py @@ -14,13 +14,15 @@ def is_chinese(c): Returns True if the character passed as parameter is a Chinese one """ num = ord(c) - return any(( - 0x2E80 <= num <= 0x2FD5, - 0x3190 <= num <= 0x319F, - 0x3400 <= num <= 0x4DBF, - 0x4E00 <= num <= 0x9FCC, - 0x6300 <= num <= 0x77FF, - )) + return any( + ( + 0x2E80 <= num <= 0x2FD5, + 0x3190 <= num <= 0x319F, + 0x3400 <= num <= 0x4DBF, + 0x4E00 <= num <= 0x9FCC, + 0x6300 <= num <= 0x77FF, + ) + ) def too_much_chinese_chars(s): @@ -51,11 +53,13 @@ def is_bot(user: User): :rtype: bool """ # Add all the checks that you consider necessary - return any(( - not is_valid_name(user), - too_much_chinese_chars(user.first_name), - is_tgmember_sect(user.first_name), - )) + return any( + ( + not is_valid_name(user), + too_much_chinese_chars(user.first_name), + is_tgmember_sect(user.first_name), + ) + ) @functools.lru_cache() @@ -111,15 +115,15 @@ def since(dt=None, reference=datetime.datetime.now()) -> str: buff = [] days = delta.days if days: - buff.append(f'{days} day' if days == 1 else f'{days} days') + buff.append(f"{days} day" if days == 1 else f"{days} days") seconds = delta.seconds if seconds > 3600: hours = seconds // 3600 - buff.append(f'{hours} hour' if hours == 1 else f'{hours} hours') + buff.append(f"{hours} hour" if hours == 1 else f"{hours} hours") seconds = seconds % 3600 minutes = seconds // 60 if minutes > 0: - buff.append(f'{minutes} minute' if minutes == 1 else f'{minutes} minutes') + buff.append(f"{minutes} minute" if minutes == 1 else f"{minutes} minutes") seconds = seconds % 60 - buff.append(f'{seconds} second' if seconds == 1 else f'{seconds} seconds') - return ' '.join(buff) + buff.append(f"{seconds} second" if seconds == 1 else f"{seconds} seconds") + return " ".join(buff) From 943b0578dded308516df1328699619657c439e9f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 16 May 2019 16:03:46 +0100 Subject: [PATCH 11/11] Los nombres de los command empiezan con el prefijo command_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Como help es un builtin, aunque obviamente no lo vamos a usar aquí, quizá estaría bien hacer un poor-man-namespacing de las funciones de comando poniendoles numbres como command_help, command_start y así. --- bot.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bot.py b/bot.py index 356b062..652550f 100644 --- a/bot.py +++ b/bot.py @@ -12,12 +12,12 @@ logger = logging.getLogger('bot') -def start(bot, update): +def command_start(bot, update): logger.info('Received command /start') bot.send_message(chat_id=update.message.chat_id, text=config.BOT_GREETING) -def help(bot, update): +def command_help(bot, update): logger.info('Received command /help') bot.send_message( chat_id=update.message.chat_id, @@ -28,6 +28,14 @@ def help(bot, update): ) +def command_status(bot, update): + logger.info('bot asked to execute /status commamd') + bot.send_message( + chat_id=update.message.chat_id, + text=f'Status is OK, running since {utils.since()}', + ) + + def welcome(bot: Bot, update: Update): logger.info('Received new user event') new_member = update.message.new_chat_members[0] @@ -69,14 +77,6 @@ def reply(bot, update): ) -def status(bot, update): - logger.info('bot asked to execute /status commamd') - bot.send_message( - chat_id=update.message.chat_id, - text=f'Status is OK, running since {utils.since()}', - ) - - def main(): logging.basicConfig( level=config.LOG_LEVEL, @@ -88,9 +88,9 @@ def main(): updater = Updater(config.TELEGRAM_BOT_TOKEN) dp = updater.dispatcher - dp.add_handler(CommandHandler('start', start)) - dp.add_handler(CommandHandler('help', help)) - dp.add_handler(CommandHandler('status', status)) + dp.add_handler(CommandHandler('start', command_start)) + dp.add_handler(CommandHandler('help', command_help)) + dp.add_handler(CommandHandler('status', command_status)) dp.add_handler(MessageHandler(Filters.status_update.new_chat_members, welcome)) dp.add_handler(MessageHandler(Filters.group, reply))