1
0
Fork 0
mirror of https://github.com/EDeev/y.calendarkin.git synced 2026-10-08 00:39:27 +03:00
y.calendarkin/code/bot.py
Egor Deev 4055242ddf
Уведомления о новых, удалённых и перенесённых событиях (#5)
При обновлении календаря (раз в 13 минут) старая выгрузка сравнивается с новой по UID события.
Бот присылает одно сообщение со списком изменений: новое событие, удалено, перенесено (было/стало).
Учитываются и отдельные вхождения повторяющихся событий — перенос (RECURRENCE-ID) и удаление
(EXDATE). Прошедшие события пропускаются: Яндекс выгружает всю историю календаря. В сообщении —
не больше 10 изменений, остальные одной строкой «…и ещё N».

Команда /changes включает и выключает эти уведомления; по умолчанию включены у всех, колонка
alarms.changes добавляется при запуске. При подписке и замене ссылки сравнения нет.

Closes #2
2026-10-07 16:27:58 +03:00

125 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import logging
from datetime import datetime, time
from init import bot, dp, du, dc
import changes
from script import delta_time, download_ical, dt_now, message_form, read_ical, text_ical
from handlers import router
async def send(tg_id, text):
await bot.send_message(chat_id=tg_id, disable_web_page_preview=True, text=text)
async def check_user(user_id):
events = sorted(text_ical(user_id, du.get_tz(user_id)), key=lambda e: (e[0], e[1], e[2]))
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 = "<b>События сегодня</b>\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])
# для событий на весь день напоминания по времени не шлём
if event[3]["all_day"]:
continue
delta_start = delta_time(d_event, 1, 0)
if start and delta_start[0] < time_check <= delta_start[1]:
await send(tg_id, "<b>Событие начинается!</b>\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 send(tg_id, f"<b>Напоминаю!</b>\n<i>Через {alarm[0]} минут "
f"будет событие:</i>\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 send(tg_id, f"<b>Напоминаю!</b>\n<i>Через {alarm[1]} минут "
f"будет событие:</i>\n\n{message_form(0, event[3])}")
if daily and delta_daily1 <= time_check < delta_daily2:
if counter:
await send(tg_id, txt)
else:
await send(tg_id, "Сегодня событий <b>нет</b>")
# ПРОВЕРКА НА СОБЫТИЕ
async def alarm(wait_for):
while True:
await asyncio.sleep(wait_for)
for (user_id,) in du.all_users():
# ошибка у одного пользователя (заблокировал бота, битый календарь) не должна
# останавливать рассылку остальным и весь бот
try:
if dc.clock_exists(user_id) and du.get_status(user_id):
await check_user(user_id)
except Exception:
logging.exception("Ошибка проверки событий пользователя %s", user_id)
async def update(wait_for):
while True:
await asyncio.sleep(wait_for)
for (user_id,) in du.all_users():
try:
if not (du.url_exists(user_id) and du.get_status(user_id)):
continue
old = read_ical(user_id)
new = await download_ical(du.get_url(user_id), user_id)
except Exception as err:
# при сбое остаётся прошлая версия календаря
logging.warning("Не удалось обновить календарь пользователя %s: %s", user_id, err)
continue
try:
if old and old != new and dc.clock_exists(user_id) and dc.get_changes(user_id):
text = changes.report(old, new, du.get_tz(user_id))
if text:
await send(str(du.get_first_user_id(user_id)), text)
except Exception:
logging.exception("Ошибка уведомления об изменениях пользователя %s", user_id)
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())
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