Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c963a014 | |||
| af57c3500c | |||
| 0783775cdd | |||
| 2c80ed1a52 | |||
| 9cf1f44a8b | |||
| 9f68133217 |
+21
-6
@@ -95,7 +95,7 @@ def calc_freshness_score(published_at: datetime | None, max_score: int = 20) ->
|
||||
return int(max_score * ratio)
|
||||
|
||||
|
||||
def calc_korean_relevance(text: str, rules: dict) -> int:
|
||||
def calc_korean_relevance(text: str, rules: dict, rss_category: str = '') -> int:
|
||||
"""한국 독자 관련성 점수"""
|
||||
max_score = rules['scoring']['korean_relevance']['max']
|
||||
keywords = rules['scoring']['korean_relevance']['keywords']
|
||||
@@ -107,11 +107,14 @@ def calc_korean_relevance(text: str, rules: dict) -> int:
|
||||
base = 15 # 한국어 텍스트면 기본 15점
|
||||
elif korean_ratio >= 0.05:
|
||||
base = 8
|
||||
elif rss_category:
|
||||
# RSS 카테고리가 지정된 영문 소스는 큐레이션된 것이므로 기본점수 부여
|
||||
base = 10
|
||||
else:
|
||||
base = 0
|
||||
|
||||
# 브랜드/지역 키워드 보너스
|
||||
matched = sum(1 for kw in keywords if kw in text)
|
||||
matched = sum(1 for kw in keywords if kw.lower() in text.lower())
|
||||
bonus = min(matched * 5, max_score - base)
|
||||
|
||||
return min(base + bonus, max_score)
|
||||
@@ -199,7 +202,11 @@ def apply_discard_rules(item: dict, rules: dict, published_titles: list[str]) ->
|
||||
|
||||
|
||||
def assign_corner(item: dict, topic_type: str) -> str:
|
||||
"""글감에 코너 배정"""
|
||||
"""글감에 코너 배정 — RSS 카테고리가 있으면 우선 사용"""
|
||||
rss_cat = item.get('_rss_category', '')
|
||||
if rss_cat:
|
||||
return rss_cat
|
||||
|
||||
title = item.get('topic', '').lower()
|
||||
source = item.get('source', 'rss').lower()
|
||||
|
||||
@@ -227,7 +234,7 @@ def calculate_quality_score(item: dict, rules: dict) -> int:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
kr_score = calc_korean_relevance(text, rules)
|
||||
kr_score = calc_korean_relevance(text, rules, rss_category=item.get('_rss_category', ''))
|
||||
fresh_score = calc_freshness_score(pub_at)
|
||||
# search_demand: pytrends 연동 후 실제값 사용 (RSS 기본값 12)
|
||||
search_score = item.get('search_demand_score', 12)
|
||||
@@ -379,9 +386,15 @@ def collect_rss_feeds(sources_cfg: dict) -> list[dict]:
|
||||
pub_at = None
|
||||
if hasattr(entry, 'published_parsed') and entry.published_parsed:
|
||||
pub_at = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc).isoformat()
|
||||
title_text = entry.get('title', '')
|
||||
desc_text = entry.get('summary', '') or entry.get('description', '')
|
||||
# 한국어 문자가 거의 없으면 영문 소스로 판단
|
||||
combined = title_text + desc_text
|
||||
kr_chars = sum(1 for c in combined if '\uac00' <= c <= '\ud7a3')
|
||||
is_english = kr_chars / max(len(combined), 1) < 0.05
|
||||
items.append({
|
||||
'topic': entry.get('title', ''),
|
||||
'description': entry.get('summary', '') or entry.get('description', ''),
|
||||
'topic': title_text,
|
||||
'description': desc_text,
|
||||
'source': 'rss',
|
||||
'source_name': feed_cfg.get('name', ''),
|
||||
'source_url': entry.get('link', ''),
|
||||
@@ -389,6 +402,8 @@ def collect_rss_feeds(sources_cfg: dict) -> list[dict]:
|
||||
'search_demand_score': 8,
|
||||
'topic_type': 'trending',
|
||||
'_trust_override': trust,
|
||||
'_rss_category': feed_cfg.get('category', ''),
|
||||
'is_english': is_english,
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning(f"RSS 수집 실패 ({url}): {e}")
|
||||
|
||||
+116
-11
@@ -151,16 +151,25 @@ def _safe_slug(text: str) -> str:
|
||||
|
||||
def _build_openclaw_prompt(topic_data: dict) -> tuple[str, str]:
|
||||
topic = topic_data.get('topic', '').strip()
|
||||
corner = topic_data.get('corner', '쉬운세상').strip() or '쉬운세상'
|
||||
corner = topic_data.get('corner', 'AI인사이트').strip() or 'AI인사이트'
|
||||
description = topic_data.get('description', '').strip()
|
||||
source = topic_data.get('source_url') or topic_data.get('source') or ''
|
||||
published_at = topic_data.get('published_at', '')
|
||||
is_english = topic_data.get('is_english', False)
|
||||
system = (
|
||||
"당신은 The 4th Path 블로그 엔진의 전문 에디터다. "
|
||||
"당신은 'AI? 그게 뭔데?' 블로그의 편집자 eli다. "
|
||||
"비전문가도 쉽게 읽을 수 있는 친근한 톤으로 글을 쓴다. "
|
||||
"반드시 아래 섹션 헤더 형식만 사용해 완성된 Blogger-ready HTML 원고를 출력하라. "
|
||||
"본문(BODY)은 HTML로 작성하고, KEY_POINTS는 3줄 이내로 작성한다."
|
||||
)
|
||||
prompt = f"""다음 글감을 바탕으로 한국어 블로그 원고를 작성해줘.
|
||||
if is_english:
|
||||
system += (
|
||||
" 영문 원문을 단순 번역하지 말고, 한국 독자 관점에서 재해석하여 작성하라. "
|
||||
"한국 시장/사용자에게 어떤 의미인지, 국내 대안이나 비교 서비스가 있다면 함께 언급하라. "
|
||||
"제목도 한국어로 매력적으로 새로 작성하라."
|
||||
)
|
||||
lang_note = "\n⚠️ 영문 원문입니다. 단순 번역이 아닌, 한국 독자 맥락으로 재작성해주세요." if is_english else ""
|
||||
prompt = f"""다음 글감을 바탕으로 한국어 블로그 원고를 작성해줘.{lang_note}
|
||||
|
||||
주제: {topic}
|
||||
코너: {corner}
|
||||
@@ -205,8 +214,8 @@ def _build_openclaw_prompt(topic_data: dict) -> tuple[str, str]:
|
||||
return system, prompt
|
||||
|
||||
|
||||
def _call_openclaw(topic_data: dict, output_path: Path):
|
||||
logger.info(f"OpenClaw 작성 요청: {topic_data.get('topic', '')}")
|
||||
def _call_openclaw(topic_data: dict, output_path: Path, direction: str = ''):
|
||||
logger.info(f"글 작성 요청: {topic_data.get('topic', '')}")
|
||||
sys.path.insert(0, str(BASE_DIR))
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
|
||||
@@ -214,18 +223,20 @@ def _call_openclaw(topic_data: dict, output_path: Path):
|
||||
from article_parser import parse_output
|
||||
|
||||
system, prompt = _build_openclaw_prompt(topic_data)
|
||||
if direction:
|
||||
prompt += f"\n\n운영자 지시사항: {direction}"
|
||||
writer = EngineLoader().get_writer()
|
||||
raw_output = writer.write(prompt, system=system).strip()
|
||||
if not raw_output:
|
||||
raise RuntimeError('OpenClaw writer 응답이 비어 있습니다.')
|
||||
raise RuntimeError('Writer 응답이 비어 있습니다.')
|
||||
|
||||
article = parse_output(raw_output)
|
||||
if not article:
|
||||
raise RuntimeError('OpenClaw writer 출력 파싱 실패')
|
||||
raise RuntimeError('Writer 출력 파싱 실패')
|
||||
|
||||
article.setdefault('title', topic_data.get('topic', '').strip())
|
||||
article['slug'] = article.get('slug') or _safe_slug(article['title'])
|
||||
article['corner'] = article.get('corner') or topic_data.get('corner', '쉬운세상')
|
||||
article['corner'] = article.get('corner') or topic_data.get('corner', 'AI인사이트')
|
||||
article['topic'] = topic_data.get('topic', '')
|
||||
article['description'] = topic_data.get('description', '')
|
||||
article['quality_score'] = topic_data.get('quality_score', 0)
|
||||
@@ -239,7 +250,28 @@ def _call_openclaw(topic_data: dict, output_path: Path):
|
||||
json.dumps(article, ensure_ascii=False, indent=2),
|
||||
encoding='utf-8',
|
||||
)
|
||||
logger.info(f"OpenClaw 원고 저장 완료: {output_path.name}")
|
||||
logger.info(f"원고 저장 완료: {output_path.name}")
|
||||
|
||||
|
||||
def _publish_next():
|
||||
"""originals/ → pending_review/ 이동 (안전장치 체크)"""
|
||||
sys.path.insert(0, str(BASE_DIR / 'bots'))
|
||||
import publisher_bot
|
||||
originals_dir = DATA_DIR / 'originals'
|
||||
pending_dir = DATA_DIR / 'pending_review'
|
||||
pending_dir.mkdir(exist_ok=True)
|
||||
safety_cfg = publisher_bot.load_config('safety_keywords.json')
|
||||
for f in sorted(originals_dir.glob('*.json')):
|
||||
try:
|
||||
article = json.loads(f.read_text(encoding='utf-8'))
|
||||
needs_review, reason = publisher_bot.check_safety(article, safety_cfg)
|
||||
article['pending_reason'] = reason or '수동 승인 필요'
|
||||
dest = pending_dir / f.name
|
||||
dest.write_text(json.dumps(article, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
f.unlink()
|
||||
logger.info(f"검토 대기로 이동: {f.name} ({reason})")
|
||||
except Exception as e:
|
||||
logger.error(f"publish_next 오류 ({f.name}): {e}")
|
||||
|
||||
|
||||
def job_convert():
|
||||
@@ -577,6 +609,76 @@ async def cmd_resume_publish(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
await update.message.reply_text("🟢 발행이 재개되었습니다.")
|
||||
|
||||
|
||||
async def cmd_collect(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("🔄 글감 수집을 시작합니다...")
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
await loop.run_in_executor(None, job_collector)
|
||||
topics_dir = DATA_DIR / 'topics'
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
files = sorted(topics_dir.glob(f'{today}_*.json'))
|
||||
if not files:
|
||||
await update.message.reply_text("✅ 수집 완료! 오늘 수집된 글감이 없습니다.")
|
||||
return
|
||||
lines = [f"✅ 수집 완료! 오늘 글감 {len(files)}개:"]
|
||||
for i, f in enumerate(files[:15], 1):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding='utf-8'))
|
||||
lines.append(f" {i}. [{data.get('corner','')}] {data.get('topic','')[:50]}")
|
||||
except Exception:
|
||||
pass
|
||||
lines.append("\n✍️ /write [번호] 로 글 작성")
|
||||
await update.message.reply_text('\n'.join(lines))
|
||||
except Exception as e:
|
||||
await update.message.reply_text(f"❌ 수집 오류: {e}")
|
||||
|
||||
|
||||
async def cmd_write(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
topics_dir = DATA_DIR / 'topics'
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
files = sorted(topics_dir.glob(f'{today}_*.json'))
|
||||
if not files:
|
||||
await update.message.reply_text("오늘 수집된 글감이 없습니다. /collect 먼저 실행하세요.")
|
||||
return
|
||||
args = context.args
|
||||
if not args:
|
||||
lines = ["📋 글감 목록 (번호를 선택하세요):"]
|
||||
for i, f in enumerate(files[:10], 1):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding='utf-8'))
|
||||
lines.append(f" {i}. [{data.get('corner','')}] {data.get('topic','')[:50]}")
|
||||
except Exception:
|
||||
pass
|
||||
lines.append("\n사용법: /write [번호] [방향(선택)]")
|
||||
await update.message.reply_text('\n'.join(lines))
|
||||
return
|
||||
try:
|
||||
idx = int(args[0]) - 1
|
||||
if idx < 0 or idx >= len(files):
|
||||
await update.message.reply_text(f"❌ 1~{len(files)} 사이 번호를 입력하세요.")
|
||||
return
|
||||
except ValueError:
|
||||
await update.message.reply_text("❌ 숫자를 입력하세요. 예: /write 1")
|
||||
return
|
||||
topic_file = files[idx]
|
||||
topic_data = json.loads(topic_file.read_text(encoding='utf-8'))
|
||||
direction = ' '.join(args[1:]) if len(args) > 1 else ''
|
||||
draft_path = DATA_DIR / 'originals' / topic_file.name
|
||||
(DATA_DIR / 'originals').mkdir(exist_ok=True)
|
||||
await update.message.reply_text(
|
||||
f"✍️ 글 작성 중...\n주제: {topic_data.get('topic','')[:50]}"
|
||||
+ (f"\n방향: {direction}" if direction else "")
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
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 으로 검토하세요.")
|
||||
except Exception as e:
|
||||
await update.message.reply_text(f"❌ 글 작성 오류: {e}")
|
||||
|
||||
|
||||
async def cmd_show_topics(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
topics_dir = DATA_DIR / 'topics'
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
@@ -585,12 +687,13 @@ async def cmd_show_topics(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("오늘 수집된 글감이 없습니다.")
|
||||
return
|
||||
lines = [f"📋 오늘 수집된 글감 ({len(files)}개):"]
|
||||
for f in files[:10]:
|
||||
for i, f in enumerate(files[:15], 1):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding='utf-8'))
|
||||
lines.append(f" [{data.get('quality_score',0)}점][{data.get('corner','')}] {data.get('topic','')[:50]}")
|
||||
lines.append(f" {i}. [{data.get('quality_score',0)}점][{data.get('corner','')}] {data.get('topic','')[:50]}")
|
||||
except Exception:
|
||||
pass
|
||||
lines.append("\n✍️ /write [번호] 로 글 작성")
|
||||
await update.message.reply_text('\n'.join(lines))
|
||||
|
||||
|
||||
@@ -1124,6 +1227,8 @@ async def main():
|
||||
|
||||
# 발행 관련
|
||||
app.add_handler(CommandHandler('status', cmd_status))
|
||||
app.add_handler(CommandHandler('collect', cmd_collect))
|
||||
app.add_handler(CommandHandler('write', cmd_write))
|
||||
app.add_handler(CommandHandler('approve', cmd_approve))
|
||||
app.add_handler(CommandHandler('reject', cmd_reject))
|
||||
app.add_handler(CommandHandler('pending', cmd_pending))
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"tagline": "어렵지 않아요, 그냥 읽어봐요",
|
||||
"active": true,
|
||||
"phase": 1,
|
||||
"labels": ["AI인사이트", "여행맛집", "스타트업", "제품리뷰", "생활꿀팁", "앱추천", "재테크절약", "팩트체크"]
|
||||
"labels": ["AI인사이트", "여행맛집", "스타트업", "TV로보는세상", "제품리뷰", "생활꿀팁", "앱추천", "재테크", "팩트체크"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"korean_relevance": {
|
||||
"max": 30,
|
||||
"description": "한국 독자 관련성",
|
||||
"keywords": ["한국", "국내", "한글", "카카오", "네이버", "쿠팡", "삼성", "LG", "현대", "기아", "배달", "토스", "당근", "야놀자"]
|
||||
"keywords": ["한국", "국내", "한글", "카카오", "네이버", "쿠팡", "삼성", "LG", "현대", "기아", "배달", "토스", "당근", "야놀자", "AI", "GPT", "ChatGPT", "Claude", "Gemini", "Apple", "Google", "iPhone", "갤럭시", "Netflix", "넷플릭스", "YouTube"]
|
||||
},
|
||||
"freshness": {
|
||||
"max": 20,
|
||||
@@ -63,7 +63,7 @@
|
||||
{
|
||||
"id": "clickbait",
|
||||
"description": "클릭베이트성 주제",
|
||||
"patterns": ["충격", "경악", "난리", "ㅋㅋ", "ㅠㅠ", "대박", "레전드", "역대급"]
|
||||
"patterns": ["경악", "난리", "ㅋㅋ", "ㅠㅠ"]
|
||||
}
|
||||
],
|
||||
"evergreen_keywords": [
|
||||
|
||||
+26
-6
@@ -7,6 +7,8 @@
|
||||
{ "name": "테크크런치 AI", "url": "https://techcrunch.com/category/artificial-intelligence/feed/", "category": "AI인사이트", "trust_level": "high" },
|
||||
{ "name": "MIT 테크리뷰", "url": "https://www.technologyreview.com/feed/", "category": "AI인사이트", "trust_level": "high" },
|
||||
{ "name": "전자신문", "url": "https://www.etnews.com/rss/rss.xml", "category": "AI인사이트", "trust_level": "high" },
|
||||
{ "name": "딥러닝 뉴스", "url": "https://news.google.com/rss/search?q=AI+인공지능&hl=ko&gl=KR&ceid=KR:ko", "category": "AI인사이트", "trust_level": "medium" },
|
||||
{ "name": "Ars Technica AI", "url": "https://feeds.arstechnica.com/arstechnica/technology-lab", "category": "AI인사이트", "trust_level": "high" },
|
||||
|
||||
{ "name": "Bloter", "url": "https://www.bloter.net/feed", "category": "스타트업", "trust_level": "high" },
|
||||
{ "name": "플래텀", "url": "https://platum.kr/feed", "category": "스타트업", "trust_level": "high" },
|
||||
@@ -20,11 +22,15 @@
|
||||
{ "name": "한국관광공사", "url": "https://kto.visitkorea.or.kr/rss/rss.kto", "category": "여행맛집", "trust_level": "medium" },
|
||||
{ "name": "대한항공 뉴스", "url": "https://www.koreanair.com/content/koreanair/global/en/footer/about-korean-air/news-and-pr/press-releases.rss.xml", "category": "여행맛집", "trust_level": "medium" },
|
||||
{ "name": "론리플래닛", "url": "https://www.lonelyplanet.com/news/feed", "category": "여행맛집", "trust_level": "medium" },
|
||||
{ "name": "다나와 여행", "url": "https://news.google.com/rss/search?q=국내여행+맛집&hl=ko&gl=KR&ceid=KR:ko", "category": "여행맛집", "trust_level": "medium" },
|
||||
{ "name": "마이리얼트립 블로그","url": "https://blog.myrealtrip.com/feed", "category": "여행맛집", "trust_level": "medium" },
|
||||
|
||||
{ "name": "ITWorld Korea", "url": "https://www.itworld.co.kr/rss/feed", "category": "제품리뷰", "trust_level": "medium" },
|
||||
{ "name": "디지털데일리", "url": "https://www.ddaily.co.kr/rss/rss.xml", "category": "제품리뷰", "trust_level": "medium" },
|
||||
{ "name": "The Verge", "url": "https://www.theverge.com/rss/index.xml", "category": "제품리뷰", "trust_level": "high" },
|
||||
{ "name": "Engadget", "url": "https://www.engadget.com/rss.xml", "category": "제품리뷰", "trust_level": "high" },
|
||||
{ "name": "뽐뿌 뉴스", "url": "https://www.ppomppu.co.kr/rss.php?id=news", "category": "제품리뷰", "trust_level": "medium" },
|
||||
{ "name": "Wired", "url": "https://www.wired.com/feed/rss", "category": "제품리뷰", "trust_level": "high" },
|
||||
|
||||
{ "name": "위키트리", "url": "https://www.wikitree.co.kr/rss/", "category": "생활꿀팁", "trust_level": "medium" },
|
||||
{ "name": "오마이뉴스 라이프", "url": "https://rss2.ohmynews.com/rss/ohmyrss.xml", "category": "생활꿀팁", "trust_level": "medium" },
|
||||
@@ -34,12 +40,23 @@
|
||||
{ "name": "Product Hunt", "url": "https://www.producthunt.com/feed", "category": "앱추천", "trust_level": "medium" },
|
||||
{ "name": "테크크런치 앱", "url": "https://techcrunch.com/category/apps/feed/", "category": "앱추천", "trust_level": "high" },
|
||||
{ "name": "앱스토리", "url": "https://www.appstory.co.kr/rss/rss.xml", "category": "앱추천", "trust_level": "medium" },
|
||||
{ "name": "9to5Mac", "url": "https://9to5mac.com/feed/", "category": "앱추천", "trust_level": "high" },
|
||||
{ "name": "Android Authority", "url": "https://www.androidauthority.com/feed/", "category": "앱추천", "trust_level": "high" },
|
||||
|
||||
{ "name": "매일경제 IT", "url": "https://rss.mk.co.kr/rss/30000001/", "category": "재테크절약", "trust_level": "high" },
|
||||
{ "name": "머니투데이", "url": "https://rss.mt.co.kr/news/mt_news.xml", "category": "재테크절약", "trust_level": "high" },
|
||||
{ "name": "한국경제 재테크", "url": "https://www.hankyung.com/feed/finance", "category": "재테크절약", "trust_level": "high" },
|
||||
{ "name": "뱅크샐러드 블로그", "url": "https://blog.banksalad.com/rss.xml", "category": "재테크절약", "trust_level": "medium" },
|
||||
{ "name": "서울경제", "url": "https://www.sedaily.com/rss/rss.xml", "category": "재테크절약", "trust_level": "high" },
|
||||
{ "name": "매일경제 IT", "url": "https://rss.mk.co.kr/rss/30000001/", "category": "재테크", "trust_level": "high" },
|
||||
{ "name": "머니투데이", "url": "https://rss.mt.co.kr/news/mt_news.xml", "category": "재테크", "trust_level": "high" },
|
||||
{ "name": "한국경제 재테크", "url": "https://www.hankyung.com/feed/finance", "category": "재테크", "trust_level": "high" },
|
||||
{ "name": "뱅크샐러드 블로그", "url": "https://blog.banksalad.com/rss.xml", "category": "재테크", "trust_level": "medium" },
|
||||
{ "name": "서울경제", "url": "https://www.sedaily.com/rss/rss.xml", "category": "재테크", "trust_level": "high" },
|
||||
{ "name": "조선비즈 경제", "url": "https://biz.chosun.com/site/data/rss/rss.xml", "category": "재테크", "trust_level": "high" },
|
||||
{ "name": "이데일리 금융", "url": "https://www.edaily.co.kr/rss/rss.asp?media_key=20", "category": "재테크", "trust_level": "high" },
|
||||
|
||||
{ "name": "스포츠조선 연예", "url": "https://sports.chosun.com/site/data/rss/rss.xml", "category": "TV로보는세상","trust_level": "medium" },
|
||||
{ "name": "연합뉴스 연예", "url": "https://www.yna.co.kr/rss/entertainment.xml", "category": "TV로보는세상","trust_level": "high" },
|
||||
{ "name": "한국경제 연예", "url": "https://www.hankyung.com/feed/entertainment", "category": "TV로보는세상","trust_level": "high" },
|
||||
{ "name": "MBC 연예", "url": "https://imnews.imbc.com/rss/entertainment.xml", "category": "TV로보는세상","trust_level": "high" },
|
||||
{ "name": "TV리포트", "url": "https://www.tvreport.co.kr/rss/allArticle.xml", "category": "TV로보는세상","trust_level": "medium" },
|
||||
{ "name": "OSEN 연예", "url": "https://www.osen.co.kr/rss/osen.xml", "category": "TV로보는세상","trust_level": "medium" },
|
||||
|
||||
{ "name": "연합뉴스 팩트체크", "url": "https://www.yna.co.kr/rss/factcheck.xml", "category": "팩트체크", "trust_level": "high" },
|
||||
{ "name": "SBS 뉴스", "url": "https://news.sbs.co.kr/news/SectionRssFeed.do?sectionId=01&plink=RSSREADER", "category": "팩트체크", "trust_level": "high" },
|
||||
@@ -56,7 +73,10 @@
|
||||
"맛집 추천",
|
||||
"스타트업 소식",
|
||||
"재테크 방법",
|
||||
"쇼핑 추천"
|
||||
"쇼핑 추천",
|
||||
"TV 예능 화제",
|
||||
"드라마 추천",
|
||||
"넷플릭스 신작"
|
||||
],
|
||||
"github_trending": {
|
||||
"url": "https://github.com/trending",
|
||||
|
||||
Reference in New Issue
Block a user