Sasha commited on
Commit
81cc161
·
1 Parent(s): ae9e59e

fix: resolve timezone offset in VOD end_time calculations

Browse files
local_worker/backfill_chats_only.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import requests
5
+ from dotenv import load_dotenv
6
+
7
+ # Load local environment settings
8
+ load_dotenv()
9
+
10
+ # Import helper functions and config from vod_backfiller
11
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
12
+ from vod_backfiller import get_vod_details, download_vod_chat, headers, API_URL, TWITCH_CHANNEL
13
+
14
+ def backfill_single_vod(vod_id):
15
+ print(f"\n=========================================================")
16
+ print(f" Начинаем обработку VOD: {vod_id}...")
17
+ print(f"=========================================================")
18
+
19
+ # 1. Fetch metadata
20
+ print("[Sync] Получение метаданных VOD...")
21
+ details = get_vod_details(vod_id)
22
+ if not details:
23
+ print(f"[Error] Не удалось получить информацию о VOD {vod_id}. Пропускаем.")
24
+ return False
25
+
26
+ title = details.get("title", "Архивный стрим")
27
+ created_at_raw = details.get("createdAt") # e.g. "2026-06-09T17:15:30Z"
28
+ length_seconds = details.get("lengthSeconds", 0)
29
+
30
+ # timezone-safe calculation using datetime
31
+ from datetime import datetime, timedelta
32
+ created_at = created_at_raw.replace("+00:00", "").replace("Z", "") + "Z"
33
+ dt_start = datetime.fromisoformat(created_at_raw.replace("Z", "+00:00"))
34
+ dt_end = dt_start + timedelta(seconds=length_seconds)
35
+ end_time = dt_end.strftime("%Y-%m-%dT%H:%M:%SZ")
36
+
37
+ print(f"Название: {title}")
38
+ print(f"Начало: {created_at}")
39
+ print(f"Длительность: {length_seconds // 3600}ч {(length_seconds % 3600) // 60}м")
40
+
41
+ # 2. Sync stream record in DB
42
+ print("\n[Sync] Регистрация сессии стрима на бэкенде...")
43
+ try:
44
+ url = f"{API_URL}/api/streams/sync-vod-direct"
45
+ payload = {
46
+ "twitchStreamId": f"vod-{vod_id}",
47
+ "title": title,
48
+ "category": "Архив",
49
+ "startTime": created_at,
50
+ "endTime": end_time
51
+ }
52
+ res = requests.post(url, json=payload, headers=headers, timeout=5)
53
+ if res.status_code != 200:
54
+ print(f"[Error] Не удалось синхронизировать сессию стрима: {res.status_code} - {res.text}")
55
+ return False
56
+ print("-> Сессия стрима успешно зарегистрирована в базе!")
57
+ except Exception as e:
58
+ print(f"[Error] Ошибка сети при обращении к бэкенду: {e}")
59
+ return False
60
+
61
+ # 3. Check for existing backfill status
62
+ chat_start_offset = 0
63
+ try:
64
+ status_url = f"{API_URL}/api/streams/backfill-status?twitchStreamId=vod-{vod_id}"
65
+ res = requests.get(status_url, headers=headers, timeout=5)
66
+ if res.status_code == 200:
67
+ status_data = res.json()
68
+ msg_count = status_data.get("messageCount", 0)
69
+ latest_msg_ts = status_data.get("latestMessageTimestamp")
70
+ if msg_count > 0 and latest_msg_ts:
71
+ from datetime import datetime
72
+ clean_ts = latest_msg_ts.replace('Z', '+00:00')
73
+ latest_epoch = datetime.fromisoformat(clean_ts).timestamp()
74
+
75
+ clean_created = created_at.replace('Z', '+00:00')
76
+ created_epoch = datetime.fromisoformat(clean_created).timestamp()
77
+
78
+ diff = int(latest_epoch - created_epoch)
79
+ if diff > 0:
80
+ chat_start_offset = diff
81
+ print(f"[Sync] Найден существующий чат в базе ({msg_count} сообщений). Возобновляем загрузку с секунды {chat_start_offset}...")
82
+ except Exception as e:
83
+ print(f"[Sync Warning] Не удалось получить статус дозаписи: {e}")
84
+
85
+ # 4. Download and Upload Chat comments
86
+ chat_comments = download_vod_chat(vod_id, start_offset=chat_start_offset)
87
+ if chat_comments:
88
+ print("\n[Sync] Отправка чата на сервер (пакетами по 100 сообщений)...")
89
+ batch_size = 100
90
+ for i in range(0, len(chat_comments), batch_size):
91
+ batch = chat_comments[i:i+batch_size]
92
+ try:
93
+ url = f"{API_URL}/api/log/messages"
94
+ res = requests.post(url, json={"messages": batch}, headers=headers, timeout=5)
95
+ if res.status_code == 200:
96
+ print(f"-> Отправлено сообщений: {i + len(batch)} / {len(chat_comments)}", end="\r")
97
+ else:
98
+ print(f"\n-> Ошибка отправки пакета: {res.status_code}")
99
+ except Exception as e:
100
+ print(f"\n-> Ошибка сети при отправке пакета: {e}")
101
+ print("\n-> Синхронизация чата успешно завершена!")
102
+ else:
103
+ print("\n-> Новых сообщений для загрузки не найдено.")
104
+
105
+ # 5. Mark Completed on Server
106
+ try:
107
+ url_mark = f"{API_URL}/api/streams/mark-backfilled"
108
+ requests.post(url_mark, json={"twitchStreamId": f"vod-{vod_id}"}, headers=headers, timeout=5)
109
+ print("-> Статус импорта успешно обновлен на сервере!")
110
+ except Exception as e:
111
+ print(f"[Warning] Не удалось отметить стрим как импортированный: {e}")
112
+
113
+ print(f"\n[Done] Обработка VOD {vod_id} завершена!")
114
+ return True
115
+
116
+ if __name__ == "__main__":
117
+ vod_ids = ['2798809640']
118
+
119
+ print("=========================================================")
120
+ print(" Автоматический импорт чатов для последних VOD ")
121
+ print("=========================================================")
122
+ print(f"Список VOD: {', '.join(vod_ids)}")
123
+ print(f"Канал: {TWITCH_CHANNEL}")
124
+ print(f"Бэкенд: {API_URL}")
125
+ print("=========================================================")
126
+
127
+ for vid in vod_ids:
128
+ try:
129
+ backfill_single_vod(vid)
130
+ except Exception as e:
131
+ print(f"[Error] Критическая ошибка при обработке VOD {vid}: {e}")
132
+
133
+ print("\nВсе три VOD успешно обработаны!")
local_worker/vod_backfiller.py CHANGED
@@ -368,11 +368,12 @@ if __name__ == "__main__":
368
  created_at_raw = details.get("createdAt") # e.g. "2026-06-09T17:15:30Z"
369
  length_seconds = details.get("lengthSeconds", 0)
370
 
371
- # format timestamp to strict Z
 
372
  created_at = created_at_raw.replace("+00:00", "").replace("Z", "") + "Z"
373
-
374
- end_time_epoch = time.mktime(time.strptime(created_at, "%Y-%m-%dT%H:%M:%SZ")) + length_seconds
375
- end_time = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(end_time_epoch))
376
 
377
  print(f"\nНазвание: {title}")
378
  print(f"Начало: {created_at}")
 
368
  created_at_raw = details.get("createdAt") # e.g. "2026-06-09T17:15:30Z"
369
  length_seconds = details.get("lengthSeconds", 0)
370
 
371
+ # timezone-safe calculation using datetime
372
+ from datetime import datetime, timedelta
373
  created_at = created_at_raw.replace("+00:00", "").replace("Z", "") + "Z"
374
+ dt_start = datetime.fromisoformat(created_at_raw.replace("Z", "+00:00"))
375
+ dt_end = dt_start + timedelta(seconds=length_seconds)
376
+ end_time = dt_end.strftime("%Y-%m-%dT%H:%M:%SZ")
377
 
378
  print(f"\nНазвание: {title}")
379
  print(f"Начало: {created_at}")