feat(06-02): add /export command handler

- export_command handles /export and /export project
- Sends markdown as Telegram document attachment
- Registered in handlers/__init__.py
- Updated HELP_TEXT with export options
This commit is contained in:
Mikkel Georgsen 2026-01-17 01:49:03 +00:00
parent 152d6173d6
commit e5108c6705
3 changed files with 89 additions and 1 deletions

View file

@ -16,6 +16,7 @@ from moai.bot.handlers.discussion import (
open_command,
stop_command,
)
from moai.bot.handlers.export import export_command
from moai.bot.handlers.projects import project_command, projects_command
from moai.bot.handlers.status import status_command
@ -44,6 +45,7 @@ def register_handlers(app: Application) -> None:
app.add_handler(CommandHandler("next", next_command))
app.add_handler(CommandHandler("stop", stop_command))
app.add_handler(CommandHandler("consensus", consensus_command))
app.add_handler(CommandHandler("export", export_command))
# @mention handler - MessageHandler registered AFTER CommandHandlers
# Matches messages starting with @claude, @gpt, or @gemini followed by content

View file

@ -28,7 +28,8 @@ Multi-AI collaborative brainstorming platform.
*Output Commands:*
/consensus - Generate consensus summary
/export - Export project as markdown
/export - Export current discussion as markdown
/export project - Export entire project as markdown
*Utility:*
/status - Show current state

View file

@ -0,0 +1,85 @@
"""Export command handlers for MoAI bot.
Provides /export command to export discussions and projects as markdown documents.
"""
import io
from telegram import Update
from telegram.ext import ContextTypes
from moai.core.exporter import export_discussion, export_project
from moai.core.services.discussion import get_active_discussion, get_discussion, list_discussions
from moai.core.services.project import get_project
def get_selected_project(context: ContextTypes.DEFAULT_TYPE) -> str | None:
"""Get the currently selected project ID from user data."""
return context.user_data.get("selected_project_id")
async def export_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /export command - export discussion or project as markdown.
Usage:
/export - Export current active discussion
/export project - Export entire project with all discussions
"""
project_id = get_selected_project(context)
if not project_id:
await update.message.reply_text("No project selected. Use /project select first.")
return
project = await get_project(project_id)
if not project:
await update.message.reply_text("Selected project not found. Use /project select.")
return
args = context.args
export_full_project = args and args[0].lower() == "project"
if export_full_project:
# Export entire project
discussions_list = await list_discussions(project_id)
if not discussions_list:
await update.message.reply_text("No discussions in this project to export.")
return
# Fetch each discussion with full eager loading
loaded_discussions = []
for disc in discussions_list:
full_disc = await get_discussion(disc.id)
if full_disc:
loaded_discussions.append(full_disc)
markdown = export_project(project, loaded_discussions)
filename = f"{project.name.replace(' ', '_')}-export.md"
await update.message.reply_document(
document=io.BytesIO(markdown.encode("utf-8")),
filename=filename,
caption=f"📄 Full project export: {project.name}",
)
else:
# Export active discussion
discussion = await get_active_discussion(project_id)
if not discussion:
await update.message.reply_text(
"No active discussion. Start one with /open or /discuss."
)
return
# Re-fetch with full loading including consensus
full_discussion = await get_discussion(discussion.id)
if not full_discussion:
await update.message.reply_text("Failed to load discussion.")
return
markdown = export_discussion(full_discussion)
filename = f"{project.name.replace(' ', '_')}-discussion.md"
await update.message.reply_document(
document=io.BytesIO(markdown.encode("utf-8")),
filename=filename,
caption=f"📄 Discussion export: {full_discussion.question[:50]}...",
)