feat: Ghost CMS integration — auto-publish blog + /publish command
Build & Deploy ResearchOwl / build-and-push (push) Successful in 6s
Build & Deploy ResearchOwl / build-and-push (push) Successful in 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,7 @@ async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||
"`/sources` — List all sources found\n"
|
||||
"`/outputs` — List generated outputs\n"
|
||||
"`/export` — Exportar último output como PDF\n"
|
||||
"`/publish` — Publicar último output en Ghost como borrador\n"
|
||||
"`/compare <tema1> vs <tema2>` — Análisis comparativo\n"
|
||||
"`/costs` — Show API usage costs\n"
|
||||
"`/watch <topic> [h]` — Schedule periodic research\n"
|
||||
@@ -931,6 +932,77 @@ async def cmd_purge(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||
await db_conn.close()
|
||||
|
||||
|
||||
async def cmd_publish(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||
if not is_authorized(update.effective_user.id):
|
||||
return
|
||||
|
||||
chat_id = update.effective_chat.id
|
||||
db_conn = await get_db()
|
||||
db = ResearchDB(db_conn)
|
||||
|
||||
try:
|
||||
from src.generator.generator import GhostPublisher, _extract_title
|
||||
|
||||
ghost = GhostPublisher()
|
||||
if not ghost.is_configured():
|
||||
await update.message.reply_text(
|
||||
"❌ Ghost no configurado. Asegúrate de que `GHOST_URL` y `GHOST_API_KEY` están definidos.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
cursor = await db_conn.execute(
|
||||
"SELECT * FROM research_sessions WHERE telegram_chat_id = ? ORDER BY created_at DESC LIMIT 1",
|
||||
(chat_id,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
await update.message.reply_text("No hay sesiones. Usa /research primero.")
|
||||
return
|
||||
|
||||
session = dict(row)
|
||||
outputs = await db.get_outputs(session["id"])
|
||||
if not outputs:
|
||||
await update.message.reply_text(
|
||||
"No hay outputs generados. Usa `/generate blog|report` primero.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
priority = ["blog_extended", "blog", "report_extended", "report",
|
||||
"podcast_extended", "podcast", "thread"]
|
||||
chosen = None
|
||||
for ptype in priority:
|
||||
for o in outputs:
|
||||
if o["output_type"] == ptype:
|
||||
chosen = o
|
||||
break
|
||||
if chosen:
|
||||
break
|
||||
if not chosen:
|
||||
chosen = outputs[-1]
|
||||
|
||||
msg = await update.message.reply_text("📤 Publicando en Ghost como borrador…")
|
||||
|
||||
title = _extract_title(chosen["content"]) or session["topic"]
|
||||
result = await ghost.publish_draft(title, chosen["content"])
|
||||
post = result["posts"][0]
|
||||
admin_url = f"{ghost.url}/ghost/#/editor/post/{post['id']}"
|
||||
|
||||
await msg.edit_text(
|
||||
f"✅ *Publicado en Ghost como borrador*\n\n"
|
||||
f"📝 Título: `{title}`\n"
|
||||
f"🔗 Editar: {admin_url}",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Publish to Ghost failed", error=str(e))
|
||||
await update.message.reply_text(f"❌ Error publicando en Ghost: {str(e)[:200]}")
|
||||
finally:
|
||||
await db_conn.close()
|
||||
|
||||
|
||||
async def cmd_compare(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||
if not is_authorized(update.effective_user.id):
|
||||
return
|
||||
@@ -1097,6 +1169,7 @@ def create_bot() -> Application:
|
||||
app.add_handler(CommandHandler("process", cmd_process))
|
||||
app.add_handler(CommandHandler("cancel", cmd_cancel))
|
||||
app.add_handler(CommandHandler("purge", cmd_purge))
|
||||
app.add_handler(CommandHandler("publish", cmd_publish))
|
||||
app.add_handler(CommandHandler("compare", cmd_compare))
|
||||
|
||||
return app
|
||||
|
||||
Reference in New Issue
Block a user