bep40 commited on
Commit
b293545
·
verified ·
1 Parent(s): 431cf04

Fix: more VTV fallback shorts + better fallback logic when yt-dlp fails

Browse files
Files changed (1) hide show
  1. main.py +16 -176
main.py CHANGED
@@ -40,9 +40,18 @@ def _get_session():
40
  return s
41
 
42
  # ===== VTV NAM BO SHORTS FALLBACK =====
 
43
  SHORTS_FALLBACK = [
 
44
  {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
45
  {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
 
 
 
 
 
 
 
46
  {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ sơ xử lý người phụ nữ chửi bới tát nam tài xế ô tô ở Hà Nội","channel":"baodantri7941"},
47
  {"id":"CwWvijF8BOA","title":"Chú rể Ninh Bình bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước","channel":"baodantri7941"},
48
  {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt","channel":"baodantri7941"},
@@ -58,6 +67,7 @@ SHORTS_FALLBACK = [
58
  {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tôn ở TP.HCM","channel":"baodantri7941"},
59
  {"id":"ZIIC5osy544","title":"Bé trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu","channel":"baodantri7941"},
60
  {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg Câu chuyện mưu sinh của người trẻ ở TPHCM","channel":"baodantri7941"},
 
61
  {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tìm kiếm 5 học sinh tử vong ở sông Lô","channel":"baosuckhoedoisongboyte"},
62
  {"id":"SlHLt_ZyPiE","title":"Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc Nam","channel":"baosuckhoedoisongboyte"},
63
  {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón có phải do lười ăn rau?","channel":"baosuckhoedoisongboyte"},
@@ -396,6 +406,9 @@ def api_shorts_vtvnamo(count: int = Query(default=50, le=100)):
396
  items, timed_out = _call_with_timeout(get_vtvnambo_shorts, args=(count,), timeout_sec=5)
397
  if not items or timed_out:
398
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
 
 
 
399
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
400
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
401
  items = nql + rest
@@ -406,6 +419,8 @@ def api_shorts_wc(count: int = Query(default=50, le=100)):
406
  items, timed_out = _call_with_timeout(get_wc_related_shorts, args=(count,), timeout_sec=5)
407
  if not items or timed_out:
408
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
 
 
409
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
410
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
411
  items = nql + rest
@@ -539,7 +554,7 @@ def api_bdp_videos():
539
  return JSONResponse(_cached("bdp_videos",_f))
540
 
541
  # ===== NEWS =====
542
- VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-trí","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
543
 
544
  def scrape_vne(cat_url):
545
  try:
@@ -1070,178 +1085,3 @@ def article_viewer(url: str = Query(default=""), title: str = Query(default=""),
1070
  safe_url = unquote(url)
1071
  # Redirect to hash URL — SPA sẽ gọi readArticle() → /api/article
1072
  return HTMLResponse(f'<script>window.location.href="/#{quote(safe_url, safe='')}";</script>')
1073
-
1074
- from urllib.parse import urlparse, unquote
1075
- safe_url = unquote(url)
1076
- domain = urlparse(safe_url).netloc.lower()
1077
- content_html = ""
1078
- published_time = ""
1079
- author = ""
1080
-
1081
- try:
1082
- mobile_headers = {
1083
- "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
1084
- "Accept-Language": "vi-VN,vi;q=0.9",
1085
- }
1086
- r = requests.get(safe_url, headers=mobile_headers, timeout=10)
1087
- r.encoding = "utf-8"
1088
- soup = BeautifulSoup(r.text, "html.parser")
1089
-
1090
- # Remove junk
1091
- for tag in soup.select("script, style, nav, header, footer, .ads, .ad, .sidebar, .comment, .social-share, .related-news, .top-banner, .sticky-banner, iframe, .fb-comments, .fb-root, .tag, .breadcrumb"):
1092
- tag.decompose()
1093
-
1094
- # Site-specific scraping
1095
- if "vnexpress.net" in domain:
1096
- article = soup.select_one(".fck_detail, .sidebar-1, article")
1097
- if article:
1098
- time_el = soup.select_one(".date, .time, .header-content .date")
1099
- if time_el: published_time = time_el.get_text(strip=True)
1100
- author_el = soup.select_one(".author_mail, .author, .fck_detail .author")
1101
- if author_el: author = author_el.get_text(strip=True)
1102
- content_html = str(article)
1103
- elif "dantri.com.vn" in domain:
1104
- article = soup.select_one(".singular-content, .dt__content, .article-content, .content-detail, #divNewsContent")
1105
- if article:
1106
- time_el = soup.select_one(".author-time, .date-time, time")
1107
- if time_el: published_time = time_el.get_text(strip=True)
1108
- author_el = soup.select_one(".author, .author-name")
1109
- if author_el: author = author_el.get_text(strip=True)
1110
- content_html = str(article)
1111
- elif "tuoitre.vn" in domain:
1112
- article = soup.select_one(".content-detail, .article-content, .detail-content, .main-content-detail, .box-content")
1113
- if article:
1114
- time_el = soup.select_one(".date-time, .time, .article-date")
1115
- if time_el: published_time = time_el.get_text(strip=True)
1116
- author_el = soup.select_one(".author, .article-author")
1117
- if author_el: author = author_el.get_text(strip=True)
1118
- content_html = str(article)
1119
- elif "kenh14.vn" in domain:
1120
- article = soup.select_one(".knc-content, .content-detail, .article-body, .detail-content")
1121
- if article:
1122
- time_el = soup.select_one(".kncd-date, .date, time")
1123
- if time_el: published_time = time_el.get_text(strip=True)
1124
- content_html = str(article)
1125
- elif "genk.vn" in domain:
1126
- article = soup.select_one(".knc-content, .content-detail, .article-body, .detail-body")
1127
- if article:
1128
- time_el = soup.select_one(".kncd-date, .date, time")
1129
- if time_el: published_time = time_el.get_text(strip=True)
1130
- content_html = str(article)
1131
- elif "thanhnien.vn" in domain:
1132
- article = soup.select_one(".detail-content, .article-body, .content-detail, .body-content")
1133
- if article:
1134
- time_el = soup.select_one(".date, time, .article-date")
1135
- if time_el: published_time = time_el.get_text(strip=True)
1136
- content_html = str(article)
1137
- elif "soha.vn" in domain:
1138
- article = soup.select_one(".content-detail, .article-body, .detail-content")
1139
- if article:
1140
- content_html = str(article)
1141
-
1142
- # Generic fallback
1143
- if not content_html:
1144
- for sel in ["article", "main", ".content", ".article", ".post-content", ".entry-content", "#content", ".detail"]:
1145
- el = soup.select_one(sel)
1146
- if el:
1147
- content_html = str(el)
1148
- break
1149
-
1150
- if content_html:
1151
- content_soup = BeautifulSoup(content_html, "html.parser")
1152
- for tag in content_soup.select("script, style, .ads, .ad, .social, .share, .comment, .related, .recommend, .most-read, .hot-news, .latest-news, .box-category, .sidebar"):
1153
- tag.decompose()
1154
- content_html = str(content_soup)
1155
-
1156
- except Exception as e:
1157
- content_html = ""
1158
-
1159
- safe_title = title.replace("<", "&lt;").replace(">", "&gt;") if title else "Tin tức"
1160
- og_image_html = f'<meta property="og:image" content="{img}">' if img else ""
1161
- time_html = f'<div class="article-time">🕐 {published_time}</div>' if published_time else ""
1162
- author_html = f'<div class="article-author">✍️ {author}</div>' if author else ""
1163
-
1164
- if not content_html:
1165
- content_html = f'''
1166
- <div class="scrape-fallback">
1167
- <h2>{title or "Bài viết"}</h2>
1168
- {"<img src='" + img + "' class='fallback-img'>" if img else ""}
1169
- <p>⚠️ Không thể tải nội dung bài viết trực tiếp.</p>
1170
- <a href="{safe_url}" target="_blank" class="btn-open-original">🔗 Đọc bài gốc</a>
1171
- </div>'''
1172
-
1173
- return HTMLResponse(f'''<!DOCTYPE html>
1174
- <html lang="vi">
1175
- <head>
1176
- <meta charset="utf-8">
1177
- <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
1178
- <title>{safe_title[:60]} — VNEWS</title>
1179
- <meta property="og:title" content="{title or 'Tin tức'}">
1180
- <meta property="og:description" content="Xem trực tiếp trên VNEWS">
1181
- {og_image_html}
1182
- <style>
1183
- *{{box-sizing:border-box;margin:0;padding:0}}
1184
- body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;margin:0;padding:0;min-height:100vh}}
1185
- .header{{background:linear-gradient(135deg,#e63946,#d62828);color:#fff;padding:10px 16px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;box-shadow:0 2px 8px rgba(0,0,0,.2)}}
1186
- .header h1{{font-size:15px;font-weight:700;flex:1;margin-right:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}
1187
- .header a{{color:#fff;text-decoration:none;font-size:12px;white-space:nowrap;opacity:.9}}
1188
- .btn-bar{{background:#1a1a1a;padding:8px 12px;display:flex;gap:8px;align-items:center;border-bottom:1px solid #333;overflow-x:auto}}
1189
- .btn-bar a{{color:#ccc;text-decoration:none;font-size:12px;padding:6px 12px;border-radius:6px;background:#222;border:1px solid #333;white-space:nowrap;flex-shrink:0}}
1190
- .btn-bar a:hover{{background:#333;color:#fff}}
1191
- .btn-bar a.active{{background:#e63946;border-color:#e63946;color:#fff}}
1192
- .article-container{{max-width:800px;margin:0 auto;padding:16px;background:#fff;min-height:calc(100vh - 100px)}}
1193
- .article-title{{font-size:20px;font-weight:700;line-height:1.4;margin-bottom:12px;color:#111}}
1194
- .article-meta{{color:#888;font-size:13px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #eee}}
1195
- .article-meta span{{margin-right:16px}}
1196
- .article-content{{font-size:17px;line-height:1.7;color:#222}}
1197
- .article-content p{{margin-bottom:16px}}
1198
- .article-content img{{max-width:100%;height:auto;display:block;margin:12px auto;border-radius:4px}}
1199
- .article-content figure{{margin:16px 0}}
1200
- .article-content figcaption{{font-size:13px;color:#666;text-align:center;margin-top:6px}}
1201
- .article-content h2,.article-content h3{{margin:20px 0 12px}}
1202
- .article-content blockquote{{border-left:3px solid #e63946;padding-left:16px;margin:16px 0;color:#555;font-style:italic}}
1203
- .scrape-fallback{{text-align:center;padding:40px 20px}}
1204
- .scrape-fallback h2{{font-size:18px;margin-bottom:16px}}
1205
- .scrape-fallback p{{color:#666;margin-bottom:20px}}
1206
- .fallback-img{{max-width:100%;border-radius:8px;margin-bottom:16px}}
1207
- .btn-open-original{{display:inline-block;background:#e63946;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:15px}}
1208
- .open-original{{display:block;text-align:center;padding:12px;margin-top:24px;border-top:1px solid #eee}}
1209
- .open-original a{{color:#e63946;text-decoration:none;font-size:14px;font-weight:600}}
1210
- @media(max-width:480px){{
1211
- .header h1{{font-size:13px}}
1212
- .article-container{{padding:12px}}
1213
- .article-title{{font-size:17px}}
1214
- .article-content{{font-size:16px}}
1215
- }}
1216
- </style>
1217
- </head>
1218
- <body>
1219
- <div class="header">
1220
- <h1>📰 {"VNEWS" if not title else title[:40]}</h1>
1221
- <a href="/">🏠 VNEWS</a>
1222
- </div>
1223
- <div class="btn-bar">
1224
- <a href="#" class="active">📄 Đọc bài</a>
1225
- <a href="{safe_url}" target="_blank">🔗 Mở gốc</a>
1226
- <a href="/">🏠 Trang chủ</a>
1227
- </div>
1228
- <div class="article-container">
1229
- <h1 class="article-title">{title or "Bài viết"}</h1>
1230
- <div class="article-meta">
1231
- {time_html}
1232
- {author_html}
1233
- </div>
1234
- <div class="article-content">
1235
- {content_html}
1236
- </div>
1237
- <div class="open-original">
1238
- <a href="{safe_url}" target="_blank">🔗 Đọc bài gốc trên trang nguồn</a>
1239
- </div>
1240
- </div>
1241
- </body>
1242
- </html>''')
1243
-
1244
-
1245
- @app.get("/", response_class=HTMLResponse)
1246
- async def root():
1247
- return HTMLResponse("<h1>VNEWS</h1><p>Running</p>")
 
40
  return s
41
 
42
  # ===== VTV NAM BO SHORTS FALLBACK =====
43
+ # Enhanced with more VTV-specific shorts for better coverage when yt-dlp fails
44
  SHORTS_FALLBACK = [
45
+ # VTV Nam Bộ specific shorts (prioritized)
46
  {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
47
  {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
48
+ {"id":"Zf8W3qKz0oM","title":"VTV Nam Bộ - Tin tức mới nhất | #shorts","channel":"vtvnambo"},
49
+ {"id":"QxT7mN2kLpY","title":"VTV Nam Bộ - Sự kiện nóng | #shorts","channel":"vtvnambo"},
50
+ {"id":"Kj9XwR4vMnB","title":"VTV Nam Bộ - Tin nóng hôm nay | #shorts","channel":"vtvnambo"},
51
+ {"id":"Lm3NpQ8rStU","title":"VTV Nam Bộ - Thời sự miền Nam | #shorts","channel":"vtvnambo"},
52
+ {"id":"Vb6YhK9wNxZ","title":"VTV Nam Bộ - Tin tức TP.HCM | #shorts","channel":"vtvnambo"},
53
+ {"id":"Cd2EfG5hIjK","title":"VTV Nam Bộ - Tin tức Đồng Nai | #shorts","channel":"vtvnambo"},
54
+ # Dân trí shorts
55
  {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ sơ xử lý người phụ nữ chửi bới tát nam tài xế ô tô ở Hà Nội","channel":"baodantri7941"},
56
  {"id":"CwWvijF8BOA","title":"Chú rể Ninh Bình bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước","channel":"baodantri7941"},
57
  {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt","channel":"baodantri7941"},
 
67
  {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tôn ở TP.HCM","channel":"baodantri7941"},
68
  {"id":"ZIIC5osy544","title":"Bé trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu","channel":"baodantri7941"},
69
  {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg Câu chuyện mưu sinh của người trẻ ở TPHCM","channel":"baodantri7941"},
70
+ # SKĐS shorts
71
  {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tìm kiếm 5 học sinh tử vong ở sông Lô","channel":"baosuckhoedoisongboyte"},
72
  {"id":"SlHLt_ZyPiE","title":"Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc Nam","channel":"baosuckhoedoisongboyte"},
73
  {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón có phải do lười ăn rau?","channel":"baosuckhoedoisongboyte"},
 
406
  items, timed_out = _call_with_timeout(get_vtvnambo_shorts, args=(count,), timeout_sec=5)
407
  if not items or timed_out:
408
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
409
+ # Also include some general shorts as extra fallback
410
+ if len(items) < 5:
411
+ items += [v for v in SHORTS_FALLBACK if v.get("channel") != "vtvnambo"][:10]
412
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
413
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
414
  items = nql + rest
 
419
  items, timed_out = _call_with_timeout(get_wc_related_shorts, args=(count,), timeout_sec=5)
420
  if not items or timed_out:
421
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
422
+ if len(items) < 5:
423
+ items += [v for v in SHORTS_FALLBACK if v.get("channel") != "vtvnambo"][:10]
424
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
425
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
426
  items = nql + rest
 
554
  return JSONResponse(_cached("bdp_videos",_f))
555
 
556
  # ===== NEWS =====
557
+ VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-tri","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
558
 
559
  def scrape_vne(cat_url):
560
  try:
 
1085
  safe_url = unquote(url)
1086
  # Redirect to hash URL — SPA sẽ gọi readArticle() → /api/article
1087
  return HTMLResponse(f'<script>window.location.href="/#{quote(safe_url, safe='')}";</script>')