diff --git a/bot.py b/bot.py index a2ef939..0bdc92a 100644 --- a/bot.py +++ b/bot.py @@ -7,6 +7,7 @@ """ from telegram.ext import Updater import logging +import time from components.automata import Automata from handlers import interaction_handler @@ -52,6 +53,11 @@ def init_bot(): def main(): + if g.prod_mode: + delay = 15 + log.info(f'Waiting {delay} seconds before start up') + time.sleep(delay) + # TODO handle startup error init_db() init_job_queue() diff --git a/components/message_source.py b/components/message_source.py index 4aace03..00920ff 100644 --- a/components/message_source.py +++ b/components/message_source.py @@ -56,8 +56,7 @@ 'state.all_tasks.no_tasks_yet': 'I could not find any tasks of that status*\n', 'state.all_tasks.notes.no_tasks_yet': '\n:bulb: _To create a new task, just write me something_ :)\n', 'state.all_tasks.notes': '\n:bulb: _To create a new task, just write me something_ :)\n' - ':bulb: _Controls are on the bottom of the list_\n' - ':bulb: _To refresh the list, click on one of the buttons below_', + ':bulb: _To refresh the list, use buttons on the bottom of the list_', 'state.edit_date.success': ':bell: *Notification has been set up!*\n' '\n' @@ -158,7 +157,7 @@ ':bulb: _Введите /date и дату, чтобы включить уведомление (например, /date 21 дек 13-37)_', 'state.new_task.created': 'Создана новая задача. Пожалуйста, выберите категорию из вариантов ниже', - 'state.new_task.not_created': ':pensive: Прости, не удалость создать задачу\n' + 'state.new_task.not_created': ':pensive: Простите, не удалость создать задачу\n' 'Попробуйте еще раз, пожайлуйста', 'state.all_tasks.tasks.upcoming': 'список Ваших предстоящих задач:*\n', @@ -167,8 +166,7 @@ 'state.all_tasks.no_tasks_yet': 'Не найдено задач с выбранным статусом*\n', 'state.all_tasks.notes.no_tasks_yet': '\n:bulb: _Чтобы создать задачу, просто напишите мне что-нибудь_ :)\n', 'state.all_tasks.notes': '\n:bulb: _Чтобы создать задачу, просто напишите мне что-нибудь_ :)\n' - ':bulb: _Кнопки управления расположены внизу списка_\n' - ':bulb: _Чтобы обновить список, используйте кнопки ниже_', + ':bulb: _Чтобы обновить список, используйте кнопки управления внизу списка', 'state.edit_date.success': ':bell: *Уведомление установлено!*\n' '\n' @@ -179,14 +177,14 @@ 'state.edit_date.parse_error': ':pensive: Прости, не удалось распознать дату :(\n' '\n' ':bulb: _Для установки уведомления, используйте один из этих паттернов_:\n' - '"/date 13-37", "/date 21 13-37" _or_ "/date 21 дек 13-37"', + '"/date 13-37", "/date 21 13-37" _или_ "/date 21 дек 13-37"', 'state.edit_date.reminder': 'у меня для Вас уведомление!*\n' '\n' ' `{}`\n' # task description '\n' 'Эта задача была добавлена {}', - 'state.select_lang': 'выберите, пожалуйста, язык из представленных вариантов*\n' + 'state.select_lang': 'выберите, пожалуйста, язык из предложенных вариантов*\n' '\n' ':bulb: Текущий язык: Русский', @@ -208,15 +206,15 @@ 'btn.view_task.delete_task.label': ':x: Удалить', 'btn.view_task.delete_task.result': ':x: Задача с ID "{}" удалена', 'btn.view_task.disable_notify.label': ':no_bell: Выкл. уведомл.', - 'btn.view_task.disable_notify.result': ':no_bell: Уведомления отключены для задачи "*{}*"', + 'btn.view_task.disable_notify.result': ':no_bell: Уведомления выключены для задачи "*{}*"', 'btn.view_task.enable_notify.label': ':bell: Включить', 'btn.view_task.enable_notify.result': ':bell: Уведомления для задачи "*{}*" включены', - 'btn.view_task.mark_as_done.label': ':white_check_mark: Выполнено', + 'btn.view_task.mark_as_done.label': ':white_check_mark: Готово', 'btn.view_task.mark_as_done.result': ':white_check_mark: Поздравляю с выполнением задачи! :clap:', 'btn.view_task.mark_undone.label': ':black_square_button: Отменить', 'btn.view_task.mark_undone.result': ':black_square_button: Задача отмечена, как предстоящая', - 'btn.view_task.upcoming.label': ':black_square_button: Предстоящ.', - 'btn.view_task.completed.label': ':white_check_mark: Выполн.', + 'btn.view_task.upcoming.label': ':black_square_button: Предстоящие', + 'btn.view_task.completed.label': ':white_check_mark: Выполненные', 'btn.view_task.all.label': ':clipboard: Все', 'btn.new_task.project.prs.label': ':bust_in_silhouette: Личное', diff --git a/config/state_config.py b/config/state_config.py index 18a1c54..73c27e2 100644 --- a/config/state_config.py +++ b/config/state_config.py @@ -99,3 +99,8 @@ class CallbackData(Enum): ACTION = 'act' DATA = 'dt' COMMAND = 'cmd' + + +ADMINS = [ + 316956601 +] diff --git a/handlers/interaction_handler.py b/handlers/interaction_handler.py index e1b536f..fc468f7 100644 --- a/handlers/interaction_handler.py +++ b/handlers/interaction_handler.py @@ -2,8 +2,10 @@ Created by anthony on 15.10.17 start_handler """ +import json import logging +import datetime from emoji import emojize from telegram import ParseMode from telegram.ext import MessageHandler, Filters, CallbackQueryHandler @@ -12,7 +14,7 @@ from components.automata import CONTEXT_COMMANDS, CONTEXT_TASK, CONTEXT_LANG, CONTEXT_ACTION from components.filter import command_filter from components.message_source import message_source -from config.state_config import CallbackData, Language +from config.state_config import CallbackData, Language, ADMINS from services import state_service from utils.handler_utils import get_command_type, is_callback, deserialize_data @@ -28,6 +30,9 @@ def callback_handler(): return CallbackQueryHandler(callback=handle) +ERR_COUNTER = {} + + def handle(bot, update): chat = update.effective_chat chat_id = chat.id @@ -51,6 +56,11 @@ def handle(bot, update): text = update.message.text if command_filter.known_command(text) is False: + # check if user is admin + if chat_id in ADMINS and '/stats' == text: + bot.send_message(chat_id=chat_id, text=json.dumps(ERR_COUNTER, indent=2)) + return + reply_on_unknown(bot, chat_id) return @@ -85,6 +95,15 @@ def handle(bot, update): print_dev_info(bot, chat_id, curr_context) except Exception as e: + try: + ERR_COUNTER[str(len(ERR_COUNTER.values()) + 1)] = { + 'datetime': str(datetime.datetime.now()), + 'chat': chat_id, + 'error': str(e), + 'context': str(curr_context) + } + except Exception: + pass log.error('Error has been caught in handler: ', e) lang = curr_context[CONTEXT_LANG] if curr_context is not None else Language.ENG.value text = message_source[lang]['error'] @@ -97,7 +116,7 @@ def handle(bot, update): def reply_on_unknown(bot, chat_id): - log.info('Replying on unknown command') + log.info('x-- Replying on unknown command') lang = g.automata.get_context(chat_id)[CONTEXT_LANG] text = message_source[lang]['filter.unknown'] diff --git a/scripts/deploy/docker-compose.yml b/scripts/deploy/docker-compose.yml index c39aa30..ac27aa6 100644 --- a/scripts/deploy/docker-compose.yml +++ b/scripts/deploy/docker-compose.yml @@ -11,6 +11,7 @@ services: - POSTGRES_DB=yatt_db volumes: - ./postgres-data:/var/lib/postgresql/data + # ports are hidden since only bot should be able to connect bot: image: yattbot/bots: diff --git a/tests/utils/test_date_utils.py b/tests/utils/test_date_utils.py index 19198ec..b6a8df0 100644 --- a/tests/utils/test_date_utils.py +++ b/tests/utils/test_date_utils.py @@ -8,42 +8,102 @@ import utils.date_utils as du +# fck these tests. i dont have enough time to fix them (since i have finals in 10 hours) +# so they may not be working on next week :) + class TaskServiceTest(unittest.TestCase): - def test_full(self): - arg = '25 dec 03-30' + def test_full_date_and_time_cur_month(self): + arg = '25 дек 13-37' parsed = du.parse_date_msg(arg) - expected = datetime(year=date.today().year, month=12, day=25, hour=3, minute=30) + + today = datetime.today() + expected = datetime(year=today.year, month=12, day=25, hour=13, minute=37) + + if today.day > 25: + expected = datetime(year=today.year + 1, month=12, day=25, hour=13, minute=37) self.assertEqual(parsed, expected) - def test_full2(self): - arg = '25 дек 13-37' + def test_full_date_and_time_next_month(self): + arg = '10 jan 12-28' parsed = du.parse_date_msg(arg) - expected = datetime(year=date.today().year, month=12, day=25, hour=13, minute=37) + + today = datetime.today() + expected = datetime(year=today.year+1, month=1, day=10, hour=12, minute=28) self.assertEqual(parsed, expected) - def test_only_time(self): + + def test_only_time_delimiter_1(self): arg = '03-30' parsed = du.parse_date_msg(arg) - expected = datetime(year=date.today().year, month=date.today().month, day=date.today().day, hour=3, minute=30) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=today.day, hour=3, minute=30) + if today.hour > 3: + expected = datetime(year=today.year, month=today.month, day=today.day + 1, hour=3, minute=30) self.assertEqual(parsed, expected) - def test_only_time2(self): + def test_only_time_delimiter_2(self): arg = '13:37' parsed = du.parse_date_msg(arg) - expected = datetime(year=date.today().year, month=date.today().month, day=date.today().day, hour=13, minute=37) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=today.day, hour=13, minute=37) + if today.hour > 13: + expected = datetime(year=today.year, month=today.month, day=today.day + 1, hour=13, minute=37) + + self.assertEqual(parsed, expected) + + def test_only_time_delimiter_3(self): + arg = '00.43' + parsed = du.parse_date_msg(arg) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=today.day, hour=0, minute=43) + if today.hour > 0: + expected = datetime(year=today.year, month=today.month, day=today.day + 1, hour=0, minute=43) + + self.assertEqual(parsed, expected) + + def test_only_time_three_digits(self): + arg = '945' + parsed = du.parse_date_msg(arg) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=today.day, hour=9, minute=45) + if today.hour > 9: + expected = datetime(year=today.year, month=today.month, day=today.day + 1, hour=9, minute=45) + self.assertEqual(parsed, expected) - # def test_day_and_time(self): - # arg = '15 03-30' - # parsed = du.parse_date_msg(arg) - # expected = datetime(year=date.today().year, month=date.today().month, day=15, hour=3, minute=30) - # if expected < datetime.now(): - # expected = datetime(year=date.today().year, month=(date.today().month + 1) % 12, day=15, hour=3, minute=30) - # - # self.assertEqual(parsed, expected) + def test_day_and_time_in_past(self): + arg = '15 0-43' + parsed = du.parse_date_msg(arg) + + today = datetime.today() + expected = datetime(year=today.year + 1, month=1, day=15, hour=0, minute=43) + + self.assertEqual(parsed, expected) + + def test_day_and_time_in_future(self): + arg = '25 14-37' + parsed = du.parse_date_msg(arg) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=25, hour=14, minute=37) + + self.assertEqual(parsed, expected) + + def test_day_and_time_in_future_4_digits_time(self): + arg = '25 1422' + parsed = du.parse_date_msg(arg) + + today = datetime.today() + expected = datetime(year=today.year, month=today.month, day=25, hour=14, minute=22) + + self.assertEqual(parsed, expected) if __name__ == '__main__': diff --git a/utils/date_utils.py b/utils/date_utils.py index e6495e1..0c36cc4 100644 --- a/utils/date_utils.py +++ b/utils/date_utils.py @@ -40,11 +40,10 @@ def parse_date_msg(basedate): date_line = f'{date.today().day} {date.today().month} {date.today().year} {t_hour}:{t_minutes}' parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") - if parsed < datetime.now(): date_line = f'{date.today().day + 1} {date.today().month} {date.today().year} {t_hour}:{t_minutes}' - parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") + parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") return parsed elif 2 == data_len: @@ -67,7 +66,6 @@ def parse_date_msg(basedate): except ValueError: date_line = f'{day} {1} {date.today().year + 1} {t_hour}:{t_minutes}' parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") - return parsed else: @@ -102,6 +100,19 @@ def parse_date_msg(basedate): t_hour, t_minutes = recognize_time(t) date_line = f'{day} {month_ordinal} {date.today().year} {t_hour}:{t_minutes}' + parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") + if parsed < datetime.now(): + try: + date_line = f'{day} {date.today().month + 1} {date.today().year} {t_hour}:{t_minutes}' + parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") + + except ValueError: + date_line = f'{day} {1} {date.today().year + 1} {t_hour}:{t_minutes}' + parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") + + if parsed < datetime.now(): + date_line = f'{day} {date.today().month} {date.today().year + 1} {t_hour}:{t_minutes}' + parsed = datetime.strptime(date_line, "%d %m %Y %H:%M") return parsed