Phoe2004 commited on
Commit
b4084c8
ยท
verified ยท
1 Parent(s): 0301fc3

Rename main.py to yt_to_tiktok.py

Browse files
Files changed (2) hide show
  1. main.py +0 -429
  2. yt_to_tiktok.py +49 -0
main.py DELETED
@@ -1,429 +0,0 @@
1
- """
2
- YouTube to TikTok Auto Reposter
3
- ================================
4
- RSS feed แ€€แ€”แ€ฑ YouTube channel แ€กแ€žแ€…แ€บ video แ€€แ€ญแ€ฏ detect แ€•แ€ผแ€ฎแ€ธ TikTok แ€แ€„แ€บแ€แ€ฒแ€ท script
5
- """
6
-
7
- import os
8
- import json
9
- import time
10
- import random
11
- import logging
12
- import requests
13
- import feedparser
14
- import subprocess
15
- from datetime import datetime, timedelta
16
- from pathlib import Path
17
-
18
- # โ”€โ”€โ”€ CONFIG โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
-
20
- # แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€™แ€ฒแ€ท YouTube Channel ID แ€แ€ฝแ€ฑ
21
- CHANNEL_IDS = [
22
- "UCzHMxST9eZUo0XIaSyaTizA", # โ† แ€’แ€ฎแ€™แ€พแ€ฌ channel ID แ€‘แ€Šแ€ทแ€บแ€•แ€ซ
23
- # "UCyyyyyyyyyyyyyyyyyyyyyy", # channel แ€‘แ€•แ€บแ€‘แ€Šแ€ทแ€บแ€แ€ปแ€„แ€บแ€›แ€„แ€บ uncomment
24
- ]
25
-
26
- # TikTok Hashtags
27
- HASHTAGS = "#movierecap #myanmar #fyp #viral #trending"
28
-
29
- # แ€แ€…แ€บแ€›แ€€แ€บ upload แ€›แ€™แ€ฒแ€ท แ€กแ€™แ€ปแ€ฌแ€ธแ€†แ€ฏแ€ถแ€ธ video แ€กแ€›แ€ฑแ€กแ€แ€ฝแ€€แ€บ
30
- MAX_UPLOADS_PER_DAY = 1
31
-
32
- # Upload แ€แ€ปแ€ญแ€”แ€บ window (24-hour format)
33
- UPLOAD_WINDOW_START = 8 # แ€™แ€”แ€€แ€บ แˆ แ€”แ€ฌแ€›แ€ฎ
34
- UPLOAD_WINDOW_END = 22 # แ€Šแ€”แ€ฑ แแ€ แ€”แ€ฌแ€›แ€ฎ
35
-
36
- # RSS check interval (seconds)
37
- CHECK_INTERVAL = 10 * 60 # แแ€ แ€™แ€ญแ€”แ€…แ€บ
38
-
39
- # โ”€โ”€โ”€ PATHS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
40
-
41
- BASE_DIR = Path(__file__).parent
42
- COOKIES_DIR = BASE_DIR / "cookies"
43
- LOGS_DIR = BASE_DIR / "logs"
44
- STATE_FILE = BASE_DIR / "state.json"
45
- VIDEO_FILE = BASE_DIR / "input_video.mp4"
46
-
47
- COOKIES_DIR.mkdir(exist_ok=True)
48
- LOGS_DIR.mkdir(exist_ok=True)
49
-
50
- COOKIES_TXT = COOKIES_DIR / "cookies.txt"
51
-
52
- # โ”€โ”€โ”€ LOGGING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
53
-
54
- def get_logger():
55
- today = datetime.now().strftime("%Y-%m-%d")
56
- log_file = LOGS_DIR / f"{today}.log"
57
- logger = logging.getLogger("reposter")
58
- logger.setLevel(logging.DEBUG)
59
-
60
- if not logger.handlers:
61
- # File handler
62
- fh = logging.FileHandler(log_file, encoding="utf-8")
63
- fh.setLevel(logging.DEBUG)
64
- # Console handler
65
- ch = logging.StreamHandler()
66
- ch.setLevel(logging.INFO)
67
-
68
- fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s",
69
- datefmt="%Y-%m-%d %H:%M:%S")
70
- fh.setFormatter(fmt)
71
- ch.setFormatter(fmt)
72
- logger.addHandler(fh)
73
- logger.addHandler(ch)
74
-
75
- return logger
76
-
77
- log = get_logger()
78
-
79
- # โ”€โ”€โ”€ STATE (last_video_id แ€žแ€ญแ€™แ€บแ€ธแ€แ€ผแ€„แ€บแ€ธ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
80
-
81
- def load_state() -> dict:
82
- """state.json แ€€แ€ญแ€ฏ load โ€” แ€™แ€›แ€พแ€ญแ€›แ€„แ€บ empty dict แ€•แ€ผแ€”แ€บแ€•แ€ฑแ€ธ"""
83
- if STATE_FILE.exists():
84
- try:
85
- return json.loads(STATE_FILE.read_text(encoding="utf-8"))
86
- except Exception:
87
- pass
88
- return {}
89
-
90
- def save_state(state: dict):
91
- STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2),
92
- encoding="utf-8")
93
-
94
- # โ”€โ”€โ”€ RSS FEED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
95
-
96
- def get_latest_video(channel_id: str) -> dict | None:
97
- """
98
- YouTube RSS feed แ€€แ€”แ€ฑ latest video แ€€แ€ญแ€ฏ แ€›แ€šแ€ฐ
99
- Returns: {"id": "...", "title": "...", "url": "..."} or None
100
- """
101
- rss_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
102
- try:
103
- feed = feedparser.parse(rss_url)
104
- if not feed.entries:
105
- log.warning(f"[{channel_id}] RSS entries แ€™แ€›แ€พแ€ญแ€˜แ€ฐแ€ธ")
106
- return None
107
-
108
- entry = feed.entries[0]
109
- vid_id = entry.get("yt_videoid") or entry.get("id", "").split(":")[-1]
110
- title = entry.get("title", "No Title")
111
- url = f"https://www.youtube.com/watch?v={vid_id}"
112
-
113
- return {"id": vid_id, "title": title, "url": url}
114
-
115
- except Exception as e:
116
- log.error(f"[{channel_id}] RSS fetch error: {e}")
117
- return None
118
-
119
- # โ”€โ”€โ”€ VIDEO DOWNLOAD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
120
-
121
- def download_video(url: str) -> bool:
122
- """yt-dlp แ€”แ€ฒแ€ท lowest resolution mp4 download"""
123
- if VIDEO_FILE.exists():
124
- VIDEO_FILE.unlink()
125
-
126
- cmd = [
127
- "yt-dlp",
128
- "--no-playlist",
129
- "-f", "worstvideo[ext=mp4]+worstaudio[ext=m4a]/worst[ext=mp4]/worst",
130
- "--merge-output-format", "mp4",
131
- "--no-check-certificates",
132
- "-o", str(VIDEO_FILE),
133
- url,
134
- ]
135
-
136
- # cookies file แ€›แ€พแ€ญแ€›แ€„แ€บ แ€‘แ€Šแ€ทแ€บแ€žแ€ฏแ€ถแ€ธ
137
- if COOKIES_TXT.exists():
138
- cmd += ["--cookies", str(COOKIES_TXT)]
139
-
140
- log.info(f"Downloading: {url}")
141
- try:
142
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
143
- if result.returncode == 0 and VIDEO_FILE.exists():
144
- size_mb = VIDEO_FILE.stat().st_size / (1024 * 1024)
145
- log.info(f"Download OK โ€” {size_mb:.1f} MB")
146
- return True
147
- else:
148
- log.error(f"yt-dlp failed:\n{result.stderr[-500:]}")
149
- return False
150
- except subprocess.TimeoutExpired:
151
- log.error("Download timeout (5 min)")
152
- return False
153
- except Exception as e:
154
- log.error(f"Download exception: {e}")
155
- return False
156
-
157
- # โ”€โ”€โ”€ COOKIE PARSING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
158
-
159
- def parse_session_id() -> str | None:
160
- """
161
- cookies.txt แ€€แ€”แ€ฑ sessionid แ€‘แ€ฏแ€แ€บ
162
- Netscape format (yt-dlp style) แ€”แ€ฒแ€ท raw cookie string แ€”แ€พแ€…แ€บแ€แ€ฏแ€œแ€ฏแ€ถแ€ธ support
163
- """
164
- if not COOKIES_TXT.exists():
165
- log.error(f"cookies.txt แ€™แ€›แ€พแ€ญแ€˜แ€ฐแ€ธ: {COOKIES_TXT}")
166
- return None
167
-
168
- content = COOKIES_TXT.read_text(encoding="utf-8")
169
-
170
- # Netscape/Mozilla format: lines with tab-separated fields
171
- for line in content.splitlines():
172
- line = line.strip()
173
- if not line or line.startswith("#"):
174
- continue
175
- parts = line.split("\t")
176
- if len(parts) >= 7 and "sessionid" in parts[5]:
177
- return parts[6]
178
-
179
- # Raw cookie string format: "key=value; key2=value2; ..."
180
- for part in content.replace("\n", ";").split(";"):
181
- kv = part.strip()
182
- if kv.startswith("sessionid="):
183
- return kv.split("=", 1)[1]
184
-
185
- log.error("sessionid แ€€แ€ญแ€ฏ cookies.txt แ€‘แ€ฒแ€™แ€พแ€ฌ แ€™แ€แ€ฝแ€ฑแ€ทแ€˜แ€ฐแ€ธ")
186
- return None
187
-
188
- def build_cookie_dict() -> dict:
189
- """
190
- cookies.txt raw string แ€€แ€”แ€ฑ dict build
191
- TikTok upload API request แ€™แ€พแ€ฌ แ€žแ€ฏแ€ถแ€ธแ€–แ€ญแ€ฏแ€ท
192
- """
193
- if not COOKIES_TXT.exists():
194
- return {}
195
-
196
- content = COOKIES_TXT.read_text(encoding="utf-8").strip()
197
- result = {}
198
-
199
- # Netscape format แ€แ€ฝแ€ฑ skip โ€” raw string แ€€แ€ญแ€ฏแ€•แ€ฒ parse
200
- lines = [l for l in content.splitlines()
201
- if l.strip() and not l.strip().startswith("#")]
202
-
203
- # Single-line raw cookie string แ€–แ€ผแ€…แ€บแ€›แ€„แ€บ
204
- raw = " ".join(lines)
205
- for part in raw.split(";"):
206
- kv = part.strip()
207
- if "=" in kv:
208
- k, v = kv.split("=", 1)
209
- result[k.strip()] = v.strip()
210
-
211
- return result
212
-
213
- # โ”€โ”€โ”€ TIKTOK UPLOAD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
214
-
215
- def upload_to_tiktok(title: str) -> bool:
216
- """
217
- TikTok Web API แ€€แ€”แ€ฑ video upload
218
- Endpoint: https://www.tiktok.com/api/post/item/upload/
219
- """
220
- session_id = parse_session_id()
221
- if not session_id:
222
- return False
223
-
224
- if not VIDEO_FILE.exists():
225
- log.error("input_video.mp4 แ€™แ€›แ€พแ€ญแ€˜แ€ฐแ€ธ")
226
- return False
227
-
228
- cookie_dict = build_cookie_dict()
229
- caption = f"{title} {HASHTAGS}"
230
-
231
- # โ”€โ”€ Step 1: Upload init (multipart form) โ”€โ”€
232
- upload_url = "https://www.tiktok.com/api/post/item/upload/"
233
-
234
- headers = {
235
- "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
236
- "AppleWebKit/537.36 (KHTML, like Gecko) "
237
- "Chrome/120.0.0.0 Safari/537.36"),
238
- "Referer": "https://www.tiktok.com/",
239
- "Origin": "https://www.tiktok.com",
240
- }
241
-
242
- try:
243
- session = requests.Session()
244
- session.cookies.update(cookie_dict)
245
-
246
- file_size = VIDEO_FILE.stat().st_size
247
- log.info(f"TikTok upload start โ€” '{caption[:60]}...' ({file_size/1024/1024:.1f}MB)")
248
-
249
- with open(VIDEO_FILE, "rb") as vf:
250
- files = {"video": ("video.mp4", vf, "video/mp4")}
251
- payload = {
252
- "caption": caption,
253
- "privacy_level": "PUBLIC_TO_EVERYONE",
254
- "disable_duet": "false",
255
- "disable_stitch": "false",
256
- "disable_comment": "false",
257
- "video_cover_timestamp_ms": "1000",
258
- }
259
-
260
- resp = session.post(
261
- upload_url,
262
- headers=headers,
263
- data=payload,
264
- files=files,
265
- timeout=300,
266
- )
267
-
268
- log.debug(f"TikTok response [{resp.status_code}]: {resp.text[:300]}")
269
-
270
- if resp.status_code == 200:
271
- data = resp.json()
272
- # status_code 0 = success (TikTok internal code)
273
- if data.get("status_code") == 0 or data.get("status_msg") == "success":
274
- log.info(f"โœ… TikTok upload SUCCESS: {title}")
275
- return True
276
- else:
277
- log.error(f"TikTok API error: {data.get('status_msg', 'unknown')}")
278
- return False
279
- elif resp.status_code == 403:
280
- log.error("TikTok 403 โ€” sessionid expired แ€–แ€ผแ€…แ€บแ€”แ€ญแ€ฏแ€„แ€บแ€แ€šแ€บแŠ cookie แ€•แ€ผแ€”แ€บแ€‘แ€ฏแ€แ€บแ€•แ€ซ")
281
- return False
282
- else:
283
- log.error(f"TikTok HTTP {resp.status_code}: {resp.text[:200]}")
284
- return False
285
-
286
- except requests.Timeout:
287
- log.error("TikTok upload timeout")
288
- return False
289
- except Exception as e:
290
- log.error(f"TikTok upload exception: {e}")
291
- return False
292
-
293
- # โ”€โ”€โ”€ SCHEDULING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
294
-
295
- def get_random_upload_time() -> datetime:
296
- """
297
- UPLOAD_WINDOW_START ~ UPLOAD_WINDOW_END แ€กแ€แ€ฝแ€„แ€บแ€ธ random upload time แ€›แ€šแ€ฐ
298
- """
299
- now = datetime.now()
300
- start_dt = now.replace(hour=UPLOAD_WINDOW_START, minute=0, second=0, microsecond=0)
301
- end_dt = now.replace(hour=UPLOAD_WINDOW_END, minute=0, second=0, microsecond=0)
302
-
303
- total_secs = int((end_dt - start_dt).total_seconds())
304
- rand_offset = random.randint(0, total_secs)
305
- return start_dt + timedelta(seconds=rand_offset)
306
-
307
- def can_upload_today(state: dict) -> bool:
308
- """
309
- แ€’แ€ฎแ€”แ€ฑแ€ท upload แ€•แ€ผแ€ฎแ€ธแ€•แ€ผแ€ฎแ€œแ€ฌแ€ธ แ€…แ€…แ€บ
310
- """
311
- today = datetime.now().strftime("%Y-%m-%d")
312
- uploaded = state.get("uploads_today", {})
313
- return uploaded.get(today, 0) < MAX_UPLOADS_PER_DAY
314
-
315
- def record_upload(state: dict, video_id: str, title: str):
316
- """Upload แ€กแ€ฑแ€ฌแ€„แ€บแ€™แ€ผแ€„แ€บแ€›แ€„แ€บ state แ€™แ€พแ€ฌ แ€™แ€พแ€แ€บ"""
317
- today = datetime.now().strftime("%Y-%m-%d")
318
- if "uploads_today" not in state:
319
- state["uploads_today"] = {}
320
- state["uploads_today"][today] = state["uploads_today"].get(today, 0) + 1
321
- state["last_upload"] = {
322
- "video_id": video_id,
323
- "title": title,
324
- "time": datetime.now().isoformat(),
325
- }
326
- save_state(state)
327
-
328
- # โ”€โ”€โ”€ MAIN LOOP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
329
-
330
- def process_channel(channel_id: str, state: dict) -> bool:
331
- """
332
- Channel แ€แ€…แ€บแ€แ€ฏแ€€แ€ญแ€ฏ แ€…แ€…แ€บแ€•แ€ผแ€ฎแ€ธ แ€กแ€žแ€…แ€บ video แ€›แ€พแ€ญแ€›แ€„แ€บ download+upload
333
- Returns True if upload was done
334
- """
335
- latest = get_latest_video(channel_id)
336
- if not latest:
337
- return False
338
-
339
- vid_id = latest["id"]
340
- title = latest["title"]
341
- url = latest["url"]
342
-
343
- # Last seen video ID แ€”แ€ฒแ€ท compare
344
- last_seen = state.get("channels", {}).get(channel_id, {}).get("last_video_id")
345
-
346
- if last_seen == vid_id:
347
- log.debug(f"[{channel_id}] No new video (last={vid_id[:8]})")
348
- return False
349
-
350
- log.info(f"[{channel_id}] ๐Ÿ†• New video: '{title}' ({vid_id})")
351
-
352
- # Last seen ID update (download แ€™แ€แ€ญแ€ฏแ€„แ€บแ€แ€„แ€บแ€•แ€ฒ update โ€” duplicate upload แ€™แ€–แ€ผแ€…แ€บแ€–แ€ญแ€ฏแ€ท)
353
- if "channels" not in state:
354
- state["channels"] = {}
355
- if channel_id not in state["channels"]:
356
- state["channels"][channel_id] = {}
357
- state["channels"][channel_id]["last_video_id"] = vid_id
358
- save_state(state)
359
-
360
- # โ”€โ”€ Upload slot แ€…แ€…แ€บ โ”€โ”€
361
- if not can_upload_today(state):
362
- log.info(f"แ€’แ€ฎแ€”แ€ฑแ€ท upload quota แ€•แ€ผแ€Šแ€ทแ€บแ€•แ€ผแ€ฎ ({MAX_UPLOADS_PER_DAY}/day)")
363
- return False
364
-
365
- # โ”€โ”€ Random upload time แ€…แ€…แ€บ โ”€โ”€
366
- upload_time = get_random_upload_time()
367
- now = datetime.now()
368
-
369
- if now < upload_time:
370
- wait_secs = (upload_time - now).total_seconds()
371
- log.info(f"Scheduled upload at {upload_time.strftime('%H:%M:%S')} "
372
- f"(wait {wait_secs/60:.1f} min)")
373
- time.sleep(wait_secs)
374
-
375
- # โ”€โ”€ Download โ”€โ”€
376
- if not download_video(url):
377
- log.error(f"Download failed: {url}")
378
- return False
379
-
380
- # โ”€โ”€ Upload โ”€โ”€
381
- success = upload_to_tiktok(title)
382
-
383
- if success:
384
- record_upload(state, vid_id, title)
385
- log.info(f"โœ… Done: '{title}'")
386
- else:
387
- log.error(f"โŒ Upload failed: '{title}'")
388
-
389
- # Downloaded file แ€–แ€ปแ€€แ€บ (disk space แ€žแ€€แ€บแ€žแ€ฌแ€–แ€ญแ€ฏแ€ท)
390
- if VIDEO_FILE.exists():
391
- VIDEO_FILE.unlink()
392
- log.debug("Cleaned up video file")
393
-
394
- return success
395
-
396
- def run():
397
- log.info("=" * 60)
398
- log.info("YouTube โ†’ TikTok Auto Reposter started")
399
- log.info(f"Monitoring {len(CHANNEL_IDS)} channel(s), checking every {CHECK_INTERVAL//60} min")
400
- log.info("=" * 60)
401
-
402
- if not COOKIES_TXT.exists():
403
- log.warning(f"โš ๏ธ {COOKIES_TXT} แ€™แ€›แ€พแ€ญแ€˜แ€ฐแ€ธ โ€” cookies/cookies.txt แ€‘แ€Šแ€ทแ€บแ€•แ€ซ")
404
-
405
- state = load_state()
406
-
407
- while True:
408
- try:
409
- log.info(f"--- Checking {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---")
410
-
411
- for ch_id in CHANNEL_IDS:
412
- uploaded = process_channel(ch_id, state)
413
- if uploaded:
414
- # Upload แ€แ€…แ€บแ€แ€ฏแ€•แ€ผแ€ฎแ€ธแ€›แ€„แ€บ แ แ€”แ€ฌแ€›แ€ฎ gap แ€‘แ€ฌแ€ธแ€•แ€ซ
415
- log.info("Uploaded โ€” waiting 1 hour before next check")
416
- time.sleep(3600)
417
- break # แ€’แ€ฎ check round แ€€แ€ฏแ€”แ€บแ€•แ€ผแ€ฎ
418
-
419
- except KeyboardInterrupt:
420
- log.info("Stopped by user")
421
- break
422
- except Exception as e:
423
- log.error(f"Main loop error: {e}", exc_info=True)
424
-
425
- log.debug(f"Next check in {CHECK_INTERVAL//60} minutes")
426
- time.sleep(CHECK_INTERVAL)
427
-
428
- if __name__ == "__main__":
429
- run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
yt_to_tiktok.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json, time, subprocess, threading, logging
2
+ from pathlib import Path
3
+ import requests
4
+ import xml.etree.ElementTree as ET
5
+
6
+ logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s')
7
+ log = logging.getLogger(__name__)
8
+
9
+ BASE_DIR = Path(__file__).parent
10
+ DOWNLOADS_DIR = BASE_DIR / 'downloads'
11
+ TIKTOK_TXT = BASE_DIR / 'cookies.txt'
12
+ LAST_VIDEO_FILE = BASE_DIR / 'last_video_id.json'
13
+ QUEUE_FILE = BASE_DIR / 'queue.json'
14
+ CHANNEL_ID = 'UCzHMxST9eZUo0XIaSyaTizA'
15
+ RSS_URL = f'https://www.youtube.com/feeds/videos.xml?channel_id={CHANNEL_ID}'
16
+
17
+ DOWNLOADS_DIR.mkdir(exist_ok=True)
18
+
19
+ def get_tiktok_sessionid():
20
+ try:
21
+ content = TIKTOK_TXT.read_text()
22
+ for part in content.replace('\n', ';').split(';'):
23
+ if part.strip().startswith('sessionid='):
24
+ return part.split('=', 1)[1]
25
+ except:
26
+ return None
27
+ return None
28
+
29
+ def upload_to_tiktok(video_path, title):
30
+ sessionid = get_tiktok_sessionid()
31
+ if not sessionid:
32
+ log.error("No sessionid")
33
+ return False
34
+ # Simple upload - can be extended
35
+ log.info(f"Uploading {title} to TikTok...")
36
+ return True
37
+
38
+ def main():
39
+ log.info("YouTube Auto Poster Started")
40
+ sessionid = get_tiktok_sessionid()
41
+ if not sessionid:
42
+ log.error("cookies.txt not found or no sessionid")
43
+ return
44
+ log.info("Auto poster running...")
45
+ while True:
46
+ time.sleep(300)
47
+
48
+ if __name__ == '__main__':
49
+ main()