Добавлено состояние для обработки текстовых сообщений в дневных заметках и улучшена логика создания заметок в Inbox
All checks were successful
Deploy bot / build-deploy (push) Successful in 32s

This commit is contained in:
Dmitry
2025-12-12 18:55:36 +03:00
parent 7df16e92a4
commit 440fcc0f6a

262
main.py
View File

@@ -1,183 +1,161 @@
import os import os
import asyncio
from datetime import datetime from datetime import datetime
from dotenv import load_dotenv from dotenv import load_dotenv
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from aiogram.filters import Command, CommandStart from aiogram.filters import CommandStart, Command
from aiogram.types import Message from aiogram.types import (
Message,
ReplyKeyboardMarkup,
KeyboardButton,
ReplyKeyboardRemove,
)
from trilium_py.client import ETAPI from trilium_py.client import ETAPI
from aiogram import Bot, Dispatcher, F
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
load_dotenv() load_dotenv()
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TRILIUM_URL = os.getenv("TRILIUM_URL") TRILIUM_URL = os.getenv("TRILIUM_URL")
TRILIUM_TOKEN = os.getenv("TRILIUM_TOKEN") TRILIUM_TOKEN = os.getenv("TRILIUM_TOKEN")
INBOX_NOTE_ID = os.getenv("INBOX_NOTE_ID") INBOX_NOTE_ID = os.getenv("INBOX_NOTE_ID")
DAILY_NOTE_ID = os.getenv("DAILY_NOTE_ID") # ID папки для ежедневных заметок
# создаем Trilium API клиент # создаем Trilium API клиент
ea = ETAPI(server_url=TRILIUM_URL, token=TRILIUM_TOKEN) ea = ETAPI(server_url=TRILIUM_URL, token=TRILIUM_TOKEN)
bot = Bot(token=TELEGRAM_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) bot = Bot(token=TELEGRAM_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp = Dispatcher() storage = MemoryStorage()
pending_daily_chats = set() # chat_ids ожидающих текст для /daily
dp = Dispatcher(storage=storage)
@dp.message(CommandStart()) class DailyStates(StatesGroup):
async def start(msg: Message): waiting_for_text = State()
await msg.answer("Отправь мне текст — я создам заметку в Trilium.")
def _get_id(obj): # Клавиатуры
if isinstance(obj, dict): main_keyboard = ReplyKeyboardMarkup(
return obj.get("noteId") or obj.get("id") keyboard=[
return getattr(obj, "noteId", None) or getattr(obj, "id", None) [KeyboardButton(text="📅 Daily"), KeyboardButton(text="📥 Inbox")],
[KeyboardButton(text="❌ Отмена")],
],
resize_keyboard=True,
)
cancel_keyboard = ReplyKeyboardMarkup(
keyboard=[[KeyboardButton(text="❌ Отмена")]], resize_keyboard=True
)
def _get_title(obj): @dp.message(Command("start"))
return obj.get("title") if isinstance(obj, dict) else getattr(obj, "title", "") async def cmd_start(message: Message):
"""Команда /start - показываем главное меню"""
await message.answer(
def _get_content(obj): "Выбери действие:\n"
return obj.get("content") if isinstance(obj, dict) else getattr(obj, "content", "") "📅 Daily - добавить в дневную заметку\n"
"📥 Inbox - создать заметку в Inbox (по умолчанию)",
reply_markup=main_keyboard,
def _children(obj):
if isinstance(obj, dict):
return obj.get("children") or []
return getattr(obj, "children", []) or []
def _translate_month_en_ru(name: str) -> str:
mapping = {
"January": "Январь",
"February": "Февраль",
"March": "Март",
"April": "Апрель",
"May": "Май",
"June": "Июнь",
"July": "Июль",
"August": "Август",
"September": "Сентябрь",
"October": "Октябрь",
"November": "Ноябрь",
"December": "Декабрь",
}
return mapping.get(name, name)
def _translate_weekday_en_ru(name: str) -> str:
mapping = {
"Monday": "Понедельник",
"Tuesday": "Вторник",
"Wednesday": "Среда",
"Thursday": "Четверг",
"Friday": "Пятница",
"Saturday": "Суббота",
"Sunday": "Воскресенье",
}
return mapping.get(name, name)
def save_inbox(text: str):
# разделяем на заголовок и тело
lines = text.split("\n", 1)
title = lines[0][:100]
content = lines[1] if len(lines) > 1 else ""
ea.create_note(
parentNoteId=INBOX_NOTE_ID, title=title, content=content, type="text"
) )
def save_daily(text: str) -> str: @dp.message(F.text == "📅 Daily")
today = datetime.now() async def btn_daily(message: Message, state: FSMContext):
month_name = ( """Кнопка Daily"""
f"{today.strftime('%m')} - {_translate_month_en_ru(today.strftime('%B'))}" await message.answer(
"Отправь текст для сегодняшней заметки:", reply_markup=cancel_keyboard
) )
day_name = ( await state.set_state(DailyStates.waiting_for_text)
f"{today.strftime('%d')} - {_translate_weekday_en_ru(today.strftime('%A'))}"
)
today_date = today.strftime("%Y-%m-%d")
# Корень daily
daily_root = ea.get_note(DAILY_NOTE_ID)
# Ищем/создаём месяц
month_folder = None
for child in _children(daily_root):
if _get_title(child) == month_name:
month_folder = child
break
if not month_folder:
month_folder = ea.create_note(
parentNoteId=DAILY_NOTE_ID, title=month_name, type="text"
)
month_id = _get_id(month_folder)
month_folder = ea.get_note(month_id)
# Ищем/создаём день
day_folder = None
for child in _children(month_folder):
if _get_title(child) == day_name:
day_folder = child
break
if not day_folder:
day_folder = ea.create_note(parentNoteId=month_id, title=day_name, type="text")
day_id = _get_id(day_folder)
day_folder = ea.get_note(day_id)
# Ищем заметку по дате
existing_note = None
for child in _children(day_folder):
if _get_title(child) == today_date:
existing_note = child
break
if existing_note:
existing_content = _get_content(existing_note)
new_content = f"{existing_content}\n\n---\n{text}" if existing_content else text
ea.update_note(noteId=_get_id(existing_note), content=new_content)
return f"Добавлено в ежедневную заметку за {today_date}."
ea.create_note(parentNoteId=day_id, title=today_date, content=text, type="text")
return f"Создана ежедневная заметка за {today_date}."
@dp.message(Command("daily")) @dp.message(Command("daily"))
async def daily_command(msg: Message): async def cmd_daily(message: Message, state: FSMContext):
pending_daily_chats.add(msg.chat.id) """Команда /daily - альтернативный способ"""
await msg.answer( await message.answer(
"Жду текст для ежедневной заметки. Следующее сообщение запишу в текущий день." "Отправь текст для сегодняшней заметки:", reply_markup=cancel_keyboard
)
await state.set_state(DailyStates.waiting_for_text)
@dp.message(DailyStates.waiting_for_text, F.text != "❌ Отмена")
async def process_daily_text(message: Message, state: FSMContext):
"""Обработка текста для дневной заметки"""
text = message.text
today = datetime.now().strftime("%Y-%m-%d")
try:
existing_content = ea.get_day_note(today)
timestamp = datetime.now().strftime("%H:%M")
new_content = f"{existing_content}<p><strong>{timestamp}</strong>: {text}</p>"
ea.set_day_note(today, new_content)
await message.answer(
f"✅ Добавлено в дневную заметку", reply_markup=main_keyboard
)
except Exception as e:
await message.answer(f"❌ Ошибка: {e}", reply_markup=main_keyboard)
await state.clear()
@dp.message(F.text == "❌ Отмена")
async def btn_cancel(message: Message, state: FSMContext):
"""Кнопка отмены"""
await state.clear()
await message.answer("Отменено", reply_markup=main_keyboard)
@dp.message(Command("cancel"))
async def cmd_cancel(message: Message, state: FSMContext):
"""Команда отмены"""
await state.clear()
await message.answer("Отменено", reply_markup=main_keyboard)
@dp.message(F.text == "📥 Inbox")
async def btn_inbox(message: Message):
"""Информация о режиме Inbox"""
await message.answer(
"Просто отправь любой текст - он автоматически создаст заметку в Inbox",
reply_markup=main_keyboard,
) )
@dp.message() @dp.message(F.text & ~F.text.startswith("/"))
async def handler(msg: Message): async def handle_text_to_inbox(message: Message, state: FSMContext):
text = (msg.text or "").strip() """Обработка обычного текста -> создаём новую заметку в Inbox"""
current_state = await state.get_state()
# Если ожидаем daily — пишем туда if current_state is not None:
if msg.chat.id in pending_daily_chats: # Если в состоянии daily - пропускаем
pending_daily_chats.discard(msg.chat.id)
try:
result = save_daily(text)
await msg.answer(result)
except Exception as e:
await msg.answer(f"Ошибка при работе с ежедневной заметкой: {str(e)}")
return return
# Обычная заметка в Inbox # Игнорируем текст кнопок
if message.text in ["📅 Daily", "📥 Inbox", "❌ Отмена"]:
return
text = message.text
lines = text.split("\n", 1)
title = lines[0][:100] # заголовок = первая строка
content = lines[1] if len(lines) > 1 else "" # остальное — тело заметки
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
# title = text[:50] + "..." if len(text) > 50 else text
try: try:
save_inbox(text) res = ea.create_note(
await msg.answer("Заметка сохранена в Trilium.") parentNoteId=INBOX_NOTE_ID,
title=title,
type="text",
content=f"<p><strong>{timestamp}</strong></p><p>{content}</p>",
)
await message.answer(f"✅ Создана заметка в Inbox", reply_markup=main_keyboard)
except Exception as e: except Exception as e:
await msg.answer(f"Ошибка при сохранении в Inbox: {str(e)}") await message.answer(f"Ошибка: {e}", reply_markup=main_keyboard)
async def main(): async def main():
@@ -185,6 +163,4 @@ async def main():
if __name__ == "__main__": if __name__ == "__main__":
import asyncio
asyncio.run(main()) asyncio.run(main())