Spaces:
Sleeping
Sleeping
Fix: override openLeaguePlayer completely in HIGHLIGHT_INTERACT_JS with all buttons"
Browse files- app_final.py +109 -107
app_final.py
CHANGED
|
@@ -1,28 +1,23 @@
|
|
| 1 |
-
"""Final wrapper
|
| 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
|
| 9 |
-
os.makedirs(DATA_DIR,
|
| 10 |
-
HL_STATS_FILE
|
| 11 |
|
| 12 |
-
def
|
| 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
|
| 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
|
| 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=
|
| 37 |
-
st=
|
| 38 |
-
|
| 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=
|
| 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 |
-
#
|
| 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
|
| 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 |
-
#
|
| 73 |
-
|
| 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 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
};
|
| 103 |
|
| 104 |
-
//
|
| 105 |
-
window.
|
| 106 |
-
var
|
| 107 |
-
var
|
| 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 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
var
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
};
|
| 138 |
-
//
|
| 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
|
| 151 |
-
var home=document.getElementById('view-home');
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
if(!
|
| 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'
|
| 161 |
}).catch(function(){});
|
| 162 |
}
|
| 163 |
},4000);
|
| 164 |
-
setTimeout(function(){clearInterval(
|
| 165 |
})();
|
| 166 |
</script>
|
| 167 |
'''
|
| 168 |
|
| 169 |
@app.get('/')
|
| 170 |
async def _index_fixed():
|
| 171 |
-
html
|
| 172 |
-
body
|
| 173 |
-
body
|
| 174 |
-
body
|
| 175 |
-
body
|
| 176 |
-
body
|
| 177 |
-
body
|
| 178 |
-
body
|
| 179 |
-
body
|
| 180 |
-
body
|
| 181 |
-
body
|
| 182 |
-
return HTMLResponse(html.replace('</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{'&':'&','<':'<','>':'>','"':'"',"'":'''}[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)
|