diff --git a/bots/scheduler.py b/bots/scheduler.py index 24c6154..8f0cba7 100644 --- a/bots/scheduler.py +++ b/bots/scheduler.py @@ -21,8 +21,8 @@ ensure_project_runtime( from apscheduler.schedulers.asyncio import AsyncIOScheduler from dotenv import load_dotenv -from telegram import Update -from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes import anthropic import re @@ -683,7 +683,31 @@ async def cmd_write(update: Update, context: ContextTypes.DEFAULT_TYPE): await loop.run_in_executor(None, _call_openclaw, topic_data, draft_path, direction) # 자동으로 pending_review로 이동 await loop.run_in_executor(None, _publish_next) - await update.message.reply_text("✅ 완료! /pending 으로 검토하세요.") + # pending_review에서 방금 작성된 글 찾기 + pending_dir = DATA_DIR / 'pending_review' + pending_file = pending_dir / topic_file.name + if pending_file.exists(): + article = json.loads(pending_file.read_text(encoding='utf-8')) + title = article.get('title', '')[:50] + corner = article.get('corner', '') + body_preview = article.get('body', '')[:200].replace('<', '<').replace('>', '>') + # 인라인 버튼으로 승인/거부 + keyboard = InlineKeyboardMarkup([ + [ + InlineKeyboardButton("✅ 승인 발행", callback_data=f"approve:{topic_file.name}"), + InlineKeyboardButton("🗑 거부", callback_data=f"reject:{topic_file.name}"), + ] + ]) + await update.message.reply_text( + f"📝 [수동 검토 필요]\n\n" + f"{title}\n" + f"코너: {corner}\n\n" + f"미리보기:\n{body_preview}...\n", + parse_mode='HTML', + reply_markup=keyboard, + ) + else: + await update.message.reply_text("✅ 완료! /pending 으로 검토하세요.") except Exception as e: await update.message.reply_text(f"❌ 글 작성 오류: {e}") @@ -721,12 +745,28 @@ async def cmd_pending(update: Update, context: ContextTypes.DEFAULT_TYPE): if not pending: await update.message.reply_text("수동 검토 대기 글이 없습니다.") return - lines = [f"🔍 수동 검토 대기 ({len(pending)}개):"] - for i, item in enumerate(pending[:5], 1): - lines.append(f" {i}. [{item.get('corner','')}] {item.get('title','')[:50]}") - lines.append(f" 사유: {item.get('pending_reason','')}") - lines.append("\n/approve [번호] /reject [번호]") - await update.message.reply_text('\n'.join(lines)) + for i, item in enumerate(pending[:10], 1): + filepath = item.get('_filepath', '') + filename = Path(filepath).name if filepath else '' + title = item.get('title', '')[:50] + corner = item.get('corner', '') + reason = item.get('pending_reason', '') + body_preview = item.get('body', '')[:150].replace('<', '<').replace('>', '>') + keyboard = InlineKeyboardMarkup([ + [ + InlineKeyboardButton("✅ 승인 발행", callback_data=f"approve:{filename}"), + InlineKeyboardButton("🗑 거부", callback_data=f"reject:{filename}"), + ] + ]) + await update.message.reply_text( + f"🔍 [{i}/{len(pending)}] 수동 검토 대기\n\n" + f"{title}\n" + f"코너: {corner}\n" + f"사유: {reason}\n\n" + f"{body_preview}...", + parse_mode='HTML', + reply_markup=keyboard, + ) async def cmd_approve(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -763,6 +803,33 @@ async def cmd_reject(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text(f"🗑 거부 완료: {pending[idx].get('title','')}") +async def callback_approve_reject(update: Update, context: ContextTypes.DEFAULT_TYPE): + """인라인 버튼 콜백: 승인/거부""" + query = update.callback_query + await query.answer() + data = query.data # "approve:filename.json" or "reject:filename.json" + action, filename = data.split(':', 1) + pending_dir = DATA_DIR / 'pending_review' + filepath = pending_dir / filename + + if not filepath.exists(): + await query.edit_message_text("⚠️ 해당 글을 찾을 수 없습니다. (이미 처리됨)") + return + + sys.path.insert(0, str(BASE_DIR / 'bots')) + import publisher_bot + + if action == 'approve': + success = publisher_bot.approve_pending(str(filepath)) + if success: + await query.edit_message_text(f"✅ 발행 완료!\n\n{query.message.text_html or query.message.text}", parse_mode='HTML') + else: + await query.edit_message_text("❌ 발행 실패. 로그를 확인하세요.") + elif action == 'reject': + publisher_bot.reject_pending(str(filepath)) + await query.edit_message_text(f"🗑 거부 완료\n\n{query.message.text_html or query.message.text}", parse_mode='HTML') + + async def cmd_report(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("주간 리포트 생성 중...") sys.path.insert(0, str(BASE_DIR / 'bots')) @@ -1249,6 +1316,7 @@ async def main(): app.add_handler(CommandHandler('approve', cmd_approve)) app.add_handler(CommandHandler('reject', cmd_reject)) app.add_handler(CommandHandler('pending', cmd_pending)) + app.add_handler(CallbackQueryHandler(callback_approve_reject, pattern=r'^(approve|reject):')) app.add_handler(CommandHandler('report', cmd_report)) app.add_handler(CommandHandler('topics', cmd_show_topics)) app.add_handler(CommandHandler('convert', cmd_convert))