mirror of
https://github.com/EDeev/y.calendarkin.git
synced 2026-10-08 00:39:27 +03:00
Ошибки: - исключение в фоновых циклах (сбой сети при обновлении календаря, заблокировавший бота пользователь, битый календарь) останавливало весь бот — теперь ошибка у одного пользователя логируется и не мешает остальным; - повторяющиеся события с UNTIL местным временем или датой роняли разбор календаря (ValueError из dateutil) — UNTIL приводится к UTC; - названия и описания событий подставлялись в HTML без экранирования: «<» или «&» ломали отправку уведомления; - сообщение до /start и кривой ввод времени напоминаний роняли обработчик; - при отсутствии файла календаря разбор падал. Безопасность: ссылка скачивалась без проверки (wget), бота можно было заставить ходить по любым адресам, включая внутренние. Теперь принимается только https://calendar.yandex.*, скачивание — через aiohttp с таймаутом и без редиректов, содержимое проверяется как iCal до замены старого файла. Уведомления (по issue #3): без даты и года — только время, пустые описание и организатор не показываются, организатор без «mailto:», для событий на весь день — «Весь день» и без напоминаний в 00:01; новые команды /link и /unsubscribe. Прочее: токен из BOT_TOKEN, таблицы SQLite создаются при первом запуске (пустые базы убраны из репозитория), ссылка «АВТОР» — на EDeev вместо чужого теперь профиля IGlek.
180 lines
9.1 KiB
Python
180 lines
9.1 KiB
Python
from pytz import timezone
|
||
import sqlite3
|
||
|
||
|
||
class Users:
|
||
def __init__(self, database):
|
||
"""Подключаемся к БД и сохраняем курсор соединения"""
|
||
self.connection = sqlite3.connect(database)
|
||
self.cursor = self.connection.cursor()
|
||
|
||
# на новой установке (например, в Docker с пустым томом) таблиц ещё нет
|
||
with self.connection:
|
||
self.cursor.execute("CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL)")
|
||
self.cursor.execute("CREATE TABLE IF NOT EXISTS url (user_id INTEGER NOT NULL, "
|
||
"status BOOLEAN NOT NULL DEFAULT (True), url_ical STRING, time_zone STRING)")
|
||
|
||
# КОМАНДЫ USER
|
||
def user_exists(self, user_id):
|
||
"""Проверяем, есть ли уже пользователь в базе"""
|
||
with self.connection:
|
||
result = self.cursor.execute('SELECT * FROM `user` WHERE `user_id` = ?', (user_id,)).fetchall()
|
||
return bool(len(result))
|
||
|
||
def all_users(self):
|
||
"""Список айди"""
|
||
with self.connection:
|
||
return self.cursor.execute('SELECT `id` FROM `user`').fetchall()
|
||
|
||
def add_user(self, user_id):
|
||
"""Добавляем нового пользователя"""
|
||
with self.connection:
|
||
return self.cursor.execute("INSERT INTO `user` (`user_id`) VALUES(?)", (user_id,))
|
||
|
||
def get_user_id(self, user_id):
|
||
"""Получаем короткое айди юзера"""
|
||
with self.connection:
|
||
return self.cursor.execute('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('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('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("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('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('SELECT `status` FROM `url` WHERE `user_id` = ?', (user_id,)).fetchone()[0]
|
||
|
||
def get_url(self, user_id):
|
||
"""Получаем ссылку на календарь"""
|
||
with self.connection:
|
||
return self.cursor.execute('SELECT `url_ical` FROM `url` WHERE `user_id` = ?', (user_id,)).fetchone()[0]
|
||
|
||
def delete_url(self, user_id):
|
||
"""Удаляем подписку на календарь"""
|
||
with self.connection:
|
||
return self.cursor.execute("DELETE FROM `url` WHERE `user_id` = ?", (user_id,))
|
||
|
||
def get_tz(self, user_id):
|
||
"""Получаем указанный в календаре часовой пояс"""
|
||
with self.connection:
|
||
return timezone(self.cursor.execute('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()
|
||
|
||
with self.connection:
|
||
self.cursor.execute("CREATE TABLE IF NOT EXISTS alarm (user_id INTEGER NOT NULL, "
|
||
"daily BOOLEAN NOT NULL DEFAULT (False), start BOOLEAN NOT NULL DEFAULT (True), "
|
||
"alarm_1 INTEGER, alarm_2 INTEGER, status_2 BOOLEAN NOT NULL DEFAULT (True))")
|
||
|
||
# КОМАНДЫ ALARM
|
||
def clock_exists(self, user_id):
|
||
"""Проверяем, есть ли данные уже в базе"""
|
||
with self.connection:
|
||
result = self.cursor.execute('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("INSERT INTO `alarm` (`user_id`, `alarm_1`, `alarm_2`) VALUES(?, ?, ?)",
|
||
(user_id, 15, 5))
|
||
|
||
def delete_clock(self, user_id):
|
||
"""Удаляем настройки оповещений"""
|
||
with self.connection:
|
||
return self.cursor.execute("DELETE FROM `alarm` WHERE `user_id` = ?", (user_id,))
|
||
|
||
# КОМАНДЫ ПОЛУЧЕНИЯ ССЫЛОК
|
||
def get_alarm(self, user_id):
|
||
"""Получаем задержки таймера"""
|
||
with self.connection:
|
||
return self.cursor.execute('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('SELECT `start` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0]
|
||
|
||
def get_daily(self, user_id):
|
||
"""Получаем статус ежедневного уведомления"""
|
||
with self.connection:
|
||
return self.cursor.execute('SELECT `daily` FROM `alarm` WHERE `user_id` = ?', (user_id,)).fetchone()[0]
|
||
|
||
def get_status2(self, user_id):
|
||
"""Получаем статус отправки второго уведомления"""
|
||
with self.connection:
|
||
return self.cursor.execute('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('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('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('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()
|