feat: Reddit 수집, 쇼츠 텔레그램 미리보기, 코너 9개 체계 정비
- Reddit 트렌딩 수집기 추가 (/reddit collect, /pick 명령어) - 쇼츠 영상 텔레그램 미리보기 후 승인 기반 YouTube 업로드 - 코너 9개로 통합 (앱추천→제품리뷰, 재테크절약→재테크, TV로보는세상/건강정보 추가) - RSS 피드 73개로 확대 (9개 코너 전체 커버) - 블로그 중복 검토 알림 수정, 글 잘림 방지 (max_tokens 8192) - 제품리뷰 다중 이미지 지원, 저품질 이미지 필터링 강화 - HookOptimizer LLM 연동, 인스타/X/틱톡 스케줄러 비활성화 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,9 +53,9 @@ CORNER_CAPTION_MAP = {
|
||||
'스타트업': 'hormozi',
|
||||
'제품리뷰': 'hormozi',
|
||||
'생활꿀팁': 'tiktok_viral',
|
||||
'앱추천': 'brand_4thpath',
|
||||
'재테크절약': 'hormozi',
|
||||
'재테크': 'hormozi',
|
||||
'TV로보는세상': 'tiktok_viral',
|
||||
'건강정보': 'brand_4thpath',
|
||||
'팩트체크': 'brand_4thpath',
|
||||
# 레거시 코너 (하위 호환)
|
||||
'쉬운세상': 'hormozi',
|
||||
|
||||
@@ -171,7 +171,7 @@ def _extract_via_claude_api(post_text: str) -> Optional[dict]:
|
||||
|
||||
msg = client.messages.create(
|
||||
model='claude-haiku-4-5-20251001',
|
||||
max_tokens=512,
|
||||
max_tokens=1024,
|
||||
messages=[{'role': 'user', 'content': prompt}],
|
||||
)
|
||||
raw = msg.content[0].text
|
||||
@@ -198,14 +198,20 @@ def _extract_rule_based(article: dict) -> dict:
|
||||
if not hook.endswith('?'):
|
||||
hook = f'{title[:20]}... 알고 계셨나요?'
|
||||
|
||||
# body: KEY_POINTS 앞 3개
|
||||
body = [p.strip('- ').strip() for p in key_points[:3]] if key_points else [title]
|
||||
# body: KEY_POINTS 앞 7개 (35-45초 분량)
|
||||
body = [p.strip('- ').strip() for p in key_points[:7]] if key_points else [title]
|
||||
|
||||
# closer: 코너별 CTA
|
||||
cta_map = {
|
||||
'쉬운세상': '블로그에서 더 자세히 확인해보세요.',
|
||||
'숨은보물': '이 꿀팁, 주변에 공유해보세요.',
|
||||
'웹소설': '전편 블로그에서 읽어보세요.',
|
||||
'AI인사이트': '더 깊은 AI 이야기, 블로그에서 확인하세요.',
|
||||
'여행맛집': '숨은 맛집 더 보기, 블로그 링크 클릭!',
|
||||
'스타트업': '스타트업 트렌드, 블로그에서 자세히 보세요.',
|
||||
'제품리뷰': '실사용 후기 전문은 블로그에서 확인하세요.',
|
||||
'생활꿀팁': '이 꿀팁, 주변에 공유해보세요.',
|
||||
'재테크': '재테크 꿀팁 더 보기, 블로그에서 확인!',
|
||||
'TV로보는세상': '화제의 장면, 블로그에서 더 보세요.',
|
||||
'건강정보': '건강 정보 더 보기, 블로그에서 확인하세요.',
|
||||
'팩트체크': '팩트체크 전문은 블로그에서 확인하세요.',
|
||||
}
|
||||
closer = cta_map.get(corner, '구독하고 다음 편도 기대해주세요.')
|
||||
|
||||
|
||||
@@ -55,7 +55,10 @@ def _search_pexels(keyword: str, api_key: str, prefer_vertical: bool = True) ->
|
||||
})
|
||||
req = urllib.request.Request(
|
||||
f'{PEXELS_VIDEO_URL}?{params}',
|
||||
headers={'Authorization': api_key},
|
||||
headers={
|
||||
'Authorization': api_key,
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; BlogWriter/1.0)',
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
@@ -93,7 +96,10 @@ def _search_pixabay(keyword: str, api_key: str, prefer_vertical: bool = True) ->
|
||||
'video_type': 'film',
|
||||
'per_page': 10,
|
||||
})
|
||||
req = urllib.request.Request(f'{PIXABAY_VIDEO_URL}?{params}')
|
||||
req = urllib.request.Request(
|
||||
f'{PIXABAY_VIDEO_URL}?{params}',
|
||||
headers={'User-Agent': 'Mozilla/5.0 (compatible; BlogWriter/1.0)'},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
data = json.loads(resp.read())
|
||||
|
||||
@@ -396,7 +396,15 @@ def _tts_edge(text: str, output_path: Path, cfg: dict) -> list[dict]:
|
||||
communicate = edge_tts.Communicate(text, voice, rate=rate)
|
||||
await communicate.save(str(mp3_tmp))
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(_generate())
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
# 이미 루프 안에 있으면 새 스레드에서 실행
|
||||
import concurrent.futures
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
pool.submit(lambda: asyncio.run(_generate())).result()
|
||||
except RuntimeError:
|
||||
# 루프 없음 — 직접 실행
|
||||
asyncio.run(_generate())
|
||||
|
||||
# mp3 → wav
|
||||
_mp3_to_wav(mp3_tmp, output_path)
|
||||
|
||||
Reference in New Issue
Block a user