bep40 commited on
Commit
5a5b626
·
verified ·
1 Parent(s): b1c64f6

Fix: override openLeaguePlayer completely in HIGHLIGHT_INTERACT_JS with all buttons"

Browse files
Files changed (1) hide show
  1. app_final.py +109 -107
app_final.py CHANGED
@@ -1,28 +1,23 @@
1
- """Final wrapper: all fixes + highlight interactions with persistent counts + 16:9 toggle."""
2
  import json, os, time
3
  from app_patch_unified import *
4
  from app_patch_unified import app, UNIFIED_INJECT, f5, f6, rt, PATCH_INJECT
5
  from fastapi.responses import HTMLResponse, JSONResponse
6
  from fastapi import Request, Query
7
 
8
- DATA_DIR = "/data" if os.path.isdir('/data') else "/app/data"
9
- os.makedirs(DATA_DIR, exist_ok=True)
10
- HL_STATS_FILE = os.path.join(DATA_DIR, 'highlight_stats.json')
11
 
12
- def _load_hl_stats():
13
  try:
14
- if os.path.exists(HL_STATS_FILE):
15
- return json.load(open(HL_STATS_FILE,'r',encoding='utf-8'))
16
  except:pass
17
  return {}
18
- def _save_hl_stats(db):
19
- try:
20
- tmp=HL_STATS_FILE+'.tmp'
21
- open(tmp,'w',encoding='utf-8').write(json.dumps(db,ensure_ascii=False))
22
- os.replace(tmp,HL_STATS_FILE)
23
  except:pass
24
 
25
- # Add highlight interaction endpoint
26
  app.router.routes=[r for r in app.router.routes if not (
27
  (getattr(r,'path',None)=='/api/highlight/interact' and 'POST' in getattr(r,'methods',set())) or
28
  (getattr(r,'path',None)=='/api/highlight/stats' and 'GET' in getattr(r,'methods',set())) or
@@ -30,28 +25,22 @@ app.router.routes=[r for r in app.router.routes if not (
30
  )]
31
 
32
  @app.post('/api/highlight/interact')
33
- async def _hl_interact(request:Request):
34
  b=await request.json();vid=str(b.get('id','')).strip();action=str(b.get('action','')).strip()
35
  if not vid or action not in ('view','like','share'):return JSONResponse({'error':'invalid'},status_code=400)
36
- db=_load_hl_stats()
37
- st=db.get(vid,{'views':0,'likes':0,'shares':0})
38
- if action=='view':st['views']=st.get('views',0)+1
39
- elif action=='like':st['likes']=st.get('likes',0)+1
40
- elif action=='share':st['shares']=st.get('shares',0)+1
41
- db[vid]=st;_save_hl_stats(db)
42
- return JSONResponse({'stats':st})
43
 
44
  @app.get('/api/highlight/stats')
45
  def _hl_stats(ids:str=Query(default='')):
46
- db=_load_hl_stats()
47
- if not ids:return JSONResponse({'stats':{}})
48
- out={}
49
  for vid in ids.split(','):
50
  vid=vid.strip()
51
  if vid:out[vid]=db.get(vid,{'views':0,'likes':0,'shares':0})
52
  return JSONResponse({'stats':out})
53
 
54
- # Build fixed UNIFIED_INJECT with interaction buttons + 16:9 toggle
55
  UNIFIED_INJECT_FIXED = UNIFIED_INJECT.replace(
56
  """Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});""",
57
  """Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});
@@ -63,120 +52,133 @@ Object.defineProperty(window,'loadPatchedWall',{get:function(){return function()
63
  Object.defineProperty(window,'refreshFinalWall3',{get:function(){return function(){}},set:function(){},configurable:true});"""
64
  )
65
 
66
- # Fix highlight: fetch from API if _hlLeagueData empty
67
  UNIFIED_INJECT_FIXED = UNIFIED_INJECT_FIXED.replace(
68
  "var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){el.innerHTML=",
69
  "var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){try{var _r=await fetch('/api/highlights/'+league);articles=await _r.json();if(!Array.isArray(articles))articles=[];}catch(e){articles=[];}}\n if(!articles.length){el.innerHTML="
70
  )
71
 
72
- # Replace the highlight tiktok-right section to add counts + 16:9 toggle
73
- UNIFIED_INJECT_FIXED = UNIFIED_INJECT_FIXED.replace(
74
- """h+='<div class="tiktok-slide" id="tslide-'+i+'">'+vtag+'<div class="tiktok-bottom"><span class="badge badge-fpt">'+esc(cfg.name)+'</span><p class="tiktok-title">'+esc(v.title)+'</p></div><div class="tiktok-right"><button class="tiktok-right-btn" onclick="event.stopPropagation()"><div class="icon">👁</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation()"><div class="icon">❤️</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();if(typeof doShareVideo===\\'function\\')doShareVideo(\\''+esc(v.title)+'\\'\\',\\''+esc(v.link||'')+'\\'\\',\\''+esc(v.poster||v.img||'')+'\\'\\',\\'highlights\\')"><div class="icon">📤</div></button></div><span class="tiktok-counter">'+(i+1)+'/'+ordered.length+'</span></div>';""",
75
- """h+='<div class="tiktok-slide" id="tslide-'+i+'" data-hlid="'+encodeURIComponent(v.link||v.title)+'">'+vtag+'<div class="tiktok-bottom"><span class="badge badge-fpt">'+esc(cfg.name)+'</span><p class="tiktok-title">'+esc(v.title)+'</p></div><div class="tiktok-right"><button class="tiktok-right-btn" onclick="event.stopPropagation();hlInteract(this,\\'view\\')"><div class="icon">👁</div><div class="count" data-action="views">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();hlInteract(this,\\'like\\')"><div class="icon">❤️</div><div class="count" data-action="likes">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();hlInteract(this,\\'share\\');if(typeof doShareVideo===\\'function\\')doShareVideo(\\''+esc(v.title)+'\\'\\',\\''+esc(v.link||'')+'\\'\\',\\''+esc(v.poster||v.img||'')+'\\'\\',\\'highlights\\')"><div class="icon">📤</div><div class="count" data-action="shares">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();toggleHlRatio(this)"><div class="icon">⬜</div><div class="count">16:9</div></button></div><span class="tiktok-counter">'+(i+1)+'/'+ordered.length+'</span></div>';"""
76
- )
77
-
78
- # Add extra JS for highlight interactions + stats loading + 16:9 toggle
79
- HIGHLIGHT_INTERACT_JS = r'''
80
  <style>
81
  .tiktok-slide.ratio-wide video,.tiktok-slide.ratio-wide iframe{object-fit:contain!important}
 
82
  </style>
 
83
  <script>
84
  (function(){
85
- // Highlight interaction: persist views/likes/shares
86
- window.hlInteract=async function(btn,action){
87
- var slide=btn.closest('.tiktok-slide');
88
- var id=slide?slide.dataset.hlid:'';
89
- if(!id)return;
90
- try{
91
- var r=await fetch('/api/highlight/interact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,action:action})});
92
- var j=await r.json();
93
- if(j.stats){
94
- var right=slide.querySelector('.tiktok-right');
95
- if(right){
96
- var vc=right.querySelector('[data-action="views"]');if(vc)vc.textContent=j.stats.views||0;
97
- var lc=right.querySelector('[data-action="likes"]');if(lc)lc.textContent=j.stats.likes||0;
98
- var sc=right.querySelector('[data-action="shares"]');if(sc)sc.textContent=j.stats.shares||0;
99
- }
100
- }
101
- }catch(e){}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  };
103
 
104
- // Load initial stats for all visible highlight slides
105
- window.loadHlStats=function(){
106
- var slides=document.querySelectorAll('.tiktok-slide[data-hlid]');
107
- var ids=[];slides.forEach(function(sl){var id=sl.dataset.hlid;if(id)ids.push(id);});
108
- if(!ids.length)return;
109
- fetch('/api/highlight/stats?ids='+ids.map(encodeURIComponent).join(',')).then(function(r){return r.json()}).then(function(j){
110
- var stats=j.stats||{};
111
- slides.forEach(function(sl){
112
- var id=sl.dataset.hlid;var st=stats[id];if(!st)return;
113
- var right=sl.querySelector('.tiktok-right');if(!right)return;
114
- var vc=right.querySelector('[data-action="views"]');if(vc)vc.textContent=st.views||0;
115
- var lc=right.querySelector('[data-action="likes"]');if(lc)lc.textContent=st.likes||0;
116
- var sc=right.querySelector('[data-action="shares"]');if(sc)sc.textContent=st.shares||0;
117
- });
118
- }).catch(function(){});
119
  };
120
- // Auto-load stats after feed renders
121
- var _origAct=null;
122
- var _hlStatsLoaded=false;
123
- var _hlObserver=new MutationObserver(function(){
124
- if(!_hlStatsLoaded&&document.querySelector('.tiktok-slide[data-hlid]')){
125
- _hlStatsLoaded=true;setTimeout(loadHlStats,500);
126
- }
127
- });
128
- _hlObserver.observe(document.body,{childList:true,subtree:true});
129
 
130
  // 16:9 toggle
131
- window.toggleHlRatio=function(btn){
132
- var slide=btn.closest('.tiktok-slide');
133
- if(!slide)return;
134
- slide.classList.toggle('ratio-wide');
135
- var label=btn.querySelector('.count');
136
- if(label)label.textContent=slide.classList.contains('ratio-wide')?'1:1':'16:9';
 
 
 
 
 
 
 
 
 
 
 
137
  };
138
- // Auto-view on slide activation
139
- var _origInitFeed=window.initFeed;
140
  })();
141
  </script>
142
  '''
143
 
144
  EXTRA_WALL_FIX = r'''
145
- <style>
146
- [data-wall-live="1"]{display:none!important}
147
- </style>
148
  <script>
149
  (function(){
150
- var _wallCheck=setInterval(function(){
151
- var home=document.getElementById('view-home');
152
- if(!home||!home.classList.contains('active'))return;
153
- var hasWall=document.getElementById('short-ai-final-slide');
154
- if(!hasWall&&typeof renderShortAISlide==='function'){renderShortAISlide();}
155
- var patchWall=document.querySelector('.slider-wrap[data-wall-live]');
156
- if(!patchWall){
157
  fetch('/api/ai_wall').then(function(r){return r.json()}).then(function(j){
158
  var posts=(j&&j.posts)||[];if(!posts.length)return;
159
  if(typeof window._serverWall!=='undefined')window._serverWall=posts;
160
- if(typeof prependWallPost==='function'&&posts[0])prependWallPost(posts[0]);
161
  }).catch(function(){});
162
  }
163
  },4000);
164
- setTimeout(function(){clearInterval(_wallCheck);},30000);
165
  })();
166
  </script>
167
  '''
168
 
169
  @app.get('/')
170
  async def _index_fixed():
171
- html = f5.f4.f3.f2.f1._load_index_html()
172
- body = ''
173
- body += getattr(rt.old,'PATCH_INJECT','')
174
- body += f5.f4.f3.f2.f1.FINAL_INJECT + f5.f4.f3.FINAL3_INJECT + f5.f4.FINAL4_INJECT + f5.FINAL5_INJECT
175
- body += getattr(f6,'FINAL6_INJECT','')
176
- body += getattr(f6,'FINAL6_FAST_HOME_INJECT','')
177
- body += getattr(f6,'FINAL6E_INJECT','')
178
- body += PATCH_INJECT
179
- body += UNIFIED_INJECT_FIXED
180
- body += HIGHLIGHT_INTERACT_JS
181
- body += EXTRA_WALL_FIX
182
- return HTMLResponse(html.replace('</body>', body + '\n</body>') if '</body>' in html else html + body)
 
1
+ """Final wrapper with complete highlight override including interaction buttons."""
2
  import json, os, time
3
  from app_patch_unified import *
4
  from app_patch_unified import app, UNIFIED_INJECT, f5, f6, rt, PATCH_INJECT
5
  from fastapi.responses import HTMLResponse, JSONResponse
6
  from fastapi import Request, Query
7
 
8
+ DATA_DIR="/data" if os.path.isdir('/data') else "/app/data"
9
+ os.makedirs(DATA_DIR,exist_ok=True)
10
+ HL_STATS_FILE=os.path.join(DATA_DIR,'highlight_stats.json')
11
 
12
+ def _load_hl():
13
  try:
14
+ if os.path.exists(HL_STATS_FILE):return json.load(open(HL_STATS_FILE,'r',encoding='utf-8'))
 
15
  except:pass
16
  return {}
17
+ def _save_hl(db):
18
+ try:open(HL_STATS_FILE+'.tmp','w',encoding='utf-8').write(json.dumps(db,ensure_ascii=False));os.replace(HL_STATS_FILE+'.tmp',HL_STATS_FILE)
 
 
 
19
  except:pass
20
 
 
21
  app.router.routes=[r for r in app.router.routes if not (
22
  (getattr(r,'path',None)=='/api/highlight/interact' and 'POST' in getattr(r,'methods',set())) or
23
  (getattr(r,'path',None)=='/api/highlight/stats' and 'GET' in getattr(r,'methods',set())) or
 
25
  )]
26
 
27
  @app.post('/api/highlight/interact')
28
+ async def _hl_act(request:Request):
29
  b=await request.json();vid=str(b.get('id','')).strip();action=str(b.get('action','')).strip()
30
  if not vid or action not in ('view','like','share'):return JSONResponse({'error':'invalid'},status_code=400)
31
+ db=_load_hl();st=db.get(vid,{'views':0,'likes':0,'shares':0})
32
+ st[action+'s']=st.get(action+'s',0)+1
33
+ db[vid]=st;_save_hl(db);return JSONResponse({'stats':st})
 
 
 
 
34
 
35
  @app.get('/api/highlight/stats')
36
  def _hl_stats(ids:str=Query(default='')):
37
+ db=_load_hl();out={}
 
 
38
  for vid in ids.split(','):
39
  vid=vid.strip()
40
  if vid:out[vid]=db.get(vid,{'views':0,'likes':0,'shares':0})
41
  return JSONResponse({'stats':out})
42
 
43
+ # PRE_KILL fix (block destroyers)
44
  UNIFIED_INJECT_FIXED = UNIFIED_INJECT.replace(
45
  """Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});""",
46
  """Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});
 
52
  Object.defineProperty(window,'refreshFinalWall3',{get:function(){return function(){}},set:function(){},configurable:true});"""
53
  )
54
 
55
+ # Fix highlight fetch
56
  UNIFIED_INJECT_FIXED = UNIFIED_INJECT_FIXED.replace(
57
  "var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){el.innerHTML=",
58
  "var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){try{var _r=await fetch('/api/highlights/'+league);articles=await _r.json();if(!Array.isArray(articles))articles=[];}catch(e){articles=[];}}\n if(!articles.length){el.innerHTML="
59
  )
60
 
61
+ # Complete highlight override with ALL buttons (comment, ask AI, like, share, 16:9 toggle)
62
+ HIGHLIGHT_FULL_OVERRIDE = r'''
 
 
 
 
 
 
63
  <style>
64
  .tiktok-slide.ratio-wide video,.tiktok-slide.ratio-wide iframe{object-fit:contain!important}
65
+ .hl-ask-panel{position:fixed;bottom:0;left:0;right:0;max-height:50vh;background:#181818;border-radius:16px 16px 0 0;z-index:99999;padding:14px;display:none;overflow-y:auto}.hl-ask-panel.active{display:block}.hl-ask-panel textarea,.hl-ask-panel input{width:100%;background:#222;border:1px solid #444;color:#eee;border-radius:10px;padding:9px;margin:6px 0}.hl-ask-panel button{background:#2d8659;border:0;color:#fff;border-radius:10px;padding:8px 12px;margin:4px}.hl-ask-answer{white-space:pre-wrap;color:#ccc;font-size:12px;margin-top:8px}
66
  </style>
67
+ <div id="hl-ask-panel" class="hl-ask-panel"></div>
68
  <script>
69
  (function(){
70
+ function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]});}
71
+
72
+ // === FULL openLeaguePlayer override with interaction buttons ===
73
+ window.openLeaguePlayer=async function(league,idx){
74
+ showView('view-tiktok');document.querySelectorAll('.cat').forEach(function(x){x.classList.remove('active')});
75
+ var el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải highlight...</div>';
76
+ var cfg=(window.HL_CONFIG||{})[league]||{name:league,emoji:'🎬'};
77
+ var articles=(window._hlLeagueData||{})[league]||[];
78
+ if(!articles.length){try{var resp=await fetch('/api/highlights/'+league);articles=await resp.json();if(!Array.isArray(articles))articles=[];}catch(e){articles=[];}}
79
+ if(!articles.length){el.innerHTML='<div class="loading">Không có video</div>';return;}
80
+ var vids=[];
81
+ var results=await Promise.all(articles.map(async function(a,i){try{var r=await fetch('/api/video_url?url='+encodeURIComponent(a.link));var v=await r.json();if(v&&v.src)return Object.assign({},a,v,{_idx:i});}catch(e){}return null;}));
82
+ results.forEach(function(r){if(r)vids.push(r);});
83
+ vids.sort(function(a,b){return a._idx-b._idx;});
84
+ if(!vids.length){el.innerHTML='<div class="loading">Không tìm thấy video</div>';return;}
85
+ var ti=vids.findIndex(function(v){return v._idx===idx;});if(ti<0)ti=0;
86
+ var ordered=ti>0?vids.slice(ti).concat(vids.slice(0,ti)):vids;
87
+ var h='<button class="back-btn" onclick="switchCat(\'home\')">← '+esc(cfg.emoji)+' '+esc(cfg.name)+'</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';
88
+ ordered.forEach(function(v,i){
89
+ var hlid=encodeURIComponent(v.link||v.title);
90
+ var isYT=v.type==='youtube';var isHLS=!isYT&&v.src&&v.src.indexOf('.m3u8')>-1;
91
+ var poster=v.poster?' poster="'+v.poster+'"':'';
92
+ var vtag=isYT?'<iframe data-yt-src="'+v.src+'" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" style="width:100%;height:100%;border:none"></iframe>':isHLS?'<video playsinline preload="none"'+poster+' data-hls="'+v.src+'" loop controls style="width:100%;height:100%;object-fit:cover"></video>':'<video playsinline preload="none"'+poster+' loop controls style="width:100%;height:100%;object-fit:cover"><source src="'+v.src+'" type="video/mp4"></video>';
93
+ h+='<div class="tiktok-slide" id="tslide-'+i+'" data-hlid="'+hlid+'">';
94
+ h+=vtag;
95
+ h+='<div class="tiktok-bottom"><span class="badge badge-fpt">'+esc(cfg.name)+'</span><p class="tiktok-title">'+esc(v.title)+'</p></div>';
96
+ h+='<div class="tiktok-right">';
97
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'view\')"><div class="icon">👁</div><div class="count" data-a="views">0</div></button>';
98
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'like\')"><div class="icon">❤️</div><div class="count" data-a="likes">0</div></button>';
99
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();openHlComments(\''+hlid+'\')"><div class="icon">💬</div><div class="count">BL</div></button>';
100
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();openHlAsk(\''+hlid+'\',\''+esc(v.title)+'\')"><div class="icon">🤖</div><div class="count">Hỏi</div></button>';
101
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'share\');if(typeof doShareVideo===\'function\')doShareVideo(\''+esc(v.title)+'\',\''+esc(v.link||'')+'\',\''+esc(v.poster||v.img||'')+'\',\'highlights\')"><div class="icon">📤</div><div class="count" data-a="shares">0</div></button>';
102
+ h+='<button class="tiktok-right-btn" onclick="event.stopPropagation();toggleHlRatio(this)"><div class="icon">⬜</div><div class="count">16:9</div></button>';
103
+ h+='</div>';
104
+ h+='<span class="tiktok-counter">'+(i+1)+'/'+ordered.length+'</span>';
105
+ h+='</div>';
106
+ });
107
+ h+='</div></div>';el.innerHTML=h;
108
+ // Init feed scroll
109
+ var feed=document.getElementById('tiktok-feed');if(!feed)return;
110
+ var slides=feed.querySelectorAll('.tiktok-slide');var cur=-1;
111
+ function act(i){if(i===cur)return;slides.forEach(function(sl,idx){var v=sl.querySelector('video');var fr=sl.querySelector('iframe');if(idx===i){if(v&&v.dataset.hls){if(!v._hls&&typeof Hls!=='undefined'&&Hls.isSupported()){var hls=new Hls();hls.loadSource(v.dataset.hls);hls.attachMedia(v);hls.on(Hls.Events.MANIFEST_PARSED,function(){v.play().catch(function(){});});v._hls=hls;}else if(v._hls)v.play().catch(function(){});}else if(v)v.play().catch(function(){});if(fr&&!fr.src&&fr.dataset.ytSrc)fr.src=fr.dataset.ytSrc;hlAct(sl.querySelector('.tiktok-right .tiktok-right-btn'),'view');}else{if(v){v.pause();if(v._hls){v._hls.destroy();v._hls=null;}}if(fr&&fr.src)fr.src='';}});cur=i;}
112
+ var sT;feed.addEventListener('scroll',function(){clearTimeout(sT);sT=setTimeout(function(){var rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,best=-1,bestD=1e9;slides.forEach(function(sl,i){var d=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(d<bestD){bestD=d;best=i;}});if(best>=0)act(best);},150);});
113
+ setTimeout(function(){act(0);},400);
114
+ slides.forEach(function(sl){var v=sl.querySelector('video');if(v)v.addEventListener('click',function(e){e.preventDefault();v.paused?v.play().catch(function(){}):v.pause();});});
115
+ // Load stats
116
+ var ids=[];slides.forEach(function(sl){if(sl.dataset.hlid)ids.push(sl.dataset.hlid);});
117
+ if(ids.length)fetch('/api/highlight/stats?ids='+ids.join(',')).then(function(r){return r.json()}).then(function(j){var stats=j.stats||{};slides.forEach(function(sl){var st=stats[sl.dataset.hlid];if(!st)return;var r=sl.querySelector('.tiktok-right');if(!r)return;var vc=r.querySelector('[data-a="views"]');if(vc)vc.textContent=st.views||0;var lc=r.querySelector('[data-a="likes"]');if(lc)lc.textContent=st.likes||0;var sc=r.querySelector('[data-a="shares"]');if(sc)sc.textContent=st.shares||0;});}).catch(function(){});
118
  };
119
 
120
+ // Highlight interaction
121
+ window.hlAct=async function(btn,action){
122
+ var slide=btn?btn.closest('.tiktok-slide'):null;var id=slide?slide.dataset.hlid:'';if(!id)return;
123
+ try{var r=await fetch('/api/highlight/interact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,action:action})});var j=await r.json();if(j.stats&&slide){var right=slide.querySelector('.tiktok-right');if(right){var vc=right.querySelector('[data-a="views"]');if(vc)vc.textContent=j.stats.views||0;var lc=right.querySelector('[data-a="likes"]');if(lc)lc.textContent=j.stats.likes||0;var sc=right.querySelector('[data-a="shares"]');if(sc)sc.textContent=j.stats.shares||0;}}}catch(e){}
 
 
 
 
 
 
 
 
 
 
 
124
  };
 
 
 
 
 
 
 
 
 
125
 
126
  // 16:9 toggle
127
+ window.toggleHlRatio=function(btn){var slide=btn.closest('.tiktok-slide');if(!slide)return;slide.classList.toggle('ratio-wide');var label=btn.querySelector('.count');if(label)label.textContent=slide.classList.contains('ratio-wide')?'1:1':'16:9';};
128
+
129
+ // Comments panel for highlights
130
+ window.openHlComments=async function(id){
131
+ var panel=document.getElementById('hl-ask-panel');
132
+ var j=await fetch('/api/short/comments?id='+id).then(function(r){return r.json()}).catch(function(){return{comments:[]}});
133
+ var cmts=j.comments||[];
134
+ panel.innerHTML='<h3 style="color:#5cb87a;font-size:14px">💬 Bình luận</h3><div id="hl-cmt-list">'+(cmts.map(function(c){return'<div style="background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px">'+esc(c.text)+'</div>'}).join('')||'<div style="color:#777;font-size:12px">Chưa có bình luận</div>')+'</div><textarea id="hl-cmt-text" placeholder="Nhập bình luận..."></textarea><button onclick="submitHlCmt(\''+id+'\')">Gửi</button><button onclick="document.getElementById(\'hl-ask-panel\').classList.remove(\'active\')">Đóng</button>';
135
+ panel.classList.add('active');
136
+ };
137
+ window.submitHlCmt=async function(id){var t=document.getElementById('hl-cmt-text');if(!t||!t.value.trim())return;var j=await fetch('/api/short/comment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,text:t.value.trim()})}).then(function(r){return r.json()}).catch(function(){return{comments:[]}});document.getElementById('hl-cmt-list').innerHTML=(j.comments||[]).map(function(c){return'<div style="background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px">'+esc(c.text)+'</div>'}).join('');t.value='';};
138
+
139
+ // Ask AI about highlight
140
+ window.openHlAsk=function(id,title){
141
+ var panel=document.getElementById('hl-ask-panel');
142
+ panel.innerHTML='<h3 style="color:#5cb87a;font-size:14px">🤖 Hỏi AI về video</h3><input id="hl-ask-q" placeholder="Hỏi về video: '+esc(title)+'..."><div id="hl-ask-ans" class="hl-ask-answer"></div><button onclick="submitHlAsk(\''+id+'\',\''+esc(title)+'\')">Hỏi</button><button onclick="document.getElementById(\'hl-ask-panel\').classList.remove(\'active\')">Đóng</button>';
143
+ panel.classList.add('active');
144
  };
145
+ window.submitHlAsk=async function(id,title){var q=document.getElementById('hl-ask-q');if(!q||!q.value.trim())return;var ans=document.getElementById('hl-ask-ans');ans.textContent='Đang hỏi...';try{var r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({question:q.value.trim(),context:'Video highlight bóng đá: '+decodeURIComponent(title||id)})});var j=await r.json();ans.textContent=j.answer||'Không trả lời được';}catch(e){ans.textContent='Lỗi: '+e.message}};
 
146
  })();
147
  </script>
148
  '''
149
 
150
  EXTRA_WALL_FIX = r'''
151
+ <style>[data-wall-live="1"]{display:none!important}</style>
 
 
152
  <script>
153
  (function(){
154
+ var _wc=setInterval(function(){
155
+ var home=document.getElementById('view-home');if(!home||!home.classList.contains('active'))return;
156
+ var has=document.getElementById('short-ai-final-slide');
157
+ if(!has&&typeof renderShortAISlide==='function')renderShortAISlide();
158
+ if(!document.querySelector('.slider-wrap[data-wall-live]')){
 
 
159
  fetch('/api/ai_wall').then(function(r){return r.json()}).then(function(j){
160
  var posts=(j&&j.posts)||[];if(!posts.length)return;
161
  if(typeof window._serverWall!=='undefined')window._serverWall=posts;
162
+ if(typeof prependWallPost==='function')prependWallPost(posts[0]);
163
  }).catch(function(){});
164
  }
165
  },4000);
166
+ setTimeout(function(){clearInterval(_wc);},30000);
167
  })();
168
  </script>
169
  '''
170
 
171
  @app.get('/')
172
  async def _index_fixed():
173
+ html=f5.f4.f3.f2.f1._load_index_html()
174
+ body=''
175
+ body+=getattr(rt.old,'PATCH_INJECT','')
176
+ body+=f5.f4.f3.f2.f1.FINAL_INJECT+f5.f4.f3.FINAL3_INJECT+f5.f4.FINAL4_INJECT+f5.FINAL5_INJECT
177
+ body+=getattr(f6,'FINAL6_INJECT','')
178
+ body+=getattr(f6,'FINAL6_FAST_HOME_INJECT','')
179
+ body+=getattr(f6,'FINAL6E_INJECT','')
180
+ body+=PATCH_INJECT
181
+ body+=UNIFIED_INJECT_FIXED
182
+ body+=HIGHLIGHT_FULL_OVERRIDE # This LAST override of openLeaguePlayer wins
183
+ body+=EXTRA_WALL_FIX
184
+ return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)