Добавлено состояние для обработки текстовых сообщений в дневных заметках и улучшена логика создания заметок в Inbox
All checks were successful
Deploy bot / build-deploy (push) Successful in 32s
All checks were successful
Deploy bot / build-deploy (push) Successful in 32s
This commit is contained in:
260
main.py
260
main.py
@@ -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(
|
||||||
def _get_title(obj):
|
keyboard=[[KeyboardButton(text="❌ Отмена")]], resize_keyboard=True
|
||||||
return obj.get("title") if isinstance(obj, dict) else getattr(obj, "title", "")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_content(obj):
|
|
||||||
return obj.get("content") if isinstance(obj, dict) else getattr(obj, "content", "")
|
|
||||||
|
|
||||||
|
|
||||||
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(Command("start"))
|
||||||
today = datetime.now()
|
async def cmd_start(message: Message):
|
||||||
month_name = (
|
"""Команда /start - показываем главное меню"""
|
||||||
f"{today.strftime('%m')} - {_translate_month_en_ru(today.strftime('%B'))}"
|
await message.answer(
|
||||||
)
|
"Выбери действие:\n"
|
||||||
day_name = (
|
"📅 Daily - добавить в дневную заметку\n"
|
||||||
f"{today.strftime('%d')} - {_translate_weekday_en_ru(today.strftime('%A'))}"
|
"📥 Inbox - создать заметку в Inbox (по умолчанию)",
|
||||||
)
|
reply_markup=main_keyboard,
|
||||||
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)
|
|
||||||
|
|
||||||
# Ищем/создаём день
|
@dp.message(F.text == "📅 Daily")
|
||||||
day_folder = None
|
async def btn_daily(message: Message, state: FSMContext):
|
||||||
for child in _children(month_folder):
|
"""Кнопка Daily"""
|
||||||
if _get_title(child) == day_name:
|
await message.answer(
|
||||||
day_folder = child
|
"Отправь текст для сегодняшней заметки:", reply_markup=cancel_keyboard
|
||||||
break
|
)
|
||||||
|
await state.set_state(DailyStates.waiting_for_text)
|
||||||
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())
|
||||||
|
|||||||
Reference in New Issue
Block a user