Files
kantgbot/main.py
2025-08-03 10:06:47 +03:00

184 lines
6.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 os
import logging
from aiogram import Bot, Dispatcher, Router, types, F
from aiogram.filters import CommandStart, StateFilter
from aiogram.fsm.state import StatesGroup, State
from aiogram.fsm.context import FSMContext
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
from dotenv import load_dotenv
import kanboard
# Логирование
logging.basicConfig(level=logging.INFO)
# Загрузка токена из .env
load_dotenv()
BOT_TOKEN = os.getenv("TELEGRAM_TOKEN")
KB_URI = os.getenv("KB_URI")
BOT_LOGIN = os.getenv("BOT_LOGIN")
BOT_PASS = os.getenv("BOT_PASS")
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
router = Router()
# Kanboard client (укажи свои адрес и логин/пароль)
kb_connect = "http://"+KB_URI+"/jsonrpc.php"
kb = kanboard.Client(kb_connect,BOT_LOGIN, BOT_PASS)
# FSM состояния
class ProjectStates(StatesGroup):
waiting_project_choice = State()
project_menu = State()
waiting_for_task_title = State()
# /start — главное меню
@router.message(CommandStart())
async def start(msg: types.Message, state: FSMContext):
keyboard = ReplyKeyboardMarkup(
keyboard=[[KeyboardButton(text="Список проектов")]],
resize_keyboard=True
)
await msg.answer(
"Добро пожаловать! Выберите действие:",
reply_markup=keyboard
)
await state.clear()
# Кнопка "Список проектов"
@router.message(F.text == "Список проектов")
async def show_projects(msg: types.Message, state: FSMContext):
try:
projects = kb.get_my_projects()
await state.update_data(projects=projects)
except Exception as e:
await msg.answer(f"Ошибка получения проектов: {e}")
return
# Список проектов с номерами
lines = [f"{idx + 1}. {proj['name']}" for idx, proj in enumerate(projects)]
text = "Выберите проект (отправьте его номер):\n" + "\n".join(lines)
await msg.answer(text)
await state.set_state(ProjectStates.waiting_project_choice)
# Выбор проекта по номеру
@router.message(StateFilter(ProjectStates.waiting_project_choice), F.text)
async def project_chosen(msg: types.Message, state: FSMContext):
data = await state.get_data()
projects = data.get("projects", [])
# Проверяем, что введено число
try:
idx = int(msg.text.strip()) - 1
except ValueError:
await msg.answer("Пожалуйста, введите номер проекта.")
return
if not (0 <= idx < len(projects)):
await msg.answer("Нет проекта с таким номером. Попробуйте ещё раз.")
return
selected = projects[idx]
await state.update_data(selected_project=selected)
keyboard = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="Список задач")],
[KeyboardButton(text="Добавить задачу")],
[KeyboardButton(text="Назад")],
],
resize_keyboard=True
)
await msg.answer(
f"Вы выбрали проект: {selected['name']}\nВыберите действие:",
reply_markup=keyboard
)
await state.set_state(ProjectStates.project_menu)
# Кнопка "Список задач"
@router.message(StateFilter(ProjectStates.project_menu), F.text == "Список задач")
async def show_tasks(msg: types.Message, state: FSMContext):
data = await state.get_data()
project = data.get("selected_project")
if not project:
await msg.answer("Ошибка: проект не выбран.")
return
try:
tasks = kb.get_all_tasks(project_id=project['id'])
if not tasks:
await msg.answer("В этом проекте нет задач.")
else:
text = "\n".join([
f"{t['title']}"
for t in tasks
])
await msg.answer(f"Текущие задачи:\n{text}")
except Exception as e:
await msg.answer(f"Ошибка получения задач: {e}")
# Кнопка "Добавить задачу"
@router.message(StateFilter(ProjectStates.project_menu), F.text == "Добавить задачу")
async def add_task(msg: types.Message, state: FSMContext):
await msg.answer("Напишите название задачи одним сообщением:")
await state.set_state(ProjectStates.waiting_for_task_title)
# Кнопка "Назад" — возвращает к выбору проекта по номеру
@router.message(StateFilter(ProjectStates.project_menu), F.text == "Назад")
async def go_back_to_projects(msg: types.Message, state: FSMContext):
data = await state.get_data()
projects = data.get("projects", [])
if not projects:
# если вдруг projects нет в state (например, после сброса)
try:
projects = kb.get_my_projects()
await state.update_data(projects=projects)
except Exception as e:
await msg.answer(f"Ошибка получения проектов: {e}")
return
lines = [f"{idx + 1}. {proj['name']}" for idx, proj in enumerate(projects)]
text = "Выберите проект (отправьте его номер):\n" + "\n".join(lines)
await state.update_data(selected_project=None)
keyboard = ReplyKeyboardMarkup(
keyboard=[[KeyboardButton(text="Список проектов")]],
resize_keyboard=True
)
await msg.answer(
text,
reply_markup=keyboard
)
await state.set_state(ProjectStates.waiting_project_choice)
# Принимаем название задачи и создаём её
@router.message(StateFilter(ProjectStates.waiting_for_task_title), F.text)
async def task_title_received(msg: types.Message, state: FSMContext):
data = await state.get_data()
project = data.get("selected_project")
title = msg.text
try:
kb.create_task(project_id=project['id'], title=title)
await msg.answer(f"Задача '{title}' добавлена!")
except Exception as e:
await msg.answer(f"Ошибка добавления задачи: {e}")
# Меню проекта после добавления задачи
keyboard = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="Список задач")],
[KeyboardButton(text="Добавить задачу")],
[KeyboardButton(text="Назад")],
],
resize_keyboard=True
)
await msg.answer("Выберите действие:", reply_markup=keyboard)
await state.set_state(ProjectStates.project_menu)
# Регистрация роутера
dp.include_router(router)
async def main():
await dp.start_polling(bot)
if __name__ == "__main__":
import asyncio
asyncio.run(main())