Spaces:
Running
Running
Patch 53ee7c5 restore for live YouTube Shorts display and playback
Browse files- restore_53ee7c5.py +119 -1
restore_53ee7c5.py
CHANGED
|
@@ -13,12 +13,130 @@ snapshot_dir = snapshot_download(
|
|
| 13 |
local_dir_use_symlinks=False,
|
| 14 |
)
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
os.chdir(snapshot_dir)
|
| 17 |
sys.path.insert(0, snapshot_dir)
|
| 18 |
|
| 19 |
cmd = [
|
| 20 |
"uvicorn",
|
| 21 |
-
"
|
| 22 |
"--host",
|
| 23 |
"0.0.0.0",
|
| 24 |
"--port",
|
|
|
|
| 13 |
local_dir_use_symlinks=False,
|
| 14 |
)
|
| 15 |
|
| 16 |
+
# Write a tiny runtime patch inside the restored snapshot.
|
| 17 |
+
patch_code = r'''
|
| 18 |
+
import re, time, requests
|
| 19 |
+
import ai_runtime_final6 as baseapp
|
| 20 |
+
from ai_runtime_final6 import app, HTMLResponse, JSONResponse, Query, rt
|
| 21 |
+
try:
|
| 22 |
+
import main as main_mod
|
| 23 |
+
except Exception:
|
| 24 |
+
main_mod = None
|
| 25 |
+
|
| 26 |
+
_CACHE = {"t": 0, "d": []}
|
| 27 |
+
CHANNELS = ["baodantri7941", "baosuckhoedoisongboyte"]
|
| 28 |
+
|
| 29 |
+
def clean(s):
|
| 30 |
+
import html as html_lib
|
| 31 |
+
return re.sub(r"\s+", " ", html_lib.unescape(s or "")).strip()
|
| 32 |
+
|
| 33 |
+
def fallback_shorts():
|
| 34 |
+
out=[];seen=set();c=[]
|
| 35 |
+
try:c += getattr(main_mod, 'SHORTS_FALLBACK', []) or []
|
| 36 |
+
except Exception:pass
|
| 37 |
+
hard=[
|
| 38 |
+
('Lu_iCQ5YwNM','Công an lập hồ sơ xử lý người phụ nữ chửi bới, tát tài xế ô tô | Dân trí','baodantri7941'),
|
| 39 |
+
('CwWvijF8BOA','Chú rể bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước | Dân trí','baodantri7941'),
|
| 40 |
+
('tvPewsc2ph4','Tính năng ẩn trên iPhone giúp giảm mỏi mắt | Dân trí','baodantri7941'),
|
| 41 |
+
('b1Nxzv9ixlU','Y án 3 năm tù với nữ tài xế uống 8 lon bia lái xe gây tai nạn | Dân trí','baodantri7941'),
|
| 42 |
+
('7Pd6vZ2Lz1M','Hành động ấm lòng trong tìm kiếm học sinh tử vong ở sông Lô | SKĐS','baosuckhoedoisongboyte'),
|
| 43 |
+
('SlHLt_ZyPiE','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 | SKĐS','baosuckhoedoisongboyte'),
|
| 44 |
+
('IUOprcJyYr4','Phụ nữ táo bón có phải do lười ăn rau? | SKĐS','baosuckhoedoisongboyte'),
|
| 45 |
+
]
|
| 46 |
+
for vid,title,ch in hard:c.append({'id':vid,'title':title,'channel':ch})
|
| 47 |
+
for v in c:
|
| 48 |
+
vid=v.get('id')
|
| 49 |
+
if vid and vid not in seen:
|
| 50 |
+
seen.add(vid)
|
| 51 |
+
out.append({'id':vid,'title':v.get('title') or 'YouTube Short','channel':v.get('channel',''),'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
|
| 52 |
+
return out
|
| 53 |
+
|
| 54 |
+
def yt_dlp_shorts(handle, count=20):
|
| 55 |
+
try:
|
| 56 |
+
import yt_dlp
|
| 57 |
+
with yt_dlp.YoutubeDL({'quiet':True,'extract_flat':True,'skip_download':True,'playlistend':count,'ignoreerrors':True,'no_warnings':True}) as ydl:
|
| 58 |
+
info=ydl.extract_info(f'https://www.youtube.com/@{handle}/shorts', download=False)
|
| 59 |
+
out=[]
|
| 60 |
+
for e in (info or {}).get('entries') or []:
|
| 61 |
+
vid=e.get('id') or ''
|
| 62 |
+
if re.match(r'^[A-Za-z0-9_-]{11}$', vid):
|
| 63 |
+
out.append({'id':vid,'title':e.get('title') or 'YouTube Short','channel':handle,'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
|
| 64 |
+
return out
|
| 65 |
+
except Exception:return []
|
| 66 |
+
|
| 67 |
+
def html_shorts(handle, count=20):
|
| 68 |
+
try:
|
| 69 |
+
headers={"User-Agent":"Mozilla/5.0","Accept-Language":"vi,en;q=0.8"}
|
| 70 |
+
html=requests.get(f'https://www.youtube.com/@{handle}/shorts',headers=headers,timeout=15).text
|
| 71 |
+
ids=[];out=[]
|
| 72 |
+
for m in re.finditer(r'"videoId":"([A-Za-z0-9_-]{11})"', html):
|
| 73 |
+
vid=m.group(1)
|
| 74 |
+
if vid in ids:continue
|
| 75 |
+
ids.append(vid)
|
| 76 |
+
snip=html[max(0,m.start()-1200):m.start()+2000]
|
| 77 |
+
mt=re.search(r'"title":\{"runs":\[\{"text":"([^"]+)"',snip) or re.search(r'"accessibilityText":"([^"]+)"',snip)
|
| 78 |
+
title=clean(mt.group(1).replace('\\n',' ')) if mt else 'YouTube Short'
|
| 79 |
+
out.append({'id':vid,'title':title,'channel':handle,'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
|
| 80 |
+
if len(out)>=count:break
|
| 81 |
+
return out
|
| 82 |
+
except Exception:return []
|
| 83 |
+
|
| 84 |
+
def fresh_shorts():
|
| 85 |
+
seen=set();out=[]
|
| 86 |
+
for ch in CHANNELS:
|
| 87 |
+
for v in (yt_dlp_shorts(ch,24) or html_shorts(ch,24)):
|
| 88 |
+
if v['id'] not in seen:
|
| 89 |
+
seen.add(v['id']);out.append(v)
|
| 90 |
+
for v in fallback_shorts():
|
| 91 |
+
if v['id'] not in seen:
|
| 92 |
+
seen.add(v['id']);out.append(v)
|
| 93 |
+
return out[:60]
|
| 94 |
+
|
| 95 |
+
# remove existing /api/shorts and /
|
| 96 |
+
app.router.routes=[r for r in app.router.routes if not ((getattr(r,'path',None)=='/api/shorts' and 'GET' in getattr(r,'methods',set())) or (getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set())))]
|
| 97 |
+
|
| 98 |
+
@app.get('/api/shorts')
|
| 99 |
+
def api_shorts(refresh:int=Query(default=0)):
|
| 100 |
+
now=time.time()
|
| 101 |
+
if not refresh and _CACHE['d'] and now-_CACHE['t']<900:
|
| 102 |
+
return JSONResponse(_CACHE['d'])
|
| 103 |
+
data=fresh_shorts()
|
| 104 |
+
_CACHE.update({'t':now,'d':data})
|
| 105 |
+
return JSONResponse(data)
|
| 106 |
+
|
| 107 |
+
PATCH = r'''
|
| 108 |
+
<style>
|
| 109 |
+
#shorts-live-fix{margin:6px 4px;background:#1a1a1a;border:1px solid #2a2a2a;border-radius:8px;overflow:hidden}.ytshort-action{position:absolute;right:8px;bottom:95px;display:flex;flex-direction:column;gap:12px;z-index:20}.ytshort-action button{background:none;border:0;color:#fff;text-align:center;font-size:10px}.ytshort-action .ico{width:44px;height:44px;border-radius:50%;background:rgba(0,0,0,.48);display:flex;align-items:center;justify-content:center;font-size:21px}
|
| 110 |
+
</style>
|
| 111 |
+
<script>
|
| 112 |
+
(function(){
|
| 113 |
+
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 114 |
+
let shortsLive=[];
|
| 115 |
+
async function loadShortsLive(){shortsLive=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);return shortsLive;}
|
| 116 |
+
function renderShortsLive(){let home=document.getElementById('view-home');if(!home||document.getElementById('shorts-live-fix'))return;loadShortsLive().then(sh=>{if(!sh.length)return;let wrap=document.createElement('div');wrap.id='shorts-live-fix';let h='<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS</span><span class="slider-note">YouTube mới nhất</span></div><div class="slider-track">';sh.slice(0,30).forEach((a,i)=>{h+=`<div class="slider-item shorts-item" onclick="openShortsLive(${i})"><div class="slider-thumb shorts-thumb">${a.img?`<img src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${esc(a.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let after=document.querySelector('.ai-compose')||home.firstChild;if(after)after.after(wrap);else home.prepend(wrap);});}
|
| 117 |
+
window.openShortsLive=async function(start){let arts=shortsLive.length?shortsLive:await loadShortsLive();if(!arts.length)return alert('Không tải được Shorts');let ordered=start>0?arts.slice(start).concat(arts.slice(0,start)):arts;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Shorts Dân trí & SKĐS</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((v,i)=>{let src='https://www.youtube.com/embed/'+v.id+'?autoplay=1&rel=0&playsinline=1';h+=`<div class="tiktok-slide"><iframe src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture"></iframe><div class="tiktok-bottom"><span class="badge badge-fpt">YT</span><p class="tiktok-title">${esc(v.title)}</p></div><div class="ytshort-action"><button onclick="navigator.share?navigator.share({title:'${esc(v.title)}',url:'${v.link}'}):navigator.clipboard.writeText('${v.link}')"><div class="ico">📤</div><span>Share</span></button></div><span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;}
|
| 118 |
+
let oldOpen=window.openTikTok;window.openTikTok=function(type,i){if(type==='shorts')return openShortsLive(i||0);return oldOpen?oldOpen(type,i):null;};
|
| 119 |
+
setTimeout(()=>{renderShortsLive();document.querySelectorAll('.slider-label').forEach(label=>{if((label.textContent||'').includes('Shorts'))label.closest('.slider-wrap')?.querySelectorAll('.slider-item').forEach((el,i)=>el.setAttribute('onclick',`openShortsLive(${i})`));});},1000);setInterval(()=>{if(document.getElementById('view-home')?.classList.contains('active'))renderShortsLive();},3000);
|
| 120 |
+
})();
|
| 121 |
+
</script>
|
| 122 |
+
'''
|
| 123 |
+
|
| 124 |
+
@app.get('/')
|
| 125 |
+
async def index():
|
| 126 |
+
html=baseapp.f5.f4.f3.f2.f1._load_index_html()
|
| 127 |
+
body=getattr(baseapp.rt.old,'PATCH_INJECT','')+baseapp.f5.f4.f3.f2.f1.FINAL_INJECT+baseapp.f5.f4.f3.FINAL3_INJECT+baseapp.f5.f4.FINAL4_INJECT+baseapp.f5.FINAL5_INJECT+PATCH
|
| 128 |
+
return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)
|
| 129 |
+
'''
|
| 130 |
+
|
| 131 |
+
with open(os.path.join(snapshot_dir, "runtime_53ee7c5_patch.py"), "w", encoding="utf-8") as f:
|
| 132 |
+
f.write(patch_code)
|
| 133 |
+
|
| 134 |
os.chdir(snapshot_dir)
|
| 135 |
sys.path.insert(0, snapshot_dir)
|
| 136 |
|
| 137 |
cmd = [
|
| 138 |
"uvicorn",
|
| 139 |
+
"runtime_53ee7c5_patch:app",
|
| 140 |
"--host",
|
| 141 |
"0.0.0.0",
|
| 142 |
"--port",
|