feat: v3.0 엔진 추상화 + 소설 파이프라인 추가
[1순위] 엔진 추상화 리팩토링 - config/engine.json: 단일 설정 파일로 writing/tts/image/video/publishing 엔진 제어 - bots/engine_loader.py: EngineLoader 팩토리 클래스 (Claude/OpenClaw/Gemini Writer, gTTS/GoogleCloud/OpenAI/ElevenLabs TTS, DALL-E/External 이미지) [2순위] VideoEngine 추상화 - bots/converters/video_engine.py: VideoEngine ABC + FFmpegSlidesEngine/SeedanceEngine/SoraEngine/RunwayEngine/VeoEngine 구현 - Seedance 2.0 API 연동 + 실패 시 ffmpeg_slides 자동 fallback [3순위] 소설 연재 파이프라인 - bots/novel/novel_writer.py: AI 에피소드 자동 생성 (Claude/엔진 추상화) - bots/novel/novel_blog_converter.py: 에피소드 → 장르별 테마 Blogger HTML - bots/novel/novel_shorts_converter.py: key_scenes → TTS + Pillow + VideoEngine → MP4 - bots/novel/novel_manager.py: 전체 파이프라인 조율 + Telegram 명령 처리 - config/novels/shadow-protocol.json: 예시 소설 설정 (2040 서울 SF 스릴러) [스케줄러] 소설 파이프라인 통합 - 매주 월/목 09:00 자동 실행 (job_novel_pipeline) - Telegram 명령: /novel_list, /novel_gen, /novel_status [기타 수정] - collector_bot.py: 한국어 유니코드 감지 + RSS 신뢰도 override 버그 수정 - quality_rules.json: min_score 70→60 - scripts/get_token.py: YouTube OAuth scope 추가 - .env.example: SEEDANCE/ELEVENLABS/GEMINI/RUNWAY API 키 항목 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,9 @@ CLAUDE_SYSTEM_PROMPT = """당신은 The 4th Path 블로그 자동 수익 엔진
|
||||
/report — 주간 리포트
|
||||
/images — 이미지 제작 현황
|
||||
/convert — 수동 변환 실행
|
||||
/novel_list — 연재 소설 목록
|
||||
/novel_gen [novel_id] — 에피소드 즉시 생성
|
||||
/novel_status — 소설 파이프라인 진행 현황
|
||||
|
||||
사용자의 자연어 요청을 이해하고 적절히 안내하거나 답변해주세요.
|
||||
한국어로 간결하게 답변하세요."""
|
||||
@@ -403,6 +406,30 @@ def job_image_prompt_batch():
|
||||
logger.error(f"이미지 배치 오류: {e}")
|
||||
|
||||
|
||||
def job_novel_pipeline():
|
||||
"""소설 파이프라인 — 월/목 09:00 활성 소설 에피소드 자동 생성"""
|
||||
logger.info("[스케줄] 소설 파이프라인 시작")
|
||||
try:
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
from novel.novel_manager import NovelManager
|
||||
manager = NovelManager()
|
||||
results = manager.run_all()
|
||||
if results:
|
||||
for r in results:
|
||||
if r.get('error'):
|
||||
logger.error(f"소설 파이프라인 오류 [{r['novel_id']}]: {r['error']}")
|
||||
else:
|
||||
logger.info(
|
||||
f"소설 에피소드 완료 [{r['novel_id']}] "
|
||||
f"제{r['episode_num']}화 blog={bool(r['blog_path'])} "
|
||||
f"shorts={bool(r['shorts_path'])}"
|
||||
)
|
||||
else:
|
||||
logger.info("[소설] 오늘 발행 예정 소설 없음")
|
||||
except Exception as e:
|
||||
logger.error(f"소설 파이프라인 오류: {e}")
|
||||
|
||||
|
||||
# ─── Telegram 명령 핸들러 ────────────────────────────
|
||||
|
||||
async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
@@ -590,6 +617,27 @@ async def cmd_imgbatch(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("📤 프롬프트 배치 전송 완료.")
|
||||
|
||||
|
||||
async def cmd_novel_list(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""소설 목록 조회"""
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
from novel.novel_manager import handle_novel_command
|
||||
await handle_novel_command(update, context, 'list', [])
|
||||
|
||||
|
||||
async def cmd_novel_gen(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""소설 에피소드 즉시 생성: /novel_gen [novel_id]"""
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
from novel.novel_manager import handle_novel_command
|
||||
await handle_novel_command(update, context, 'gen', context.args or [])
|
||||
|
||||
|
||||
async def cmd_novel_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""소설 파이프라인 진행 현황"""
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
from novel.novel_manager import handle_novel_command
|
||||
await handle_novel_command(update, context, 'status', [])
|
||||
|
||||
|
||||
async def cmd_imgcancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""이미지 대기 상태 취소"""
|
||||
chat_id = update.message.chat_id
|
||||
@@ -780,7 +828,12 @@ def setup_scheduler() -> AsyncIOScheduler:
|
||||
day_of_week='mon', hour=10, minute=0, id='image_batch')
|
||||
logger.info("이미지 request 모드: 매주 월요일 10:00 배치 전송 등록")
|
||||
|
||||
logger.info("스케줄러 설정 완료 (v3 시차 배포 포함)")
|
||||
# 소설 파이프라인: 매주 월/목 09:00
|
||||
scheduler.add_job(job_novel_pipeline, 'cron',
|
||||
day_of_week='mon,thu', hour=9, minute=0, id='novel_pipeline')
|
||||
logger.info("소설 파이프라인: 매주 월/목 09:00 등록")
|
||||
|
||||
logger.info("스케줄러 설정 완료 (v3 시차 배포 + 소설 파이프라인)")
|
||||
return scheduler
|
||||
|
||||
|
||||
@@ -807,6 +860,11 @@ async def main():
|
||||
app.add_handler(CommandHandler('imgbatch', cmd_imgbatch))
|
||||
app.add_handler(CommandHandler('imgcancel', cmd_imgcancel))
|
||||
|
||||
# 소설 파이프라인
|
||||
app.add_handler(CommandHandler('novel_list', cmd_novel_list))
|
||||
app.add_handler(CommandHandler('novel_gen', cmd_novel_gen))
|
||||
app.add_handler(CommandHandler('novel_status', cmd_novel_status))
|
||||
|
||||
# 이미지 파일 수신
|
||||
app.add_handler(MessageHandler(filters.PHOTO, handle_photo))
|
||||
app.add_handler(MessageHandler(filters.Document.IMAGE, handle_document))
|
||||
|
||||
Reference in New Issue
Block a user