Khรดng tรฌm thแบฅy bร i viแบฟt.
"
if "vnexpress.net" in url: return render_article_html(scrape_vne_article(url))
return render_article_html(scrape_bdp_article(url))
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# HTML RENDERERS
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
def render_video_slider_html(videos):
"""Render a horizontal video slider for the homepage."""
if not videos:
return ""
items = []
for i, v in enumerate(videos):
img = safe_url(v.get("img", ""))
link = v.get("link", "#")
title = v.get("title", "")
aid = make_id(link)
sl = slug(title) if title else f"video-{i}"
click_js = f"window.bdpOpen('{esc(link)}','{aid}','{sl}')"
items.append(f'''Khรดng tรฌm thแบฅy tin tแปฉc.
"
now=datetime.now().strftime("%H:%M:%S %d/%m/%Y")
# Fetch latest videos for slider (in parallel with rendering)
try:
videos = fetch_latest_videos()
except:
videos = []
slider_html = render_video_slider_html(videos)
groups={}
for a in articles: groups.setdefault(a.get("group","Khรกc"),[]).append(a)
parts=[f'{slider_html}
โฑ {now}๐ฐ Tin nแปi bแบญt
']
for gn in ["Thแปi Sแปฑ","Thแบฟ Giแปi","Kinh Doanh","Cรดng Nghแป","Thแป Thao","Giแบฃi Trรญ","Bรณng ฤรก"]:
arts=groups.get(gn,[])
if not arts: continue
feat=[a for a in arts if a.get("featured")][:2]
reg=[a for a in arts if not a.get("featured")][:4]
display=feat+reg
if not display: continue
parts.append(f'
{gn}
')
for i,art in enumerate(display[:6]): parts.append(_list_card(art,i
')
parts.append('
')
return '\n'.join(parts)
def render_list_html(articles,group_name=""):
if not articles: return "
Khรดng tรฌm thแบฅy tin tแปฉc.
"
now=datetime.now().strftime("%H:%M:%S %d/%m/%Y")
feat=[a for a in articles if a.get("featured")]
reg=[a for a in articles if not a.get("featured")]
parts=[f'
โฑ {now}๐ฐ {len(articles)} tin ยท {group_name}
']
if feat:
parts.append('
')
for a in feat[:6]: parts.append(_list_card(a,True))
parts.append('
')
if reg:
parts.append('
')
for a in reg[:40]: parts.append(_list_card(a,False))
parts.append('
')
parts.append('
')
return '\n'.join(parts)
def _list_card(art,big):
img_html=""
if art.get("img"):
c="bdp-card-img bdp-card-img-big" if big else "bdp-card-img"
is_video = art.get("is_video", False) or "/video/" in art.get("link","")
play_overlay = '
โถ
' if is_video else ""
img_html=f'

{play_overlay}
'
time_html=f'
๐ {art["time"]}' if art.get("time") else ""
summ_html=f'
{art["summary"][:120]}...
' if art.get("summary") and len(art["summary"])>10 else ""
link=art.get("link","#"); aid=make_id(link)
tc="bdp-card-title bdp-card-title-big" if big else "bdp-card-title"
grp=art.get("group",""); badge=""
if art.get("source")=="vne": badge=f'
{grp or "VnExpress"}'
elif art.get("source")=="bdp": badge=f'
{grp or "BongDaPlus"}'
sl=slug(art["title"])
share_js=f"event.stopPropagation();window.bdpShareOriginal('{esc(art['title'])}','{esc(link)}')"
click_js=f"window.bdpOpen('{esc(link)}','{aid}','{sl}')"
return f"""
{img_html}
{badge}
{art['title']}
{summ_html}"""
def render_article_html(article):
aid=make_id(article["source_url"]); sl=slug(article["title"])
src_url=article.get("source_url","")
og_img=safe_url(article.get("og_image",""))
share_js=f"window.bdpShareOriginal('{esc(article['title'])}','{esc(src_url)}')"
src_label="VnExpress" if article.get("source")=="vne" else "BongDaPlus"
seo=f'
'
parts=[f"""{seo}
{article['title']}
๐ {article['time']} ยท {src_label}
"""]
if article.get("summary"):
parts.append(f'
{article["summary"]}
')
for item in article.get("body",[]):
if item["type"]=="video":
poster=safe_url(item.get("poster",""))
poster_attr=f' poster="{poster}"' if poster else ""
caption=item.get("caption","")
cap_html=f'
{caption}
' if caption else ""
vtype=item.get("vtype","mp4")
vsrc=item["src"]
if vtype=="hls":
# HLS stream - use data-hls-src, handled by global MutationObserver + HLS.js
parts.append(f'
{cap_html}
')
else:
# Direct MP4
parts.append(f'
{cap_html}
')
elif item["type"]=="img":
alt=item.get("alt",""); cap=f'
{alt}' if alt else ""
parts.append(f'
{cap}')
elif item["type"]=="p":
parts.append(f'
{item["html"]}
')
elif item["type"]=="heading":
parts.append(f'
{item["text"]}
')
elif item["type"]=="quote":
parts.append(f'
{item["text"]}
')
if article.get("related"):
parts.append('
')
parts.append(f"""""")
parts.append('
')
return '\n'.join(parts)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CSS = """
body,html{margin:0!important;padding:0!important;overflow-x:hidden;background:#111!important}
.gradio-container{max-width:100%!important;width:100%!important;margin:0!important;padding:0!important;border-radius:0!important;background:#111!important}
.main,.contain{max-width:100%!important;width:100%!important;padding:0!important;margin:0!important}
.gradio-container>.main>.contain{padding-top:0!important}
.gap{gap:0!important}
footer,.built-with{display:none!important}
#article-url-input,#btn-read-article{display:none!important;height:0!important;overflow:hidden!important}
.bdp-header{background:linear-gradient(135deg,#0d1117,#1a3a2a 50%,#8b7500);padding:14px 16px;text-align:center}
.bdp-header h1{color:#fff;font-size:20px;margin:0;font-weight:800;text-shadow:0 2px 6px rgba(0,0,0,.4)}
.bdp-header p{color:rgba(255,255,255,.6);font-size:11px;margin:2px 0 0}
@media(min-width:768px){.bdp-header h1{font-size:26px}.bdp-header{padding:20px}}
.controls-row{padding:0 8px!important;background:#111!important}
.controls-row>div{background:#111!important;border:none!important}
.controls-row label{color:#aaa!important;font-size:12px!important}
.controls-row .wrap{border-color:#333!important;background:#1a1a1a!important}
.controls-row input,.controls-row select,.controls-row button{color:#eee!important}
/* โโ Video Slider โโ */
.vslide-wrap{margin:8px;background:#1a1a1a;border-radius:12px;overflow:hidden;border:1px solid #2a2a2a}
.vslide-header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px 6px}
.vslide-label{color:#f0c040;font-size:15px;font-weight:700}
.vslide-nav{display:flex;gap:6px}
.vslide-btn{background:#333;color:#fff;border:none;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:13px;display:flex;align-items:center;justify-content:center;transition:background .15s}
.vslide-btn:hover{background:#555}
.vslide-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;gap:10px;padding:6px 14px 14px;scrollbar-width:none}
.vslide-track::-webkit-scrollbar{display:none}
.vslide-item{flex:0 0 200px;scroll-snap-align:start;cursor:pointer;transition:transform .15s}
.vslide-item:hover{transform:scale(1.03)}
@media(min-width:768px){.vslide-item{flex:0 0 240px}}
.vslide-thumb{position:relative;width:100%;aspect-ratio:16/9;border-radius:8px;overflow:hidden;background:#222}
.vslide-thumb img{width:100%;height:100%;object-fit:cover}
.vslide-play{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;background:rgba(0,0,0,.6);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;pointer-events:none;transition:background .15s}
.vslide-item:hover .vslide-play{background:rgba(220,50,50,.8)}
.vslide-title{color:#ccc;font-size:12px;margin:6px 0 0;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* โโ Main content โโ */
.bdp-wrap{padding:8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
.bdp-topbar{display:flex;justify-content:space-between;padding:4px 4px 8px;color:#666;font-size:11px}
.bdp-empty{text-align:center;color:#666;padding:60px 20px}
.bdp-section{margin-bottom:16px}
.bdp-section-title{font-size:16px;font-weight:700;color:#5cb87a;margin:4px 0 8px;border-left:3px solid #5cb87a;padding-left:8px}
@media(min-width:768px){.bdp-section-title{font-size:18px}}
.bdp-grid{display:grid;grid-template-columns:1fr;gap:8px}
.bdp-grid-featured,.bdp-grid-home{margin-bottom:4px}
@media(min-width:420px){.bdp-grid{grid-template-columns:repeat(2,1fr)}}
@media(min-width:768px){.bdp-grid{grid-template-columns:repeat(3,1fr);gap:10px}}
@media(min-width:1100px){.bdp-grid{grid-template-columns:repeat(4,1fr)}}
.bdp-card{background:#1a1a1a;border-radius:10px;overflow:hidden;cursor:pointer;transition:transform .15s,box-shadow .15s;border:1px solid #222}
.bdp-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.5)}
.bdp-card:active{transform:scale(.98)}
.bdp-card-img{width:100%;height:130px;overflow:hidden;background:#222;position:relative}
.bdp-card-img-big{height:170px}
.bdp-card-img img{width:100%;height:100%;object-fit:cover}
@media(min-width:768px){.bdp-card-img{height:150px}.bdp-card-img-big{height:190px}}
.bdp-play-overlay{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:36px;height:36px;background:rgba(0,0,0,.55);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:15px;pointer-events:none}
.bdp-card-body{padding:8px 10px 6px}
.bdp-card-title{font-size:13px;font-weight:600;color:#eee;margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.bdp-card-title-big{font-size:14.5px}
@media(min-width:768px){.bdp-card-title{font-size:13.5px}.bdp-card-title-big{font-size:15px}}
.bdp-card-summ{font-size:11.5px;color:#777;margin:4px 0 0;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.bdp-card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:6px}
.bdp-card-time{color:#555;font-size:10.5px}
.bdp-share-btn{background:none;border:none;cursor:pointer;font-size:15px;padding:3px 5px;border-radius:6px;transition:background .15s;color:#777}
.bdp-share-btn:hover{background:#333}
.bdp-badge{font-size:9px;padding:1px 6px;border-radius:3px;font-weight:700;display:inline-block;margin-bottom:4px}
.bdp-badge-vne{background:#c0392b;color:#fff}
.bdp-badge-bdp{background:#1a5c35;color:#fff}
.bdp-article{padding:14px 12px 30px;max-width:720px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
@media(min-width:768px){.bdp-article{padding:20px 16px 50px}}
.bdp-article-title{font-size:21px;font-weight:800;color:#f0f0f0;line-height:1.3;margin:0 0 8px}
@media(min-width:768px){.bdp-article-title{font-size:27px}}
.bdp-article-meta{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;color:#666;font-size:12px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #2a2a2a}
.bdp-share-article-btn{background:linear-gradient(135deg,#1a5c35,#2d8659);color:#fff;border:none;padding:6px 14px;border-radius:18px;font-size:12px;cursor:pointer;font-weight:600}
.bdp-share-article-btn:hover{opacity:.85}
.bdp-article-summary{background:#1a2a1f;border-left:4px solid #2d8659;padding:12px 14px;margin-bottom:16px;border-radius:0 8px 8px 0;font-weight:600;color:#ccc;line-height:1.5;font-size:14.5px}
.bdp-article-p{font-size:15.5px;line-height:1.75;color:#ccc;margin:0 0 12px}
@media(min-width:768px){.bdp-article-p{font-size:16.5px}}
.bdp-article-h2{font-size:19px;font-weight:700;color:#eee;margin:24px 0 10px}
.bdp-quote{border-left:4px solid #b8960c;padding:10px 14px;margin:14px 0;background:#1a1a10;font-style:italic;color:#bbb;border-radius:0 6px 6px 0}
.bdp-figure{margin:14px 0;text-align:center}
.bdp-figure img{max-width:100%;height:auto;border-radius:8px}
.bdp-figcap{color:#666;font-size:11.5px;margin-top:4px;font-style:italic}
.bdp-video-wrap{margin:14px 0;border-radius:10px;overflow:hidden;background:#000}
.bdp-video{width:100%;max-height:70vh;display:block;border-radius:10px}
.bdp-related{margin-top:24px;padding-top:14px;border-top:1px solid #2a2a2a}
.bdp-related h3{font-size:16px;color:#eee;margin:0 0 8px}
.bdp-related-item{padding:8px 10px;margin-bottom:5px;border:1px solid #262626;border-radius:8px;cursor:pointer;transition:background .15s}
.bdp-related-item:hover{background:#222}
.bdp-related-item span{font-size:13.5px;color:#5cb87a;font-weight:500}
.bdp-comments{margin-top:28px;padding-top:16px;border-top:1px solid #2a2a2a}
.bdp-comments h3{font-size:16px;color:#eee;margin:0 0 10px}
.bdp-cmt-item{background:#1a1a1a;border:1px solid #262626;border-radius:8px;padding:10px 12px;margin-bottom:8px}
.bdp-cmt-author{font-weight:700;color:#5cb87a;font-size:13px}
.bdp-cmt-date{color:#555;font-size:11px;margin-left:8px}
.bdp-cmt-body{color:#ccc;font-size:14px;margin-top:4px;line-height:1.5}
.bdp-cmt-form{margin-top:12px}
.bdp-cmt-input,.bdp-cmt-textarea{width:100%;padding:8px 10px;background:#1a1a1a;border:1px solid #333;border-radius:6px;color:#eee;font-size:13px;box-sizing:border-box}
.bdp-cmt-input{margin-bottom:6px}
.bdp-cmt-textarea{resize:vertical}
.bdp-cmt-submit{background:linear-gradient(135deg,#1a5c35,#2d8659);color:#fff;border:none;padding:8px 20px;border-radius:18px;font-size:13px;cursor:pointer;font-weight:600;margin-top:8px}
.bdp-cmt-submit:hover{opacity:.85}
.bdp-cmt-empty{color:#555;font-size:13px;font-style:italic;padding:8px 0}
.bdp-toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#2d8659;color:#fff;padding:10px 22px;border-radius:22px;font-size:13px;z-index:9999;opacity:0;transition:opacity .3s;pointer-events:none;font-weight:500}
.bdp-toast.show{opacity:1}
.gr-group,.gr-box,.gr-panel{background:#111!important;border:none!important}
.label-wrap{background:#111!important}
"""
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# JS โ ALL logic in head=, NO inline
๐ฌ Bรฌnh luแบญn