bep40 commited on
Commit
ea46a09
·
verified ·
1 Parent(s): 636795b

Delete static/shorts_fresh.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. static/shorts_fresh.js +0 -291
static/shorts_fresh.js DELETED
@@ -1,291 +0,0 @@
1
- /**
2
- * VNEWS Fresh Shorts Fetcher v4
3
- * Fetches latest shorts from YouTube, translates titles to Vietnamese via noembed
4
- */
5
- (function() {
6
- 'use strict';
7
-
8
- const PROXY = 'https://api.allorigins.win/raw?url=';
9
- const NOEMBED = 'https://noembed.com/embed?url=';
10
- const CHANNELS = [
11
- { handle: 'baodantri7941', name: 'Dân trí', badge: 'DT' },
12
- { handle: 'baosuckhoedoisongboyte', name: 'SKĐS', badge: 'SKĐS' },
13
- ];
14
-
15
- function toast(msg) {
16
- let t = document.getElementById('short-progress-toast');
17
- if (!t) { t = document.createElement('div'); t.id = 'short-progress-toast'; document.body.appendChild(t); }
18
- t.textContent = msg; t.style.display = 'block';
19
- setTimeout(() => { t.style.display = 'none'; }, 4000);
20
- }
21
-
22
- function esc(s) {
23
- return String(s||'').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
24
- }
25
-
26
- function proxyImg(url) {
27
- if (!url || typeof url !== 'string') return '';
28
- if (url.startsWith('http') && !url.includes(location.host)) {
29
- return '/api/proxy/img?url=' + encodeURIComponent(url);
30
- }
31
- return url;
32
- }
33
-
34
- async function fetchWithRetry(url, maxRetries) {
35
- for (let i = 0; i < maxRetries; i++) {
36
- try {
37
- const r = await fetch(PROXY + encodeURIComponent(url));
38
- if (r.ok) {
39
- const text = await r.text();
40
- if (text.includes('ytInitialData')) return text;
41
- }
42
- await new Promise(r => setTimeout(r, 2000 * (i + 1)));
43
- } catch(e) {}
44
- }
45
- return null;
46
- }
47
-
48
- // Get Vietnamese title from noembed
49
- async function getViTitle(videoId) {
50
- try {
51
- const r = await fetch(NOEMBED + encodeURIComponent('https://www.youtube.com/watch?v=' + videoId));
52
- if (r.ok) {
53
- const data = await r.json();
54
- if (data && data.title) {
55
- return data.title.replace(/\s*\|\s*#shorts\s*$/i, '').trim();
56
- }
57
- }
58
- } catch(e) {}
59
- return null;
60
- }
61
-
62
- // Batch translate titles (with delay to avoid rate limit)
63
- async function translateTitles(shorts) {
64
- const results = [];
65
- for (const s of shorts) {
66
- const viTitle = await getViTitle(s.id);
67
- if (viTitle) {
68
- s.title = viTitle;
69
- }
70
- results.push(s);
71
- await new Promise(r => setTimeout(r, 300));
72
- }
73
- return results;
74
- }
75
-
76
- function extractShorts(html, handle) {
77
- const match = html.match(/var ytInitialData = ({.+?});<\/script>/s);
78
- if (!match) return [];
79
-
80
- const data = JSON.parse(match[1]);
81
- const tabs = data.contents.twoColumnBrowseResultsRenderer.tabs;
82
-
83
- let shortsTab = null;
84
- for (const tab of tabs) {
85
- const content = (tab.tabRenderer || {}).content || {};
86
- if (content.richGridRenderer) {
87
- shortsTab = content.richGridRenderer;
88
- break;
89
- }
90
- }
91
- if (!shortsTab) return [];
92
-
93
- const items = shortsTab.contents || [];
94
- const shorts = [];
95
- const seen = new Set();
96
-
97
- for (const item of items) {
98
- if (!item.richItemRenderer) continue;
99
- const inner = item.richItemRenderer.content || {};
100
-
101
- if (inner.shortsLockupViewModel) {
102
- const slvm = inner.shortsLockupViewModel;
103
- const cmd = (slvm.onTap || {}).innertubeCommand || {};
104
- const vid = (cmd.watchEndpoint || {}).videoId || (cmd.reelWatchEndpoint || {}).videoId || '';
105
- if (!vid || seen.has(vid)) continue;
106
- seen.add(vid);
107
-
108
- // Get title from overlay (may be English)
109
- let title = (slvm.overlayMetadata || {}).primaryText?.content || slvm.accessibilityText || 'YouTube Short';
110
- // Clean up title
111
- title = title.replace(/\s*,\s*\d+[\d,]*\s*views.*$/i, '').replace(/\s*-\s*play Short$/i, '').trim();
112
-
113
- let img = (slvm.thumbnail?.sources || [])[0]?.url || '';
114
-
115
- shorts.push({
116
- id: vid,
117
- title: title.substring(0, 120),
118
- img: img || ('https://i.ytimg.com/vi/' + vid + '/hqdefault.jpg'),
119
- link: 'https://www.youtube.com/shorts/' + vid,
120
- channel: handle,
121
- source: 'yt'
122
- });
123
- }
124
- else if (inner.reelItemRenderer) {
125
- const rir = inner.reelItemRenderer;
126
- const vid = rir.videoId || '';
127
- if (!vid || seen.has(vid)) continue;
128
- seen.add(vid);
129
- shorts.push({
130
- id: vid,
131
- title: (rir.headline || 'YouTube Short').substring(0, 120),
132
- img: 'https://i.ytimg.com/vi/' + vid + '/hqdefault.jpg',
133
- link: 'https://www.youtube.com/shorts/' + vid,
134
- channel: handle,
135
- source: 'yt'
136
- });
137
- }
138
- else if (inner.videoRenderer) {
139
- const vr = inner.videoRenderer;
140
- const vid = vr.videoId || '';
141
- if (!vid || seen.has(vid)) continue;
142
- seen.add(vid);
143
- const runs = ((vr.title || {}).runs) || [];
144
- const title = runs.map(r => r.text).join('') || 'YouTube Short';
145
- shorts.push({
146
- id: vid,
147
- title: title.substring(0, 120),
148
- img: 'https://i.ytimg.com/vi/' + vid + '/hqdefault.jpg',
149
- link: 'https://www.youtube.com/shorts/' + vid,
150
- channel: handle,
151
- source: 'yt'
152
- });
153
- }
154
-
155
- if (shorts.length >= 20) break;
156
- }
157
-
158
- // Regex fallback
159
- if (shorts.length === 0) {
160
- const vids = [...new Set([...html.matchAll(/"videoId":"([A-Za-z0-9_-]{11})"/g)].map(m => m[1]))];
161
- for (const vid of vids) {
162
- if (!seen.has(vid)) {
163
- seen.add(vid);
164
- shorts.push({
165
- id: vid,
166
- title: 'YouTube Short from @' + handle,
167
- img: 'https://i.ytimg.com/vi/' + vid + '/hqdefault.jpg',
168
- link: 'https://www.youtube.com/shorts/' + vid,
169
- channel: handle,
170
- source: 'yt'
171
- });
172
- if (shorts.length >= 20) break;
173
- }
174
- }
175
- }
176
-
177
- return shorts;
178
- }
179
-
180
- function interleaveShorts(shorts) {
181
- const dt = shorts.filter(s => s.channel === 'baodantri7941');
182
- const sk = shorts.filter(s => s.channel === 'baosuckhoedoisongboyte');
183
- const result = [];
184
- let i = 0, j = 0;
185
- while (i < dt.length || j < sk.length) {
186
- if (i < dt.length) result.push(dt[i++]);
187
- if (j < sk.length) result.push(sk[j++]);
188
- }
189
- return result;
190
- }
191
-
192
- function rerenderShortsSlide(shorts) {
193
- let existingSlide = document.getElementById('shorts-live-slide');
194
- if (!existingSlide) {
195
- const labels = document.querySelectorAll('.slider-wrap .slider-label');
196
- for (const label of labels) {
197
- if ((label.textContent || '').includes('Shorts')) {
198
- existingSlide = label.closest('.slider-wrap');
199
- break;
200
- }
201
- }
202
- }
203
-
204
- const mixed = interleaveShorts(shorts);
205
- if (!mixed.length) return;
206
-
207
- let h = '<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS</span><span class="slider-note">🟢 Tiếng Việt · vừa cập nhật</span></div><div class="slider-track">';
208
- mixed.slice(0, 30).forEach((a, i) => {
209
- const badge = a.channel === 'baosuckhoedoisongboyte' ? 'SKĐS' : 'Dân trí';
210
- h += '<div class="slider-item shorts-item" onclick="openYTShortsFeed(' + i + ')"><div class="slider-thumb shorts-thumb">' + (a.img ? '<img src="' + esc(proxyImg(a.img)) + '" loading="lazy">' : '') + '<div class="card-play">▶</div></div><div class="slider-title"><span style="color:#f0c040;font-size:8px">' + badge + '</span> ' + esc(a.title) + '</div></div>';
211
- });
212
- h += '</div>';
213
-
214
- if (existingSlide) {
215
- existingSlide.innerHTML = h;
216
- existingSlide.id = 'shorts-live-slide';
217
- existingSlide.style.boxShadow = '0 0 8px #5cb87a';
218
- setTimeout(() => { existingSlide.style.boxShadow = 'none'; }, 2000);
219
- } else {
220
- const wrap = document.createElement('div');
221
- wrap.id = 'shorts-live-slide';
222
- wrap.className = 'slider-wrap';
223
- wrap.innerHTML = h;
224
- const homeEl = document.getElementById('view-home');
225
- if (homeEl) homeEl.insertBefore(wrap, homeEl.children[1] || null);
226
- }
227
- }
228
-
229
- window.refreshShorts = async function() {
230
- try {
231
- toast('🔄 Đang tải shorts mới...');
232
-
233
- const all = [];
234
- for (const ch of CHANNELS) {
235
- const url = 'https://www.youtube.com/@' + ch.handle + '/shorts';
236
- const html = await fetchWithRetry(url, 3);
237
- if (html) {
238
- const shorts = extractShorts(html, ch.handle);
239
- all.push(...shorts);
240
- }
241
- await new Promise(r => setTimeout(r, 1500));
242
- }
243
-
244
- const seen = new Set();
245
- const fresh = all.filter(s => seen.has(s.id) ? false : (seen.add(s.id), true));
246
-
247
- if (fresh.length > 0) {
248
- // Translate titles to Vietnamese
249
- toast('🇻🇳 Đang dịch tiêu đề tiếng Việt...');
250
- const translated = await translateTitles(fresh.slice(0, 12)); // Limit to avoid rate limit
251
- // Keep untranslated ones as-is
252
- for (let i = translated.length; i < fresh.length; i++) {
253
- translated.push(fresh[i]);
254
- }
255
-
256
- window._shortsData = translated;
257
- rerenderShortsSlide(translated);
258
- localStorage.setItem('vnews_shorts', JSON.stringify({ ts: Date.now(), data: translated }));
259
- toast('✓ Đã cập nhật ' + translated.length + ' shorts (TV)!');
260
- return translated;
261
- } else {
262
- toast('⚠️ Không tải được shorts mới');
263
- return window._shortsData || [];
264
- }
265
- } catch(e) {
266
- console.error('Shorts refresh error:', e);
267
- toast('⚠️ Lỗi, dùng cache cũ');
268
- return window._shortsData || [];
269
- }
270
- };
271
-
272
- function loadCache() {
273
- try {
274
- const c = JSON.parse(localStorage.getItem('vnews_shorts') || '{}');
275
- if (c.data && c.data.length > 0 && (Date.now() - c.ts) < 7200000) {
276
- return c.data;
277
- }
278
- } catch(e) {}
279
- return null;
280
- }
281
-
282
- const cached = loadCache();
283
- if (cached) {
284
- window._shortsData = cached;
285
- setTimeout(() => rerenderShortsSlide(cached), 3000);
286
- }
287
-
288
- setTimeout(() => { window.refreshShorts().catch(() => {}); }, 4000);
289
-
290
- console.log('[Shorts] Fresh fetcher v4 loaded (Vietnamese titles)');
291
- })();