fix(anime): 파이프라인 5건 수정 — 에피소드 정규식(v2/S01E), 릴리스 그룹 필터, 자막 보호, 배치 다운로드, 타임아웃
This commit is contained in:
229
handlers/anime_handler.py
Normal file
229
handlers/anime_handler.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""애니메이션 핸들러 — Discord Bot에서 분리된 애니 관련 처리.
|
||||
|
||||
AnimePipeline을 직접 호출하여 결과를 Discord Embed로 렌더링합니다.
|
||||
NLU에서 mode="anime"로 분류된 요청도 처리합니다.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
|
||||
from handlers.renderer import safe_send_embed
|
||||
|
||||
logger = logging.getLogger("variet.handlers.anime")
|
||||
|
||||
|
||||
async def handle_anime_message(
|
||||
message: discord.Message,
|
||||
parsed: dict,
|
||||
):
|
||||
"""NLU에서 anime으로 분류된 메시지 처리.
|
||||
|
||||
Args:
|
||||
message: Discord 메시지
|
||||
parsed: NLU 분류 결과 dict {mode, action, title, episode?, ...}
|
||||
"""
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
|
||||
pipeline = AnimePipeline()
|
||||
action = parsed.get("action", "search")
|
||||
title = parsed.get("title", parsed.get("query", ""))
|
||||
|
||||
async with message.channel.typing():
|
||||
try:
|
||||
if action in ("list", "scan"):
|
||||
# NAS 현황 조회
|
||||
from tools.nas_scanner import NasScanner
|
||||
scanner = NasScanner()
|
||||
if not scanner.is_accessible():
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title="❌ NAS 접근 불가",
|
||||
description=f"경로: `{scanner.base_path}`",
|
||||
color=0xE74C3C,
|
||||
))
|
||||
return
|
||||
|
||||
# title이 있으면 키워드 검색, 없으면 이번 분기
|
||||
if title:
|
||||
folders = scanner.search(title)
|
||||
label = f"'{title}' 검색 결과"
|
||||
# 키워드 검색 0건이면 이번 분기로 fallback
|
||||
if not folders:
|
||||
folders = scanner.get_current_quarter_anime()
|
||||
label = "이번 분기 애니"
|
||||
else:
|
||||
folders = scanner.get_current_quarter_anime()
|
||||
label = "이번 분기 애니"
|
||||
|
||||
if not folders:
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title=f"📁 {label}",
|
||||
description="해당하는 폴더가 없습니다.",
|
||||
color=0xF39C12,
|
||||
))
|
||||
return
|
||||
|
||||
desc_lines = []
|
||||
for f in folders[:15]:
|
||||
sub_info = f"자막 {f.subtitle_count}" if f.subtitle_count else "자막 없음"
|
||||
desc_lines.append(
|
||||
f"• `{f.folder_name}`\n 영상 {f.video_count}개 | {sub_info} | {f.total_size_gb:.1f}GB"
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=f"📁 {label} ({len(folders)}개)",
|
||||
description="\n".join(desc_lines)[:2000],
|
||||
color=0x3498DB,
|
||||
)
|
||||
await safe_send_embed(message.channel, embed)
|
||||
return
|
||||
|
||||
elif action == "search" and title:
|
||||
result = await pipeline.search(title)
|
||||
elif action == "search" and not title:
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title="🔍 애니 검색",
|
||||
description="검색할 제목을 입력해주세요.\n예: `프리렌 검색해줘`",
|
||||
color=0xF39C12,
|
||||
))
|
||||
return
|
||||
elif action == "download" and title:
|
||||
mode = parsed.get("download_mode", "auto")
|
||||
episode = parsed.get("episode")
|
||||
result = await pipeline.download(title, mode=mode, episode=episode)
|
||||
elif action == "status":
|
||||
status = await pipeline.get_status()
|
||||
if not status:
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title="🎬 다운로드 현황",
|
||||
description="다운로드 중인 항목 없음",
|
||||
color=0x3498DB,
|
||||
))
|
||||
return
|
||||
desc = "\n".join(
|
||||
f"• {s['progress']} `{s['name'][:40]}` {s['speed']}"
|
||||
for s in status
|
||||
)
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title=f"🎬 다운로드 현황 ({len(status)}건)",
|
||||
description=desc[:2000],
|
||||
color=0x3498DB,
|
||||
))
|
||||
return
|
||||
else:
|
||||
# 알 수 없는 action → search로 fallback
|
||||
if title:
|
||||
result = await pipeline.search(title)
|
||||
else:
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title="❓ 애니 명령",
|
||||
description="무엇을 도와드릴까요?\n• 목록 조회: `NAS에 뭐있어?`\n• 검색: `프리렌 검색`\n• 다운로드: `프리렌 10화 받아줘`\n• 상태: `다운로드 현황`",
|
||||
color=0xF39C12,
|
||||
))
|
||||
return
|
||||
|
||||
# 결과 임베드
|
||||
embed = discord.Embed(
|
||||
title=f"🎬 {result.message[:100]}" if result.message else "🎬 결과",
|
||||
description=result.message[:2000] if result.message else "완료",
|
||||
color=0x2ECC71 if result.success else 0xE74C3C,
|
||||
)
|
||||
if result.errors:
|
||||
embed.add_field(
|
||||
name="⚠️ 오류",
|
||||
value="\n".join(f"• {e}" for e in result.errors[:5])[:1000],
|
||||
inline=False,
|
||||
)
|
||||
await safe_send_embed(message.channel, embed)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"애니 핸들러 오류: {e}", exc_info=True)
|
||||
await message.channel.send(embed=discord.Embed(
|
||||
title="❌ 애니 처리 오류",
|
||||
description=f"```{str(e)[:300]}```",
|
||||
color=0xE74C3C,
|
||||
))
|
||||
|
||||
|
||||
def register_anime_commands(bot, ws_manager):
|
||||
"""애니메이션 슬래시 커맨드 등록."""
|
||||
anime_group = app_commands.Group(name="anime", description="애니메이션 자막/영상 자동화")
|
||||
|
||||
@anime_group.command(name="search", description="애니 검색 (편성표 + 자막 + 토렌트)")
|
||||
@app_commands.describe(title="검색할 애니 제목 (한글)")
|
||||
async def anime_search(interaction: discord.Interaction, title: str):
|
||||
await interaction.response.defer()
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
pipeline = AnimePipeline()
|
||||
result = await pipeline.search(title)
|
||||
embed = discord.Embed(
|
||||
title=f"🔍 {result.anime.subject}" if result.anime else f"🔍 {title}",
|
||||
description=result.message[:2000],
|
||||
color=0x2ECC71 if result.success else 0xE74C3C,
|
||||
)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
@anime_group.command(name="download", description="자막+영상 자동 다운로드")
|
||||
@app_commands.describe(title="애니 제목 (한글)", episode="특정 화수 (없으면 최신)")
|
||||
async def anime_download(interaction: discord.Interaction, title: str, episode: int = None):
|
||||
await interaction.response.defer()
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
pipeline = AnimePipeline()
|
||||
result = await pipeline.download(title, mode="auto", episode=episode)
|
||||
embed = discord.Embed(
|
||||
title=f"📥 {title}",
|
||||
description=result.message[:2000],
|
||||
color=0x2ECC71 if result.success else 0xE74C3C,
|
||||
)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
@anime_group.command(name="sub", description="자막만 다운로드")
|
||||
@app_commands.describe(title="애니 제목 (한글)", episode="특정 화수")
|
||||
async def anime_sub(interaction: discord.Interaction, title: str, episode: int = None):
|
||||
await interaction.response.defer()
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
pipeline = AnimePipeline()
|
||||
result = await pipeline.download(title, mode="sub_only", episode=episode)
|
||||
embed = discord.Embed(
|
||||
title=f"📝 자막: {title}",
|
||||
description=result.message[:2000],
|
||||
color=0x2ECC71 if result.success else 0xE74C3C,
|
||||
)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
@anime_group.command(name="video", description="영상만 다운로드 (자막 없어도 강제)")
|
||||
@app_commands.describe(title="애니 제목 (한글)", episode="특정 화수")
|
||||
async def anime_video(interaction: discord.Interaction, title: str, episode: int = None):
|
||||
await interaction.response.defer()
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
pipeline = AnimePipeline()
|
||||
result = await pipeline.download(title, mode="video_only", episode=episode)
|
||||
embed = discord.Embed(
|
||||
title=f"🎬 영상: {title}",
|
||||
description=result.message[:2000],
|
||||
color=0x2ECC71 if result.success else 0xE74C3C,
|
||||
)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
@anime_group.command(name="status", description="현재 다운로드 큐 상태")
|
||||
async def anime_status(interaction: discord.Interaction):
|
||||
await interaction.response.defer()
|
||||
from tools.anime_pipeline import AnimePipeline
|
||||
pipeline = AnimePipeline()
|
||||
status = await pipeline.get_status()
|
||||
if not status:
|
||||
desc = "다운로드 중인 항목 없음"
|
||||
else:
|
||||
desc = "\n".join(
|
||||
f"• {s['progress']} `{s['name'][:40]}` {s['speed']}"
|
||||
for s in status
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=f"🎬 다운로드 현황 ({len(status)}건)",
|
||||
description=desc[:2000],
|
||||
color=0x3498DB,
|
||||
)
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
bot.tree.add_command(anime_group)
|
||||
logger.info("애니메이션 슬래시 커맨드 등록 완료")
|
||||
Reference in New Issue
Block a user