wuhp commited on
Commit
752f720
Β·
verified Β·
1 Parent(s): 843fb8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -67
app.py CHANGED
@@ -15,8 +15,10 @@ CSV_FILE = 'endpoints.csv'
15
  # -------------------------------------------------------------------------
16
 
17
  def load_data():
 
18
  if not os.path.exists(CSV_FILE):
19
  return []
 
20
  data = []
21
  try:
22
  with open(CSV_FILE, 'r', encoding='utf-8') as f:
@@ -30,66 +32,138 @@ def load_data():
30
  return []
31
 
32
  def get_type1_choices():
 
33
  data = load_data()
34
  if not data:
35
  return []
36
  return list(set([item.get('TYPE1') for item in data if item.get('TYPE1')]))
37
 
38
  def get_type2_choices(selected_type1):
 
39
  data = load_data()
40
  if not data or not selected_type1:
41
  return []
 
42
  choices = [item.get('TYPE2') for item in data if item.get('TYPE1') == selected_type1]
43
  unique_choices = list(set(choices))
44
  unique_choices.sort()
 
45
  all_option = f"ALL {selected_type1.upper()} REPORTS"
46
  return [all_option] + unique_choices
47
 
48
  # -------------------------------------------------------------------------
49
- # LINK RESOLVER
50
  # -------------------------------------------------------------------------
51
 
52
  def resolve_tiktok_link(url):
 
 
 
53
  if not url:
54
  return "", "", "", "", "❌ Error: Please enter a URL first."
55
 
56
  headers = {
57
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
58
  "Referer": "https://www.tiktok.com/",
 
59
  }
60
 
61
  try:
62
- response = requests.get(url, headers=headers, timeout=15)
 
63
  html = response.text
64
- data_found = {"object_id": "", "owner_id": "", "sec_uid": "", "nickname": ""}
65
-
66
- # Regex fallback strategies
67
- vid_match = re.search(r'/video/(\d+)', response.url)
68
- if vid_match:
69
- data_found['object_id'] = vid_match.group(1)
70
 
71
- # User ID fallbacks
72
- id_match = re.search(r'"ownerId":"(\d+)"', html) or re.search(r'"authorId":"(\d+)"', html)
73
- if id_match:
74
- data_found['owner_id'] = id_match.group(1)
75
- # If profile, object_id is the owner_id
76
- if "/@ " in response.url or not data_found['object_id']:
77
- data_found['object_id'] = data_found['owner_id']
78
-
79
- sec_match = re.search(r'"secUid":"(MS4w[^"]+)"', html)
80
- if sec_match:
81
- data_found['sec_uid'] = sec_match.group(1)
82
-
83
- nick_match = re.search(r'"uniqueId":"([^"]+)"', html)
84
- if nick_match:
85
- data_found['nickname'] = nick_match.group(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  if data_found['object_id']:
88
- return data_found['object_id'], data_found['owner_id'], data_found['sec_uid'], data_found['nickname'], f"βœ… Resolved: {data_found['object_id']}"
89
- return "", "", "", "", "⚠️ Could not resolve IDs."
 
 
 
 
 
 
 
90
 
91
  except Exception as e:
92
- return "", "", "", "", f"❌ Error: {str(e)}"
93
 
94
  # -------------------------------------------------------------------------
95
  # REPORT EXECUTION LOGIC
@@ -111,14 +185,6 @@ def patch_url(original_url, new_params):
111
  if new_value and new_value.strip():
112
  query_params[param_key] = [new_value.strip()]
113
 
114
- # REMOVE INVALID SIGNATURES
115
- # Since we changed the data, these signatures are now invalid and will cause rejection.
116
- # It is cleaner to send NO signature than a WRONG signature, though the API may still require one.
117
- keys_to_remove = ['X-Bogus', 'msToken', '_signature', 'X-Gnarly']
118
- for k in keys_to_remove:
119
- if k in query_params:
120
- del query_params[k]
121
-
122
  new_query = urlencode(query_params, doseq=True)
123
  new_url = urlunparse(parsed._replace(query=new_query))
124
  return new_url
@@ -126,61 +192,139 @@ def patch_url(original_url, new_params):
126
  def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
127
  data = load_data()
128
  if not data:
129
- yield "❌ Error: endpoints.csv missing."
130
  return
131
 
132
  all_check_str = f"ALL {type1.upper()} REPORTS"
133
- targets = [item for item in data if item.get('TYPE1') == type1] if type2 == all_check_str else \
134
- [item for item in data if item.get('TYPE1') == type1 and item.get('TYPE2') == type2]
 
 
 
 
135
 
136
  if not targets:
137
- yield "❌ No matching endpoints."
138
  return
139
 
140
- custom_data = {'object_id': obj_id, 'owner_id': own_id, 'sec_uid': sec_uid, 'nickname': nick}
 
 
 
 
 
 
141
  headers = {
142
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
143
  "Referer": "https://www.tiktok.com/"
144
  }
 
 
145
 
146
- log = ""
147
  for i, target in enumerate(targets):
148
- final_url = patch_url(target.get('URL'), custom_data)
 
 
 
 
 
 
 
149
  try:
150
- resp = requests.get(final_url, headers=headers)
151
- # Log exact response
152
- content = "[Empty]" if not resp.text.strip() else resp.text[:200]
153
- log += f"[{time.strftime('%H:%M:%S')}] {resp.status_code} ({i+1}/{len(targets)}): {target.get('TYPE2')}\n ➑ URL: {final_url}\n ➑ Reply: {content}\n{'-'*40}\n"
154
- yield log
155
- time.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  except Exception as e:
157
- log += f"Error: {e}\n"
158
- yield log
 
 
159
 
160
  # -------------------------------------------------------------------------
161
  # UI SETUP
162
  # -------------------------------------------------------------------------
163
 
164
  with gr.Blocks(theme=gr.themes.Soft()) as app:
165
- gr.Markdown("# πŸ€– TikTok Report Tool")
 
166
  with gr.Row():
167
- with gr.Column():
168
- link_in = gr.Textbox(label="Link")
169
- resolve_btn = gr.Button("Resolve")
170
- res_stats = gr.Markdown()
171
- obj_id, own_id = gr.Textbox(label="Object ID"), gr.Textbox(label="Owner ID")
172
- sec_uid, nick = gr.Textbox(label="SecUID"), gr.Textbox(label="Nickname")
173
- t1 = gr.Dropdown(choices=get_type1_choices(), label="Category")
174
- t2 = gr.Dropdown(choices=[], label="Type")
175
- send = gr.Button("Send", variant="primary")
176
- refresh = gr.Button("Reload CSV")
177
- with gr.Column():
178
- logs = gr.Textbox(label="Logs", lines=20)
179
-
180
- resolve_btn.click(resolve_tiktok_link, link_in, [obj_id, own_id, sec_uid, nick, res_stats])
181
- t1.change(lambda x: gr.Dropdown(choices=get_type2_choices(x)), t1, t2)
182
- refresh.click(lambda: (gr.Dropdown(choices=get_type1_choices()), gr.Dropdown(choices=[])), None, [t1, t2])
183
- send.click(execute_reports, [t1, t2, obj_id, own_id, sec_uid, nick], logs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  if __name__ == "__main__":
186
  app.launch()
 
15
  # -------------------------------------------------------------------------
16
 
17
  def load_data():
18
+ """Loads the endpoints from the CSV file."""
19
  if not os.path.exists(CSV_FILE):
20
  return []
21
+
22
  data = []
23
  try:
24
  with open(CSV_FILE, 'r', encoding='utf-8') as f:
 
32
  return []
33
 
34
  def get_type1_choices():
35
+ """Extracts unique TYPE1 categories."""
36
  data = load_data()
37
  if not data:
38
  return []
39
  return list(set([item.get('TYPE1') for item in data if item.get('TYPE1')]))
40
 
41
  def get_type2_choices(selected_type1):
42
+ """Extracts TYPE2 options based on selected TYPE1."""
43
  data = load_data()
44
  if not data or not selected_type1:
45
  return []
46
+
47
  choices = [item.get('TYPE2') for item in data if item.get('TYPE1') == selected_type1]
48
  unique_choices = list(set(choices))
49
  unique_choices.sort()
50
+
51
  all_option = f"ALL {selected_type1.upper()} REPORTS"
52
  return [all_option] + unique_choices
53
 
54
  # -------------------------------------------------------------------------
55
+ # ROBUST LINK RESOLVER
56
  # -------------------------------------------------------------------------
57
 
58
  def resolve_tiktok_link(url):
59
+ """
60
+ Attempts to fetch Object ID, Owner ID, and SecUID from a TikTok URL.
61
+ """
62
  if not url:
63
  return "", "", "", "", "❌ Error: Please enter a URL first."
64
 
65
  headers = {
66
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
67
  "Referer": "https://www.tiktok.com/",
68
+ "Accept-Language": "en-US,en;q=0.9",
69
  }
70
 
71
  try:
72
+ response = requests.get(url, headers=headers, timeout=15, allow_redirects=True)
73
+ final_url = response.url
74
  html = response.text
 
 
 
 
 
 
75
 
76
+ data_found = {
77
+ "object_id": "",
78
+ "owner_id": "",
79
+ "sec_uid": "",
80
+ "nickname": ""
81
+ }
82
+
83
+ # Strategy 1: Universal Data
84
+ script_match = re.search(r'<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application/json">([^<]+)</script>', html)
85
+ if script_match:
86
+ try:
87
+ json_data = json.loads(script_match.group(1))
88
+ default_scope = json_data.get('__DEFAULT_SCOPE__', {})
89
+
90
+ # Video Detail
91
+ video_detail = default_scope.get('webapp.video-detail', {})
92
+ if video_detail and 'itemInfo' in video_detail:
93
+ item_struct = video_detail['itemInfo'].get('itemStruct', {})
94
+ data_found['object_id'] = item_struct.get('id', '')
95
+ author = item_struct.get('author', {})
96
+ data_found['owner_id'] = author.get('id', '')
97
+ data_found['sec_uid'] = author.get('secUid', '')
98
+ data_found['nickname'] = author.get('uniqueId', '')
99
+
100
+ # User Detail
101
+ elif 'webapp.user-detail' in default_scope:
102
+ user_detail = default_scope.get('webapp.user-detail', {})
103
+ user_info = user_detail.get('userInfo', {}).get('user', {})
104
+ if user_info:
105
+ data_found['object_id'] = user_info.get('id', '')
106
+ data_found['owner_id'] = user_info.get('id', '')
107
+ data_found['sec_uid'] = user_info.get('secUid', '')
108
+ data_found['nickname'] = user_info.get('uniqueId', '')
109
+ except:
110
+ pass
111
+
112
+ # Strategy 2: SIGI State
113
+ if not data_found['object_id']:
114
+ sigi_match = re.search(r'<script id="SIGI_STATE" type="application/json">([^<]+)</script>', html)
115
+ if sigi_match:
116
+ try:
117
+ sigi_data = json.loads(sigi_match.group(1))
118
+ item_module = sigi_data.get('ItemModule', {})
119
+ if item_module:
120
+ video_data = list(item_module.values())[0]
121
+ data_found['object_id'] = video_data.get('id', '')
122
+ data_found['owner_id'] = video_data.get('authorId', '')
123
+ data_found['nickname'] = video_data.get('author', '')
124
+ user_module = sigi_data.get('UserModule', {}).get('users', {})
125
+ if data_found['nickname'] in user_module:
126
+ data_found['sec_uid'] = user_module[data_found['nickname']].get('secUid', '')
127
+ elif 'UserModule' in sigi_data:
128
+ user_module = sigi_data.get('UserModule', {}).get('users', {})
129
+ if user_module:
130
+ user_data = list(user_module.values())[0]
131
+ data_found['object_id'] = user_data.get('id', '')
132
+ data_found['owner_id'] = user_data.get('id', '')
133
+ data_found['sec_uid'] = user_data.get('secUid', '')
134
+ data_found['nickname'] = user_data.get('uniqueId', '')
135
+ except:
136
+ pass
137
+
138
+ # Strategy 3: URL/Regex Fallback
139
+ if not data_found['object_id'] and "/video/" in final_url:
140
+ vid_match = re.search(r'/video/(\d+)', final_url)
141
+ if vid_match:
142
+ data_found['object_id'] = vid_match.group(1)
143
+
144
+ if not data_found['owner_id']:
145
+ owner_match = re.search(r'"ownerId":"(\d+)"', html) or re.search(r'"authorId":"(\d+)"', html)
146
+ if owner_match:
147
+ data_found['owner_id'] = owner_match.group(1)
148
+
149
+ if not data_found['sec_uid']:
150
+ sec_match = re.search(r'"secUid":"(MS4w[^"]+)"', html)
151
+ if sec_match:
152
+ data_found['sec_uid'] = sec_match.group(1)
153
 
154
  if data_found['object_id']:
155
+ return (
156
+ data_found['object_id'],
157
+ data_found['owner_id'],
158
+ data_found['sec_uid'],
159
+ data_found['nickname'],
160
+ f"βœ… Success! Resolved: {data_found['nickname']} (Obj ID: {data_found['object_id']})"
161
+ )
162
+ else:
163
+ return "", "", "", "", "⚠️ Failed to resolve. Check the link or enter IDs manually."
164
 
165
  except Exception as e:
166
+ return "", "", "", "", f"❌ Network Error: {str(e)}"
167
 
168
  # -------------------------------------------------------------------------
169
  # REPORT EXECUTION LOGIC
 
185
  if new_value and new_value.strip():
186
  query_params[param_key] = [new_value.strip()]
187
 
 
 
 
 
 
 
 
 
188
  new_query = urlencode(query_params, doseq=True)
189
  new_url = urlunparse(parsed._replace(query=new_query))
190
  return new_url
 
192
  def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
193
  data = load_data()
194
  if not data:
195
+ yield "❌ Error: endpoints.csv not found or empty."
196
  return
197
 
198
  all_check_str = f"ALL {type1.upper()} REPORTS"
199
+ if type2 == all_check_str:
200
+ targets = [item for item in data if item.get('TYPE1') == type1]
201
+ yield f"πŸš€ Preparing ALL {len(targets)} reports for: {type1}...\n"
202
+ else:
203
+ targets = [item for item in data if item.get('TYPE1') == type1 and item.get('TYPE2') == type2]
204
+ yield f"πŸš€ Preparing specific report: {type2}...\n"
205
 
206
  if not targets:
207
+ yield "❌ No matching endpoints found in CSV."
208
  return
209
 
210
+ custom_target_data = {
211
+ 'object_id': obj_id,
212
+ 'owner_id': own_id,
213
+ 'sec_uid': sec_uid,
214
+ 'nickname': nick
215
+ }
216
+
217
  headers = {
218
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
219
  "Referer": "https://www.tiktok.com/"
220
  }
221
+
222
+ log_history = ""
223
 
 
224
  for i, target in enumerate(targets):
225
+ original_url = target.get('URL')
226
+ report_name = target.get('TYPE2')
227
+
228
+ if not original_url:
229
+ continue
230
+
231
+ final_url = patch_url(original_url, custom_target_data)
232
+
233
  try:
234
+ # 1. Send the Request
235
+ response = requests.get(final_url, headers=headers)
236
+ timestamp = time.strftime("%H:%M:%S")
237
+
238
+ # 2. Detailed Response Logging
239
+ response_content = ""
240
+ if not response.text.strip():
241
+ response_content = "[Empty Response Body] (Signature mismatch likely)"
242
+ else:
243
+ try:
244
+ parsed = response.json()
245
+ response_content = json.dumps(parsed, ensure_ascii=False)
246
+ except:
247
+ preview = response.text[:300].replace("\n", " ")
248
+ response_content = f"Raw: {preview}..." if len(response.text) > 300 else f"Raw: {response.text}"
249
+
250
+ status_icon = "βœ…" if response.status_code == 200 else "⚠️"
251
+
252
+ # 3. Build Log Entry with URL
253
+ log_entry = (
254
+ f"[{timestamp}] {status_icon} REQ ({i+1}/{len(targets)}): {report_name}\n"
255
+ f" ➑ Sending URL: {final_url}\n"
256
+ f" ⬇ Status: {response.status_code}\n"
257
+ f" ⬇ Reply: {response_content}\n"
258
+ f"{'-'*50}\n"
259
+ )
260
+
261
+ log_history += log_entry
262
+ yield log_history
263
+ time.sleep(1)
264
+
265
  except Exception as e:
266
+ log_history += f"[{time.strftime('%H:%M:%S')}] ❌ ERROR: {str(e)}\n"
267
+ yield log_history
268
+
269
+ yield log_history + "\nβœ… DONE. All tasks completed."
270
 
271
  # -------------------------------------------------------------------------
272
  # UI SETUP
273
  # -------------------------------------------------------------------------
274
 
275
  with gr.Blocks(theme=gr.themes.Soft()) as app:
276
+ gr.Markdown("# πŸ€– TikTok Report Bot Controller")
277
+
278
  with gr.Row():
279
+ with gr.Column(scale=1):
280
+ gr.Markdown("### 1. Target Configuration")
281
+ with gr.Group():
282
+ link_input = gr.Textbox(label="TikTok Link", placeholder="https://www.tiktok.com/@username...")
283
+ resolve_btn = gr.Button("πŸ” Resolve IDs from Link", size="sm")
284
+ resolve_status = gr.Markdown("")
285
+
286
+ with gr.Row():
287
+ obj_id_input = gr.Textbox(label="Target Object ID", info="User/Video ID")
288
+ own_id_input = gr.Textbox(label="Target Owner ID", info="Account ID")
289
+
290
+ with gr.Row():
291
+ sec_uid_input = gr.Textbox(label="SecUID", info="Starts with MS4w...")
292
+ nick_input = gr.Textbox(label="Nickname", info="Username")
293
+
294
+ gr.Markdown("---")
295
+ gr.Markdown("### 2. Report Settings")
296
+ t1_input = gr.Dropdown(choices=get_type1_choices(), label="Category")
297
+ t2_input = gr.Dropdown(choices=[], label="Report Type")
298
+
299
+ send_btn = gr.Button("πŸš€ Send Report(s)", variant="primary", size="lg")
300
+ refresh_btn = gr.Button("πŸ”„ Reload CSV", variant="secondary")
301
+
302
+ with gr.Column(scale=1):
303
+ gr.Markdown("### 3. Execution Logs")
304
+ logs_output = gr.Textbox(
305
+ show_label=False,
306
+ placeholder="Server responses will appear here...",
307
+ lines=25,
308
+ max_lines=25,
309
+ autoscroll=True,
310
+ elem_id="log-box"
311
+ )
312
+
313
+ resolve_btn.click(fn=resolve_tiktok_link, inputs=link_input, outputs=[obj_id_input, own_id_input, sec_uid_input, nick_input, resolve_status])
314
+
315
+ def update_second_dropdown(choice):
316
+ new_choices = get_type2_choices(choice)
317
+ return gr.Dropdown(choices=new_choices, value=new_choices[0] if new_choices else None)
318
+
319
+ t1_input.change(fn=update_second_dropdown, inputs=t1_input, outputs=t2_input)
320
+
321
+ send_btn.click(fn=execute_reports, inputs=[t1_input, t2_input, obj_id_input, own_id_input, sec_uid_input, nick_input], outputs=logs_output)
322
+
323
+ def refresh_ui():
324
+ new_t1 = get_type1_choices()
325
+ return gr.Dropdown(choices=new_t1), gr.Dropdown(choices=[], value=None)
326
+
327
+ refresh_btn.click(fn=refresh_ui, outputs=[t1_input, t2_input])
328
 
329
  if __name__ == "__main__":
330
  app.launch()