bep40 commited on
Commit
de31d3d
·
verified ·
1 Parent(s): 50ff107

Upload auto_scheduler.py

Browse files
Files changed (1) hide show
  1. auto_scheduler.py +101 -44
auto_scheduler.py CHANGED
@@ -27,25 +27,20 @@ SCHEDULE_STATE_FILE = os.path.join(DATA_DIR, 'scheduler_state.json')
27
  def _load_state():
28
  try:
29
  if os.path.exists(SCHEDULE_STATE_FILE):
30
- with open(SCHEDULE_STATE_FILE, 'r') as f:
31
- return json.load(f)
32
- except Exception:
33
- pass
34
  return {}
35
 
36
  def _save_state(state):
37
  try:
38
  tmp = SCHEDULE_STATE_FILE + '.tmp'
39
- with open(tmp, 'w') as f:
40
- json.dump(state, f, ensure_ascii=False)
41
  os.replace(tmp, SCHEDULE_STATE_FILE)
42
- except Exception as e:
43
- LOG.warning(f"Cannot save scheduler state: {e}")
44
 
45
  _STOP = set('và của các những một được trong với cho tại sau trước khi không người việt nam hôm nay mới nhất nóng tin tức cập nhật theo từ đến là có thì này đã để'.split())
46
 
47
- def _clean(s):
48
- return re.sub(r"\s+", " ", str(s or "")).strip()
49
 
50
  def _get_hot_topics():
51
  freq = {}; display = {}
@@ -79,8 +74,7 @@ def _get_hot_topics():
79
  key = phrase.lower()
80
  freq[key] = freq.get(key, 0) + 1
81
  display[key] = phrase
82
- except Exception:
83
- continue
84
  ranked = sorted(freq.items(), key=lambda x: x[1], reverse=True)
85
  topics = []; seen = set()
86
  for key, count in ranked:
@@ -96,21 +90,14 @@ def _get_hot_topics():
96
  topics.append({'label': '#' + re.sub(r'\s+', '', kw.title()), 'topic': kw, 'count': 0})
97
  return topics[:24]
98
 
99
- _ai_ext = None
100
- _ai_patch = None
101
-
102
  def _get_ai_ext():
103
  global _ai_ext
104
- if _ai_ext is None:
105
- import ai_ext as m
106
- _ai_ext = m
107
  return _ai_ext
108
-
109
  def _get_ai_patch():
110
  global _ai_patch
111
- if _ai_patch is None:
112
- import ai_patch as m
113
- _ai_patch = m
114
  return _ai_patch
115
 
116
  _RSS_FEEDS = [
@@ -127,8 +114,7 @@ _RSS_FEEDS = [
127
  ]
128
 
129
  def _search_articles_by_topic(topic, limit=4):
130
- all_articles = []
131
- seen_urls = set()
132
  topic_lower = topic.lower()
133
  topic_words = set(re.findall(r'[A-Za-zÀ-ỹ0-9]+', topic_lower))
134
  for feed_url, source in _RSS_FEEDS:
@@ -157,18 +143,13 @@ def _search_articles_by_topic(topic, limit=4):
157
  ogi = art_soup.find('meta', property='og:image')
158
  if ogi: img = ogi.get('content', '')
159
  except: pass
160
- all_articles.append({
161
- 'title': title, 'url': link, 'raw': desc or title, 'image': img,
162
- 'via': source,
163
- 'source': {'title': title, 'url': link, 'excerpt': (desc or title)[:700], 'via': source}
164
- })
165
  if len(all_articles) >= limit: break
166
  except: continue
167
  return all_articles[:limit]
168
 
169
  async def _create_ai_post(topic):
170
- ai_ext = _get_ai_ext()
171
- ai_patch = _get_ai_patch()
172
  articles = _search_articles_by_topic(topic, limit=4)
173
  if not articles:
174
  LOG.warning(f"No articles for topic: {topic}. Fallback.")
@@ -200,9 +181,8 @@ async def _create_ai_post(topic):
200
  except: pass
201
  posts.append(post)
202
  except Exception as e:
203
- LOG.error(f"Error creating post: {e}")
204
- if not posts:
205
- return await _create_fallback_post(topic, ai_ext, ai_patch)
206
  wall = posts + wall
207
  ai_ext._save_ai_wall(wall)
208
  for post in posts:
@@ -211,7 +191,7 @@ async def _create_ai_post(topic):
211
  return posts
212
 
213
  async def _create_fallback_post(topic, ai_ext, ai_patch):
214
- LOG.info(f"Fallback post: {topic}")
215
  try:
216
  text = f"• {topic} đang là chủ đề nóng hôm nay.\n• Theo dõi VNEWS để cập nhật tin tức mới nhất."
217
  img = ai_ext.pollination_image_url(topic)
@@ -244,8 +224,7 @@ def _try_generate_short(post):
244
  if p.get('id') == post_id: wall[i] = post; break
245
  ai_ext._save_ai_wall(wall); return
246
  threading.Thread(target=lambda: _generate_short_worker(post, segments, post_id, suffix, out_mp4), daemon=True).start()
247
- except Exception as e:
248
- LOG.warning(f"Short init: {e}")
249
 
250
  def _generate_short_worker(post, segments, post_id, suffix, out_mp4):
251
  import subprocess
@@ -292,10 +271,8 @@ def _generate_short_worker(post, segments, post_id, suffix, out_mp4):
292
  if p.get('id') == post_id: wall[i] = post; break
293
  ai_ext._save_ai_wall(wall)
294
  LOG.info(f"Short: {post_id}")
295
- except Exception as e:
296
- LOG.warning(f"Short fail: {e}")
297
 
298
- # ===== FIX v4: _run_async helper - works from both sync and async contexts =====
299
  def _run_async(coro):
300
  """Run async coroutine safely regardless of current event loop state."""
301
  try:
@@ -308,10 +285,90 @@ def _run_async(coro):
308
 
309
  def _run_scheduled_posting():
310
  LOG.info("=" * 50)
311
- LOG.info("Auto scheduler triggered at %s", datetime.now(VN_TZ).strftime('%H:%M %d/%m/%Y'))
312
  LOG.info("=" * 50)
313
  try:
314
  hot_topics = _get_hot_topics()
315
- if not hot_topics: return
316
- selected = []
317
- seen_labels = set()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def _load_state():
28
  try:
29
  if os.path.exists(SCHEDULE_STATE_FILE):
30
+ with open(SCHEDULE_STATE_FILE, 'r') as f: return json.load(f)
31
+ except: pass
 
 
32
  return {}
33
 
34
  def _save_state(state):
35
  try:
36
  tmp = SCHEDULE_STATE_FILE + '.tmp'
37
+ with open(tmp, 'w') as f: json.dump(state, f, ensure_ascii=False)
 
38
  os.replace(tmp, SCHEDULE_STATE_FILE)
39
+ except Exception as e: LOG.warning(f"Cannot save state: {e}")
 
40
 
41
  _STOP = set('và của các những một được trong với cho tại sau trước khi không người việt nam hôm nay mới nhất nóng tin tức cập nhật theo từ đến là có thì này đã để'.split())
42
 
43
+ def _clean(s): return re.sub(r"\s+", " ", str(s or "")).strip()
 
44
 
45
  def _get_hot_topics():
46
  freq = {}; display = {}
 
74
  key = phrase.lower()
75
  freq[key] = freq.get(key, 0) + 1
76
  display[key] = phrase
77
+ except: continue
 
78
  ranked = sorted(freq.items(), key=lambda x: x[1], reverse=True)
79
  topics = []; seen = set()
80
  for key, count in ranked:
 
90
  topics.append({'label': '#' + re.sub(r'\s+', '', kw.title()), 'topic': kw, 'count': 0})
91
  return topics[:24]
92
 
93
+ _ai_ext = None; _ai_patch = None
 
 
94
  def _get_ai_ext():
95
  global _ai_ext
96
+ if _ai_ext is None: import ai_ext as m; _ai_ext = m
 
 
97
  return _ai_ext
 
98
  def _get_ai_patch():
99
  global _ai_patch
100
+ if _ai_patch is None: import ai_patch as m; _ai_patch = m
 
 
101
  return _ai_patch
102
 
103
  _RSS_FEEDS = [
 
114
  ]
115
 
116
  def _search_articles_by_topic(topic, limit=4):
117
+ all_articles = []; seen_urls = set()
 
118
  topic_lower = topic.lower()
119
  topic_words = set(re.findall(r'[A-Za-zÀ-ỹ0-9]+', topic_lower))
120
  for feed_url, source in _RSS_FEEDS:
 
143
  ogi = art_soup.find('meta', property='og:image')
144
  if ogi: img = ogi.get('content', '')
145
  except: pass
146
+ all_articles.append({'title': title, 'url': link, 'raw': desc or title, 'image': img, 'via': source, 'source': {'title': title, 'url': link, 'excerpt': (desc or title)[:700], 'via': source}})
 
 
 
 
147
  if len(all_articles) >= limit: break
148
  except: continue
149
  return all_articles[:limit]
150
 
151
  async def _create_ai_post(topic):
152
+ ai_ext = _get_ai_ext(); ai_patch = _get_ai_patch()
 
153
  articles = _search_articles_by_topic(topic, limit=4)
154
  if not articles:
155
  LOG.warning(f"No articles for topic: {topic}. Fallback.")
 
181
  except: pass
182
  posts.append(post)
183
  except Exception as e:
184
+ LOG.error(f"Error post: {e}")
185
+ if not posts: return await _create_fallback_post(topic, ai_ext, ai_patch)
 
186
  wall = posts + wall
187
  ai_ext._save_ai_wall(wall)
188
  for post in posts:
 
191
  return posts
192
 
193
  async def _create_fallback_post(topic, ai_ext, ai_patch):
194
+ LOG.info(f"Fallback: {topic}")
195
  try:
196
  text = f"• {topic} đang là chủ đề nóng hôm nay.\n• Theo dõi VNEWS để cập nhật tin tức mới nhất."
197
  img = ai_ext.pollination_image_url(topic)
 
224
  if p.get('id') == post_id: wall[i] = post; break
225
  ai_ext._save_ai_wall(wall); return
226
  threading.Thread(target=lambda: _generate_short_worker(post, segments, post_id, suffix, out_mp4), daemon=True).start()
227
+ except Exception as e: LOG.warning(f"Short init: {e}")
 
228
 
229
  def _generate_short_worker(post, segments, post_id, suffix, out_mp4):
230
  import subprocess
 
271
  if p.get('id') == post_id: wall[i] = post; break
272
  ai_ext._save_ai_wall(wall)
273
  LOG.info(f"Short: {post_id}")
274
+ except Exception as e: LOG.warning(f"Short fail: {e}")
 
275
 
 
276
  def _run_async(coro):
277
  """Run async coroutine safely regardless of current event loop state."""
278
  try:
 
285
 
286
  def _run_scheduled_posting():
287
  LOG.info("=" * 50)
288
+ LOG.info("Scheduler triggered at %s", datetime.now(VN_TZ).strftime('%H:%M %d/%m/%Y'))
289
  LOG.info("=" * 50)
290
  try:
291
  hot_topics = _get_hot_topics()
292
+ if not hot_topics:
293
+ LOG.warning("No hot topics"); return
294
+ selected = []; seen_labels = set()
295
+ for t in hot_topics:
296
+ label = t.get('label', '')
297
+ if label and label not in seen_labels:
298
+ seen_labels.add(label); selected.append(t['topic'])
299
+ if len(selected) >= 3: break
300
+ if len(selected) < 3:
301
+ selected = ['Thời sự Việt Nam', 'Kinh tế Việt Nam', 'Thể thao']
302
+ LOG.info(f"Topics: {selected}")
303
+ async def _do_all():
304
+ results = []
305
+ for topic in selected:
306
+ try:
307
+ posts = await _create_ai_post(topic)
308
+ results.append({'topic': topic, 'posts': len(posts) if posts else 0})
309
+ LOG.info(f"{'✓' if posts else '✗'} {topic}: {len(posts) if posts else 0} posts")
310
+ except Exception as e:
311
+ LOG.error(f"Error {topic}: {e}")
312
+ results.append({'topic': topic, 'posts': 0})
313
+ return results
314
+ results = _run_async(_do_all())
315
+ LOG.info(f"Done: {len(results)} topics")
316
+ for r in results: LOG.info(f" • {r['topic']}: {r['posts']} bài")
317
+ except Exception as e:
318
+ LOG.error(f"Scheduler error: {e}", exc_info=True)
319
+
320
+ def _check_missed_slots():
321
+ try:
322
+ state = _load_state()
323
+ today_str = datetime.now(VN_TZ).strftime('%Y-%m-%d')
324
+ now = datetime.now(VN_TZ); cur_mins = now.hour * 60 + now.minute
325
+ ran = state.get(today_str, {})
326
+ for s in SCHEDULE_TIMES:
327
+ lbl = SCHEDULE_LABELS[s]; sm = s[0] * 60 + s[1]
328
+ if ran.get(lbl): continue
329
+ if cur_mins >= sm:
330
+ LOG.info(f"Catch-up: {lbl}")
331
+ _run_scheduled_posting()
332
+ if today_str not in state: state[today_str] = {}
333
+ state[today_str][lbl] = True; _save_state(state)
334
+ except Exception as e: LOG.error(f"Catch-up: {e}")
335
+
336
+ def _scheduler_loop():
337
+ LOG.info("Scheduler started")
338
+ LOG.info(f"Schedule: {', '.join(f'{h:02d}:{m:02d}' for h,m in SCHEDULE_TIMES)} VN")
339
+ state = _load_state(); today_str = datetime.now(VN_TZ).strftime('%Y-%m-%d')
340
+ ran = state.get(today_str, {})
341
+ now = datetime.now(VN_TZ); cur_mins = now.hour * 60 + now.minute
342
+ for s in SCHEDULE_TIMES:
343
+ lbl = SCHEDULE_LABELS[s]; sm = s[0] * 60 + s[1]
344
+ if ran.get(lbl): LOG.info(f" ✓ {lbl} done"); continue
345
+ if cur_mins >= sm:
346
+ LOG.info(f" → {lbl} missed! Catch-up")
347
+ _run_scheduled_posting()
348
+ if today_str not in state: state[today_str] = {}
349
+ state[today_str][lbl] = True; _save_state(state)
350
+ else: LOG.info(f" ⏩ {lbl} upcoming")
351
+ while True:
352
+ try:
353
+ now = datetime.now(VN_TZ)
354
+ ck = (now.hour, now.minute)
355
+ state = _load_state(); today_str = now.strftime('%Y-%m-%d')
356
+ ran = state.get(today_str, {})
357
+ for s in SCHEDULE_TIMES:
358
+ lbl = SCHEDULE_LABELS[s]
359
+ if ck == s and not ran.get(lbl):
360
+ LOG.info(f"On-time: {lbl}")
361
+ _run_scheduled_posting()
362
+ if today_str not in state: state[today_str] = {}
363
+ state[today_str][lbl] = True; _save_state(state)
364
+ break
365
+ time.sleep(60)
366
+ except Exception as e:
367
+ LOG.error(f"Loop: {e}")
368
+ time.sleep(60)
369
+
370
+ def start_auto_scheduler():
371
+ t = threading.Thread(target=_scheduler_loop, daemon=True, name="auto-scheduler")
372
+ t.start()
373
+ LOG.info("Auto scheduler started")
374
+ return t