Spaces:
Running
Running
Serve clean index_v2.html frontend, remove comments, mount /static
Browse files- app_main.py +32 -93
app_main.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
-
"""
|
| 2 |
from app_run import *
|
| 3 |
from app_run import app, f5, f6, rt, PATCH_INJECT, UNIFIED_INJECT_FIXED, HIGHLIGHT_FULL_OVERRIDE, EXTRA_WALL_FIX, FAST_HASHTAG_JS
|
| 4 |
-
from fastapi.responses import HTMLResponse, JSONResponse
|
|
|
|
| 5 |
from fastapi import Query
|
| 6 |
import requests as req
|
| 7 |
from urllib.parse import quote
|
| 8 |
from bs4 import BeautifulSoup
|
| 9 |
-
import re, html as html_lib
|
| 10 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 11 |
|
| 12 |
def _clean(s):return re.sub(r"\s+"," ",html_lib.unescape(str(s or ""))).strip()
|
|
@@ -103,9 +104,11 @@ def _search_all(topic, limit=30):
|
|
| 103 |
if i.get('url') and i['url'] not in seen:seen.add(i['url']);unique.append(i)
|
| 104 |
return unique[:limit]
|
| 105 |
|
|
|
|
| 106 |
app.router.routes=[r for r in app.router.routes if not (
|
| 107 |
(getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
|
| 108 |
-
(getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))
|
|
|
|
| 109 |
)]
|
| 110 |
|
| 111 |
@app.get('/api/hashtag/sources')
|
|
@@ -114,97 +117,33 @@ def _ht(topic:str=Query(...), page:int=Query(default=0)):
|
|
| 114 |
per_page=6;start=page*per_page;end=start+per_page
|
| 115 |
return JSONResponse({'sources':all_items[start:end],'topic':topic,'page':page,'has_more':end<len(all_items),'total':len(all_items)})
|
| 116 |
|
| 117 |
-
#
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
/* INSTANTLY hide all news grids on homepage — they use .section-title + .grid directly under #view-home */
|
| 121 |
-
#view-home>.section-title,#view-home>.grid{display:none!important}
|
| 122 |
-
</style>
|
| 123 |
-
<script>
|
| 124 |
-
(function(){
|
| 125 |
-
function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]});}
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
var btn=document.createElement('div');
|
| 132 |
-
btn.className='cat';btn.dataset.cat='news-all';btn.textContent='📰 Tin tức';
|
| 133 |
-
btn.onclick=function(){switchToNewsTab();};
|
| 134 |
-
var videoTab=bar.querySelector('[data-cat="video"]');
|
| 135 |
-
if(videoTab)videoTab.after(btn);else bar.appendChild(btn);
|
| 136 |
-
}
|
| 137 |
-
function switchToNewsTab(){
|
| 138 |
-
document.querySelectorAll('.cat').forEach(function(c){c.classList.remove('active')});
|
| 139 |
-
document.querySelector('[data-cat="news-all"]')?.classList.add('active');
|
| 140 |
-
showView('view-cat');loadNewsTab();
|
| 141 |
-
}
|
| 142 |
-
async function loadNewsTab(){
|
| 143 |
-
var el=document.getElementById('view-cat');
|
| 144 |
-
el.innerHTML='<div class="loading">Đang tải tin tức...</div>';
|
| 145 |
-
try{
|
| 146 |
-
var r=await fetch('/api/homepage');var news=await r.json();
|
| 147 |
-
if(!news.length){el.innerHTML='<div class="loading">Không có tin</div>';return;}
|
| 148 |
-
var groups={};news.forEach(function(a){if(!groups[a.group])groups[a.group]=[];groups[a.group].push(a);});
|
| 149 |
-
var h='';
|
| 150 |
-
for(var g in groups){var arts=groups[g];h+='<div class="section-title">'+esc(g)+'</div><div class="grid">';arts.slice(0,6).forEach(function(a){var bg=a.source==='bbc'?'badge-bbc':a.source==='dantri'?'badge-dt':'badge-vne';h+='<div class="card" onclick="readArticle(\''+esc(a.link)+'\')"><div class="card-img">'+(a.img?'<img src="'+esc(a.img)+'">':'')+'</div><div class="card-body"><span class="badge '+bg+'">'+esc(a.source)+'</span><div class="card-title">'+esc(a.title)+'</div></div></div>';});h+='</div>';}
|
| 151 |
-
el.innerHTML=h;
|
| 152 |
-
}catch(e){el.innerHTML='<div class="loading">Lỗi</div>';}
|
| 153 |
-
}
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
if(topic&&typeof showHashtagSources==='function'){showHashtagSources(topic);return true;}
|
| 161 |
-
}
|
| 162 |
-
return false;
|
| 163 |
-
}
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
</
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
HASHTAG_FIX_JS = r'''
|
| 172 |
-
<script>
|
| 173 |
-
(function(){
|
| 174 |
-
function patchChips(){
|
| 175 |
-
document.querySelectorAll('.hot-chip:not([data-fixed])').forEach(function(chip){
|
| 176 |
-
chip.dataset.fixed='1';
|
| 177 |
-
var oc=chip.getAttribute('onclick')||'';
|
| 178 |
-
var m=oc.match(/\.value='([^']+)'/);
|
| 179 |
-
var topic=m?m[1]:chip.textContent.replace(/^#/,'').trim();
|
| 180 |
-
chip.dataset.topic=topic;
|
| 181 |
-
chip.removeAttribute('onclick');
|
| 182 |
-
chip.addEventListener('click',function(e){
|
| 183 |
-
e.preventDefault();e.stopPropagation();
|
| 184 |
-
if(typeof showHashtagSources==='function')showHashtagSources(topic);
|
| 185 |
-
});
|
| 186 |
-
});
|
| 187 |
-
}
|
| 188 |
-
setTimeout(patchChips,1500);
|
| 189 |
-
setInterval(patchChips,2000);
|
| 190 |
-
})();
|
| 191 |
-
</script>
|
| 192 |
-
'''
|
| 193 |
|
|
|
|
| 194 |
@app.get('/')
|
| 195 |
-
async def
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
body+=getattr(f6,'FINAL6E_INJECT','')
|
| 203 |
-
body+=PATCH_INJECT
|
| 204 |
-
body+=UNIFIED_INJECT_FIXED
|
| 205 |
-
body+=HIGHLIGHT_FULL_OVERRIDE
|
| 206 |
-
body+=EXTRA_WALL_FIX
|
| 207 |
-
body+=FAST_HASHTAG_JS
|
| 208 |
-
body+=HASHTAG_FIX_JS
|
| 209 |
-
body+=HOMEPAGE_RESTRUCTURE_JS
|
| 210 |
-
return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)
|
|
|
|
| 1 |
+
"""VNEWS v2 - Clean frontend served from static/index_v2.html. Comments removed."""
|
| 2 |
from app_run import *
|
| 3 |
from app_run import app, f5, f6, rt, PATCH_INJECT, UNIFIED_INJECT_FIXED, HIGHLIGHT_FULL_OVERRIDE, EXTRA_WALL_FIX, FAST_HASHTAG_JS
|
| 4 |
+
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
from fastapi import Query
|
| 7 |
import requests as req
|
| 8 |
from urllib.parse import quote
|
| 9 |
from bs4 import BeautifulSoup
|
| 10 |
+
import re, html as html_lib, os
|
| 11 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 12 |
|
| 13 |
def _clean(s):return re.sub(r"\s+"," ",html_lib.unescape(str(s or ""))).strip()
|
|
|
|
| 104 |
if i.get('url') and i['url'] not in seen:seen.add(i['url']);unique.append(i)
|
| 105 |
return unique[:limit]
|
| 106 |
|
| 107 |
+
# Remove old routes we're overriding
|
| 108 |
app.router.routes=[r for r in app.router.routes if not (
|
| 109 |
(getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
|
| 110 |
+
(getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set())) or
|
| 111 |
+
(getattr(r,'path',None) in ('/api/short/comments','/api/short/comment'))
|
| 112 |
)]
|
| 113 |
|
| 114 |
@app.get('/api/hashtag/sources')
|
|
|
|
| 117 |
per_page=6;start=page*per_page;end=start+per_page
|
| 118 |
return JSONResponse({'sources':all_items[start:end],'topic':topic,'page':page,'has_more':end<len(all_items),'total':len(all_items)})
|
| 119 |
|
| 120 |
+
# Mount static files directory
|
| 121 |
+
STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
|
| 122 |
+
app.mount('/static', StaticFiles(directory=STATIC_DIR), name='static')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
# Categories endpoint for v2 frontend
|
| 125 |
+
@app.get('/api/categories')
|
| 126 |
+
def _categories():
|
| 127 |
+
return JSONResponse([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
# Storage status
|
| 130 |
+
@app.get('/api/storage_status')
|
| 131 |
+
def _storage():
|
| 132 |
+
data_dir='/data'
|
| 133 |
+
return JSONResponse({'persistent':os.path.isdir(data_dir) and os.access(data_dir, os.W_OK)})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
# Share page
|
| 136 |
+
@app.get('/s')
|
| 137 |
+
async def _share(url:str='',title:str='',img:str=''):
|
| 138 |
+
html=f'<!DOCTYPE html><html><head><meta property="og:title" content="{_clean(title)}"><meta property="og:url" content="{_clean(url)}"><meta property="og:image" content="{_clean(img)}"><meta property="og:type" content="article"><meta property="og:site_name" content="VNEWS"><meta http-equiv="refresh" content="0;url={_clean(url) or "/"}"></head><body>Redirecting...</body></html>'
|
| 139 |
+
return HTMLResponse(html)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
+
# === SERVE CLEAN V2 FRONTEND ===
|
| 142 |
@app.get('/')
|
| 143 |
+
async def _index_v2():
|
| 144 |
+
"""Serve the clean v2 frontend - no injection layers."""
|
| 145 |
+
index_path = os.path.join(STATIC_DIR, 'index_v2.html')
|
| 146 |
+
if os.path.exists(index_path):
|
| 147 |
+
return FileResponse(index_path, media_type='text/html')
|
| 148 |
+
# Fallback: old method if file missing
|
| 149 |
+
return HTMLResponse('<html><body><h1>VNEWS</h1><p>Building...</p></body></html>')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|