initial commit
This commit is contained in:
50
.drone.yml
Normal file
50
.drone.yml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build image
|
||||||
|
image: docker:24
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
DOCKER_USERNAME:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
CICD_TOKEN:
|
||||||
|
from_secret: CICD_TOKEN
|
||||||
|
commands:
|
||||||
|
- docker login git.iamninja.ru -u "$DOCKER_USERNAME" -p "$CICD_TOKEN"
|
||||||
|
- docker build -t git.iamninja.ru/iamninja/pomodoro_tg_bot:latest .
|
||||||
|
|
||||||
|
- name: push image
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: git.iamninja.ru/iamninja/pomodoro_tg_bot
|
||||||
|
tags: latest
|
||||||
|
username:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: CICD_TOKEN
|
||||||
|
|
||||||
|
- name: deploy to server
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: DOCKER_DEPLOY
|
||||||
|
username:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
port: 22025
|
||||||
|
key:
|
||||||
|
from_secret: DOCKER_SSH_KEY
|
||||||
|
script:
|
||||||
|
- mkdir -p /home/iamninja/pomodoro_tg_bot
|
||||||
|
- cd /home/iamninja/pomodoro_tg_bot
|
||||||
|
- git pull origin main
|
||||||
|
- docker compose pull
|
||||||
|
- docker compose up -d
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
host:
|
||||||
|
path: /var/run/docker.sock
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
43
bot.py
Normal file
43
bot.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from aiogram import Bot, Dispatcher, types
|
||||||
|
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
from aiogram.utils import executor
|
||||||
|
from aiogram.dispatcher.filters import Text
|
||||||
|
from user_manager import user_manager
|
||||||
|
from timer_manager import timer_manager
|
||||||
|
import os
|
||||||
|
|
||||||
|
API_TOKEN = os.getenv("BOT_TOKEN")
|
||||||
|
|
||||||
|
bot = Bot(token=API_TOKEN)
|
||||||
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
menu_kb = InlineKeyboardMarkup(row_width=2)
|
||||||
|
menu_kb.add(
|
||||||
|
InlineKeyboardButton("▶️ Pomodoro", callback_data="pomodoro"),
|
||||||
|
InlineKeyboardButton("☕ Short Break", callback_data="shortbreak"),
|
||||||
|
InlineKeyboardButton("😌 Long Break", callback_data="longbreak"),
|
||||||
|
InlineKeyboardButton("⏹ Stop", callback_data="stop")
|
||||||
|
)
|
||||||
|
|
||||||
|
@dp.message_handler(commands=['start', 'help'])
|
||||||
|
async def send_welcome(message: Message):
|
||||||
|
await message.reply("Привет! Я твой личный Pomodoro-бот. Выбирай действие:", reply_markup=menu_kb)
|
||||||
|
|
||||||
|
@dp.callback_query_handler(Text(equals=["pomodoro", "shortbreak", "longbreak", "stop"]))
|
||||||
|
async def handle_callback(call: types.CallbackQuery):
|
||||||
|
if call.data == "pomodoro":
|
||||||
|
await timer_manager.start_timer(call.from_user.id, 25*60, call.message.chat.id, 'Pomodoro')
|
||||||
|
await call.message.answer("Начался 25-минутный Pomodoro! 🔥")
|
||||||
|
elif call.data == "shortbreak":
|
||||||
|
await timer_manager.start_timer(call.from_user.id, 5*60, call.message.chat.id, 'Short Break')
|
||||||
|
await call.message.answer("Начался 5-минутный перерыв ☕")
|
||||||
|
elif call.data == "longbreak":
|
||||||
|
await timer_manager.start_timer(call.from_user.id, 15*60, call.message.chat.id, 'Long Break')
|
||||||
|
await call.message.answer("Начался длинный перерыв 😌")
|
||||||
|
elif call.data == "stop":
|
||||||
|
await timer_manager.stop_timer(call.from_user.id)
|
||||||
|
await call.message.answer("Таймер остановлен ⏹")
|
||||||
|
await call.answer()
|
||||||
|
|
||||||
|
def run_bot():
|
||||||
|
executor.start_polling(dp, skip_updates=True)
|
||||||
7
main.py
Normal file
7
main.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import asyncio
|
||||||
|
from bot import run_bot
|
||||||
|
from redis_client import init_redis
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(init_redis())
|
||||||
|
run_bot()
|
||||||
6
models.py
Normal file
6
models.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UserState:
|
||||||
|
current_timer: str = None
|
||||||
|
task: any = None
|
||||||
10
redis_client.py
Normal file
10
redis_client.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import aioredis
|
||||||
|
import os
|
||||||
|
|
||||||
|
redis = None
|
||||||
|
|
||||||
|
async def init_redis():
|
||||||
|
global redis
|
||||||
|
redis = await aioredis.from_url(
|
||||||
|
os.getenv("REDIS_URL", "redis://localhost"), decode_responses=True
|
||||||
|
)
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
aiogram==2.25.2
|
||||||
|
aioredis==2.0.1
|
||||||
34
time_magager.py
Normal file
34
time_magager.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import asyncio
|
||||||
|
from user_manager import user_manager
|
||||||
|
from models import UserState
|
||||||
|
from aiogram import Bot
|
||||||
|
import os
|
||||||
|
|
||||||
|
bot = Bot(token=os.getenv("BOT_TOKEN"))
|
||||||
|
|
||||||
|
class TimerManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.tasks = {}
|
||||||
|
|
||||||
|
async def start_timer(self, user_id, duration, chat_id, label):
|
||||||
|
await self.stop_timer(user_id)
|
||||||
|
|
||||||
|
async def timer():
|
||||||
|
await asyncio.sleep(duration)
|
||||||
|
if label == 'Pomodoro':
|
||||||
|
await user_manager.increment_pomodoros(user_id)
|
||||||
|
await bot.send_message(chat_id, f"⏰ {label} завершён!")
|
||||||
|
|
||||||
|
task = asyncio.create_task(timer())
|
||||||
|
user = user_manager.get_user(user_id)
|
||||||
|
user.current_timer = label
|
||||||
|
user.task = task
|
||||||
|
self.tasks[user_id] = task
|
||||||
|
|
||||||
|
async def stop_timer(self, user_id):
|
||||||
|
user = user_manager.get_user(user_id)
|
||||||
|
if user.task:
|
||||||
|
user.task.cancel()
|
||||||
|
user.task = None
|
||||||
|
user.current_timer = None
|
||||||
|
self.tasks.pop(user_id, None)
|
||||||
20
user_mager.py
Normal file
20
user_mager.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from models import UserState
|
||||||
|
from redis_client import redis
|
||||||
|
|
||||||
|
class UserManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.users = {}
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
if user_id not in self.users:
|
||||||
|
self.users[user_id] = UserState()
|
||||||
|
return self.users[user_id]
|
||||||
|
|
||||||
|
async def increment_pomodoros(self, user_id):
|
||||||
|
await redis.incr(f"user:{user_id}:pomodoros")
|
||||||
|
|
||||||
|
async def get_pomodoros(self, user_id):
|
||||||
|
val = await redis.get(f"user:{user_id}:pomodoros")
|
||||||
|
return int(val) if val else 0
|
||||||
|
|
||||||
|
user_manager = UserManager()
|
||||||
Reference in New Issue
Block a user