From 18e79e24a5cd03dd5312e5f66698ef1183c52ae3 Mon Sep 17 00:00:00 2001 From: Egor Deev Date: Mon, 5 Oct 2026 19:22:10 +0000 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B0=20aiogram=203=20=E2=80=94=20=D1=82=D0=B0,=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В репозитории оставался код на aiogram 2 (executor, message_handler), а requirements.txt уже требовал aiogram 3 — по README бот не запускался (issue #1). Переписанная версия с разделением на bot.py / handlers.py / init.py давно работала на сервере, теперь она и здесь. Переводы строк приведены к LF. --- .gitattributes | 3 + code/bot.py | 269 +++++------------------------------------------ code/config.py | 2 +- code/handlers.py | 197 ++++++++++++++++++++++++++++++++++ code/init.py | 15 +++ code/script.py | 224 +++++++++++++++++++-------------------- 6 files changed, 354 insertions(+), 356 deletions(-) create mode 100644 .gitattributes create mode 100644 code/handlers.py create mode 100644 code/init.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d128a69 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.jpg binary +*.db binary diff --git a/code/bot.py b/code/bot.py index 1dbb350..4ecabb1 100644 --- a/code/bot.py +++ b/code/bot.py @@ -1,235 +1,8 @@ -import logging, config, sql, asyncio, wget, os +import asyncio, logging, time, wget, os -from aiogram import Bot, Dispatcher, executor, types -from aiogram.types import ParseMode - -from datetime import datetime, time -from script import text_ical, message_form, dt_now, delta_time - -# инициализируем токен -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=config.TOKEN) -dp = Dispatcher(bot) - -# инициализируем соединение с БД -du = sql.Users('../db/users.db') -dc = sql.Clock('../db/clock.db') - - -# ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ -@dp.message_handler(commands=['start', 'help']) -async def helps(message: types.Message): - tg_id = int(message.chat.id) - if not du.user_exists(tg_id): - du.add_user(tg_id) - - buttons = [types.InlineKeyboardButton(text="КОМАНДЫ", callback_data="com"), - types.InlineKeyboardButton(text="АВТОР", callback_data="auth")] - - keyboard = types.InlineKeyboardMarkup(row_width=2) - keyboard.add(*buttons) - - await message.answer(text="Я.Календаркин - бот для оповещения о событиях из Яндекс.Календаря. " - "Для начала работы вам нужно всего лишь прислать в чат ссылку экспорта календаря в " - "формате ICal. После получения ссылки, бот начнёт оповещать о всех новых событиях " - "и появится возможность настройки оповещений. О том, какие команды есть для настройки, " - "вы можете ознакомиться по кнопке КОМАНДЫ", - parse_mode=ParseMode.HTML, reply_markup=keyboard) - - -@dp.callback_query_handler(text="auth") -async def author(call: types.CallbackQuery): - await call.message.answer(text='*| АВТОР |*\n\n*>>* Этот бот не коммерческий проект, для упрощенного получения ' - 'уведомлений о событиях в Яндекс.Календаре. Не многим этот бот будет полезен, но ' - 'людям, чья работа подразумевает его использование, он станет лишь удобным ' - 'инструментом. Я же пишу подобные небольшие проекты, о которых вы можете узнать ' - 'больше на моём [GitHub](https://github.com/IGlek).', - parse_mode=ParseMode.MARKDOWN) - - -@dp.callback_query_handler(text="com") -async def commands(call: types.CallbackQuery): - await call.message.answer(text='| КОМАНДЫ |\n\n' - '/help - вспомогательная функция для уточнения работы команд\n' - '/list - список событий календаря, запланированных на сегодняшний день\n' - '/notif - команда, отключающая рассылку уведомлений, даже при наличии событий в календаре\n' - '/daily - оповещение в 8 утра по вашему часовому поясу со списком событий на день\n' - '/moment - напоминание, приходящее в момент начала события\n\n' - '/get_alarm - информация о времени на которое настроены оповещения\n' - '/edit_alarm - изменение времени оповещений\n' - '/stop_alarm - команда, отключающая второе оповещение о событии', - parse_mode=types.ParseMode.HTML) - - -# КОМАНДЫ -@dp.message_handler(commands=['list']) -async def check_list(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if du.url_exists(user_id): - txt = "Имеющиеся события на сегодня\n\n" - - lst_events = sorted(text_ical(user_id, du.get_tz(user_id))) - today = dt_now(du.get_tz(user_id)).date() - - counter = 0 - for event in lst_events: - if event[0] == today: - counter += 1 - txt += message_form(counter, event[3]) - - if counter: - await message.answer(text=txt, parse_mode=ParseMode.HTML) - else: - await message.answer(text="В данный момент событий на сегодня найдено не было!", - parse_mode=ParseMode.HTML) - else: - await message.answer("Для отображения событий вы должны прислать ical-ссылку на календарь!") - - -@dp.message_handler(commands=['notif']) -async def notif_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if du.url_exists(user_id): - if du.get_status(user_id): - await message.answer("Уведомления о событиях выключены!") - else: - await message.answer("Уведомления о событиях включены!") - - du.update_status(user_id) - else: - await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") - - -@dp.message_handler(commands=['daily']) -async def daily_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if dc.clock_exists(user_id): - if dc.get_daily(user_id): - await message.answer("Ежедневные утренние уведомления выключены!") - else: - await message.answer("Ежедневные утренние уведомления включены!") - - dc.update_daily(user_id) - else: - await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") - - -@dp.message_handler(commands=['moment']) -async def start_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if dc.clock_exists(user_id): - if dc.get_start(user_id): - await message.answer("Уведомления в момент события выключены!") - else: - await message.answer("Уведомления в момент события включены!") - - dc.update_start(user_id) - else: - await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") - - -@dp.message_handler(commands=['get_alarm']) -async def start_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if dc.clock_exists(user_id): - alarms = dc.get_alarm(user_id) - start = dc.get_start(user_id) - status2 = dc.get_status2(user_id) - - txt = "" - - if status2: - txt += "У вас работает два оповещения" - else: - txt += "У вас работает лишь первое оповещение" - - if start: - txt += " и сообщение в момент начала события!" - else: - txt += "!" - - await message.answer(text=(txt + f"\n\nПервое оповещение приходит за {alarms[0]} минут\n" - f"Второе оповещение приходит за {alarms[1]} минут"), - parse_mode=ParseMode.HTML) - else: - await message.answer("Для того, чтобы получить таймеры, вы должны прислать ical-ссылку на свой календарь!") - - -@dp.message_handler(commands=['edit_alarm']) -async def start_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if dc.clock_exists(user_id): - await message.bot.send_photo(chat_id=message.chat.id, photo=open("../data/photo_edit_alarm.jpg", "rb"), - caption="Для изменения времени вам надо в ответ на это сообщение прислать два " - "числа через пробел: разница времени первого и второго таймера по ходу " - "времени соответственно") - else: - await message.answer("Для того, чтобы изменить таймеры, вы должны прислать ical-ссылку на свой календарь!") - - -@dp.message_handler(commands=['stop_alarm']) -async def daily_up(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if dc.clock_exists(user_id): - if dc.get_status2(user_id): - await message.answer("Второе уведомление выключено!") - else: - await message.answer("Второе уведомление включено!") - - dc.update_status2(user_id) - else: - await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") - - -# ЗАГРУЗКА ССЫЛКИ -@dp.message_handler(content_types=['text']) -async def downloading_file_ics(message: types.Message): - user_id = du.get_user_id(int(message.chat.id)) - - if message.text[:5] == "https": - try: - wget.download(message.text, f'../data/icals/{str(user_id)}_new.ics') - - try: - os.remove(f'../data/icals/{str(user_id)}.ics') - except FileNotFoundError: - pass - - os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics') - - time_zone = message.text.split("=")[-1] - if not du.url_exists(user_id): - du.add_url(user_id, message.text, time_zone) - dc.add_clock(user_id) - else: - du.update_url(user_id, message.text, time_zone) - - await message.answer("Ссылка успешно добавлена! Уведомления уже включены!") - except Exception: - await message.answer("Ошибка скачивания! Проверьте правильность ссылки и пришлите ещё раз") - - if 'reply_to_message' in message and dc.clock_exists(user_id): - text = "Для изменения времени вам надо в ответ на это сообщение прислать два числа через " \ - "пробел: разница времени первого и второго таймера по ходу времени соответственно" - - if message.reply_to_message.caption == text: - alarm_new = message.text.split() - - if int(alarm_new[0]) < 60 and int(alarm_new[1]) < 60: - dc.update_alarm1(user_id, int(alarm_new[0])) - dc.update_alarm2(user_id, int(alarm_new[1])) - - await message.answer("Время отправки уведомлений успешно обновлено!") - else: - await message.answer("Время отправки уведомлений должно быть меньше 60 минут!") +from init import * +from script import * +from handlers import router # ПРОВЕРКА НА СОБЫТИЕ @@ -273,29 +46,27 @@ async def alarm(wait_for): delta_start = delta_time(d_event, 1, 0) if start and delta_start[0] < time_check <= delta_start[1]: - await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, + await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=f"Событие начинается!\n\n" + message_form(0, event[3])) delta_alarm1 = delta_time(d_event, alarm[0], alarm[0] - 1) if delta_alarm1[0] < time_check <= delta_alarm1[1]: - await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, + await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=f"Напоминаю!\nЧерез {alarm[0]} минут " f"будет событие:\n\n{message_form(0, event[3])}") if alarm2_status: delta_alarm2 = delta_time(d_event, alarm[1], alarm[1] - 1) if delta_alarm2[0] < time_check <= delta_alarm2[1]: - await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, + await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=f"Напоминаю!\nЧерез {alarm[1]} минут " f"будет событие:\n\n{message_form(0, event[3])}") if daily and delta_daily1 <= time_check < delta_daily2: if counter: - await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, - text=txt) + await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=txt) else: - await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, - text=f"Сегодня событий нет") + await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=f"Сегодня событий нет") async def update(wait_for): @@ -314,8 +85,20 @@ async def update(wait_for): os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics') -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.create_task(alarm(60)) # ПРОВЕРКА КАЖДУЮ 1 МИНУТУ - loop.create_task(update(780)) # ПРОВЕРКА КАЖДУЮ 13 МИНУТУ - executor.start_polling(dp, skip_updates=True) +async def main() -> None: + dp.include_router(router) + await bot.delete_webhook(drop_pending_updates=True) + await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types(), skipUpdates=True) + + +async def process() -> None: + await asyncio.gather(main(), # ЗАПУСК ОСНОВНОЙ ПРОГРАММЫ + alarm(60), # ПРОВЕРКА КАЖДУЮ 1 МИНУТУ + update(780)) # ПРОВЕРКА КАЖДУЮ 13 МИНУТУ + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + + try: asyncio.run(process()) + except KeyboardInterrupt: pass diff --git a/code/config.py b/code/config.py index 1cf651a..54e365d 100644 --- a/code/config.py +++ b/code/config.py @@ -1 +1 @@ -TOKEN = "**********:***************************" +TOKEN = "**********:***************************" diff --git a/code/handlers.py b/code/handlers.py new file mode 100644 index 0000000..c726169 --- /dev/null +++ b/code/handlers.py @@ -0,0 +1,197 @@ +from aiogram import F, Router +from aiogram.types import Message, CallbackQuery, ContentType, InlineKeyboardMarkup, InlineKeyboardButton, FSInputFile +from aiogram.filters import Command + +import wget, os + +from init import * +from script import * + +router = Router() + + +# ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ +@router.message(Command('start', 'help')) +async def helps(msg: Message) -> None: + tg_id = int(msg.chat.id) + if not du.user_exists(tg_id): + du.add_user(tg_id) + + buttons = [[InlineKeyboardButton(text="КОМАНДЫ", callback_data="com"), + InlineKeyboardButton(text="АВТОР", callback_data="auth")]] + keyboard = InlineKeyboardMarkup(inline_keyboard=buttons, row_width=2) + + await msg.answer(text="Я.Календаркин - бот для оповещения о событиях из Яндекс.Календаря. " + "Для начала работы вам нужно всего лишь прислать в чат ссылку экспорта календаря в " + "формате ICal. После получения ссылки, бот начнёт оповещать о всех новых событиях " + "и появится возможность настройки оповещений. О том, какие команды есть для настройки, " + "вы можете ознакомиться по кнопке КОМАНДЫ", reply_markup=keyboard) + + +@router.callback_query(F.data == "auth") +async def author(call: CallbackQuery) -> None: + await call.message.answer(text='| АВТОР |\n\n>> Этот бот не коммерческий проект, для упрощенного ' + 'получения уведомлений о событиях в Яндекс.Календаре. Не многим этот бот будет ' + 'полезен, но людям, чья работа подразумевает его использование, он станет лишь ' + 'удобным инструментом. Я же пишу подобные небольшие проекты, о которых вы можете ' + 'узнать больше на моём GitHub.') + + +@router.callback_query(F.data == "com") +async def commands(call: CallbackQuery) -> None: + await call.message.answer(text='| КОМАНДЫ |\n\n' + '/help - вспомогательная функция для уточнения работы команд\n' + '/list - список событий календаря, запланированных на сегодняшний день\n' + '/notif - команда, отключающая рассылку уведомлений, даже при наличии событий в календаре\n' + '/daily - оповещение в 8 утра по вашему часовому поясу со списком событий на день\n' + '/moment - напоминание, приходящее в момент начала события\n\n' + '/get_alarm - информация о времени на которое настроены оповещения\n' + '/edit_alarm - изменение времени оповещений\n' + '/stop_alarm - команда, отключающая второе оповещение о событии') + + +# КОМАНДЫ +@router.message(Command('list')) +async def check_list(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if du.url_exists(user_id): + txt = "Имеющиеся события на сегодня\n\n" + + lst_events = sorted(text_ical(user_id, du.get_tz(user_id))) + today = dt_now(du.get_tz(user_id)).date() + + counter = 0 + for event in lst_events: + if event[0] == today: + counter += 1 + txt += message_form(counter, event[3]) + + if counter: await msg.answer(text=txt, disable_web_page_preview=True) + else: await msg.answer("В данный момент событий на сегодня найдено не было!") + else: await msg.answer("Для отображения событий вы должны прислать ical-ссылку на календарь!") + + +@router.message(Command('notif')) +async def notif_up(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if du.url_exists(user_id): + if du.get_status(user_id): await msg.answer("Уведомления о событиях выключены!") + else: await msg.answer("Уведомления о событиях включены!") + + du.update_status(user_id) + else: await msg.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") + + +@router.message(Command('daily')) +async def daily_up(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if dc.clock_exists(user_id): + if dc.get_daily(user_id): await msg.answer("Ежедневные утренние уведомления выключены!") + else: await msg.answer("Ежедневные утренние уведомления включены!") + + dc.update_daily(user_id) + else: await msg.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") + + +@router.message(Command('moment')) +async def moment_up(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if dc.clock_exists(user_id): + if dc.get_start(user_id): await msg.answer("Уведомления в момент события выключены!") + else: await msg.answer("Уведомления в момент события включены!") + + dc.update_start(user_id) + else: await msg.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") + + +@router.message(Command('get_alarm')) +async def alarm_get(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if dc.clock_exists(user_id): + alarms = dc.get_alarm(user_id) + start = dc.get_start(user_id) + status2 = dc.get_status2(user_id) + + txt = "" + + if status2: txt += "У вас работает два оповещения" + else: txt += "У вас работает лишь первое оповещение" + + if start: txt += " и сообщение в момент начала события!" + else: txt += "!" + + await msg.answer(txt + f"\n\nПервое оповещение приходит за {alarms[0]} минут\n" + f"Второе оповещение приходит за {alarms[1]} минут") + else: + await msg.answer("Для того, чтобы получить таймеры, вы должны прислать ical-ссылку на свой календарь!") + + +@router.message(Command('edit_alarm')) +async def alarm_edit(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if dc.clock_exists(user_id): + await msg.answer_photo(photo=FSInputFile(path="../data/photo_edit_alarm.jpg"), + caption="Для изменения времени вам надо в ответ на это сообщение прислать два " + "числа через пробел: разница времени первого и второго таймера по ходу " + "времени соответственно") + else: + await msg.answer("Для того, чтобы изменить таймеры, вы должны прислать ical-ссылку на свой календарь!") + + +@router.message(Command('stop_alarm')) +async def alarm_stop(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if dc.clock_exists(user_id): + if dc.get_status2(user_id): await msg.answer("Второе уведомление выключено!") + else: await msg.answer("Второе уведомление включено!") + + dc.update_status2(user_id) + else: await msg.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") + + +# ЗАГРУЗКА ССЫЛКИ +@router.message(F.content_type == ContentType.TEXT) +async def downloading_file_ics(msg: Message) -> None: + user_id = du.get_user_id(int(msg.chat.id)) + + if msg.text[:5] == "https": + try: + wget.download(msg.text, f'../data/icals/{str(user_id)}_new.ics') + + try: os.remove(f'../data/icals/{str(user_id)}.ics') + except FileNotFoundError: pass + + os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics') + + time_zone = msg.text.split("=")[-1] + if not du.url_exists(user_id): + du.add_url(user_id, msg.text, time_zone) + dc.add_clock(user_id) + else: + du.update_url(user_id, msg.text, time_zone) + + await msg.answer("Ссылка успешно добавлена! Уведомления уже включены!") + except Exception: + await msg.answer("Ошибка скачивания! Проверьте правильность ссылки и пришлите ещё раз") + + if (msg.reply_to_message is not None) and dc.clock_exists(user_id): + text = "Для изменения времени вам надо в ответ на это сообщение прислать два числа через " \ + "пробел: разница времени первого и второго таймера по ходу времени соответственно" + + if msg.reply_to_message.caption == text: + alarm_new = msg.text.split() + + if int(alarm_new[0]) < 60 and int(alarm_new[1]) < 60: + dc.update_alarm1(user_id, int(alarm_new[0])) + dc.update_alarm2(user_id, int(alarm_new[1])) + + await msg.answer("Время отправки уведомлений успешно обновлено!") + else: + await msg.answer("Время отправки уведомлений должно быть меньше 60 минут!") diff --git a/code/init.py b/code/init.py new file mode 100644 index 0000000..b6733a4 --- /dev/null +++ b/code/init.py @@ -0,0 +1,15 @@ +from aiogram import Bot, Dispatcher +from aiogram.enums.parse_mode import ParseMode +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.client.bot import DefaultBotProperties + +from config import botToken + +bot = Bot(token=botToken, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) +dp = Dispatcher(storage=MemoryStorage()) + + +from sql import Users, Clock + +du = Users('../db/users.db') +dc = Clock('../db/clock.db') diff --git a/code/script.py b/code/script.py index 3a3ab37..9729d67 100644 --- a/code/script.py +++ b/code/script.py @@ -1,112 +1,112 @@ -from datetime import datetime, time -import icalendar - -from dateutil.rrule import * -from dateutil.parser import * - - -def text_ical(user_id, tz): - date = dt_now(tz) - path = f'../data/icals/{user_id}.ics' - - e = open(path, 'rb') - - ecal = icalendar.Calendar.from_ical(e.read()) - events = [] - - for i, component in enumerate(ecal.walk()): - if component.name == "VEVENT": - - # НАЧАЛО - dt = icalendar.vDDDTypes.to_ical(component.get('dtstart')).decode('utf-8') - dt = dt.split("T") - - dt_start = component.decoded("dtstart") - dt_end = component.decoded("dtend") - - if len(dt) == 1: - dt_start = datetime.combine(dt_start, time(minute=1)) - dt_end = datetime.combine(dt_end, time(minute=1)) - else: - dt_start.replace(tzinfo=None) - dt_start = dt_start.astimezone(tz) - - dt_end.replace(tzinfo=None) - dt_end = dt_end.astimezone(tz) - - # НАСТРОЙКА - r_rule = component.get('rrule') - if r_rule: - list_rrule = icalendar.vRecur.to_ical(r_rule).decode('utf-8').split(';') - - until, u_flag = [elem for elem in list_rrule if 'UNTIL' in elem], True - list_rrule = [elem for elem in list_rrule if 'UNTIL' not in elem] - - if until: - until_old = until[0].split('=')[1] - until = isoparse(until_old).replace(tzinfo=None) - - if until.replace(tzinfo=None) <= date.replace(tzinfo=None): - until, u_flag = "UNTIL=" + until_old, False - - if u_flag: - until = "UNTIL=" + "".join(date.date().isoformat().split("-")) + "T235900Z" - - list_rrule.append(until) - list_rrule = ";".join(list_rrule) - - date_iso = "".join(dt_start.isoformat().split("-")) - iso = "".join(date_iso.split(":")).split("+")[0] + "Z" - - dates = list(map(lambda x: x.replace(tzinfo=None), list(rrulestr(list_rrule, dtstart=parse(iso))))) - - if dates: - dif = dt_end - dt_start - - dt_start = dates[-1] - dt_end = dt_start + dif - - if date.date() == dt_start.date(): - org = component.get("organizer") - desc = component.get("description") - - event = {"name": component.get('summary'), "desc": desc if desc else "отсутствует", - "org": org if org else "не назначен", "datetime": [dt_start, dt_end]} - - events.append([dt_start.date(), dt_start.time(), i, event]) - e.close() - - return events - - -def message_form(k, event): - txt = "" - - if k: - txt += f"-------\n" - txt += f"{k}. {event['name']}\n" - else: - txt += f"{event['name']}\n" - - txt += f"Описание: {event['desc']}\n" - txt += f"Организатор: {event['org']}\n\n" - txt += f"Начало: {event['datetime'][0].strftime('%H:%M - %d.%m.%Y года')}\n" - txt += f"Конец: {event['datetime'][1].strftime('%H:%M - %d.%m.%Y года')}\n" - - return txt - - -def delta_time(d_event, start, end): - d_start = datetime.combine(d_event.date(), time(hour=0, minute=start)) - d_end = datetime.combine(d_event.date(), time(hour=0, minute=end)) - - zero = datetime.combine(d_event.date(), time(0, 0, 0, 0)) - - dt_start = zero + (d_event - d_start) - dt_end = zero + (d_event - d_end) - - return [dt_start.time(), dt_end.time()] - - -def dt_now(tz): - return datetime.now(tz=tz) +from datetime import datetime, time +import icalendar + +from dateutil.rrule import * +from dateutil.parser import * + + +def text_ical(user_id, tz): + date = dt_now(tz) + path = f'../data/icals/{user_id}.ics' + + e = open(path, 'rb') + + ecal = icalendar.Calendar.from_ical(e.read()) + events = [] + + for i, component in enumerate(ecal.walk()): + if component.name == "VEVENT": + + # НАЧАЛО + dt = icalendar.vDDDTypes.to_ical(component.get('dtstart')).decode('utf-8') + dt = dt.split("T") + + dt_start = component.decoded("dtstart") + dt_end = component.decoded("dtend") + + if len(dt) == 1: + dt_start = datetime.combine(dt_start, time(minute=1)) + dt_end = datetime.combine(dt_end, time(minute=1)) + else: + dt_start.replace(tzinfo=None) + dt_start = dt_start.astimezone(tz) + + dt_end.replace(tzinfo=None) + dt_end = dt_end.astimezone(tz) + + # НАСТРОЙКА + r_rule = component.get('rrule') + if r_rule: + list_rrule = icalendar.vRecur.to_ical(r_rule).decode('utf-8').split(';') + + until, u_flag = [elem for elem in list_rrule if 'UNTIL' in elem], True + list_rrule = [elem for elem in list_rrule if 'UNTIL' not in elem] + + if until: + until_old = until[0].split('=')[1] + until = isoparse(until_old).replace(tzinfo=None) + + if until.replace(tzinfo=None) <= date.replace(tzinfo=None): + until, u_flag = "UNTIL=" + until_old, False + + if u_flag: + until = "UNTIL=" + "".join(date.date().isoformat().split("-")) + "T235900Z" + + list_rrule.append(until) + list_rrule = ";".join(list_rrule) + + date_iso = "".join(dt_start.isoformat().split("-")) + iso = "".join(date_iso.split(":")).split("+")[0] + "Z" + + dates = list(map(lambda x: x.replace(tzinfo=None), list(rrulestr(list_rrule, dtstart=parse(iso))))) + + if dates: + dif = dt_end - dt_start + + dt_start = dates[-1] + dt_end = dt_start + dif + + if date.date() == dt_start.date(): + org = component.get("organizer") + desc = component.get("description") + + event = {"name": component.get('summary'), "desc": desc if desc else "отсутствует", + "org": org if org else "не назначен", "datetime": [dt_start, dt_end]} + + events.append([dt_start.date(), dt_start.time(), i, event]) + e.close() + + return events + + +def message_form(k, event): + txt = "" + + if k: + txt += f"-------\n" + txt += f"{k}. {event['name']}\n" + else: + txt += f"{event['name']}\n" + + txt += f"Описание: {event['desc']}\n" + txt += f"Организатор: {event['org']}\n\n" + txt += f"Начало: {event['datetime'][0].strftime('%H:%M - %d.%m.%Y года')}\n" + txt += f"Конец: {event['datetime'][1].strftime('%H:%M - %d.%m.%Y года')}\n" + + return txt + + +def delta_time(d_event, start, end): + d_start = datetime.combine(d_event.date(), time(hour=0, minute=start)) + d_end = datetime.combine(d_event.date(), time(hour=0, minute=end)) + + zero = datetime.combine(d_event.date(), time(0, 0, 0, 0)) + + dt_start = zero + (d_event - d_start) + dt_end = zero + (d_event - d_end) + + return [dt_start.time(), dt_end.time()] + + +def dt_now(tz): + return datetime.now(tz=tz)