184 lines
6.9 KiB
Python
184 lines
6.9 KiB
Python
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())
|