bep40 commited on
Commit
7807176
·
verified ·
1 Parent(s): 95a537f

Patch 53ee7c5 restore for live YouTube Shorts display and playback

Browse files
Files changed (1) hide show
  1. 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
- "ai_runtime_final6:app",
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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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",