bep40 commited on
Commit
fd0fe75
·
verified ·
1 Parent(s): 9b3c04d

Fix: readArticle supports ALL URLs via /api/article scrape, no external links"

Browse files
Files changed (1) hide show
  1. app_run.py +78 -18
app_run.py CHANGED
@@ -1,4 +1,4 @@
1
- """Wrapper: faster + relevant hashtag sources via Google News search."""
2
  from app_final import *
3
  from app_final import app, f6, f5, rt, PATCH_INJECT, UNIFIED_INJECT_FIXED, HIGHLIGHT_FULL_OVERRIDE, EXTRA_WALL_FIX
4
  from fastapi.responses import HTMLResponse, JSONResponse
@@ -11,7 +11,6 @@ import re, html as html_lib
11
  def _clean(s):return re.sub(r"\s+"," ",html_lib.unescape(str(s or ""))).strip()
12
 
13
  def _google_news_search(topic, limit=8):
14
- """Search Google News RSS for topic-specific articles — much more relevant than generic RSS pool."""
15
  items=[]
16
  try:
17
  url='https://news.google.com/rss/search?q='+quote(topic+' tin tức')+'&hl=vi&gl=VN&ceid=VN:vi'
@@ -22,9 +21,7 @@ def _google_news_search(topic, limit=8):
22
  link=_clean(it.find('link').get_text(strip=True) if it.find('link') else '')
23
  src=_clean(it.find('source').get_text(' ',strip=True) if it.find('source') else '')
24
  if not title or not link:continue
25
- # Relevance check: topic keywords must appear in title
26
- topic_lower=topic.lower()
27
- title_lower=title.lower()
28
  topic_words=[w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',topic_lower) if len(w)>1]
29
  match_count=sum(1 for w in topic_words if w in title_lower)
30
  if match_count==0 and topic_lower not in title_lower:continue
@@ -34,7 +31,6 @@ def _google_news_search(topic, limit=8):
34
  return items
35
 
36
  def _duckduckgo_search(topic, limit=6):
37
- """Fallback search via DuckDuckGo HTML."""
38
  items=[]
39
  try:
40
  url='https://html.duckduckgo.com/html/?q='+quote(topic+' Việt Nam tin tức')
@@ -45,7 +41,6 @@ def _duckduckgo_search(topic, limit=6):
45
  if not a:continue
46
  title=_clean(a.get_text(' ',strip=True))
47
  href=a.get('href','')
48
- # Unwrap DDG redirect
49
  if 'duckduckgo.com/l/' in href:
50
  from urllib.parse import parse_qs,urlparse,unquote
51
  qs=parse_qs(urlparse(href if href.startswith('http') else 'https:'+href).query)
@@ -59,7 +54,6 @@ def _duckduckgo_search(topic, limit=6):
59
  except:pass
60
  return items
61
 
62
- # Override endpoints
63
  app.router.routes=[r for r in app.router.routes if not (
64
  (getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
65
  (getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))
@@ -67,17 +61,13 @@ app.router.routes=[r for r in app.router.routes if not (
67
 
68
  @app.get('/api/hashtag/sources')
69
  def _hashtag_relevant(topic:str=Query(...)):
70
- """Return RELEVANT sources by searching Google News + DuckDuckGo for the specific topic."""
71
  sources=_google_news_search(topic,8)
72
- if len(sources)<3:
73
- sources+=_duckduckgo_search(topic,6-len(sources))
74
- # Dedupe by URL
75
  seen=set();unique=[]
76
  for s in sources:
77
  if s['url'] not in seen:seen.add(s['url']);unique.append(s)
78
  return JSONResponse({'sources':unique[:8],'topic':topic})
79
 
80
- # Extra JS: override showHashtagSources with spinner + lazy img + topic input integration
81
  FAST_HASHTAG_JS = r'''
82
  <style>
83
  .hashtag-loading{display:flex;align-items:center;gap:8px;padding:12px;color:#888;font-size:12px}
@@ -88,6 +78,67 @@ FAST_HASHTAG_JS = r'''
88
  (function(){
89
  function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]});}
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  window.showHashtagSources=async function(topic){
92
  var home=document.getElementById('view-home');if(!home)return;
93
  document.getElementById('hashtag-sources-box')?.remove();
@@ -102,13 +153,14 @@ window.showHashtagSources=async function(topic){
102
  if(!sources.length){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#888;font-size:12px;padding:8px">Không tìm được nguồn liên quan</div>';return;}
103
  var h='<h3>🔍 '+esc(topic)+' <span style="font-size:10px;color:#888">('+sources.length+' nguồn)</span></h3>';
104
  sources.forEach(function(s,i){
105
- h+='<div class="hashtag-src-item" onclick="if(typeof readArticle===\'function\')readArticle(\''+esc(s.url||'')+'\')">';
106
  h+='<div class="hashtag-src-img" id="ht-img-'+i+'"></div>';
107
  h+='<div class="hashtag-src-text"><div class="hashtag-src-title">'+esc(s.title)+'</div><div class="hashtag-src-via">'+esc(s.via||'')+'</div>'+(s.snippet?'<div style="font-size:10px;color:#999;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">'+esc(s.snippet)+'</div>':'')+'</div>';
108
  h+='</div>';
109
  });
110
  h+='<button class="hashtag-rewrite-btn" onclick="rewriteHashtagTopic(\''+esc(topic)+'\')">🤖 Rewrite AI tổng hợp nguồn & đăng tường</button>';
111
  box.innerHTML=h;
 
112
  sources.forEach(function(s,i){
113
  if(!s.url)return;
114
  fetch('/api/article?url='+encodeURIComponent(s.url)).then(function(r){return r.json()}).then(function(d){
@@ -118,19 +170,27 @@ window.showHashtagSources=async function(topic){
118
  }catch(e){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#e74c3c;font-size:12px;padding:8px">Lỗi: '+esc(e.message)+'</div>';}
119
  };
120
 
 
 
 
 
 
 
 
 
 
 
121
  window.createTopicPost=function(){
122
  var inp=document.getElementById('ai-topic-input');
123
  var topic=(inp&&inp.value||'').trim();
124
  if(!topic){alert('Nhập chủ đề trước');return;}
125
- showHashtagSources(topic);
126
- if(inp)inp.value='';
127
  };
128
  window.createTopicPostFinal5=function(){
129
  var inp=document.getElementById('ai-topic-input-final5')||document.getElementById('ai-topic-input');
130
  var topic=(inp&&inp.value||'').trim();
131
  if(!topic){alert('Nhập chủ đề trước');return;}
132
- showHashtagSources(topic);
133
- if(inp)inp.value='';
134
  };
135
  })();
136
  </script>
 
1
+ """Wrapper: relevant hashtag + ALL articles readable in-app via scrape."""
2
  from app_final import *
3
  from app_final import app, f6, f5, rt, PATCH_INJECT, UNIFIED_INJECT_FIXED, HIGHLIGHT_FULL_OVERRIDE, EXTRA_WALL_FIX
4
  from fastapi.responses import HTMLResponse, JSONResponse
 
11
  def _clean(s):return re.sub(r"\s+"," ",html_lib.unescape(str(s or ""))).strip()
12
 
13
  def _google_news_search(topic, limit=8):
 
14
  items=[]
15
  try:
16
  url='https://news.google.com/rss/search?q='+quote(topic+' tin tức')+'&hl=vi&gl=VN&ceid=VN:vi'
 
21
  link=_clean(it.find('link').get_text(strip=True) if it.find('link') else '')
22
  src=_clean(it.find('source').get_text(' ',strip=True) if it.find('source') else '')
23
  if not title or not link:continue
24
+ topic_lower=topic.lower();title_lower=title.lower()
 
 
25
  topic_words=[w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',topic_lower) if len(w)>1]
26
  match_count=sum(1 for w in topic_words if w in title_lower)
27
  if match_count==0 and topic_lower not in title_lower:continue
 
31
  return items
32
 
33
  def _duckduckgo_search(topic, limit=6):
 
34
  items=[]
35
  try:
36
  url='https://html.duckduckgo.com/html/?q='+quote(topic+' Việt Nam tin tức')
 
41
  if not a:continue
42
  title=_clean(a.get_text(' ',strip=True))
43
  href=a.get('href','')
 
44
  if 'duckduckgo.com/l/' in href:
45
  from urllib.parse import parse_qs,urlparse,unquote
46
  qs=parse_qs(urlparse(href if href.startswith('http') else 'https:'+href).query)
 
54
  except:pass
55
  return items
56
 
 
57
  app.router.routes=[r for r in app.router.routes if not (
58
  (getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
59
  (getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))
 
61
 
62
  @app.get('/api/hashtag/sources')
63
  def _hashtag_relevant(topic:str=Query(...)):
 
64
  sources=_google_news_search(topic,8)
65
+ if len(sources)<3:sources+=_duckduckgo_search(topic,6-len(sources))
 
 
66
  seen=set();unique=[]
67
  for s in sources:
68
  if s['url'] not in seen:seen.add(s['url']);unique.append(s)
69
  return JSONResponse({'sources':unique[:8],'topic':topic})
70
 
 
71
  FAST_HASHTAG_JS = r'''
72
  <style>
73
  .hashtag-loading{display:flex;align-items:center;gap:8px;padding:12px;color:#888;font-size:12px}
 
78
  (function(){
79
  function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]});}
80
 
81
+ // === OVERRIDE readArticle to support ALL URLs via /api/article scrape ===
82
+ var _origReadArticle=window.readArticle;
83
+ window.readArticle=async function(url,source){
84
+ // Always try to read in-app first via /api/article
85
+ showView('view-article');
86
+ var el=document.getElementById('view-article');
87
+ el.innerHTML='<div class="loading">Đang tải bài viết...</div>';
88
+ try{
89
+ var r=await fetch('/api/article?url='+encodeURIComponent(url));
90
+ var data=await r.json();
91
+ if(data&&!data.error&&data.body&&data.body.length){
92
+ window._currentArticle={url:url,data:data};
93
+ var h='<button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button><div class="article-view"><h1 class="article-title">'+esc(data.title)+'</h1>';
94
+ if(data.summary)h+='<div class="article-summary">'+esc(data.summary)+'</div>';
95
+ var seenImgs={};
96
+ data.body.forEach(function(b){
97
+ if(b.type==='p')h+='<p class="article-p">'+b.text+'</p>';
98
+ else if(b.type==='img'&&b.src&&!seenImgs[b.src]){seenImgs[b.src]=1;h+='<img class="article-img" src="'+esc(b.src)+'">';}
99
+ else if(b.type==='heading')h+='<h2 class="article-h2">'+esc(b.text)+'</h2>';
100
+ });
101
+ h+='<div class="article-actions"><button class="primary" onclick="doRewriteArticle(this)">🤖 Rewrite AI đăng tường</button><button onclick="doShare(\''+esc(data.title)+'\',\''+esc(url)+'\',\''+esc(data.og_image||'')+'\')">📤 Chia sẻ</button><button onclick="window.open(\''+esc(url)+'\',\'_blank\')">🔗 Gốc</button></div>';
102
+ h+='<div class="article-ai-ask"><h3 style="font-size:14px;color:#5cb87a">🤖 Hỏi AI</h3><textarea id="article-ai-question" placeholder="Hỏi về bài viết..."></textarea><button onclick="askArticleAI()">Hỏi</button><div id="article-ai-answer" class="article-ai-answer"></div></div>';
103
+ h+='</div>';
104
+ el.innerHTML=h;
105
+ window.scrollTo(0,0);
106
+ return;
107
+ }
108
+ }catch(e){}
109
+ // Fallback: try original readArticle for supported domains
110
+ if(_origReadArticle){
111
+ try{await _origReadArticle(url,source);return;}catch(e){}
112
+ }
113
+ // Last fallback: show link to open externally
114
+ el.innerHTML='<button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button><div class="loading"><p>Không đọc được bài này trong app.</p><a href="'+esc(url)+'" target="_blank" style="color:#5cb87a">Mở link gốc →</a></div>';
115
+ };
116
+
117
+ // doRewriteArticle for articles opened from hashtag sources
118
+ window.doRewriteArticle=async function(btn){
119
+ var url=(window._currentArticle&&window._currentArticle.url)||'';
120
+ if(!url){alert('Không có URL');return;}
121
+ var ctx=document.querySelector('.article-view')?.innerText?.slice(0,14000)||'';
122
+ btn.disabled=true;btn.textContent='Đang rewrite...';
123
+ try{
124
+ var r=await fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:url,context:ctx})});
125
+ var j=await r.json();
126
+ if(!r.ok||j.error)throw new Error(j.error||'Lỗi');
127
+ alert('Đã rewrite và đăng lên Tường AI!');
128
+ }catch(e){alert(e.message);}
129
+ finally{btn.disabled=false;btn.textContent='🤖 Rewrite AI đăng tường';}
130
+ };
131
+
132
+ window.askArticleAI=async function(){
133
+ var q=document.getElementById('article-ai-question')?.value.trim();
134
+ if(!q)return alert('Nhập câu hỏi');
135
+ var a=document.getElementById('article-ai-answer');a.textContent='Đang hỏi...';
136
+ var url=(window._currentArticle&&window._currentArticle.url)||'';
137
+ var ctx=document.querySelector('.article-view')?.innerText?.slice(0,12000)||'';
138
+ try{var r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:url,question:q,context:ctx})});var j=await r.json();a.textContent=j.answer||'Không trả lời được';}catch(e){a.textContent='Lỗi: '+e.message}
139
+ };
140
+
141
+ // === HASHTAG SOURCES ===
142
  window.showHashtagSources=async function(topic){
143
  var home=document.getElementById('view-home');if(!home)return;
144
  document.getElementById('hashtag-sources-box')?.remove();
 
153
  if(!sources.length){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#888;font-size:12px;padding:8px">Không tìm được nguồn liên quan</div>';return;}
154
  var h='<h3>🔍 '+esc(topic)+' <span style="font-size:10px;color:#888">('+sources.length+' nguồn)</span></h3>';
155
  sources.forEach(function(s,i){
156
+ h+='<div class="hashtag-src-item" onclick="readArticle(\''+esc(s.url||'')+'\')">';
157
  h+='<div class="hashtag-src-img" id="ht-img-'+i+'"></div>';
158
  h+='<div class="hashtag-src-text"><div class="hashtag-src-title">'+esc(s.title)+'</div><div class="hashtag-src-via">'+esc(s.via||'')+'</div>'+(s.snippet?'<div style="font-size:10px;color:#999;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">'+esc(s.snippet)+'</div>':'')+'</div>';
159
  h+='</div>';
160
  });
161
  h+='<button class="hashtag-rewrite-btn" onclick="rewriteHashtagTopic(\''+esc(topic)+'\')">🤖 Rewrite AI tổng hợp nguồn & đăng tường</button>';
162
  box.innerHTML=h;
163
+ // Lazy load images
164
  sources.forEach(function(s,i){
165
  if(!s.url)return;
166
  fetch('/api/article?url='+encodeURIComponent(s.url)).then(function(r){return r.json()}).then(function(d){
 
170
  }catch(e){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#e74c3c;font-size:12px;padding:8px">Lỗi: '+esc(e.message)+'</div>';}
171
  };
172
 
173
+ window.rewriteHashtagTopic=async function(topic){
174
+ var btn=event?.target;if(btn){btn.disabled=true;btn.textContent='Đang tổng hợp...';}
175
+ try{
176
+ var r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic:topic})});
177
+ var j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');
178
+ if(btn)btn.textContent='✅ Đã đăng lên Tường AI!';
179
+ setTimeout(function(){document.getElementById('hashtag-sources-box')?.remove();},2000);
180
+ }catch(e){if(btn){btn.disabled=false;btn.textContent='❌ '+e.message;}}
181
+ };
182
+
183
  window.createTopicPost=function(){
184
  var inp=document.getElementById('ai-topic-input');
185
  var topic=(inp&&inp.value||'').trim();
186
  if(!topic){alert('Nhập chủ đề trước');return;}
187
+ showHashtagSources(topic);if(inp)inp.value='';
 
188
  };
189
  window.createTopicPostFinal5=function(){
190
  var inp=document.getElementById('ai-topic-input-final5')||document.getElementById('ai-topic-input');
191
  var topic=(inp&&inp.value||'').trim();
192
  if(!topic){alert('Nhập chủ đề trước');return;}
193
+ showHashtagSources(topic);if(inp)inp.value='';
 
194
  };
195
  })();
196
  </script>