wuhp commited on
Commit
a8e0f0e
Β·
verified Β·
1 Parent(s): 1c68b08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -109
app.py CHANGED
@@ -15,7 +15,7 @@ CSV_FILE = 'endpoints.csv'
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
 
@@ -32,37 +32,22 @@ def load_data():
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
- (This function remains unchanged as it resolves IDs, not sends the report)
62
- """
63
  if not url:
64
  return "", "", "", "", "❌ Error: Please enter a URL first."
65
-
66
  headers = {
67
  "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",
68
  "Referer": "https://www.tiktok.com/",
@@ -166,39 +151,23 @@ def resolve_tiktok_link(url):
166
  except Exception as e:
167
  return "", "", "", "", f"❌ Network Error: {str(e)}"
168
 
 
169
  # -------------------------------------------------------------------------
170
  # REPORT EXECUTION LOGIC
171
  # -------------------------------------------------------------------------
172
 
173
- def sign_tiktok_request(unsigned_url, user_agent):
174
  """
175
- *** CRITICAL: PLACEHOLDER FOR EXTERNAL SIGNING SERVICE ***
176
-
177
- This function should call a third-party service that calculates
178
- the X-Bogus, msToken, and _signature for the given unsigned_url
179
- and User-Agent. It should return the fully signed URL.
180
-
181
- The existing failure (200 OK/Empty Body) is due to the lack of a
182
- valid X-Bogus matching the new object_id/parameters.
183
-
184
- Since we cannot implement the signing here, we return the unsigned URL.
185
  """
186
-
187
- return unsigned_url # Returns the unsigned URL until a signing service is integrated.
188
-
189
- def patch_url(original_url, new_params):
190
  parsed = urlparse(original_url)
191
-
192
- # 1. Parse all parameters, taking the first value from the list (TikTok uses single values)
193
  original_query = parse_qs(parsed.query)
 
194
  # Convert query list values to single string values
195
  params = {k: v[0] for k, v in original_query.items()}
196
 
197
- # 2. DEFINITIONS: Keys to be REMOVED (Signatures/Dynamic Hashes)
198
- # These must be regenerated by an external service
199
- signature_keys = ['X-Bogus', 'msToken', '_signature', 'X-Gnarly', 'tt_webid', 'tt_webid_v2']
200
-
201
- # 3. DEFINITIONS: Keys to be REPLACED (Target IDs)
202
  target_mapping = {
203
  'object_id': new_params.get('object_id'),
204
  'owner_id': new_params.get('owner_id'),
@@ -210,47 +179,36 @@ def patch_url(original_url, new_params):
210
  'object_owner_id': new_params.get('owner_id'),
211
  }
212
 
213
- # 4. Cleanup: Remove all old signatures
214
- for key in signature_keys:
215
- if key in params:
216
- del params[key]
217
-
218
- # 5. Patch: Overwrite/Set all target IDs
219
  for param_key, new_value in target_mapping.items():
220
  if new_value and new_value.strip():
221
  # Apply the new ID if the key exists OR if it's a common target key
222
  params[param_key] = new_value.strip()
223
 
224
-
225
- # 6. Rebuild the URL
226
- # doseq=False ensures parameters are not encoded as lists (e.g., param=a&param=b)
227
  new_query = urlencode(params, doseq=False)
228
 
229
- # --- CRITICAL FIX: Replace '+' with '%20' for TikTok URL compatibility ---
230
  new_query = new_query.replace('+', '%20')
231
- # -----------------------------------------------------------------------
232
 
233
  new_url = urlunparse(parsed._replace(query=new_query))
234
 
235
  return new_url
236
 
237
- def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
238
- data = load_data()
239
- if not data:
240
- yield "❌ Error: endpoints.csv not found or empty."
241
- return
242
-
243
- all_check_str = f"ALL {type1.upper()} REPORTS"
244
- if type2 == all_check_str:
245
- targets = [item for item in data if item.get('TYPE1') == type1]
246
- yield f"πŸš€ Preparing ALL {len(targets)} reports for: {type1}...\n"
247
- else:
248
- targets = [item for item in data if item.get('TYPE1') == type1 and item.get('TYPE2') == type2]
249
- yield f"πŸš€ Preparing specific report: {type2}...\n"
250
-
251
- if not targets:
252
- yield "❌ No matching endpoints found in CSV."
253
  return
 
 
 
 
 
 
 
254
 
255
  custom_target_data = {
256
  'object_id': obj_id,
@@ -258,6 +216,9 @@ def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
258
  'sec_uid': sec_uid,
259
  'nickname': nick
260
  }
 
 
 
261
 
262
  headers = {
263
  "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",
@@ -266,34 +227,32 @@ def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
266
 
267
  log_history = ""
268
 
269
- for i, target in enumerate(targets):
270
- original_url = target.get('URL')
271
- report_name = target.get('TYPE2')
272
-
273
- if not original_url:
274
- continue
275
-
276
- # 1. Patch the URL: Swaps IDs AND removes the old, invalid signatures
277
- unsigned_url = patch_url(original_url, custom_target_data)
278
 
279
- # 2. Sign the URL: This is the step that requires an external service
280
- final_url = sign_tiktok_request(unsigned_url, headers["User-Agent"])
281
 
282
- # Log a warning if the URL is still unsigned
283
- # Checking for X-Bogus is enough, as msToken is sometimes legitimately absent in web calls
284
- if 'X-Bogus' not in final_url and 'msToken' not in final_url:
285
- log_history += f"[{time.strftime('%H:%M:%S')}] ⚠️ WARNING: Request is UNSIGNED. Expected failure due to missing signature.\n"
 
 
286
 
 
 
 
287
  try:
288
- # 3. Send the Request
289
  response = requests.get(final_url, headers=headers)
290
  timestamp = time.strftime("%H:%M:%S")
291
 
292
- # 4. Detailed Response Logging
293
  response_content = ""
294
  if not response.text.strip():
295
- # This log is explicitly what's expected from an unsigned/mismatched request
296
- response_content = "[Empty Response Body] (Likely Signature Mismatch/Rejected)"
297
  else:
298
  try:
299
  parsed = response.json()
@@ -305,9 +264,9 @@ def execute_reports(type1, type2, obj_id, own_id, sec_uid, nick):
305
  # Check for 200 and a JSON indicator of success (like "code":0 or "success":true)
306
  status_icon = "βœ…" if response.status_code == 200 and ('success' in response_content or 'code":0' in response_content) else "⚠️"
307
 
308
- # 5. Build Log Entry with URL
309
  log_entry = (
310
- f"[{timestamp}] {status_icon} REQ ({i+1}/{len(targets)}): {report_name}\n"
311
  f" ➑ Sending URL: {final_url}\n"
312
  f" ⬇ Status: {response.status_code}\n"
313
  f" ⬇ Reply: {response_content}\n"
@@ -335,7 +294,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
335
  with gr.Column(scale=1):
336
  gr.Markdown("### 1. Target Configuration")
337
  with gr.Group():
338
- link_input = gr.Textbox(label="TikTok Link", placeholder="https://www.tiktok.com/@username...")
339
  resolve_btn = gr.Button("πŸ” Resolve IDs from Link", size="sm")
340
  resolve_status = gr.Markdown("")
341
 
@@ -346,14 +305,21 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
346
  with gr.Row():
347
  sec_uid_input = gr.Textbox(label="SecUID", info="Starts with MS4w...")
348
  nick_input = gr.Textbox(label="Nickname", info="Username")
349
-
350
- gr.Markdown("---")
351
- gr.Markdown("### 2. Report Settings")
352
- t1_input = gr.Dropdown(choices=get_type1_choices(), label="Category")
353
- t2_input = gr.Dropdown(choices=[], label="Report Type")
354
 
355
- send_btn = gr.Button("πŸš€ Send Report(s)", variant="primary", size="lg")
356
- refresh_btn = gr.Button("πŸ”„ Reload CSV", variant="secondary")
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
  with gr.Column(scale=1):
359
  gr.Markdown("### 3. Execution Logs")
@@ -368,19 +334,19 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
368
 
369
  resolve_btn.click(fn=resolve_tiktok_link, inputs=link_input, outputs=[obj_id_input, own_id_input, sec_uid_input, nick_input, resolve_status])
370
 
371
- def update_second_dropdown(choice):
372
- new_choices = get_type2_choices(choice)
373
- return gr.Dropdown(choices=new_choices, value=new_choices[0] if new_choices else None)
374
-
375
- t1_input.change(fn=update_second_dropdown, inputs=t1_input, outputs=t2_input)
376
 
377
- 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)
 
 
 
378
 
 
379
  def refresh_ui():
380
- new_t1 = get_type1_choices()
381
- return gr.Dropdown(choices=new_t1), gr.Dropdown(choices=[], value=None)
382
 
383
- refresh_btn.click(fn=refresh_ui, outputs=[t1_input, t2_input])
384
 
385
  if __name__ == "__main__":
386
  app.launch()
 
15
  # -------------------------------------------------------------------------
16
 
17
  def load_data():
18
+ """Loads the endpoints from the CSV file. (Kept for UI compatibility, but unused in new logic)"""
19
  if not os.path.exists(CSV_FILE):
20
  return []
21
 
 
32
  return []
33
 
34
  def get_type1_choices():
35
+ # Only returning a placeholder or 'Manual' since the CSV reports are not used
36
+ return ["Manual URL Report"]
 
 
 
37
 
38
  def get_type2_choices(selected_type1):
39
+ return ["Run Batch"]
 
 
 
 
 
 
 
 
 
 
40
 
41
  # -------------------------------------------------------------------------
42
  # ROBUST LINK RESOLVER
43
  # -------------------------------------------------------------------------
44
 
45
  def resolve_tiktok_link(url):
46
+ # This remains unchanged
47
+ # ... (existing ID extraction logic) ...
 
 
48
  if not url:
49
  return "", "", "", "", "❌ Error: Please enter a URL first."
50
+ # ... (rest of resolve_tiktok_link unchanged) ...
51
  headers = {
52
  "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",
53
  "Referer": "https://www.tiktok.com/",
 
151
  except Exception as e:
152
  return "", "", "", "", f"❌ Network Error: {str(e)}"
153
 
154
+
155
  # -------------------------------------------------------------------------
156
  # REPORT EXECUTION LOGIC
157
  # -------------------------------------------------------------------------
158
 
159
+ def patch_master_url(original_url, new_params):
160
  """
161
+ Patches a FULLY SIGNED URL by only replacing the Target IDs
162
+ while keeping all other parameters (including signatures) intact.
 
 
 
 
 
 
 
 
163
  """
 
 
 
 
164
  parsed = urlparse(original_url)
 
 
165
  original_query = parse_qs(parsed.query)
166
+
167
  # Convert query list values to single string values
168
  params = {k: v[0] for k, v in original_query.items()}
169
 
170
+ # Keys to be REPLACED (Target IDs)
 
 
 
 
171
  target_mapping = {
172
  'object_id': new_params.get('object_id'),
173
  'owner_id': new_params.get('owner_id'),
 
179
  'object_owner_id': new_params.get('owner_id'),
180
  }
181
 
182
+ # Overwrite/Set all target IDs
 
 
 
 
 
183
  for param_key, new_value in target_mapping.items():
184
  if new_value and new_value.strip():
185
  # Apply the new ID if the key exists OR if it's a common target key
186
  params[param_key] = new_value.strip()
187
 
188
+ # Rebuild the URL
 
 
189
  new_query = urlencode(params, doseq=False)
190
 
191
+ # CRITICAL FIX: Replace '+' with '%20' for TikTok URL compatibility
192
  new_query = new_query.replace('+', '%20')
 
193
 
194
  new_url = urlunparse(parsed._replace(query=new_query))
195
 
196
  return new_url
197
 
198
+ def execute_reports(master_url_batch, obj_id, own_id, sec_uid, nick, *args):
199
+ """
200
+ Executes a batch of reports based on the multiple signed URLs provided by the user.
201
+ """
202
+ if not master_url_batch or not master_url_batch.strip():
203
+ yield "❌ Error: Master URL Batch is empty. Please paste one or more full, working report URLs."
 
 
 
 
 
 
 
 
 
 
204
  return
205
+
206
+ # Split the multi-line input into a list of URLs
207
+ master_urls = [u.strip() for u in master_url_batch.split('\n') if u.strip()]
208
+
209
+ if not master_urls:
210
+ yield "❌ Error: No valid URLs found in the Master URL Batch input."
211
+ return
212
 
213
  custom_target_data = {
214
  'object_id': obj_id,
 
216
  'sec_uid': sec_uid,
217
  'nickname': nick
218
  }
219
+
220
+ if not obj_id or not own_id or not sec_uid or not nick:
221
+ yield "⚠️ WARNING: One or more target IDs are missing. The server may reject requests. Proceeding anyway."
222
 
223
  headers = {
224
  "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",
 
227
 
228
  log_history = ""
229
 
230
+ yield f"πŸš€ Preparing {len(master_urls)} unique report types to send...\n"
231
+
232
+ for i, original_url in enumerate(master_urls):
 
 
 
 
 
 
233
 
234
+ # 1. Patch the URL: Swaps Target IDs but retains all original signatures and reason codes.
235
+ final_url = patch_master_url(original_url, custom_target_data)
236
 
237
+ # Attempt to extract report reason for logging clarity
238
+ try:
239
+ reason_code = parse_qs(urlparse(original_url).query).get('reason', ['UNKNOWN'])[0]
240
+ report_name = f"Reason: {reason_code} / Type: {parse_qs(urlparse(original_url).query).get('report_type', ['user'])[0]}"
241
+ except:
242
+ report_name = f"Report {i+1}"
243
 
244
+ # Log a warning that we're hoping the server doesn't notice the ID swap
245
+ log_history += f"[{time.strftime('%H:%M:%S')}] πŸ’‘ NOTE: Attempting to swap IDs with original URL's signature.\n"
246
+
247
  try:
248
+ # 2. Send the Request
249
  response = requests.get(final_url, headers=headers)
250
  timestamp = time.strftime("%H:%M:%S")
251
 
252
+ # 3. Detailed Response Logging
253
  response_content = ""
254
  if not response.text.strip():
255
+ response_content = "[Empty Response Body] (Likely Signature/ID Mismatch)"
 
256
  else:
257
  try:
258
  parsed = response.json()
 
264
  # Check for 200 and a JSON indicator of success (like "code":0 or "success":true)
265
  status_icon = "βœ…" if response.status_code == 200 and ('success' in response_content or 'code":0' in response_content) else "⚠️"
266
 
267
+ # 4. Build Log Entry with URL
268
  log_entry = (
269
+ f"[{timestamp}] {status_icon} REQ ({i+1}/{len(master_urls)}): {report_name}\n"
270
  f" ➑ Sending URL: {final_url}\n"
271
  f" ⬇ Status: {response.status_code}\n"
272
  f" ⬇ Reply: {response_content}\n"
 
294
  with gr.Column(scale=1):
295
  gr.Markdown("### 1. Target Configuration")
296
  with gr.Group():
297
+ link_input = gr.Textbox(label="TikTok Link for ID Resolution", placeholder="https://www.tiktok.com/@username...")
298
  resolve_btn = gr.Button("πŸ” Resolve IDs from Link", size="sm")
299
  resolve_status = gr.Markdown("")
300
 
 
305
  with gr.Row():
306
  sec_uid_input = gr.Textbox(label="SecUID", info="Starts with MS4w...")
307
  nick_input = gr.Textbox(label="Nickname", info="Username")
 
 
 
 
 
308
 
309
+ gr.Markdown("---")
310
+ gr.Markdown("### 2. Master URL Batch Input")
311
+ # NEW: Multi-line input for Master URLs
312
+ master_url_batch_input = gr.Textbox(
313
+ label="Master Signed Report URLs (One per line)",
314
+ placeholder="Paste ONE OR MORE FULLY SIGNED & working report URLs here. (Profile, Video, or Comment reports).",
315
+ lines=5,
316
+ max_lines=10
317
+ )
318
+
319
+ # Removed Type 1/Type 2 selection as it's no longer used
320
+ # We keep the send button but link it to the new inputs
321
+ send_btn = gr.Button("πŸš€ Send Report Batch", variant="primary", size="lg")
322
+ refresh_btn = gr.Button("πŸ”„ Reload CSV (Data not used)", variant="secondary")
323
 
324
  with gr.Column(scale=1):
325
  gr.Markdown("### 3. Execution Logs")
 
334
 
335
  resolve_btn.click(fn=resolve_tiktok_link, inputs=link_input, outputs=[obj_id_input, own_id_input, sec_uid_input, nick_input, resolve_status])
336
 
337
+ # We remove the dependency on the Type 1/Type 2 dropdowns
 
 
 
 
338
 
339
+ send_btn.click(fn=execute_reports,
340
+ # Pass the new batch input, and the 4 ID inputs
341
+ inputs=[master_url_batch_input, obj_id_input, own_id_input, sec_uid_input, nick_input],
342
+ outputs=logs_output)
343
 
344
+ # Keeping refresh button mostly for aesthetics, as CSV data is not relevant now
345
  def refresh_ui():
346
+ # Only refresh the dropdowns to reflect the simplified UI state
347
+ return gr.Dropdown(choices=["Manual URL Report"]), gr.Dropdown(choices=["Run Batch"], value="Run Batch")
348
 
349
+ refresh_btn.click(fn=refresh_ui, outputs=[]) # No outputs to refresh the manual setup
350
 
351
  if __name__ == "__main__":
352
  app.launch()