Phoe2004 commited on
Commit
d514460
ยท
verified ยท
1 Parent(s): 7d802e8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -137
app.py CHANGED
@@ -25,64 +25,167 @@ def add_log(msg):
25
  bot_logs.pop(0)
26
  print(msg)
27
 
28
- def get_tiktok_sessionid():
 
 
 
 
 
 
 
 
 
 
 
29
  try:
30
- content = open(TIKTOK_COOKIE_FILE, 'r').read()
31
- for part in content.replace('\n', ';').split(';'):
32
- if part.strip().startswith('sessionid='):
33
- return part.split('=', 1)[1]
34
- except:
35
- return None
36
- return None
 
 
37
 
38
  def upload_to_tiktok(video_path, title):
39
- sessionid = get_tiktok_sessionid()
 
40
  if not sessionid:
41
- add_log('โŒ TikTok sessionid not found')
42
  return False
43
-
 
 
 
44
  add_log(f'๐Ÿ“ค Uploading: {title[:50]}...')
45
-
46
- url = 'https://www.tiktok.com/api/v1/video/upload/'
47
- headers = {'User-Agent': 'Mozilla/5.0', 'Cookie': f'sessionid={sessionid}'}
48
  caption = f"{title}\n#movierecap #myanmar #fyp #viral"
49
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  with open(video_path, 'rb') as f:
52
- files = {'video': ('video.mp4', f, 'video/mp4')}
53
- data = {'caption': caption}
54
- response = requests.post(url, headers=headers, data=data, files=files, timeout=120)
55
- if response.status_code == 200 and response.json().get('status_code') == 0:
56
- add_log('โœ… Upload success!')
57
- return True
58
- else:
59
- add_log(f'โŒ Upload failed: {response.text[:100]}')
60
- return False
 
 
61
  except Exception as e:
62
- add_log(f'โŒ Error: {e}')
63
  return False
64
 
 
65
  def download_video(url, quality='720'):
66
  tid = uuid.uuid4().hex[:8]
67
  out_path = OUTPUT_DIR / f'{tid}.mp4'
68
-
69
  fmt_map = {'1080': 'best[height<=1080]', '720': 'best[height<=720]', '480': 'best[height<=480]'}
70
  fmt = fmt_map.get(quality, 'best[height<=720]')
71
-
72
  cmd = ['yt-dlp', '-f', fmt, '--merge-output-format', 'mp4', '-o', str(out_path), url]
73
  if os.path.exists(COOKIES_FILE):
74
  cmd += ['--cookies', COOKIES_FILE]
75
-
76
  add_log(f'๐Ÿ“ฅ Downloading: {url[:60]}...')
77
  result = subprocess.run(cmd, capture_output=True, text=True)
78
-
79
  if result.returncode == 0 and out_path.exists():
80
  add_log(f'โœ… Downloaded: {out_path.name}')
81
  return str(out_path)
82
  else:
83
- add_log(f'โŒ Download failed: {result.stderr[:100]}')
84
  return None
85
 
 
86
  # โ”€โ”€ Routes โ”€โ”€
87
  @app.route('/')
88
  def index():
@@ -90,56 +193,42 @@ def index():
90
 
91
  @app.route('/api/upload/manual', methods=['POST'])
92
  def api_upload_manual():
93
- """Manual upload - provide video URL and title"""
94
  data = request.get_json() or {}
95
  video_url = data.get('url', '').strip()
96
- title = data.get('title', '').strip()
97
- quality = data.get('quality', '720')
98
-
99
  if not video_url:
100
  return jsonify({'ok': False, 'msg': 'No URL provided'})
101
  if not title:
102
  return jsonify({'ok': False, 'msg': 'No title provided'})
103
-
104
- # Download first
105
  video_path = download_video(video_url, quality)
106
  if not video_path:
107
  return jsonify({'ok': False, 'msg': 'Download failed'})
108
-
109
- # Then upload to TikTok
110
  success = upload_to_tiktok(video_path, title)
111
-
112
- # Cleanup
113
- try:
114
- os.remove(video_path)
115
- except:
116
- pass
117
-
118
- return jsonify({'ok': success, 'msg': 'Upload completed' if success else 'Upload failed'})
119
 
120
  @app.route('/api/upload/file', methods=['POST'])
121
  def api_upload_file():
122
- """Manual upload - directly upload video file"""
123
  if 'video' not in request.files:
124
  return jsonify({'ok': False, 'msg': 'No video file'})
125
-
126
  video = request.files['video']
127
  title = request.form.get('title', 'Video')
128
-
129
- # Save temp file
130
  temp_path = OUTPUT_DIR / f'temp_{uuid.uuid4().hex[:8]}.mp4'
131
  video.save(str(temp_path))
132
-
133
- # Upload to TikTok
134
  success = upload_to_tiktok(str(temp_path), title)
135
-
136
- # Cleanup
137
- try:
138
- temp_path.unlink()
139
- except:
140
- pass
141
-
142
- return jsonify({'ok': success, 'msg': 'Upload completed' if success else 'Upload failed'})
143
 
144
  @app.route('/api/bot/start', methods=['POST'])
145
  def api_bot_start():
@@ -161,167 +250,230 @@ def api_bot_stop():
161
 
162
  @app.route('/api/bot/status', methods=['GET'])
163
  def api_bot_status():
164
- return jsonify({'running': bot_running, 'logs': bot_logs[-50:], 'tiktok_cookie': get_tiktok_sessionid() is not None})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  def auto_poster_worker():
167
  global bot_running
168
  add_log('๐Ÿค– Auto Poster started')
169
  CHANNEL_ID = 'UCUZHFZ9jIKrLroW8LcyJEQQ'
170
  last_id = None
171
-
172
  while bot_running:
173
  try:
174
  import feedparser
175
- url = f'https://www.youtube.com/feeds/videos.xml?channel_id={CHANNEL_ID}'
176
- feed = feedparser.parse(url)
177
-
178
  if feed.entries:
179
- latest = feed.entries[0]
180
  video_id = latest.get('yt_videoid', '')
181
- title = latest.get('title', '')
182
-
183
  if last_id is None:
184
  last_id = video_id
185
- add_log(f'๐Ÿ“Œ Initialized: {video_id}')
186
  elif video_id != last_id:
187
- add_log(f'๐Ÿ“น New video: {title[:50]}')
188
  video_path = download_video(f'https://youtube.com/watch?v={video_id}', '720')
189
  if video_path:
190
  upload_to_tiktok(video_path, title)
191
  try: os.remove(video_path)
192
  except: pass
193
  last_id = video_id
 
 
194
  except Exception as e:
195
- add_log(f'โš ๏ธ Error: {e}')
196
-
 
197
  for _ in range(300):
198
- if not bot_running: break
 
199
  time.sleep(1)
200
 
201
- # โ”€โ”€ HTML Template with Manual Upload โ”€โ”€
 
 
 
202
  HTML_TEMPLATE = '''
203
  <!DOCTYPE html>
204
  <html>
205
  <head>
206
  <title>YouTube to TikTok Bot</title>
207
  <style>
208
- body { font-family: monospace; max-width: 800px; margin: 0 auto; padding: 20px; background: #0a0a0a; color: #0f0; }
209
  .card { background: #111; border: 1px solid #333; border-radius: 8px; padding: 16px; margin-bottom: 20px; }
210
- button { background: #00aa00; color: black; border: none; padding: 8px 16px; cursor: pointer; margin-right: 8px; }
211
- button.red { background: #aa0000; }
212
- input, select, textarea { padding: 8px; margin: 5px 0; width: 100%; box-sizing: border-box; background: #222; color: #0f0; border: 1px solid #333; }
213
- .log-box { height: 200px; overflow-y: scroll; background: #0a0a0a; border: 1px solid #333; padding: 10px; font-size: 11px; }
214
- .status { padding: 8px; border-radius: 4px; margin-bottom: 10px; }
 
 
 
215
  .status.running { background: #1a3a1a; color: #0f0; }
216
- .status.stopped { background: #3a1a1a; color: #f00; }
217
- hr { border-color: #333; }
 
 
 
 
 
 
218
  </style>
219
  </head>
220
  <body>
221
- <h1>๐ŸŽฌ YouTube to TikTok Bot</h1>
222
-
223
- <!-- Auto Bot Control -->
 
 
 
 
 
 
 
224
  <div class="card">
225
  <h2>๐Ÿค– Auto Poster Bot</h2>
226
  <div id="status" class="status stopped">๐Ÿ”ด STOPPED</div>
227
  <button onclick="startBot()">โ–ถ๏ธ START</button>
228
  <button class="red" onclick="stopBot()">โน๏ธ STOP</button>
229
- <button onclick="refreshStatus()">๐Ÿ”„ Refresh</button>
230
  <hr>
231
  <div id="log" class="log-box"></div>
232
  </div>
233
-
234
  <!-- Manual Upload by URL -->
235
  <div class="card">
236
  <h2>๐Ÿ“ค Manual Upload (by URL)</h2>
237
- <input type="text" id="manual-url" placeholder="YouTube URL">
238
- <input type="text" id="manual-title" placeholder="Video Title">
239
  <select id="manual-quality">
240
  <option value="1080">1080p</option>
241
  <option value="720" selected>720p</option>
242
  <option value="480">480p</option>
243
  </select>
244
- <button onclick="manualUpload()">โฌ†๏ธ Download & Upload</button>
245
- <div id="manual-result"></div>
246
  </div>
247
-
248
  <!-- Manual Upload by File -->
249
  <div class="card">
250
  <h2>๐Ÿ“ Manual Upload (by File)</h2>
251
- <input type="file" id="file-input" accept="video/mp4">
252
- <input type="text" id="file-title" placeholder="Video Title">
253
  <button onclick="manualUploadFile()">โฌ†๏ธ Upload File to TikTok</button>
254
- <div id="file-result"></div>
255
  </div>
256
-
257
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  async function refreshStatus() {
259
  const res = await fetch('/api/bot/status');
260
- const data = await res.json();
261
  const statusDiv = document.getElementById('status');
262
- if (data.running) {
263
- statusDiv.className = 'status running';
264
- statusDiv.innerHTML = '๐ŸŸข RUNNING';
265
- } else {
266
- statusDiv.className = 'status stopped';
267
- statusDiv.innerHTML = '๐Ÿ”ด STOPPED';
268
- }
269
- document.getElementById('log').innerHTML = data.logs.map(l => `<div>${escapeHtml(l)}</div>`).join('');
270
  }
271
-
272
  async function startBot() {
273
  const res = await fetch('/api/bot/start', {method: 'POST'});
274
- const data = await res.json();
275
- alert(data.msg);
276
  refreshStatus();
277
  }
278
-
279
  async function stopBot() {
280
  const res = await fetch('/api/bot/stop', {method: 'POST'});
281
- const data = await res.json();
282
- alert(data.msg);
283
  refreshStatus();
284
  }
285
-
286
  async function manualUpload() {
287
- const url = document.getElementById('manual-url').value;
288
- const title = document.getElementById('manual-title').value;
289
  const quality = document.getElementById('manual-quality').value;
290
- if (!url || !title) { alert('Enter URL and Title'); return; }
291
-
292
- document.getElementById('manual-result').innerHTML = 'โณ Processing...';
293
- const res = await fetch('/api/upload/manual', {
294
- method: 'POST',
295
- headers: {'Content-Type': 'application/json'},
296
- body: JSON.stringify({url, title, quality})
297
- });
298
- const data = await res.json();
299
- document.getElementById('manual-result').innerHTML = data.ok ? 'โœ… ' + data.msg : 'โŒ ' + data.msg;
 
 
 
 
 
300
  refreshStatus();
301
  }
302
-
303
  async function manualUploadFile() {
304
- const file = document.getElementById('file-input').files[0];
305
- const title = document.getElementById('file-title').value;
306
- if (!file || !title) { alert('Select file and enter title'); return; }
307
-
 
 
308
  const formData = new FormData();
309
  formData.append('video', file);
310
  formData.append('title', title);
311
-
312
- document.getElementById('file-result').innerHTML = 'โณ Uploading...';
313
- const res = await fetch('/api/upload/file', {method: 'POST', body: formData});
314
- const data = await res.json();
315
- document.getElementById('file-result').innerHTML = data.ok ? 'โœ… ' + data.msg : 'โŒ ' + data.msg;
 
 
316
  refreshStatus();
317
  }
318
-
319
  function escapeHtml(text) {
320
  const div = document.createElement('div');
321
  div.textContent = text;
322
  return div.innerHTML;
323
  }
324
-
 
 
325
  refreshStatus();
326
  setInterval(refreshStatus, 5000);
327
  </script>
@@ -330,4 +482,4 @@ HTML_TEMPLATE = '''
330
  '''
331
 
332
  if __name__ == '__main__':
333
- app.run(host='0.0.0.0', port=7860, debug=False)
 
25
  bot_logs.pop(0)
26
  print(msg)
27
 
28
+ def parse_cookies_string(cookie_str):
29
+ """Parse cookie string (one-line format) into dict"""
30
+ cookies = {}
31
+ for part in cookie_str.strip().split(';'):
32
+ part = part.strip()
33
+ if '=' in part:
34
+ k, v = part.split('=', 1)
35
+ cookies[k.strip()] = v.strip()
36
+ return cookies
37
+
38
+ def get_tiktok_cookies():
39
+ """Read cookies.txt and return dict"""
40
  try:
41
+ content = open(TIKTOK_COOKIE_FILE, 'r').read().strip()
42
+ return parse_cookies_string(content)
43
+ except Exception as e:
44
+ add_log(f'โŒ Cookie read error: {e}')
45
+ return {}
46
+
47
+ def get_tiktok_sessionid():
48
+ cookies = get_tiktok_cookies()
49
+ return cookies.get('sessionid')
50
 
51
  def upload_to_tiktok(video_path, title):
52
+ cookies = get_tiktok_cookies()
53
+ sessionid = cookies.get('sessionid')
54
  if not sessionid:
55
+ add_log('โŒ TikTok sessionid not found in cookies.txt')
56
  return False
57
+
58
+ ms_token = cookies.get('msToken', '')
59
+ csrf = cookies.get('tt_csrf_token', '')
60
+
61
  add_log(f'๐Ÿ“ค Uploading: {title[:50]}...')
 
 
 
62
  caption = f"{title}\n#movierecap #myanmar #fyp #viral"
63
+
64
+ cookie_header = '; '.join([f'{k}={v}' for k, v in cookies.items()])
65
+ base_headers = {
66
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
67
+ 'Referer': 'https://www.tiktok.com/',
68
+ 'Origin': 'https://www.tiktok.com',
69
+ 'X-Csrf-Token': csrf,
70
+ 'Cookie': cookie_header,
71
+ }
72
+
73
+ video_size = os.path.getsize(video_path)
74
+
75
+ # โ”€โ”€ Step 1: Init upload โ”€โ”€
76
+ init_url = 'https://open.tiktokapis.com/v2/post/publish/inbox/video/init/'
77
+ init_payload = {
78
+ 'post_info': {
79
+ 'title': caption,
80
+ 'privacy_level': 'PUBLIC_TO_EVERYONE',
81
+ 'disable_duet': False,
82
+ 'disable_comment': False,
83
+ 'disable_stitch': False,
84
+ },
85
+ 'source_info': {
86
+ 'source': 'FILE_UPLOAD',
87
+ 'video_size': video_size,
88
+ 'chunk_size': video_size,
89
+ 'total_chunk_count': 1,
90
+ }
91
+ }
92
+
93
+ try:
94
+ init_headers = {**base_headers, 'Content-Type': 'application/json; charset=UTF-8'}
95
+ r = requests.post(init_url, headers=init_headers, json=init_payload, timeout=30)
96
+ add_log(f'Init response: {r.status_code} | {r.text[:300]}')
97
+ data = r.json()
98
+ upload_url = data.get('data', {}).get('upload_url')
99
+ publish_id = data.get('data', {}).get('publish_id', '')
100
+ if not upload_url:
101
+ add_log('โŒ No upload_url โ€” trying fallback method...')
102
+ return _upload_fallback(video_path, caption, base_headers, video_size)
103
+ except Exception as e:
104
+ add_log(f'โŒ Init error: {e}')
105
+ return False
106
+
107
+ # โ”€โ”€ Step 2: Upload video chunk โ”€โ”€
108
+ try:
109
+ with open(video_path, 'rb') as f:
110
+ video_data = f.read()
111
+
112
+ upload_headers = {
113
+ 'Content-Type': 'video/mp4',
114
+ 'Content-Range': f'bytes 0-{video_size - 1}/{video_size}',
115
+ 'Content-Length': str(video_size),
116
+ }
117
+ r2 = requests.put(upload_url, headers=upload_headers, data=video_data, timeout=300)
118
+ add_log(f'Upload chunk response: {r2.status_code} | {r2.text[:100]}')
119
+ if r2.status_code in (200, 201, 206):
120
+ add_log(f'โœ… Upload success! publish_id={publish_id}')
121
+ return True
122
+ else:
123
+ add_log(f'โŒ Chunk upload failed: {r2.status_code}')
124
+ return False
125
+ except Exception as e:
126
+ add_log(f'โŒ Upload error: {e}')
127
+ return False
128
+
129
+
130
+ def _upload_fallback(video_path, caption, base_headers, video_size):
131
+ """Fallback: TikTok web creator upload endpoint"""
132
+ add_log('๐Ÿ”„ Trying web creator fallback...')
133
  try:
134
+ # Step A: get upload ticket
135
+ ticket_url = 'https://www.tiktok.com/api/v1/web/creator/video/upload/'
136
+ payload = {
137
+ 'video_size': video_size,
138
+ 'chunk_size': video_size,
139
+ 'total_chunk_count': 1,
140
+ }
141
+ r = requests.post(ticket_url, headers={**base_headers, 'Content-Type': 'application/json'}, json=payload, timeout=30)
142
+ add_log(f'Ticket response: {r.status_code} | {r.text[:200]}')
143
+ data = r.json()
144
+ upload_url = data.get('data', {}).get('upload_url') or data.get('upload_url')
145
+ if not upload_url:
146
+ add_log('โŒ Fallback also failed โ€” no upload_url')
147
+ return False
148
+
149
+ # Step B: upload chunk
150
  with open(video_path, 'rb') as f:
151
+ video_data = f.read()
152
+ r2 = requests.put(upload_url, headers={
153
+ 'Content-Type': 'video/mp4',
154
+ 'Content-Range': f'bytes 0-{video_size - 1}/{video_size}',
155
+ 'Content-Length': str(video_size),
156
+ }, data=video_data, timeout=300)
157
+ add_log(f'Fallback chunk: {r2.status_code} | {r2.text[:100]}')
158
+ if r2.status_code in (200, 201):
159
+ add_log('โœ… Fallback upload success!')
160
+ return True
161
+ return False
162
  except Exception as e:
163
+ add_log(f'โŒ Fallback error: {e}')
164
  return False
165
 
166
+
167
  def download_video(url, quality='720'):
168
  tid = uuid.uuid4().hex[:8]
169
  out_path = OUTPUT_DIR / f'{tid}.mp4'
170
+
171
  fmt_map = {'1080': 'best[height<=1080]', '720': 'best[height<=720]', '480': 'best[height<=480]'}
172
  fmt = fmt_map.get(quality, 'best[height<=720]')
173
+
174
  cmd = ['yt-dlp', '-f', fmt, '--merge-output-format', 'mp4', '-o', str(out_path), url]
175
  if os.path.exists(COOKIES_FILE):
176
  cmd += ['--cookies', COOKIES_FILE]
177
+
178
  add_log(f'๐Ÿ“ฅ Downloading: {url[:60]}...')
179
  result = subprocess.run(cmd, capture_output=True, text=True)
180
+
181
  if result.returncode == 0 and out_path.exists():
182
  add_log(f'โœ… Downloaded: {out_path.name}')
183
  return str(out_path)
184
  else:
185
+ add_log(f'โŒ Download failed: {result.stderr[:200]}')
186
  return None
187
 
188
+
189
  # โ”€โ”€ Routes โ”€โ”€
190
  @app.route('/')
191
  def index():
 
193
 
194
  @app.route('/api/upload/manual', methods=['POST'])
195
  def api_upload_manual():
 
196
  data = request.get_json() or {}
197
  video_url = data.get('url', '').strip()
198
+ title = data.get('title', '').strip()
199
+ quality = data.get('quality', '720')
200
+
201
  if not video_url:
202
  return jsonify({'ok': False, 'msg': 'No URL provided'})
203
  if not title:
204
  return jsonify({'ok': False, 'msg': 'No title provided'})
205
+
 
206
  video_path = download_video(video_url, quality)
207
  if not video_path:
208
  return jsonify({'ok': False, 'msg': 'Download failed'})
209
+
 
210
  success = upload_to_tiktok(video_path, title)
211
+ try: os.remove(video_path)
212
+ except: pass
213
+
214
+ return jsonify({'ok': success, 'msg': 'Upload completed' if success else 'Upload failed โ€” check logs'})
 
 
 
 
215
 
216
  @app.route('/api/upload/file', methods=['POST'])
217
  def api_upload_file():
 
218
  if 'video' not in request.files:
219
  return jsonify({'ok': False, 'msg': 'No video file'})
220
+
221
  video = request.files['video']
222
  title = request.form.get('title', 'Video')
223
+
 
224
  temp_path = OUTPUT_DIR / f'temp_{uuid.uuid4().hex[:8]}.mp4'
225
  video.save(str(temp_path))
226
+
 
227
  success = upload_to_tiktok(str(temp_path), title)
228
+ try: temp_path.unlink()
229
+ except: pass
230
+
231
+ return jsonify({'ok': success, 'msg': 'Upload completed' if success else 'Upload failed โ€” check logs'})
 
 
 
 
232
 
233
  @app.route('/api/bot/start', methods=['POST'])
234
  def api_bot_start():
 
250
 
251
  @app.route('/api/bot/status', methods=['GET'])
252
  def api_bot_status():
253
+ cookies = get_tiktok_cookies()
254
+ return jsonify({
255
+ 'running': bot_running,
256
+ 'logs': bot_logs[-50:],
257
+ 'tiktok_cookie': bool(cookies.get('sessionid')),
258
+ 'has_mstoken': bool(cookies.get('msToken')),
259
+ })
260
+
261
+ @app.route('/api/cookie/check', methods=['GET'])
262
+ def api_cookie_check():
263
+ """Debug endpoint โ€” show which cookie keys are present"""
264
+ cookies = get_tiktok_cookies()
265
+ keys = list(cookies.keys())
266
+ return jsonify({
267
+ 'total_keys': len(keys),
268
+ 'has_sessionid': 'sessionid' in cookies,
269
+ 'has_msToken': 'msToken' in cookies,
270
+ 'has_csrf': 'tt_csrf_token' in cookies,
271
+ 'keys': keys,
272
+ })
273
 
274
  def auto_poster_worker():
275
  global bot_running
276
  add_log('๐Ÿค– Auto Poster started')
277
  CHANNEL_ID = 'UCUZHFZ9jIKrLroW8LcyJEQQ'
278
  last_id = None
279
+
280
  while bot_running:
281
  try:
282
  import feedparser
283
+ feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={CHANNEL_ID}'
284
+ feed = feedparser.parse(feed_url)
285
+
286
  if feed.entries:
287
+ latest = feed.entries[0]
288
  video_id = latest.get('yt_videoid', '')
289
+ title = latest.get('title', '')
290
+
291
  if last_id is None:
292
  last_id = video_id
293
+ add_log(f'๐Ÿ“Œ Initialized. Latest: {video_id} โ€” {title[:40]}')
294
  elif video_id != last_id:
295
+ add_log(f'๐Ÿ“น New video detected: {title[:50]}')
296
  video_path = download_video(f'https://youtube.com/watch?v={video_id}', '720')
297
  if video_path:
298
  upload_to_tiktok(video_path, title)
299
  try: os.remove(video_path)
300
  except: pass
301
  last_id = video_id
302
+ else:
303
+ add_log(f'๐Ÿ’ค No new video. Last: {video_id}')
304
  except Exception as e:
305
+ add_log(f'โš ๏ธ Worker error: {e}')
306
+
307
+ # wait 5 min
308
  for _ in range(300):
309
+ if not bot_running:
310
+ break
311
  time.sleep(1)
312
 
313
+ add_log('๐Ÿ›‘ Auto Poster stopped')
314
+
315
+
316
+ # โ”€โ”€ HTML Template โ”€โ”€
317
  HTML_TEMPLATE = '''
318
  <!DOCTYPE html>
319
  <html>
320
  <head>
321
  <title>YouTube to TikTok Bot</title>
322
  <style>
323
+ body { font-family: monospace; max-width: 820px; margin: 0 auto; padding: 20px; background: #0a0a0a; color: #0f0; }
324
  .card { background: #111; border: 1px solid #333; border-radius: 8px; padding: 16px; margin-bottom: 20px; }
325
+ button { background: #00aa00; color: black; border: none; padding: 8px 16px; cursor: pointer; margin-right: 8px; border-radius: 4px; }
326
+ button:hover { background: #00cc00; }
327
+ button.red { background: #aa0000; color: #fff; }
328
+ button.red:hover { background: #cc0000; }
329
+ button.gray { background: #444; color: #fff; }
330
+ input, select, textarea { padding: 8px; margin: 5px 0; width: 100%; box-sizing: border-box; background: #222; color: #0f0; border: 1px solid #333; border-radius: 4px; }
331
+ .log-box { height: 220px; overflow-y: scroll; background: #050505; border: 1px solid #333; padding: 10px; font-size: 11px; line-height: 1.6; }
332
+ .status { padding: 8px 12px; border-radius: 4px; margin-bottom: 10px; font-weight: bold; }
333
  .status.running { background: #1a3a1a; color: #0f0; }
334
+ .status.stopped { background: #3a1a1a; color: #f55; }
335
+ hr { border-color: #333; margin: 12px 0; }
336
+ .cookie-info { font-size: 11px; color: #888; margin-top: 6px; }
337
+ .cookie-info span { color: #0f0; }
338
+ .cookie-info span.bad { color: #f55; }
339
+ h2 { margin: 0 0 12px 0; font-size: 15px; }
340
+ h1 { font-size: 20px; }
341
+ .result { margin-top: 8px; font-size: 12px; min-height: 18px; }
342
  </style>
343
  </head>
344
  <body>
345
+ <h1>๐ŸŽฌ YouTube โ†’ TikTok Bot</h1>
346
+
347
+ <!-- Cookie Status -->
348
+ <div class="card">
349
+ <h2>๐Ÿช Cookie Status</h2>
350
+ <div id="cookie-status" class="cookie-info">Loading...</div>
351
+ <button class="gray" onclick="checkCookie()" style="margin-top:8px;">๐Ÿ” Check Cookies</button>
352
+ </div>
353
+
354
+ <!-- Auto Bot -->
355
  <div class="card">
356
  <h2>๐Ÿค– Auto Poster Bot</h2>
357
  <div id="status" class="status stopped">๐Ÿ”ด STOPPED</div>
358
  <button onclick="startBot()">โ–ถ๏ธ START</button>
359
  <button class="red" onclick="stopBot()">โน๏ธ STOP</button>
360
+ <button class="gray" onclick="refreshStatus()">๐Ÿ”„ Refresh</button>
361
  <hr>
362
  <div id="log" class="log-box"></div>
363
  </div>
364
+
365
  <!-- Manual Upload by URL -->
366
  <div class="card">
367
  <h2>๐Ÿ“ค Manual Upload (by URL)</h2>
368
+ <input type="text" id="manual-url" placeholder="YouTube / Video URL">
369
+ <input type="text" id="manual-title" placeholder="Video Title (caption)">
370
  <select id="manual-quality">
371
  <option value="1080">1080p</option>
372
  <option value="720" selected>720p</option>
373
  <option value="480">480p</option>
374
  </select>
375
+ <button onclick="manualUpload()">โฌ†๏ธ Download & Upload to TikTok</button>
376
+ <div id="manual-result" class="result"></div>
377
  </div>
378
+
379
  <!-- Manual Upload by File -->
380
  <div class="card">
381
  <h2>๐Ÿ“ Manual Upload (by File)</h2>
382
+ <input type="file" id="file-input" accept="video/mp4,video/*">
383
+ <input type="text" id="file-title" placeholder="Video Title (caption)">
384
  <button onclick="manualUploadFile()">โฌ†๏ธ Upload File to TikTok</button>
385
+ <div id="file-result" class="result"></div>
386
  </div>
387
+
388
  <script>
389
+ async function checkCookie() {
390
+ const res = await fetch('/api/cookie/check');
391
+ const d = await res.json();
392
+ const el = document.getElementById('cookie-status');
393
+ const ok = v => `<span>${v ? 'โœ…' : '<span class="bad">โŒ</span>'}</span>`;
394
+ el.innerHTML = `
395
+ sessionid: ${ok(d.has_sessionid)} &nbsp;|&nbsp;
396
+ msToken: ${ok(d.has_msToken)} &nbsp;|&nbsp;
397
+ csrf: ${ok(d.has_csrf)} &nbsp;|&nbsp;
398
+ Total keys: <span>${d.total_keys}</span>
399
+ `;
400
+ }
401
+
402
  async function refreshStatus() {
403
  const res = await fetch('/api/bot/status');
404
+ const d = await res.json();
405
  const statusDiv = document.getElementById('status');
406
+ statusDiv.className = 'status ' + (d.running ? 'running' : 'stopped');
407
+ statusDiv.innerHTML = d.running ? '๐ŸŸข RUNNING' : '๐Ÿ”ด STOPPED';
408
+ const logEl = document.getElementById('log');
409
+ logEl.innerHTML = d.logs.map(l => `<div>${escapeHtml(l)}</div>`).join('');
410
+ logEl.scrollTop = logEl.scrollHeight;
 
 
 
411
  }
412
+
413
  async function startBot() {
414
  const res = await fetch('/api/bot/start', {method: 'POST'});
415
+ const d = await res.json();
416
+ alert(d.msg);
417
  refreshStatus();
418
  }
419
+
420
  async function stopBot() {
421
  const res = await fetch('/api/bot/stop', {method: 'POST'});
422
+ const d = await res.json();
423
+ alert(d.msg);
424
  refreshStatus();
425
  }
426
+
427
  async function manualUpload() {
428
+ const url = document.getElementById('manual-url').value.trim();
429
+ const title = document.getElementById('manual-title').value.trim();
430
  const quality = document.getElementById('manual-quality').value;
431
+ if (!url || !title) { alert('URL แ€”แ€ฒแ€ท Title แ€‘แ€Šแ€ทแ€บแ€•แ€ซ'); return; }
432
+
433
+ const el = document.getElementById('manual-result');
434
+ el.innerHTML = 'โณ Downloading & uploading... (แ€แ€แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€•แ€ซ)';
435
+ try {
436
+ const res = await fetch('/api/upload/manual', {
437
+ method: 'POST',
438
+ headers: {'Content-Type': 'application/json'},
439
+ body: JSON.stringify({url, title, quality})
440
+ });
441
+ const d = await res.json();
442
+ el.innerHTML = d.ok ? 'โœ… ' + d.msg : 'โŒ ' + d.msg;
443
+ } catch(e) {
444
+ el.innerHTML = 'โŒ Request error: ' + e;
445
+ }
446
  refreshStatus();
447
  }
448
+
449
  async function manualUploadFile() {
450
+ const file = document.getElementById('file-input').files[0];
451
+ const title = document.getElementById('file-title').value.trim();
452
+ if (!file || !title) { alert('File แ€›แ€ฝแ€ฑแ€ธแ€•แ€ผแ€ฎแ€ธ Title แ€‘แ€Šแ€ทแ€บแ€•แ€ซ'); return; }
453
+
454
+ const el = document.getElementById('file-result');
455
+ el.innerHTML = 'โณ Uploading file... (แ€แ€แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€•แ€ซ)';
456
  const formData = new FormData();
457
  formData.append('video', file);
458
  formData.append('title', title);
459
+ try {
460
+ const res = await fetch('/api/upload/file', {method: 'POST', body: formData});
461
+ const d = await res.json();
462
+ el.innerHTML = d.ok ? 'โœ… ' + d.msg : 'โŒ ' + d.msg;
463
+ } catch(e) {
464
+ el.innerHTML = 'โŒ Request error: ' + e;
465
+ }
466
  refreshStatus();
467
  }
468
+
469
  function escapeHtml(text) {
470
  const div = document.createElement('div');
471
  div.textContent = text;
472
  return div.innerHTML;
473
  }
474
+
475
+ // Init
476
+ checkCookie();
477
  refreshStatus();
478
  setInterval(refreshStatus, 5000);
479
  </script>
 
482
  '''
483
 
484
  if __name__ == '__main__':
485
+ app.run(host='0.0.0.0', port=7860, debug=False)