This commit is contained in:
Yaroslav 2026-02-13 05:17:47 -06:00 committed by GitHub
commit 13bde5c495
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1671 additions and 586 deletions

63
.dockerignore Normal file
View file

@ -0,0 +1,63 @@
# Git
.git
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Environment files
.env
.env.*
env.example
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Documentation
README.md
*.md
# Temporary files
*.tmp
*.temp

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
db/
.env

33
Dockerfile Normal file
View file

@ -0,0 +1,33 @@
# Используем официальный Python образ
FROM python:3.11-slim
# Устанавливаем рабочую директорию
WORKDIR /app
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
wget \
&& rm -rf /var/lib/apt/lists/*
# Копируем файл зависимостей
COPY requirements.txt .
# Устанавливаем Python зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY code/ ./code/
COPY data/ ./data/
# Создаем директории для базы данных
RUN mkdir -p db
# Устанавливаем рабочую директорию для запуска
WORKDIR /app/code
# Открываем порт (если потребуется для мониторинга)
EXPOSE 8000
# Команда запуска
CMD ["python", "bot.py"]

View file

@ -30,13 +30,47 @@ Y.Calendarkin — некоммерческий проект, предназна
## 🚀 Установка и запуск ## 🚀 Установка и запуск
### Предварительные требования ### 🐳 Запуск с Docker (Рекомендуется)
1. **Клонируйте репозиторий:**
```bash
git clone <repository-url>
cd y.calendarkin
```
2. **Создайте файл с переменными окружения:**
```bash
cp env.example .env
```
3. **Настройте переменные окружения:**
- Откройте файл `.env`
- Замените `your_bot_token_here` на токен вашего бота от [@BotFather](https://t.me/BotFather)
4. **Запустите бота:**
```bash
docker-compose up -d
```
5. **Проверьте логи:**
```bash
docker-compose logs -f
```
6. **Остановите бота:**
```bash
docker-compose down
```
### 📦 Локальная установка
#### Предварительные требования
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
### Настройка #### Настройка
1. Создайте нового бота через [@BotFather](https://t.me/BotFather) 1. Создайте нового бота через [@BotFather](https://t.me/BotFather)
2. Получите токен и добавьте его в `config.py`: 2. Получите токен и добавьте его в `config.py`:
@ -51,7 +85,7 @@ TOKEN = "your_bot_token_here"
mkdir -p data/icals db mkdir -p data/icals db
``` ```
### Запуск #### Запуск
```bash ```bash
cd code cd code
@ -100,9 +134,23 @@ y.calendarkin/
│ ├── icals/ # Загруженные календари │ ├── icals/ # Загруженные календари
│ └── photo_edit_alarm.jpg │ └── photo_edit_alarm.jpg
├── db/ # База данных SQLite ├── db/ # База данных SQLite
├── Dockerfile # Docker образ
├── docker-compose.yml # Docker Compose конфигурация
├── .dockerignore # Исключения для Docker
├── env.example # Пример переменных окружения
└── requirements.txt └── requirements.txt
``` ```
### Управление данными
```bash
# Создание резервной копии базы данных
docker cp y-calendarkin-bot:/app/db ./backup/
# Восстановление базы данных
docker cp ./backup/db y-calendarkin-bot:/app/
```
## 📊 База данных ## 📊 База данных
Проект использует две основные таблицы: Проект использует две основные таблицы:

View file

@ -1,321 +1,326 @@
import logging, config, sql, asyncio, wget, os import logging, config, sql, asyncio, wget, os
from aiogram import Bot, Dispatcher, executor, types from aiogram import Bot, Dispatcher, types, F
from aiogram.types import ParseMode from aiogram.enums import ParseMode
from aiogram.filters import Command, or_f
from datetime import datetime, time
from script import text_ical, message_form, dt_now, delta_time from datetime import datetime, time
from script import text_ical, message_form, dt_now, delta_time
# инициализируем токен
logging.basicConfig(level=logging.INFO) # инициализируем токен
logging.basicConfig(level=logging.INFO)
bot = Bot(token=config.TOKEN)
dp = Dispatcher(bot) bot = Bot(token=config.TOKEN)
dp = Dispatcher()
# инициализируем соединение с БД
du = sql.Users('../db/users.db') # инициализируем соединение с БД
dc = sql.Clock('../db/clock.db') du = sql.Users('../db/users.db')
dc = sql.Clock('../db/clock.db')
# ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ
@dp.message_handler(commands=['start', 'help']) # ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ
async def helps(message: types.Message): @dp.message(or_f(Command('start'), Command('help')))
tg_id = int(message.chat.id) async def helps(message: types.Message):
if not du.user_exists(tg_id): tg_id = int(message.chat.id)
du.add_user(tg_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(inline_keyboard=[
[types.InlineKeyboardButton(text="КОМАНДЫ", callback_data="com"),
keyboard = types.InlineKeyboardMarkup(row_width=2) types.InlineKeyboardButton(text="АВТОР", callback_data="auth")]
keyboard.add(*buttons) ])
await message.answer(text="<b>Я.Календаркин</b> - бот для оповещения о событиях из <b>Яндекс.Календаря</b>. " await message.answer(text="<b>Я.Календаркин</b> - бот для оповещения о событиях из <b>Яндекс.Календаря</b>. "
"Для начала работы вам нужно всего лишь прислать в чат ссылку экспорта календаря в " "Для начала работы вам нужно всего лишь прислать в чат ссылку экспорта календаря в "
"<b>формате ICal</b>. После получения ссылки, бот начнёт оповещать о всех новых событиях " "<b>формате ICal</b>. После получения ссылки, бот начнёт оповещать о всех новых событиях "
"и появится возможность настройки оповещений. О том, какие команды есть для настройки, " "и появится возможность настройки оповещений. О том, какие команды есть для настройки, "
"вы можете ознакомиться по кнопке <b>КОМАНДЫ</b>", "вы можете ознакомиться по кнопке <b>КОМАНДЫ</b>",
parse_mode=ParseMode.HTML, reply_markup=keyboard) parse_mode=ParseMode.HTML, reply_markup=keyboard)
@dp.callback_query_handler(text="auth") @dp.callback_query(F.data == "auth")
async def author(call: types.CallbackQuery): async def author(call: types.CallbackQuery):
await call.message.answer(text='*| АВТОР |*\n\n*>>* Этот бот не коммерческий проект, для упрощенного получения ' await call.message.answer(text='*| АВТОР |*\n\n*>>* Этот бот не коммерческий проект, для упрощенного получения '
'уведомлений о событиях в Яндекс.Календаре. Не многим этот бот будет полезен, но ' 'уведомлений о событиях в Яндекс.Календаре. Не многим этот бот будет полезен, но '
'людям, чья работа подразумевает его использование, он станет лишь удобным ' 'людям, чья работа подразумевает его использование, он станет лишь удобным '
'инструментом. Я же пишу подобные небольшие проекты, о которых вы можете узнать ' 'инструментом. Я же пишу подобные небольшие проекты, о которых вы можете узнать '
'больше на моём [GitHub](https://github.com/IGlek).', 'больше на моём [GitHub](https://github.com/IGlek).',
parse_mode=ParseMode.MARKDOWN) parse_mode=ParseMode.MARKDOWN)
@dp.callback_query_handler(text="com") @dp.callback_query(F.data == "com")
async def commands(call: types.CallbackQuery): async def commands(call: types.CallbackQuery):
await call.message.answer(text='<b>| КОМАНДЫ |</b>\n\n' await call.message.answer(text='<b>| КОМАНДЫ |</b>\n\n'
'<b>/help</b> - вспомогательная функция для уточнения работы команд\n' '<b>/help</b> - вспомогательная функция для уточнения работы команд\n'
'<b>/list</b> - список событий календаря, запланированных на сегодняшний день\n' '<b>/list</b> - список событий календаря, запланированных на сегодняшний день\n'
'<b>/notif</b> - команда, отключающая рассылку уведомлений, даже при наличии событий в календаре\n' '<b>/notif</b> - команда, отключающая рассылку уведомлений, даже при наличии событий в календаре\n'
'<b>/daily</b> - оповещение в 8 утра по вашему часовому поясу со списком событий на день\n' '<b>/daily</b> - оповещение в 8 утра по вашему часовому поясу со списком событий на день\n'
'<b>/moment</b> - напоминание, приходящее в момент начала события\n\n' '<b>/moment</b> - напоминание, приходящее в момент начала события\n\n'
'<b>/get_alarm</b> - информация о времени на которое настроены оповещения\n' '<b>/get_alarm</b> - информация о времени на которое настроены оповещения\n'
'<b>/edit_alarm</b> - изменение времени оповещений\n' '<b>/edit_alarm</b> - изменение времени оповещений\n'
'<b>/stop_alarm</b> - команда, отключающая второе оповещение о событии', '<b>/stop_alarm</b> - команда, отключающая второе оповещение о событии',
parse_mode=types.ParseMode.HTML) parse_mode=ParseMode.HTML)
# КОМАНДЫ # КОМАНДЫ
@dp.message_handler(commands=['list']) @dp.message(Command('list'))
async def check_list(message: types.Message): async def check_list(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if du.url_exists(user_id): if du.url_exists(user_id):
txt = "<b>Имеющиеся события на сегодня</b>\n\n" txt = "<b>Имеющиеся события на сегодня</b>\n\n"
lst_events = sorted(text_ical(user_id, du.get_tz(user_id))) lst_events = sorted(text_ical(user_id, du.get_tz(user_id)))
today = dt_now(du.get_tz(user_id)).date() today = dt_now(du.get_tz(user_id)).date()
counter = 0 counter = 0
for event in lst_events: for event in lst_events:
if event[0] == today: if event[0] == today:
counter += 1 counter += 1
txt += message_form(counter, event[3]) txt += message_form(counter, event[3])
if counter: if counter:
await message.answer(text=txt, parse_mode=ParseMode.HTML) await message.answer(text=txt, parse_mode=ParseMode.HTML)
else: else:
await message.answer(text="<b>В данный момент</b> событий на сегодня найдено не было!", await message.answer(text="<b>В данный момент</b> событий на сегодня найдено не было!",
parse_mode=ParseMode.HTML) parse_mode=ParseMode.HTML)
else: else:
await message.answer("Для отображения событий вы должны прислать ical-ссылку на календарь!") await message.answer("Для отображения событий вы должны прислать ical-ссылку на календарь!")
@dp.message_handler(commands=['notif']) @dp.message(Command('notif'))
async def notif_up(message: types.Message): async def notif_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if du.url_exists(user_id): if du.url_exists(user_id):
if du.get_status(user_id): if du.get_status(user_id):
await message.answer("Уведомления о событиях выключены!") await message.answer("Уведомления о событиях выключены!")
else: else:
await message.answer("Уведомления о событиях включены!") await message.answer("Уведомления о событиях включены!")
du.update_status(user_id) du.update_status(user_id)
else: else:
await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!")
@dp.message_handler(commands=['daily']) @dp.message(Command('daily'))
async def daily_up(message: types.Message): async def daily_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if dc.clock_exists(user_id): if dc.clock_exists(user_id):
if dc.get_daily(user_id): if dc.get_daily(user_id):
await message.answer("Ежедневные утренние уведомления выключены!") await message.answer("Ежедневные утренние уведомления выключены!")
else: else:
await message.answer("Ежедневные утренние уведомления включены!") await message.answer("Ежедневные утренние уведомления включены!")
dc.update_daily(user_id) dc.update_daily(user_id)
else: else:
await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!")
@dp.message_handler(commands=['moment']) @dp.message(Command('moment'))
async def start_up(message: types.Message): async def start_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if dc.clock_exists(user_id): if dc.clock_exists(user_id):
if dc.get_start(user_id): if dc.get_start(user_id):
await message.answer("Уведомления в момент события выключены!") await message.answer("Уведомления в момент события выключены!")
else: else:
await message.answer("Уведомления в момент события включены!") await message.answer("Уведомления в момент события включены!")
dc.update_start(user_id) dc.update_start(user_id)
else: else:
await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!")
@dp.message_handler(commands=['get_alarm']) @dp.message(Command('get_alarm'))
async def start_up(message: types.Message): async def start_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if dc.clock_exists(user_id): if dc.clock_exists(user_id):
alarms = dc.get_alarm(user_id) alarms = dc.get_alarm(user_id)
start = dc.get_start(user_id) start = dc.get_start(user_id)
status2 = dc.get_status2(user_id) status2 = dc.get_status2(user_id)
txt = "" txt = ""
if status2: if status2:
txt += "<b>У вас работает два оповещения" txt += "<b>У вас работает два оповещения"
else: else:
txt += "<b>У вас работает лишь первое оповещение" txt += "<b>У вас работает лишь первое оповещение"
if start: if start:
txt += " и сообщение в момент начала события!</b>" txt += " и сообщение в момент начала события!</b>"
else: else:
txt += "!</b>" txt += "!</b>"
await message.answer(text=(txt + f"\n\n<b>Первое оповещение</b> приходит за {alarms[0]} минут\n" await message.answer(text=(txt + f"\n\n<b>Первое оповещение</b> приходит за {alarms[0]} минут\n"
f"<b>Второе оповещение</b> приходит за {alarms[1]} минут"), f"<b>Второе оповещение</b> приходит за {alarms[1]} минут"),
parse_mode=ParseMode.HTML) parse_mode=ParseMode.HTML)
else: else:
await message.answer("Для того, чтобы получить таймеры, вы должны прислать ical-ссылку на свой календарь!") await message.answer("Для того, чтобы получить таймеры, вы должны прислать ical-ссылку на свой календарь!")
@dp.message_handler(commands=['edit_alarm']) @dp.message(Command('edit_alarm'))
async def start_up(message: types.Message): async def start_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if dc.clock_exists(user_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"), await message.bot.send_photo(chat_id=message.chat.id, photo=open("../data/photo_edit_alarm.jpg", "rb"),
caption="Для изменения времени вам надо в ответ на это сообщение прислать два " caption="Для изменения времени вам надо в ответ на это сообщение прислать два "
"числа через пробел: разница времени первого и второго таймера по ходу " "числа через пробел: разница времени первого и второго таймера по ходу "
"времени соответственно") "времени соответственно")
else: else:
await message.answer("Для того, чтобы изменить таймеры, вы должны прислать ical-ссылку на свой календарь!") await message.answer("Для того, чтобы изменить таймеры, вы должны прислать ical-ссылку на свой календарь!")
@dp.message_handler(commands=['stop_alarm']) @dp.message(Command('stop_alarm'))
async def daily_up(message: types.Message): async def daily_up(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if dc.clock_exists(user_id): if dc.clock_exists(user_id):
if dc.get_status2(user_id): if dc.get_status2(user_id):
await message.answer("Второе уведомление выключено!") await message.answer("Второе уведомление выключено!")
else: else:
await message.answer("Второе уведомление включено!") await message.answer("Второе уведомление включено!")
dc.update_status2(user_id) dc.update_status2(user_id)
else: else:
await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!") await message.answer("Для взаимодействия с событиями вы должны прислать ical-ссылку на календарь!")
# ЗАГРУЗКА ССЫЛКИ # ЗАГРУЗКА ССЫЛКИ
@dp.message_handler(content_types=['text']) @dp.message(F.text)
async def downloading_file_ics(message: types.Message): async def downloading_file_ics(message: types.Message):
user_id = du.get_user_id(int(message.chat.id)) user_id = du.get_user_id(int(message.chat.id))
if message.text[:5] == "https": if message.text[:5] == "https":
try: try:
wget.download(message.text, f'../data/icals/{str(user_id)}_new.ics') wget.download(message.text, f'../data/icals/{str(user_id)}_new.ics')
try: try:
os.remove(f'../data/icals/{str(user_id)}.ics') os.remove(f'../data/icals/{str(user_id)}.ics')
except FileNotFoundError: except FileNotFoundError:
pass pass
os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics') os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics')
time_zone = message.text.split("=")[-1] time_zone = message.text.split("=")[-1]
if not du.url_exists(user_id): if not du.url_exists(user_id):
du.add_url(user_id, message.text, time_zone) du.add_url(user_id, message.text, time_zone)
dc.add_clock(user_id) dc.add_clock(user_id)
else: else:
du.update_url(user_id, message.text, time_zone) du.update_url(user_id, message.text, time_zone)
await message.answer("Ссылка успешно добавлена! Уведомления уже включены!") await message.answer("Ссылка успешно добавлена! Уведомления уже включены!")
except Exception: except Exception:
await message.answer("Ошибка скачивания! Проверьте правильность ссылки и пришлите ещё раз") await message.answer("Ошибка скачивания! Проверьте правильность ссылки и пришлите ещё раз")
if 'reply_to_message' in message and dc.clock_exists(user_id): if 'reply_to_message' in message and dc.clock_exists(user_id):
text = "Для изменения времени вам надо в ответ на это сообщение прислать два числа через " \ text = "Для изменения времени вам надо в ответ на это сообщение прислать два числа через " \
"пробел: разница времени первого и второго таймера по ходу времени соответственно" "пробел: разница времени первого и второго таймера по ходу времени соответственно"
if message.reply_to_message.caption == text: if message.reply_to_message.caption == text:
alarm_new = message.text.split() alarm_new = message.text.split()
if int(alarm_new[0]) < 60 and int(alarm_new[1]) < 60: if int(alarm_new[0]) < 60 and int(alarm_new[1]) < 60:
dc.update_alarm1(user_id, int(alarm_new[0])) dc.update_alarm1(user_id, int(alarm_new[0]))
dc.update_alarm2(user_id, int(alarm_new[1])) dc.update_alarm2(user_id, int(alarm_new[1]))
await message.answer("Время отправки уведомлений успешно обновлено!") await message.answer("Время отправки уведомлений успешно обновлено!")
else: else:
await message.answer("Время отправки уведомлений должно быть меньше 60 минут!") await message.answer("Время отправки уведомлений должно быть меньше 60 минут!")
# ПРОВЕРКА НА СОБЫТИЕ # ПРОВЕРКА НА СОБЫТИЕ
async def alarm(wait_for): async def alarm(wait_for):
while True: while True:
await asyncio.sleep(wait_for) await asyncio.sleep(wait_for)
users_id = du.all_users() users_id = du.all_users()
for user_id in users_id: for user_id in users_id:
user_id = user_id[0] user_id = user_id[0]
if dc.clock_exists(user_id): if dc.clock_exists(user_id):
if du.get_status(user_id): if du.get_status(user_id):
events = sorted(text_ical(user_id, du.get_tz(user_id))) events = sorted(text_ical(user_id, du.get_tz(user_id)))
tg_id = str(du.get_first_user_id(user_id)) tg_id = str(du.get_first_user_id(user_id))
start = dc.get_start(user_id) start = dc.get_start(user_id)
daily = dc.get_daily(user_id) daily = dc.get_daily(user_id)
alarm = dc.get_alarm(user_id) alarm = dc.get_alarm(user_id)
alarm2_status = dc.get_status2(user_id) alarm2_status = dc.get_status2(user_id)
today = dt_now(du.get_tz(user_id)).date() today = dt_now(du.get_tz(user_id)).date()
time_check = dt_now(du.get_tz(user_id)).time() time_check = dt_now(du.get_tz(user_id)).time()
# ДЛЯ DAILY # ДЛЯ DAILY
counter = 0 counter = 0
txt = "<b>События сегодня</b>\n\n" txt = "<b>События сегодня</b>\n\n"
delta_daily1 = time(hour=8, minute=0) delta_daily1 = time(hour=8, minute=0)
delta_daily2 = time(hour=8, minute=1) delta_daily2 = time(hour=8, minute=1)
# -------------------------------- # --------------------------------
for event in events: for event in events:
if event[0] == today: if event[0] == today:
d_event = datetime.combine(today, event[1]) d_event = datetime.combine(today, event[1])
if daily and delta_daily1 <= time_check < delta_daily2: if daily and delta_daily1 <= time_check < delta_daily2:
counter += 1 counter += 1
txt += message_form(counter, event[3]) txt += message_form(counter, event[3])
delta_start = delta_time(d_event, 1, 0) delta_start = delta_time(d_event, 1, 0)
if start and delta_start[0] < time_check <= delta_start[1]: 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, parse_mode=ParseMode.HTML,
text=f"<b>Событие начинается!</b>\n\n" + message_form(0, event[3])) text=f"<b>Событие начинается!</b>\n\n" + message_form(0, event[3]))
delta_alarm1 = delta_time(d_event, alarm[0], alarm[0] - 1) delta_alarm1 = delta_time(d_event, alarm[0], alarm[0] - 1)
if delta_alarm1[0] < time_check <= delta_alarm1[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, parse_mode=ParseMode.HTML,
text=f"<b>Напоминаю!</b>\n<i>Через {alarm[0]} минут " text=f"<b>Напоминаю!</b>\n<i>Через {alarm[0]} минут "
f"будет событие:</i>\n\n{message_form(0, event[3])}") f"будет событие:</i>\n\n{message_form(0, event[3])}")
if alarm2_status: if alarm2_status:
delta_alarm2 = delta_time(d_event, alarm[1], alarm[1] - 1) delta_alarm2 = delta_time(d_event, alarm[1], alarm[1] - 1)
if delta_alarm2[0] < time_check <= delta_alarm2[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, parse_mode=ParseMode.HTML,
text=f"<b>Напоминаю!</b>\n<i>Через {alarm[1]} минут " text=f"<b>Напоминаю!</b>\n<i>Через {alarm[1]} минут "
f"будет событие:</i>\n\n{message_form(0, event[3])}") f"будет событие:</i>\n\n{message_form(0, event[3])}")
if daily and delta_daily1 <= time_check < delta_daily2: if daily and delta_daily1 <= time_check < delta_daily2:
if counter: if counter:
await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML,
text=txt) text=txt)
else: else:
await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML, await bot.send_message(chat_id=tg_id, parse_mode=ParseMode.HTML,
text=f"Сегодня событий <b>нет</b>") text=f"Сегодня событий <b>нет</b>")
async def update(wait_for): async def update(wait_for):
while True: while True:
await asyncio.sleep(wait_for) await asyncio.sleep(wait_for)
users_id = du.all_users() users_id = du.all_users()
for user_id in users_id: for user_id in users_id:
user_id = user_id[0] user_id = user_id[0]
if du.url_exists(user_id): if du.url_exists(user_id):
if du.get_status(user_id): if du.get_status(user_id):
wget.download(du.get_url(user_id), f'../data/icals/{str(user_id)}_new.ics') 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.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') os.rename(f'../data/icals/{str(user_id)}_new.ics', f'../data/icals/{str(user_id)}.ics')
if __name__ == '__main__': async def main():
loop = asyncio.get_event_loop() # Создаем задачи для фоновых процессов
loop.create_task(alarm(60)) # ПРОВЕРКА КАЖДУЮ 1 МИНУТУ asyncio.create_task(alarm(60)) # ПРОВЕРКА КАЖДУЮ 1 МИНУТУ
loop.create_task(update(780)) # ПРОВЕРКА КАЖДУЮ 13 МИНУТУ asyncio.create_task(update(780)) # ПРОВЕРКА КАЖДУЮ 13 МИНУТУ
executor.start_polling(dp, skip_updates=True)
# Запускаем бота
await dp.start_polling(bot, skip_updates=True)
if __name__ == '__main__':
asyncio.run(main())

View file

@ -1 +1,7 @@
TOKEN = "**********:***************************" import os
# Получаем токен из переменной окружения
TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
if not TOKEN:
raise ValueError("TELEGRAM_BOT_TOKEN environment variable is required")

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

37
docker-compose.yml Normal file
View file

@ -0,0 +1,37 @@
services:
calendarkin-bot:
build: .
container_name: y-calendarkin-bot
restart: unless-stopped
environment:
# Токен Telegram бота (обязательно)
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
# Дополнительные переменные окружения (опционально)
- TZ=Europe/Moscow
volumes:
# Монтируем директорию с базами данных для сохранения данных
- ./db:/app/db
# Монтируем директорию с календарями
- ./data:/app/data
# Логирование
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Ограничения ресурсов
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Создаем именованные тома для данных (опционально)
volumes:
db_data:
ical_data:

7
env.example Normal file
View file

@ -0,0 +1,7 @@
# Telegram Bot Token
# Получите токен у @BotFather в Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here
# Часовой пояс (опционально)
TZ=Europe/Moscow