Compare commits
2 Commits
38d1778a2d
...
7d697dce9b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d697dce9b | ||
|
|
b8a9a244f5 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
venv
|
||||||
|
bot*.py
|
||||||
|
.env
|
||||||
183
main.py
Normal file
183
main.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
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())
|
||||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Reference in New Issue
Block a user