feat: 텔레그램 인라인 버튼으로 승인/거부 (터치 한 번으로 발행)
- /write 완료 시 미리보기 + [승인 발행] [거부] 인라인 버튼 표시 - /pending 목록도 각 글마다 인라인 버튼 포함 - 버튼 클릭 → 즉시 발행/거부 처리, 메시지 업데이트 - 기존 /approve, /reject 명령어도 유지 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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"<b>{title}</b>\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"<b>{title}</b>\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))
|
||||
|
||||
Reference in New Issue
Block a user