| """Startup: patches app.py to add 24h news shorts (9:16 vertical) carousel, then runs it.""" |
| import os, sys |
|
|
| APP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app.py") |
|
|
| def patch(): |
| with open(APP_PATH, "r", encoding="utf-8") as f: |
| code = f.read() |
|
|
| if "vslide-shorts-item" in code: |
| return |
|
|
| |
| if "from shorts_scraper import scrape_24h_news_shorts" not in code: |
| code = code.replace( |
| "from concurrent.futures import ThreadPoolExecutor, as_completed", |
| "from concurrent.futures import ThreadPoolExecutor, as_completed\nfrom shorts_scraper import scrape_24h_news_shorts", |
| 1 |
| ) |
|
|
| |
| if "h24_shorts" not in code: |
| code = code.replace(" h24_videos=[]", " h24_videos=[]\n h24_shorts=[]", 1) |
| |
| old_fetch24h = " def _fetch_24h():\n nonlocal h24_videos\n try: h24_videos=scrape_24h_video_list()[:15]\n except: pass" |
| new_fetch24h = old_fetch24h + "\n def _fetch_shorts():\n nonlocal h24_shorts\n try: h24_shorts=scrape_24h_news_shorts()[:15]\n except: pass" |
| code = code.replace(old_fetch24h, new_fetch24h, 1) |
| |
| code = code.replace(" ex.submit(_fetch_24h)\n futures=", " ex.submit(_fetch_24h)\n ex.submit(_fetch_shorts)\n futures=", 1) |
| |
| code = code.replace(" return all_articles, h24_videos", " return all_articles, h24_videos, h24_shorts", 1) |
|
|
| |
| if "h24_shorts = fetch_homepage" not in code: |
| code = code.replace( |
| " articles, h24_videos = fetch_homepage()\n return render_homepage_html(articles, h24_videos)", |
| " articles, h24_videos, h24_shorts = fetch_homepage()\n return render_homepage_html(articles, h24_videos, h24_shorts)", |
| 1 |
| ) |
|
|
| |
| if "render_shorts_carousel_html" not in code: |
| fn = [ |
| 'def render_shorts_carousel_html(shorts):', |
| ' """9:16 vertical shorts carousel from 24h video-tin-tuc."""', |
| ' vids = [v for v in shorts if v.get("img")]', |
| ' if not vids: return ""', |
| ' items = []', |
| ' for v in vids[:15]:', |
| ' img = safe_url(v.get("img",""))', |
| ' link = v.get("link","#")', |
| ' title = v.get("title","")', |
| ' aid = make_id(link)', |
| " cjs = \"window.bdpOpenTikTok('\"+esc(link)+\"','\"+aid+\"')\"", |
| ' items.append(\'<div class="vslide-shorts-item" onclick="\'+cjs+\'">\' +', |
| ' \'<div class="vslide-shorts-thumb"><img src="\'+img+\'" alt="" class="bdp-lazy-img">\' +', |
| ' \'<div class="vslide-play">\\u25b6</div></div>\' +', |
| ' \'<p class="vslide-shorts-title">\'+title+\'</p></div>\')', |
| ' h = \'<div class="vslide-wrap"><div class="vslide-header">\'', |
| ' h += \'<span class="vslide-label">\\U0001f4f1 Shorts Tin T\\u1ee9c 24h</span>\'', |
| " h += '<div class=\"vslide-nav\"><button class=\"vslide-btn\" onclick=\"window.bdpSlideScroll(-1,\\'vslide-shorts\\')\">\\u25c0</button>'", |
| " h += '<button class=\"vslide-btn\" onclick=\"window.bdpSlideScroll(1,\\'vslide-shorts\\')\">\\u25b6</button></div></div>'", |
| ' h += \'<div class="vslide-track" id="vslide-shorts">\'+\'\'.join(items)+\'</div></div>\'', |
| ' return h', |
| '', |
| ] |
| fn_code = '\n'.join(fn) + '\n' |
| code = code.replace( |
| "def render_video_carousel_html(videos):", |
| fn_code + "def render_video_carousel_html(videos):", |
| 1 |
| ) |
|
|
| |
| if "h24_shorts=None" not in code: |
| code = code.replace( |
| "def render_homepage_html(articles, h24_videos=None):", |
| "def render_homepage_html(articles, h24_videos=None, h24_shorts=None):", |
| 1 |
| ) |
|
|
| if "shorts_carousel" not in code: |
| code = code.replace( |
| " video_carousel = render_video_carousel_html(h24_videos or [])", |
| " shorts_carousel = render_shorts_carousel_html(h24_shorts or [])\n video_carousel = render_video_carousel_html(h24_videos or [])", |
| 1 |
| ) |
| code = code.replace( |
| "{video_carousel}{news_carousel}", |
| "{shorts_carousel}{video_carousel}{news_carousel}", |
| 1 |
| ) |
|
|
| |
| if "vslide-shorts-item" not in code: |
| shorts_css = ( |
| '\n/* Shorts 9:16 Slider */\n' |
| '.vslide-shorts-item{flex:0 0 110px;scroll-snap-align:start;cursor:pointer;transition:transform .15s}\n' |
| '.vslide-shorts-item:hover{transform:scale(1.04)}\n' |
| '@media(min-width:768px){.vslide-shorts-item{flex:0 0 130px}}\n' |
| '.vslide-shorts-thumb{position:relative;width:100%;aspect-ratio:9/16;border-radius:12px;overflow:hidden;background:#222}\n' |
| '.vslide-shorts-thumb img{width:100%;height:100%;object-fit:cover}\n' |
| '.vslide-shorts-title{color:#ccc;font-size:10.5px;margin:5px 0 0;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n' |
| ) |
| code = code.replace( |
| ".vslide-badge-24h{background:#e67e22;color:#fff}", |
| ".vslide-badge-24h{background:#e67e22;color:#fff}" + shorts_css, |
| 1 |
| ) |
|
|
| with open(APP_PATH, "w", encoding="utf-8") as f: |
| f.write(code) |
| print("[startup.py] Patched app.py with 9:16 shorts carousel") |
|
|
|
|
| |
| patch() |
|
|
| |
| with open(APP_PATH, encoding="utf-8") as f: |
| exec(compile(f.read(), APP_PATH, "exec"), {"__name__": "__main__", "__file__": APP_PATH}) |
|
|