bep40 commited on
Commit
4872afc
·
verified ·
1 Parent(s): 3826fdf

fix: OG image URL encoding, HLS.js for VnExpress video, GIF onerror no longer hides parent

Browse files
Files changed (1) hide show
  1. app.py +45 -16
app.py CHANGED
@@ -46,6 +46,10 @@ def strip_links(html):
46
  return re.sub(r'</a>', '', re.sub(r'<a\s[^>]*>', '', html))
47
  def esc(text):
48
  return text.replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"').replace("\n", " ")
 
 
 
 
49
  def make_id(url):
50
  return hashlib.md5(url.encode()).hexdigest()[:12]
51
  def slug(text):
@@ -119,7 +123,8 @@ def _fetch_bdp_video(embed_src):
119
  video = soup.select_one("video")
120
  if source:
121
  return {"type": "video", "src": source.get("src", ""),
122
- "poster": video.get("poster", "") if video else ""}
 
123
  except Exception:
124
  pass
125
  return None
@@ -167,9 +172,12 @@ def scrape_vne_article(url):
167
  # Check for video inside <p>
168
  vid = ch.find("video")
169
  if vid:
170
- src = vid.get("src", "")
171
- if src:
172
- body.append({"type": "video", "src": src, "poster": vid.get("poster", "")})
 
 
 
173
  continue
174
  im = ch.find("img")
175
  if im:
@@ -181,10 +189,13 @@ def scrape_vne_article(url):
181
  # Check for video in figure
182
  vid = ch.find("video")
183
  if vid:
184
- src = vid.get("src", "")
 
185
  cap = ch.find("figcaption")
186
- if src:
187
- body.append({"type": "video", "src": src, "poster": vid.get("poster", ""),
 
 
188
  "caption": cap.get_text(strip=True) if cap else ""})
189
  continue
190
  im = ch.find("img"); cap = ch.find("figcaption")
@@ -297,7 +308,7 @@ def _list_card(art, big):
297
  img_html = ""
298
  if art.get("img"):
299
  c = "bdp-card-img bdp-card-img-big" if big else "bdp-card-img"
300
- img_html = f'<div class="{c}"><img src="{art["img"]}" alt="{esc(art["title"])}" onerror="this.parentElement.style.display=\'none\'"></div>'
301
  time_html = f'<span class="bdp-card-time">🕐 {art["time"]}</span>' if art.get("time") else ""
302
  summ_html = f'<p class="bdp-card-summ">{art["summary"][:120]}...</p>' if art.get("summary") and len(art["summary"]) > 10 else ""
303
  link = art.get("link", "#"); aid = make_id(link)
@@ -305,9 +316,9 @@ def _list_card(art, big):
305
  grp = art.get("group", ""); badge = ""
306
  if art.get("source") == "vne": badge = f'<span class="bdp-badge bdp-badge-vne">{grp or "VnExpress"}</span>'
307
  elif art.get("source") == "bdp": badge = f'<span class="bdp-badge bdp-badge-bdp">{grp or "BongDaPlus"}</span>'
308
- title_slug = slug(art["title"]); img_url = esc(art.get("img","") or "")
309
- share_js = f"event.stopPropagation();window.bdpShare('{esc(art['title'])}','{aid}','{title_slug}','{img_url}')"
310
- click_js = f"window.bdpOpen('{esc(link)}','{aid}','{title_slug}','{img_url}')"
311
  return f"""<div class="bdp-card" onclick="{click_js}">
312
  {img_html}<div class="bdp-card-body">{badge}<h3 class="{tc}">{art['title']}</h3>
313
  {summ_html}<div class="bdp-card-footer">{time_html}
@@ -315,7 +326,7 @@ def _list_card(art, big):
315
 
316
  def render_article_html(article):
317
  aid = make_id(article["source_url"]); title_slug = slug(article["title"])
318
- og_img = article.get("og_image", "")
319
  share_js = f"window.bdpShare('{esc(article['title'])}','{aid}','{title_slug}','{esc(og_img)}')"
320
  src_label = "VnExpress" if article.get("source") == "vne" else "BongDaPlus"
321
  seo = f'<div style="display:none" itemscope itemtype="https://schema.org/NewsArticle"><meta itemprop="headline" content="{esc(article["title"])}"><meta itemprop="datePublished" content="{article.get("time","")}"><meta itemprop="publisher" content="{src_label}"><meta itemprop="image" content="{og_img}"><meta itemprop="description" content="{esc(article.get("summary","")[:160])}"></div>'
@@ -326,16 +337,33 @@ def render_article_html(article):
326
  if article.get("summary"): parts.append(f'<div class="bdp-article-summary">{article["summary"]}</div>')
327
  for item in article.get("body", []):
328
  if item["type"] == "video":
329
- poster = item.get("poster", "")
330
  poster_attr = f' poster="{poster}"' if poster else ""
331
  caption = item.get("caption", "")
332
  cap_html = f'<p class="bdp-figcap">{caption}</p>' if caption else ""
333
- parts.append(f"""<div class="bdp-video-wrap">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  <video controls playsinline preload="metadata"{poster_attr} class="bdp-video">
335
- <source src="{item['src']}" type="video/mp4">Video không hỗ trợ trên trình duyệt này.</video>{cap_html}</div>""")
336
  elif item["type"] == "img":
337
  alt = item.get("alt", ""); cap = f'<figcaption class="bdp-figcap">{alt}</figcaption>' if alt else ""
338
- parts.append(f'<figure class="bdp-figure"><img src="{item["src"]}" alt="{alt}" onerror="this.parentElement.style.display=\'none\'">{cap}</figure>')
339
  elif item["type"] == "p": parts.append(f'<p class="bdp-article-p">{item["html"]}</p>')
340
  elif item["type"] == "heading": parts.append(f'<h2 class="bdp-article-h2">{item["text"]}</h2>')
341
  elif item["type"] == "quote": parts.append(f'<blockquote class="bdp-quote">{item["text"]}</blockquote>')
@@ -452,6 +480,7 @@ JS_HEAD = """
452
  <meta name="twitter:card" content="summary_large_image">
453
  <meta name="twitter:image" content="" id="tw-image">
454
  <link rel="canonical" href="https://bep40-bongdaplus-news.hf.space">
 
455
  <script>
456
  (function(){
457
  window.bdpOpen=function(url,aid,sl,img){
 
46
  return re.sub(r'</a>', '', re.sub(r'<a\s[^>]*>', '', html))
47
  def esc(text):
48
  return text.replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"').replace("\n", " ")
49
+ def esc_url(url):
50
+ """URL-encode spaces and special chars for safe use in JS/HTML attributes."""
51
+ if not url: return ""
52
+ return url.replace(" ", "%20").replace("'", "%27").replace('"', "%22")
53
  def make_id(url):
54
  return hashlib.md5(url.encode()).hexdigest()[:12]
55
  def slug(text):
 
123
  video = soup.select_one("video")
124
  if source:
125
  return {"type": "video", "src": source.get("src", ""),
126
+ "poster": video.get("poster", "") if video else "",
127
+ "vtype": "mp4"}
128
  except Exception:
129
  pass
130
  return None
 
172
  # Check for video inside <p>
173
  vid = ch.find("video")
174
  if vid:
175
+ vsrc = vid.get("src", "")
176
+ vid_id = vid.get("id", "")
177
+ if vsrc:
178
+ vtype = "hls" if "m3u8" in vsrc else "mp4"
179
+ body.append({"type": "video", "src": vsrc, "poster": vid.get("poster", ""),
180
+ "vtype": vtype, "vid_id": vid_id})
181
  continue
182
  im = ch.find("img")
183
  if im:
 
189
  # Check for video in figure
190
  vid = ch.find("video")
191
  if vid:
192
+ vsrc = vid.get("src", "")
193
+ vid_id = vid.get("id", "")
194
  cap = ch.find("figcaption")
195
+ if vsrc:
196
+ vtype = "hls" if "m3u8" in vsrc else "mp4"
197
+ body.append({"type": "video", "src": vsrc, "poster": vid.get("poster", ""),
198
+ "vtype": vtype, "vid_id": vid_id,
199
  "caption": cap.get_text(strip=True) if cap else ""})
200
  continue
201
  im = ch.find("img"); cap = ch.find("figcaption")
 
308
  img_html = ""
309
  if art.get("img"):
310
  c = "bdp-card-img bdp-card-img-big" if big else "bdp-card-img"
311
+ img_html = f'<div class="{c}"><img src="{esc_url(art["img"])}" alt="{esc(art["title"])}" onerror="this.style.display=\'none\'"></div>'
312
  time_html = f'<span class="bdp-card-time">🕐 {art["time"]}</span>' if art.get("time") else ""
313
  summ_html = f'<p class="bdp-card-summ">{art["summary"][:120]}...</p>' if art.get("summary") and len(art["summary"]) > 10 else ""
314
  link = art.get("link", "#"); aid = make_id(link)
 
316
  grp = art.get("group", ""); badge = ""
317
  if art.get("source") == "vne": badge = f'<span class="bdp-badge bdp-badge-vne">{grp or "VnExpress"}</span>'
318
  elif art.get("source") == "bdp": badge = f'<span class="bdp-badge bdp-badge-bdp">{grp or "BongDaPlus"}</span>'
319
+ title_slug = slug(art["title"]); img_url = esc_url(art.get("img","") or "")
320
+ share_js = f"event.stopPropagation();window.bdpShare('{esc(art['title'])}','{aid}','{title_slug}','{esc(img_url)}')"
321
+ click_js = f"window.bdpOpen('{esc(link)}','{aid}','{title_slug}','{esc(img_url)}')"
322
  return f"""<div class="bdp-card" onclick="{click_js}">
323
  {img_html}<div class="bdp-card-body">{badge}<h3 class="{tc}">{art['title']}</h3>
324
  {summ_html}<div class="bdp-card-footer">{time_html}
 
326
 
327
  def render_article_html(article):
328
  aid = make_id(article["source_url"]); title_slug = slug(article["title"])
329
+ og_img = esc_url(article.get("og_image", ""))
330
  share_js = f"window.bdpShare('{esc(article['title'])}','{aid}','{title_slug}','{esc(og_img)}')"
331
  src_label = "VnExpress" if article.get("source") == "vne" else "BongDaPlus"
332
  seo = f'<div style="display:none" itemscope itemtype="https://schema.org/NewsArticle"><meta itemprop="headline" content="{esc(article["title"])}"><meta itemprop="datePublished" content="{article.get("time","")}"><meta itemprop="publisher" content="{src_label}"><meta itemprop="image" content="{og_img}"><meta itemprop="description" content="{esc(article.get("summary","")[:160])}"></div>'
 
337
  if article.get("summary"): parts.append(f'<div class="bdp-article-summary">{article["summary"]}</div>')
338
  for item in article.get("body", []):
339
  if item["type"] == "video":
340
+ poster = esc_url(item.get("poster", ""))
341
  poster_attr = f' poster="{poster}"' if poster else ""
342
  caption = item.get("caption", "")
343
  cap_html = f'<p class="bdp-figcap">{caption}</p>' if caption else ""
344
+ vtype = item.get("vtype", "mp4")
345
+ vid_id = item.get("vid_id", f"vid-{aid}")
346
+ vsrc = item["src"]
347
+ if vtype == "hls":
348
+ # HLS stream: use HLS.js for Chrome/Firefox, native for Safari
349
+ parts.append(f"""<div class="bdp-video-wrap">
350
+ <video id="{vid_id}" controls playsinline preload="metadata"{poster_attr} class="bdp-video"></video>{cap_html}
351
+ <script>
352
+ (function(){{var v=document.getElementById('{vid_id}');if(!v)return;
353
+ var src='{vsrc}';
354
+ if(v.canPlayType('application/vnd.apple.mpegURL')){{v.src=src;}}
355
+ else if(window.Hls&&Hls.isSupported()){{var h=new Hls();h.loadSource(src);h.attachMedia(v);}}
356
+ else{{v.innerHTML='<source src="'+src+'" type="application/x-mpegURL">';}}
357
+ }})();
358
+ </script></div>""")
359
+ else:
360
+ # Direct MP4
361
+ parts.append(f"""<div class="bdp-video-wrap">
362
  <video controls playsinline preload="metadata"{poster_attr} class="bdp-video">
363
+ <source src="{vsrc}" type="video/mp4">Video không hỗ trợ.</video>{cap_html}</div>""")
364
  elif item["type"] == "img":
365
  alt = item.get("alt", ""); cap = f'<figcaption class="bdp-figcap">{alt}</figcaption>' if alt else ""
366
+ parts.append(f'<figure class="bdp-figure"><img src="{esc_url(item["src"])}" alt="{alt}" onerror="this.style.display=\'none\'">{cap}</figure>')
367
  elif item["type"] == "p": parts.append(f'<p class="bdp-article-p">{item["html"]}</p>')
368
  elif item["type"] == "heading": parts.append(f'<h2 class="bdp-article-h2">{item["text"]}</h2>')
369
  elif item["type"] == "quote": parts.append(f'<blockquote class="bdp-quote">{item["text"]}</blockquote>')
 
480
  <meta name="twitter:card" content="summary_large_image">
481
  <meta name="twitter:image" content="" id="tw-image">
482
  <link rel="canonical" href="https://bep40-bongdaplus-news.hf.space">
483
+ <script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
484
  <script>
485
  (function(){
486
  window.bdpOpen=function(url,aid,sl,img){