From 1ee0493327021e6f9905b3cbf1fefbdecdbe984c Mon Sep 17 00:00:00 2001 From: Egor Deev <67710823+IGlek@users.noreply.github.com> Date: Sun, 28 Jan 2024 14:29:05 +0400 Subject: [PATCH] v. 1.0 --- code/bot.py | 318 +++++++++++++++++++++++++++++++ code/config.py | 1 + code/script.py | 68 +++++++ code/sql.py | 159 ++++++++++++++++ data/icals/1.ics | 386 ++++++++++++++++++++++++++++++++++++++ data/photo_edit_alarm.jpg | Bin 0 -> 24673 bytes db/clock.db | Bin 0 -> 12288 bytes db/users.db | Bin 0 -> 16384 bytes 8 files changed, 932 insertions(+) create mode 100644 code/bot.py create mode 100644 code/config.py create mode 100644 code/script.py create mode 100644 code/sql.py create mode 100644 data/icals/1.ics create mode 100644 data/photo_edit_alarm.jpg create mode 100644 db/clock.db create mode 100644 db/users.db diff --git a/code/bot.py b/code/bot.py new file mode 100644 index 0000000..27c850e --- /dev/null +++ b/code/bot.py @@ -0,0 +1,318 @@ +import logging, config, sql, asyncio, 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]) + + await message.answer(text=txt, 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 минут!") + + + +# ПРОВЕРКА НА СОБЫТИЕ +async def alarm(wait_for): + while True: + await asyncio.sleep(wait_for) + + users_id = du.all_users() + for user_id in users_id: + user_id = user_id[0] + + if dc.clock_exists(user_id): + if du.get_status(user_id): + events = sorted(text_ical(user_id, du.get_tz(user_id))) + tg_id = str(du.get_first_user_id(user_id)) + + start = dc.get_start(user_id) + daily = dc.get_daily(user_id) + alarm = dc.get_alarm(user_id) + + alarm2_status = dc.get_status2(user_id) + + today = dt_now(du.get_tz(user_id)).date() + time_check = dt_now(du.get_tz(user_id)).time() + + # ДЛЯ DAILY + counter = 0 + txt = "События сегодня\n\n" + + delta_daily1 = time(hour=8, minute=0) + delta_daily2 = time(hour=8, minute=1) + # -------------------------------- + + for event in events: + if event[0] == today: + d_event = datetime.combine(today, event[1]) + + if daily and delta_daily1 <= time_check < delta_daily2: + counter += 1 + txt += message_form(counter, event[3]) + + 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, + 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, + 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, + 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) + else: + await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, + text=f"Сегодня событий нет") + + +async def update(wait_for): + while True: + await asyncio.sleep(wait_for) + + users_id = du.all_users() + for user_id in users_id: + user_id = user_id[0] + + if du.url_exists(user_id): + if du.get_status(user_id): + wget.download(du.get_url(user_id), f'../data/icals/{str(user_id)}_new.ics') + + os.remove(f'../data/icals/{str(user_id)}.ics') + 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(1620)) # ПРОВЕРКА КАЖДУЮ 27 МИНУТУ + executor.start_polling(dp, skip_updates=True) diff --git a/code/config.py b/code/config.py new file mode 100644 index 0000000..1cf651a --- /dev/null +++ b/code/config.py @@ -0,0 +1 @@ +TOKEN = "**********:***************************" diff --git a/code/script.py b/code/script.py new file mode 100644 index 0000000..72c1baf --- /dev/null +++ b/code/script.py @@ -0,0 +1,68 @@ +from datetime import datetime, time +import icalendar + + +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_start = component.decoded("dtstart") + dt_start.replace(tzinfo=None) + dt_start = dt_start.astimezone(tz) + + dt_end = component.decoded("dtend") + dt_end.replace(tzinfo=None) + dt_end = dt_end.astimezone(tz) + + if date.date() == component.decoded("dtstart").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) diff --git a/code/sql.py b/code/sql.py new file mode 100644 index 0000000..87ef9c2 --- /dev/null +++ b/code/sql.py @@ -0,0 +1,159 @@ +from pytz import timezone +import sqlite3 + + +class Users: + def __init__(self, database): + """Подключаемся к БД и сохраняем курсор соединения""" + self.connection = sqlite3.connect(database) + self.cursor = self.connection.cursor() + + # КОМАНДЫ USER + def user_exists(self, user_id): + """Проверяем, есть ли уже пользователь в базе""" + with self.connection: + result = self.cursor.execute(f'SELECT * FROM `user` WHERE `user_id` = ?', (user_id,)).fetchall() + return bool(len(result)) + + def all_users(self): + """Список айди""" + with self.connection: + return self.cursor.execute(f'SELECT `id` FROM `user`').fetchall() + + def add_user(self, user_id): + """Добавляем нового пользователя""" + with self.connection: + return self.cursor.execute(f"INSERT INTO `user` (`user_id`) VALUES(?)", (user_id,)) + + def get_user_id(self, user_id): + """Получаем короткое айди юзера""" + with self.connection: + return self.cursor.execute(f'SELECT `id` FROM `user` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + def get_first_user_id(self, user_id): + """Получаем длинное айди юзера""" + with self.connection: + return self.cursor.execute(f'SELECT `user_id` FROM `user` WHERE `id` = ?', (user_id,)).fetchone()[0] + + # КОМАНДЫ URL + def url_exists(self, user_id): + """Проверяем, есть ли данные уже в базе""" + with self.connection: + result = self.cursor.execute(f'SELECT * FROM `url` WHERE `user_id` = ?', (user_id,)).fetchall() + return bool(len(result)) + + def add_url(self, user_id, url_ical, time_zone): + """Добавляем ссылку на календарь""" + with self.connection: + return self.cursor.execute(f"INSERT INTO `url` (`user_id`, `url_ical`, `time_zone`) VALUES(?, ?, ?)", + (user_id, url_ical, time_zone)) + + def update_status(self, user_id): + """Обновляем статус рассылки уведомлений""" + with self.connection: + status = self.cursor.execute(f'SELECT `status` FROM `url` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + return self.cursor.execute("UPDATE `url` SET `status` = ? WHERE `user_id` = ?", (not status, user_id)) + + def update_url(self, user_id, url_ical, time_zone): + """Обновляем ссылку и часовой пояс в базе""" + with self.connection: + return self.cursor.execute("UPDATE `url` SET `url_ical` = ?, `time_zone` = ? WHERE `user_id` = ?", + (url_ical, time_zone, user_id)) + + def get_status(self, user_id): + """Получаем статус работы""" + with self.connection: + return self.cursor.execute(f'SELECT `status` FROM `url` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + def get_url(self, user_id): + """Получаем ссылку на календарь""" + with self.connection: + return self.cursor.execute(f'SELECT `url_ical` FROM `url` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + def get_tz(self, user_id): + """Получаем указанный в календаре часовой пояс""" + with self.connection: + return timezone(self.cursor.execute(f'SELECT `time_zone` FROM `url` WHERE `user_id` = ?', + (user_id,)).fetchone()[0]) + + # ЗАКРЫТИЕ ВЫЗОВА + def close(self): + """Закрываем соединение с БД""" + self.connection.close() + + +class Clock: + def __init__(self, database): + """Подключаемся к БД и сохраняем курсор соединения""" + self.connection = sqlite3.connect(database) + self.cursor = self.connection.cursor() + + # КОМАНДЫ ALARM + def clock_exists(self, user_id): + """Проверяем, есть ли данные уже в базе""" + with self.connection: + result = self.cursor.execute(f'SELECT * FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchall() + return bool(len(result)) + + def add_clock(self, user_id): + """Добавляем параметры уведомления""" + with self.connection: + return self.cursor.execute(f"INSERT INTO `alarm` (`user_id`, `alarm_1`, `alarm_2`) VALUES(?, ?, ?)", + (user_id, 15, 5)) + + # КОМАНДЫ ПОЛУЧЕНИЯ ССЫЛОК + def get_alarm(self, user_id): + """Получаем задержки таймера""" + with self.connection: + return self.cursor.execute(f'SELECT `alarm_1`, `alarm_2` FROM `alarm` WHERE `user_id` = ?', + (user_id,)).fetchone() + + def get_start(self, user_id): + """Получаем статус уведомления в момент исполнения""" + with self.connection: + return self.cursor.execute(f'SELECT `start` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + def get_daily(self, user_id): + """Получаем статус ежедневного уведомления""" + with self.connection: + return self.cursor.execute(f'SELECT `daily` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + def get_status2(self, user_id): + """Получаем статус отправки второго уведомления""" + with self.connection: + return self.cursor.execute(f'SELECT `status_2` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + + # КОМАНДЫ ОБНОВЛЕНИЯ БУЛЕВЫХ СТАТУСОВ + def update_daily(self, user_id): + """Обновляем статус ежедневного уведомления""" + with self.connection: + daily = self.cursor.execute(f'SELECT `daily` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + return self.cursor.execute("UPDATE `alarm` SET `daily` = ? WHERE `user_id` = ?", (not daily, user_id)) + + def update_start(self, user_id): + """Обновляем статус уведомления в момент исполнения""" + with self.connection: + start = self.cursor.execute(f'SELECT `start` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + return self.cursor.execute("UPDATE `alarm` SET `start` = ? WHERE `user_id` = ?", (not start, user_id)) + + # КОМАНДЫ ОБНОВЛЕНИЯ ВРЕМЕННОГО ДИАПАЗОНА + def update_alarm1(self, user_id, alarm_1): + """Обновляем время первого оповещения""" + with self.connection: + return self.cursor.execute("UPDATE `alarm` SET `alarm_1` = ? WHERE `user_id` = ?", (alarm_1, user_id)) + + def update_alarm2(self, user_id, alarm_2): + """Обновляем время второго оповещения""" + with self.connection: + return self.cursor.execute("UPDATE `alarm` SET `alarm_2` = ? WHERE `user_id` = ?", (alarm_2, user_id)) + + def update_status2(self, user_id): + """Обновляем статус отправки второго уведомления""" + with self.connection: + status_2 = self.cursor.execute(f'SELECT `status_2` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0] + return self.cursor.execute("UPDATE `alarm` SET `status_2` = ? WHERE `user_id` = ?", (not status_2, user_id)) + + # ЗАКРЫТИЕ ВЫЗОВА + def close(self): + """Закрываем соединение с БД""" + self.connection.close() diff --git a/data/icals/1.ics b/data/icals/1.ics new file mode 100644 index 0000000..06a840e --- /dev/null +++ b/data/icals/1.ics @@ -0,0 +1,386 @@ +BEGIN:VCALENDAR +PRODID:-//Yandex LLC//Yandex Calendar//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-TIMEZONE:Europe/Samara +X-WR-CALNAME:Привет +BEGIN:VTIMEZONE +TZID:Asia/Krasnoyarsk +TZURL:http://tzurl.org/zoneinfo/Asia/Krasnoyarsk +X-LIC-LOCATION:Asia/Krasnoyarsk +BEGIN:STANDARD +TZOFFSETFROM:+061126 +TZOFFSETTO:+0600 +TZNAME:+06 +DTSTART:19200106T000000 +RDATE:19200106T000000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0600 +TZOFFSETTO:+0700 +TZNAME:+07 +DTSTART:19300621T000000 +RDATE:19300621T000000 +RDATE:19920119T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0700 +TZOFFSETTO:+0800 +TZNAME:+08 +DTSTART:19810401T000000 +RDATE:19810401T000000 +RDATE:19820401T000000 +RDATE:19830401T000000 +RDATE:19840401T000000 +RDATE:19850331T020000 +RDATE:19860330T020000 +RDATE:19870329T020000 +RDATE:19880327T020000 +RDATE:19890326T020000 +RDATE:19900325T020000 +RDATE:19920329T020000 +RDATE:19930328T020000 +RDATE:19940327T020000 +RDATE:19950326T020000 +RDATE:19960331T020000 +RDATE:19970330T020000 +RDATE:19980329T020000 +RDATE:19990328T020000 +RDATE:20000326T020000 +RDATE:20010325T020000 +RDATE:20020331T020000 +RDATE:20030330T020000 +RDATE:20040328T020000 +RDATE:20050327T020000 +RDATE:20060326T020000 +RDATE:20070325T020000 +RDATE:20080330T020000 +RDATE:20090329T020000 +RDATE:20100328T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0800 +TZOFFSETTO:+0700 +TZNAME:+07 +DTSTART:19811001T000000 +RDATE:19811001T000000 +RDATE:19821001T000000 +RDATE:19831001T000000 +RDATE:19840930T030000 +RDATE:19850929T030000 +RDATE:19860928T030000 +RDATE:19870927T030000 +RDATE:19880925T030000 +RDATE:19890924T030000 +RDATE:19900930T030000 +RDATE:19920927T030000 +RDATE:19930926T030000 +RDATE:19940925T030000 +RDATE:19950924T030000 +RDATE:19961027T030000 +RDATE:19971026T030000 +RDATE:19981025T030000 +RDATE:19991031T030000 +RDATE:20001029T030000 +RDATE:20011028T030000 +RDATE:20021027T030000 +RDATE:20031026T030000 +RDATE:20041031T030000 +RDATE:20051030T030000 +RDATE:20061029T030000 +RDATE:20071028T030000 +RDATE:20081026T030000 +RDATE:20091025T030000 +RDATE:20101031T030000 +RDATE:20141026T020000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0700 +TZOFFSETTO:+0700 +TZNAME:+07 +DTSTART:19910331T020000 +RDATE:19910331T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0700 +TZOFFSETTO:+0600 +TZNAME:+06 +DTSTART:19910929T030000 +RDATE:19910929T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0700 +TZOFFSETTO:+0800 +TZNAME:+08 +DTSTART:20110327T020000 +RDATE:20110327T020000 +END:STANDARD +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:Europe/Samara +TZURL:http://tzurl.org/zoneinfo/Europe/Samara +X-LIC-LOCATION:Europe/Samara +BEGIN:STANDARD +TZOFFSETFROM:+032020 +TZOFFSETTO:+0300 +TZNAME:+03 +DTSTART:19190701T032020 +RDATE:19190701T032020 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0300 +TZOFFSETTO:+0400 +TZNAME:+04 +DTSTART:19300621T000000 +RDATE:19300621T000000 +RDATE:19911020T030000 +RDATE:20110327T020000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0400 +TZOFFSETTO:+0400 +TZNAME:+04 +DTSTART:19350127T000000 +RDATE:19350127T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0400 +TZOFFSETTO:+0500 +TZNAME:+05 +DTSTART:19810401T000000 +RDATE:19810401T000000 +RDATE:19820401T000000 +RDATE:19830401T000000 +RDATE:19840401T000000 +RDATE:19850331T020000 +RDATE:19860330T020000 +RDATE:19870329T020000 +RDATE:19880327T020000 +RDATE:19920329T020000 +RDATE:19930328T020000 +RDATE:19940327T020000 +RDATE:19950326T020000 +RDATE:19960331T020000 +RDATE:19970330T020000 +RDATE:19980329T020000 +RDATE:19990328T020000 +RDATE:20000326T020000 +RDATE:20010325T020000 +RDATE:20020331T020000 +RDATE:20030330T020000 +RDATE:20040328T020000 +RDATE:20050327T020000 +RDATE:20060326T020000 +RDATE:20070325T020000 +RDATE:20080330T020000 +RDATE:20090329T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0500 +TZOFFSETTO:+0400 +TZNAME:+04 +DTSTART:19811001T000000 +RDATE:19811001T000000 +RDATE:19821001T000000 +RDATE:19831001T000000 +RDATE:19840930T030000 +RDATE:19850929T030000 +RDATE:19860928T030000 +RDATE:19870927T030000 +RDATE:19880925T030000 +RDATE:19920927T030000 +RDATE:19930926T030000 +RDATE:19940925T030000 +RDATE:19950924T030000 +RDATE:19961027T030000 +RDATE:19971026T030000 +RDATE:19981025T030000 +RDATE:19991031T030000 +RDATE:20001029T030000 +RDATE:20011028T030000 +RDATE:20021027T030000 +RDATE:20031026T030000 +RDATE:20041031T030000 +RDATE:20051030T030000 +RDATE:20061029T030000 +RDATE:20071028T030000 +RDATE:20081026T030000 +RDATE:20091025T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0400 +TZOFFSETTO:+0400 +TZNAME:+04 +DTSTART:19890326T020000 +RDATE:19890326T020000 +RDATE:20100328T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0400 +TZOFFSETTO:+0300 +TZNAME:+03 +DTSTART:19890924T030000 +RDATE:19890924T030000 +RDATE:19900930T030000 +RDATE:20101031T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0300 +TZOFFSETTO:+0400 +TZNAME:+04 +DTSTART:19900325T020000 +RDATE:19900325T020000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETFROM:+0300 +TZOFFSETTO:+0300 +TZNAME:+03 +DTSTART:19910331T020000 +RDATE:19910331T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0300 +TZOFFSETTO:+0300 +TZNAME:+03 +DTSTART:19910929T030000 +RDATE:19910929T030000 +END:STANDARD +BEGIN:STANDARD +TZNAME:+04 +TZOFFSETFROM:+0400 +TZOFFSETTO:+0400 +DTSTART:20101031T030000 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=Asia/Krasnoyarsk:20240123T213000 +DTEND;TZID=Asia/Krasnoyarsk:20240123T220000 +SUMMARY:Собрание +UID:zwdqywax0s4lz9ybyr9yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240123T110608Z +DESCRIPTION:Очень важное +URL:https://calendar.yandex.ru/event?event_id=1980777684 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240123T110608Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Asia/Krasnoyarsk:20240123T203000 +DTEND;TZID=Asia/Krasnoyarsk:20240123T210000 +SUMMARY:Без названия +UID:zwdqywax0sz8640h6m8yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240123T110935Z +URL:https://calendar.yandex.ru/event?event_id=1980779802 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240123T110935Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Asia/Krasnoyarsk:20240123T190000 +DTEND;TZID=Asia/Krasnoyarsk:20240123T193000 +SUMMARY:Без названия +UID:zwdqywax0szm0iv8br3yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240123T110938Z +URL:https://calendar.yandex.ru/event?event_id=1980779831 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240123T110938Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Asia/Krasnoyarsk:20240123T223000 +DTEND;TZID=Asia/Krasnoyarsk:20240123T230000 +SUMMARY:Без названия +UID:zwdqywax0t03d7xqp32yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240123T110941Z +URL:https://calendar.yandex.ru/event?event_id=1980779871 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240123T110941Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Samara:20240124T163000 +DTEND;TZID=Europe/Samara:20240124T170000 +SUMMARY:Привет +UID:zwdqywax08ke3e4lgn57yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240124T090329Z +URL:https://calendar.yandex.ru/event?event_id=1981462656 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240124T090329Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Samara:20240126T130000 +DTEND;TZID=Europe/Samara:20240126T133000 +SUMMARY:Новое событие +UID:zwdqywax25jvjlx7r4e9yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240126T080645Z +DESCRIPTION:Надо встретиться +URL:https://calendar.yandex.ru/event?event_id=1983061891 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240126T080645Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Samara:20240126T153000 +DTEND;TZID=Europe/Samara:20240126T160000 +SUMMARY:Ещё одно новое событие +UID:zwdqywax25jy3fg604q4yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240126T080704Z +DESCRIPTION:Реально надо встретиться +URL:https://calendar.yandex.ru/event?event_id=1983062020 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240126T080704Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Samara:20240127T153000 +DTEND;TZID=Europe/Samara:20240127T160000 +SUMMARY:Без названия +UID:zwdqywax25sz6nhk0zfyandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240126T084915Z +URL:https://calendar.yandex.ru/event?event_id=1983093315 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240126T084915Z +CLASS:PRIVATE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Samara:20240126T140000 +DTEND;TZID=Europe/Samara:20240126T143000 +SUMMARY:Тут ещё одно событие +UID:zwdqywax26494cx3kbv7yandex.ru +SEQUENCE:0 +DTSTAMP:20240126T105030Z +CREATED:20240126T094235Z +DESCRIPTION:Забей хуй на него +URL:https://calendar.yandex.ru/event?event_id=1983139091 +TRANSP:OPAQUE +CATEGORIES:Привет +LAST-MODIFIED:20240126T094235Z +CLASS:PRIVATE +END:VEVENT +END:VCALENDAR diff --git a/data/photo_edit_alarm.jpg b/data/photo_edit_alarm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..94305e25b67a4279a1fd8ee655c64efce34a26f2 GIT binary patch literal 24673 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 T;~)@%>|a@4O)l?=iO>H4T1d31 literal 0 HcmV?d00001 diff --git a/db/clock.db b/db/clock.db new file mode 100644 index 0000000000000000000000000000000000000000..a7fda41147edd344bf861b43caa4f175a4e133a6 GIT binary patch literal 12288 zcmeI#&riZI6bJBDBt{a0o_2z~BP5H7c;w>1Voc_UIL=EABUv&;x9;H4AAhZXfM>h1 z5m1pUCf_C(wQZxy zPNyG7I*ZqvtlrcQ3~#83l0R8ap5+eTeLwwG*!__ABaM|CX+QcCyNf`LL85t zHsimO$et|VHV=cfsa9FgrFx}|G?N;pA39BuuJ2jyz3tJ^G z<9kBsW}0OS=QrRo%Frkht;Aa5Y;8^PWx5c3)K3P#(@n2`vK4`%$aO3gYSpR~RabDL z81egZ8)?LmzZbAsia)Dzd_aQ$1Rwwb2tWV=5P$##AOHafKwwt|@c+N7mBqRs009U< p00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafK;S