bep40 commited on
Commit
547080f
·
verified ·
1 Parent(s): d376c09

Upload static/shorts_fresh.js

Browse files
Files changed (1) hide show
  1. static/shorts_fresh.js +95 -82
static/shorts_fresh.js CHANGED
@@ -1,11 +1,10 @@
1
  /**
2
- * VNEWS Fresh Shorts Fetcher v8 - 24h filter
3
- * Only shows posts from last 24h
4
  */
5
  (function() {
6
  'use strict';
7
 
8
- const MAX_AGE = 86400000; // 24h in ms
9
 
10
  function toast(msg) {
11
  let t = document.getElementById('short-progress-toast');
@@ -31,30 +30,28 @@
31
  return url;
32
  }
33
 
34
- // Filter items to only 24h
35
- function filter24h(items) {
36
  const now = Date.now();
37
  return items.filter(item => {
38
- // Check if item has created timestamp
39
- if (item.created) {
40
- return (now - item.created) <= MAX_AGE;
 
 
41
  }
42
- // Default: keep (server-side will filter)
43
- return true;
44
  });
45
  }
46
 
47
- // Fetch via server-side API
48
  async function fetchShorts() {
49
  try {
50
  const r = await fetch('/api/shorts?t=' + Date.now());
51
  if (r.ok) {
52
  let data = await r.json();
53
- // Filter 24h if items have timestamps
54
  if (Array.isArray(data)) {
55
  data = filter24h(data);
56
- }
57
- if (Array.isArray(data) && data.length > 0) {
58
  return data.map(item => ({
59
  id: item.id,
60
  title: item.title || 'YouTube Short',
@@ -65,26 +62,21 @@
65
  }));
66
  }
67
  }
68
- } catch(e) {
69
- console.warn('Server API failed:', e);
70
- }
71
  return [];
72
  }
73
 
74
- // Fetch from YouTube RSS
75
  async function fetchFromRSS() {
76
  try {
77
  const r = await fetch('/api/shorts/rss?t=' + Date.now());
78
  if (r.ok) {
79
  const data = await r.json();
80
  if (data.shorts && data.shorts.length > 0) {
81
- // RSS already filtered by server
82
- return data.shorts;
83
  }
84
  }
85
- } catch(e) {
86
- console.warn('RSS fetch failed:', e);
87
- }
88
  return [];
89
  }
90
 
@@ -101,16 +93,10 @@
101
  }
102
 
103
  function rerenderShortsSlide(shorts) {
104
- let existingSlide = document.getElementById('shorts-live-slide');
105
- if (!existingSlide) {
106
- const labels = document.querySelectorAll('.slider-wrap .slider-label');
107
- for (const label of labels) {
108
- if ((label.textContent || '').includes('Shorts')) {
109
- existingSlide = label.closest('.slider-wrap');
110
- break;
111
- }
112
- }
113
- }
114
 
115
  const mixed = interleaveShorts(shorts);
116
  if (!mixed.length) return;
@@ -118,71 +104,98 @@
118
  let h = '<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS (24h)</span><span class="slider-note">🟢 vừa cập nhật</span></div><div class="slider-track">';
119
  mixed.slice(0, 25).forEach((a, i) => {
120
  const badge = a.channel === 'baosuckhoedoisongboyte' ? 'SKĐS' : 'Dân trí';
121
- 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" onerror="this.src=\'https://i.ytimg.com/vi/' + a.id + '/hqdefault.jpg\'">' : '') + '<div class="card-play">▶</div></div><div class="slider-title"><span style="color:#f0c040;font-size:8px">' + badge + '</span> ' + esc(a.title) + '</div></div>';
 
 
122
  });
123
  h += '</div>';
124
 
125
  if (existingSlide) {
126
  existingSlide.innerHTML = h;
127
  existingSlide.id = 'shorts-live-slide';
128
- existingSlide.style.boxShadow = '0 0 8px #5cb87a';
129
- setTimeout(() => { existingSlide.style.boxShadow = 'none'; }, 2000);
130
  }
131
  }
132
 
133
  window.refreshShorts = async function() {
134
- try {
135
- toast('🔄 Đang tải shorts mới...');
136
-
137
- // Try RSS first (freshest)
138
- let all = await fetchFromRSS();
139
-
140
- // Fallback to server API
141
- if (all.length === 0) {
142
- console.log('[Shorts] RSS empty, trying server API...');
143
- all = await fetchShorts();
144
- }
145
-
146
- // Deduplicate
147
- const seen = new Set();
148
- const fresh = all.filter(s => seen.has(s.id) ? false : (seen.add(s.id), true));
149
-
150
- if (fresh.length > 0) {
151
- window._shortsData = fresh;
152
- rerenderShortsSlide(fresh);
153
- localStorage.setItem('vnews_shorts', JSON.stringify({ ts: Date.now(), data: fresh }));
154
- toast('✓ Đã cập nhật ' + fresh.length + ' shorts (24h)!');
155
- return fresh;
156
- } else {
157
- toast('⚠️ Không có shorts mới trong 24h');
158
- return window._shortsData || [];
159
- }
160
- } catch(e) {
161
- console.error('Shorts refresh error:', e);
162
- toast('⚠️ Lỗi tải shorts');
163
- return window._shortsData || [];
164
  }
165
  };
166
 
167
- function loadCache() {
168
- try {
169
- const c = JSON.parse(localStorage.getItem('vnews_shorts') || '{}');
170
- if (c.data && c.data.length > 0 && (Date.now() - c.ts) < 7200000) {
171
- return filter24h(c.data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
- } catch(e) {}
174
- return null;
175
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- // Load cache first
178
- const cached = loadCache();
179
- if (cached && cached.length > 0) {
180
- window._shortsData = cached;
181
- setTimeout(() => rerenderShortsSlide(cached), 2000);
182
- }
 
 
 
 
 
 
 
 
183
 
184
- // Fetch fresh after delay
185
  setTimeout(() => { window.refreshShorts().catch(() => {}); }, 3000);
186
 
187
- console.log('[Shorts] Fetcher v8 - 24h filter enabled');
188
  })();
 
1
  /**
2
+ * VNEWS Fresh Shorts Fetcher v8 - 24h filter for both shorts and wall
 
3
  */
4
  (function() {
5
  'use strict';
6
 
7
+ const MAX_AGE_MS = 86400000; // 24h in ms
8
 
9
  function toast(msg) {
10
  let t = document.getElementById('short-progress-toast');
 
30
  return url;
31
  }
32
 
33
+ // Filter items by 24h
34
+ function filter24h(items, timeField = 'created') {
35
  const now = Date.now();
36
  return items.filter(item => {
37
+ const created = item[timeField] || (typeof item.created === 'number' ? item.created * 1000 : item.created);
38
+ if (created) {
39
+ // Handle both timestamp (ms) and Unix timestamp (s)
40
+ const ts = created > 1000000000000 ? created : created * 1000;
41
+ return (now - ts) <= MAX_AGE_MS;
42
  }
43
+ return true; // Keep if no timestamp
 
44
  });
45
  }
46
 
47
+ // Fetch shorts from server API
48
  async function fetchShorts() {
49
  try {
50
  const r = await fetch('/api/shorts?t=' + Date.now());
51
  if (r.ok) {
52
  let data = await r.json();
 
53
  if (Array.isArray(data)) {
54
  data = filter24h(data);
 
 
55
  return data.map(item => ({
56
  id: item.id,
57
  title: item.title || 'YouTube Short',
 
62
  }));
63
  }
64
  }
65
+ } catch(e) {}
 
 
66
  return [];
67
  }
68
 
69
+ // Fetch shorts from YouTube RSS (fresh)
70
  async function fetchFromRSS() {
71
  try {
72
  const r = await fetch('/api/shorts/rss?t=' + Date.now());
73
  if (r.ok) {
74
  const data = await r.json();
75
  if (data.shorts && data.shorts.length > 0) {
76
+ return data.shorts; // Server already filtered
 
77
  }
78
  }
79
+ } catch(e) {}
 
 
80
  return [];
81
  }
82
 
 
93
  }
94
 
95
  function rerenderShortsSlide(shorts) {
96
+ const existingSlide = document.getElementById('shorts-live-slide') ||
97
+ Array.from(document.querySelectorAll('.slider-wrap')).find(el =>
98
+ (el.querySelector('.slider-label')?.textContent || '').includes('Shorts')
99
+ );
 
 
 
 
 
 
100
 
101
  const mixed = interleaveShorts(shorts);
102
  if (!mixed.length) return;
 
104
  let h = '<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS (24h)</span><span class="slider-note">🟢 vừa cập nhật</span></div><div class="slider-track">';
105
  mixed.slice(0, 25).forEach((a, i) => {
106
  const badge = a.channel === 'baosuckhoedoisongboyte' ? 'SKĐS' : 'Dân trí';
107
+ h += '<div class="slider-item shorts-item" onclick="openYTShortsFeed(' + i + ')"><div class="slider-thumb shorts-thumb">' +
108
+ (a.img ? '<img src="' + esc(proxyImg(a.img)) + '" loading="lazy" onerror="this.src=\'https://i.ytimg.com/vi/' + a.id + '/hqdefault.jpg\'">' : '') +
109
+ '<div class="card-play">▶</div></div><div class="slider-title"><span style="color:#f0c040;font-size:8px">' + badge + '</span> ' + esc(a.title) + '</div></div>';
110
  });
111
  h += '</div>';
112
 
113
  if (existingSlide) {
114
  existingSlide.innerHTML = h;
115
  existingSlide.id = 'shorts-live-slide';
 
 
116
  }
117
  }
118
 
119
  window.refreshShorts = async function() {
120
+ let all = await fetchFromRSS();
121
+ if (all.length === 0) all = await fetchShorts();
122
+
123
+ const seen = new Set();
124
+ const fresh = all.filter(s => seen.has(s.id) ? false : (seen.add(s.id), true));
125
+
126
+ if (fresh.length > 0) {
127
+ window._shortsData = fresh;
128
+ rerenderShortsSlide(fresh);
129
+ localStorage.setItem('vnews_shorts', JSON.stringify({ ts: Date.now(), data: fresh }));
130
+ toast('✓ ' + fresh.length + ' shorts (24h)!');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
  };
133
 
134
+ // Patch _renderWallIn to filter 24h
135
+ const origRenderWallIn = window._renderWallIn;
136
+ window._renderWallIn = function(afterEl) {
137
+ if (!window._wallPosts || !window._wallPosts.length || !afterEl) return;
138
+
139
+ // Filter to 24h
140
+ const now = Date.now();
141
+ const posts = window._wallPosts.filter(p => {
142
+ const created = p.created || 0;
143
+ const ts = created > 1000000000000 ? created : created * 1000;
144
+ return (now - ts) <= MAX_AGE_MS;
145
+ }).slice(0, 20);
146
+
147
+ if (!posts.length) return;
148
+
149
+ // Render Short AI slide (videos only)
150
+ const aiShorts = posts.filter(p => p.video);
151
+ if (aiShorts.length) {
152
+ let wrap = document.getElementById('short-ai-section');
153
+ if (!wrap) {
154
+ wrap = document.createElement('div');
155
+ wrap.className = 'slider-wrap';
156
+ wrap.id = 'short-ai-section';
157
+ afterEl.parentNode.insertBefore(wrap, afterEl);
158
  }
159
+ let h = '<div class="slider-header"><span class="slider-label">🎬 Short AI (24h)</span></div><div class="slider-track">';
160
+ aiShorts.forEach((p, i) => {
161
+ h += '<div class="slider-item shorts-item" onclick="openShortAIFeed(' + i + ')"><div class="slider-thumb shorts-thumb"><video src="' + esc(p.video) + '" muted preload="metadata"></video><div class="card-play">▶</div></div><div class="slider-title">' + esc(p.title) + '</div></div>';
162
+ });
163
+ h += '</div>';
164
+ wrap.innerHTML = h;
165
+ }
166
+
167
+ // Render AI Wall
168
+ wrap = document.createElement('div');
169
+ wrap.className = 'slider-wrap';
170
+ wrap.id = 'ai-wall-wrap';
171
+ let h = '<div class="slider-header"><span class="slider-label">🧱 Tường AI (24h)</span></div><div class="slider-track" id="ai-wall-track">';
172
+ posts.forEach((p, i) => {
173
+ h += '<div class="wall-item" id="wall-item-' + esc(p.id) + '"><div class="wall-thumb">' +
174
+ (p.img ? '<img src="' + proxyImg(p.img) + '">' : (p.video ? '<video src="' + esc(p.video) + '" muted></video>' : '')) +
175
+ '</div><div class="wall-title">' + esc(p.title) + '</div></div>';
176
+ });
177
+ h += '</div>';
178
+ wrap.innerHTML = h;
179
+ afterEl.parentNode.insertBefore(wrap, afterEl);
180
+ };
181
 
182
+ // Load cache
183
+ try {
184
+ const c = JSON.parse(localStorage.getItem('vnews_shorts') || '{}');
185
+ if (c.data && c.data.length > 0 && (Date.now() - c.ts) < 7200000) {
186
+ window._shortsData = c.data.filter((item, i, arr) => {
187
+ const created = item.created || c.ts;
188
+ const ts = created > 1000000000000 ? created : created * 1000;
189
+ return (Date.now() - ts) <= MAX_AGE_MS;
190
+ });
191
+ if (window._shortsData.length > 0) {
192
+ setTimeout(() => rerenderShortsSlide(window._shortsData), 2000);
193
+ }
194
+ }
195
+ } catch(e) {}
196
 
197
+ // Fetch fresh
198
  setTimeout(() => { window.refreshShorts().catch(() => {}); }, 3000);
199
 
200
+ console.log('[Shorts] Fetcher v8 - 24h filter active');
201
  })();