nakas commited on
Commit
bcb2dcb
·
verified ·
1 Parent(s): cf419e6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +468 -145
app.py CHANGED
@@ -79,9 +79,147 @@ def verify_playwright_installation():
79
  logger.warning("Playwright browser not found in expected locations")
80
  return False
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  def run_instagram_liker(username, password, max_likes):
83
  """Run the Instagram auto-liker with Playwright"""
84
- status_updates = ["Starting Instagram Auto-Liker with Playwright..."]
85
  image_path = save_placeholder_image("start")
86
 
87
  # Check if browsers are installed, if not install them
@@ -113,7 +251,11 @@ def run_instagram_liker(username, password, max_likes):
113
  status_updates.append("Launching Playwright browser...")
114
  with sync_playwright() as p:
115
  try:
116
- # Launch chromium with specific arguments to ensure it works in containerized environments
 
 
 
 
117
  browser = p.chromium.launch(
118
  headless=True,
119
  args=[
@@ -125,18 +267,39 @@ def run_instagram_liker(username, password, max_likes):
125
  ]
126
  )
127
 
128
- # Create a new browser context
 
 
 
 
 
 
 
 
129
  context = browser.new_context(
130
- 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',
131
- viewport={'width': 1280, 'height': 720}
 
 
132
  )
133
 
 
 
 
 
 
 
 
 
 
 
134
  # Open a new page
135
  page = context.new_page()
136
 
137
  # Test browser by visiting Google
138
  status_updates.append("Testing browser connection...")
139
  page.goto("https://www.google.com")
 
140
  status_updates.append(f"Browser working. Title: {page.title()}")
141
 
142
  # Take a screenshot
@@ -148,10 +311,14 @@ def run_instagram_liker(username, password, max_likes):
148
  except Exception as e:
149
  status_updates.append(f"Error taking screenshot: {str(e)}")
150
 
151
- # Navigate to Instagram
152
  status_updates.append("Navigating to Instagram...")
153
  page.goto("https://www.instagram.com/")
154
- page.wait_for_load_state('networkidle')
 
 
 
 
155
 
156
  # Take a screenshot to see what's happening
157
  try:
@@ -162,22 +329,25 @@ def run_instagram_liker(username, password, max_likes):
162
  except Exception as e:
163
  status_updates.append(f"Error taking screenshot: {str(e)}")
164
 
165
- # Introduce delay to let the page fully load
166
- status_updates.append("Waiting for page to fully load...")
167
- page.wait_for_timeout(5000)
 
 
 
 
 
168
 
169
- # Take another screenshot after waiting
170
- try:
171
- loaded_screenshot = f"screenshots/fully_loaded_{int(time.time())}.png"
172
- page.screenshot(path=loaded_screenshot)
173
- image_path = loaded_screenshot
174
- status_updates.append("Page fully loaded, screenshot saved")
175
- except Exception as e:
176
- status_updates.append(f"Error taking screenshot: {str(e)}")
177
 
178
- # Try clicking outside any potential popups
179
  try:
180
- page.mouse.click(10, 10)
 
 
181
  status_updates.append("Clicked to dismiss any initial popups")
182
  except Exception as e:
183
  status_updates.append(f"Click error: {str(e)}")
@@ -194,10 +364,20 @@ def run_instagram_liker(username, password, max_likes):
194
 
195
  for button_selector in cookie_buttons:
196
  try:
197
- if page.query_selector(button_selector):
198
- page.click(button_selector)
 
 
 
 
 
 
 
 
 
 
199
  status_updates.append(f"Clicked cookie consent button: {button_selector}")
200
- page.wait_for_timeout(2000)
201
  break
202
  except Exception as e:
203
  logger.debug(f"Button {button_selector} not found: {str(e)}")
@@ -205,7 +385,7 @@ def run_instagram_liker(username, password, max_likes):
205
  except Exception as e:
206
  status_updates.append(f"Cookie dialog handling: {str(e)}")
207
 
208
- # Look for the login form
209
  status_updates.append("Looking for login form...")
210
 
211
  # Take a screenshot to see what we're working with
@@ -217,6 +397,10 @@ def run_instagram_liker(username, password, max_likes):
217
  except Exception as e:
218
  status_updates.append(f"Error taking screenshot: {str(e)}")
219
 
 
 
 
 
220
  # Try multiple selectors for username input
221
  username_selectors = [
222
  "input[name='username']",
@@ -276,10 +460,34 @@ def run_instagram_liker(username, password, max_likes):
276
  browser.close()
277
  return "\n".join(status_updates), image_path
278
 
279
- # Enter credentials
280
  status_updates.append(f"Entering username: {username}")
281
- username_field.fill(username)
282
- password_field.fill(password)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  status_updates.append("Credentials entered")
284
 
285
  # Take a screenshot of filled form
@@ -321,13 +529,20 @@ def run_instagram_liker(username, password, max_likes):
321
  browser.close()
322
  return "\n".join(status_updates), image_path
323
 
324
- # Click login button
325
  status_updates.append("Clicking login button...")
326
- login_button.click()
 
 
 
 
 
 
327
 
328
- # Wait for navigation to complete
329
  status_updates.append("Waiting for login process...")
330
- page.wait_for_timeout(5000)
 
331
 
332
  # Take post-login screenshot
333
  try:
@@ -338,6 +553,10 @@ def run_instagram_liker(username, password, max_likes):
338
  except Exception as e:
339
  status_updates.append(f"Error taking screenshot: {str(e)}")
340
 
 
 
 
 
341
  # Check if login was successful
342
  current_url = page.url
343
  if "/accounts/login" in current_url or "/login" in current_url:
@@ -372,7 +591,7 @@ def run_instagram_liker(username, password, max_likes):
372
 
373
  # Handle "Save Login Info" popup if it appears
374
  try:
375
- page.wait_for_timeout(2000) # Wait a bit for dialog to appear
376
 
377
  save_info_selectors = [
378
  "text=Save Login Info",
@@ -395,15 +614,24 @@ def run_instagram_liker(username, password, max_likes):
395
  break
396
 
397
  if dialog_found:
 
 
 
398
  # Try to click "Not Now"
399
  dismissed = False
400
  for not_now in not_now_selectors:
401
  try:
402
  button = page.query_selector(not_now)
403
  if button:
404
- button.click()
 
 
 
 
 
 
405
  status_updates.append(f"Dismissed 'Save Login Info' popup using: {not_now}")
406
- page.wait_for_timeout(2000)
407
  dismissed = True
408
  break
409
  except Exception as e:
@@ -428,7 +656,7 @@ def run_instagram_liker(username, password, max_likes):
428
 
429
  # Handle notifications popup if it appears
430
  try:
431
- page.wait_for_timeout(2000) # Wait a bit for dialog to appear
432
 
433
  notifications_selectors = [
434
  "text=Turn on Notifications",
@@ -451,15 +679,24 @@ def run_instagram_liker(username, password, max_likes):
451
  break
452
 
453
  if dialog_found:
 
 
 
454
  # Try to click "Not Now"
455
  dismissed = False
456
  for not_now in not_now_selectors:
457
  try:
458
  button = page.query_selector(not_now)
459
  if button:
460
- button.click()
 
 
 
 
 
 
461
  status_updates.append(f"Dismissed notifications popup using: {not_now}")
462
- page.wait_for_timeout(2000)
463
  dismissed = True
464
  break
465
  except Exception as e:
@@ -482,148 +719,234 @@ def run_instagram_liker(username, password, max_likes):
482
  except Exception as e:
483
  status_updates.append(f"Error taking screenshot: {str(e)}")
484
 
 
 
 
 
485
  status_updates.append("Successfully navigated to Instagram feed!")
486
 
487
- # Start liking posts
488
- status_updates.append(f"Starting to like posts (target: {max_likes})...")
 
489
 
490
  # Like posts
491
  likes_count = 0
492
  scroll_count = 0
493
- max_scrolls = 30
494
 
495
  # Wait for the feed to load fully before looking for posts
496
- page.wait_for_timeout(3000)
497
-
498
- # Try to find posts with multiple selectors
499
- article_selectors = [
500
- "article",
501
- "div[role='presentation']",
502
- "div[data-testid='post-content']",
503
- "div._aac4._aac5._aac6",
504
- "div._ab6k"
505
- ]
506
-
507
- article_found = False
508
- for selector in article_selectors:
509
  try:
510
- if page.query_selector(selector):
511
- article_found = True
512
- status_updates.append(f"Found posts using selector: {selector}")
513
- break
514
- except Exception as e:
515
- logger.debug(f"Article selector {selector} failed: {str(e)}")
516
-
517
- if not article_found:
518
- status_updates.append("Could not find posts on feed. Instagram may have changed their interface.")
519
- browser.close()
520
- return "\n".join(status_updates), image_path
521
-
522
- # Like one post at a time with full scroll stabilization
523
- while likes_count < max_likes and scroll_count < max_scrolls:
524
- try:
525
- # Take a screenshot before each scroll to aid debugging
526
- if scroll_count % 5 == 0:
527
- try:
528
- scroll_screenshot = f"screenshots/scroll_{scroll_count}_{int(time.time())}.png"
529
- page.screenshot(path=scroll_screenshot)
530
- image_path = scroll_screenshot
531
- except Exception as e:
532
- status_updates.append(f"Error taking scroll screenshot: {str(e)}")
533
-
534
- # Wait for the page to be stable after scrolling
535
- page.wait_for_timeout(2000)
536
-
537
- # IMPORTANT FIX: Evaluate the selector directly in the page context to get fresh elements
538
- # This addresses the "Element is not attached to the DOM" issue
539
- like_buttons = page.evaluate("""() => {
540
- // Try different selectors to find like buttons that haven't been clicked yet
541
  const selectors = [
 
 
542
  "article section svg[aria-label='Like']",
543
- "article svg[aria-label='Like']",
544
  "svg[aria-label='Like']",
545
  "span[class*='_aamw'] svg[aria-label='Like']",
546
- "article button[type='button'] svg:not([aria-label='Unlike'])"
 
 
547
  ];
548
 
549
- for (const selector of selectors) {
550
- const buttons = Array.from(document.querySelectorAll(selector));
551
- if (buttons.length > 0) {
552
- // We need to find the actual button elements (parents of the SVGs)
553
- const buttonElements = buttons.map(svg => {
554
- // Find closest button parent
555
- return svg.closest('button');
556
- }).filter(btn => btn !== null);
 
 
 
 
 
 
557
 
558
- if (buttonElements.length > 0) {
559
- // Return info about the first valid button
560
- const button = buttonElements[0];
561
- const rect = button.getBoundingClientRect();
 
 
 
 
 
 
 
 
 
562
 
563
  return {
564
  found: true,
565
- count: buttonElements.length,
566
  x: rect.x + rect.width / 2,
567
- y: rect.y + rect.height / 2
 
 
568
  };
569
  }
570
  }
571
  }
572
 
573
- return { found: false, count: 0 };
574
  }""")
575
 
576
- if like_buttons['found']:
577
- status_updates.append(f"Found {like_buttons['count']} like buttons on scroll {scroll_count}")
 
578
 
579
- # Click the first like button directly by coordinates
 
 
 
 
 
 
 
 
580
  try:
581
- # Take pre-click screenshot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  try:
583
- pre_like_screenshot = f"screenshots/pre_like_{likes_count}_{int(time.time())}.png"
584
- page.screenshot(path=pre_like_screenshot)
585
- except Exception as e:
586
- status_updates.append(f"Error taking pre-like screenshot: {str(e)}")
587
-
588
- # Click by coordinates rather than on the element
589
- page.mouse.click(like_buttons['x'], like_buttons['y'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
 
591
- likes_count += 1
592
- status_updates.append(f"Liked post {likes_count}/{max_likes}")
 
 
 
 
593
 
594
- # Take post-click screenshot
595
- try:
596
- post_like_screenshot = f"screenshots/post_like_{likes_count}_{int(time.time())}.png"
597
- page.screenshot(path=post_like_screenshot)
598
- image_path = post_like_screenshot
599
- except Exception as e:
600
- status_updates.append(f"Error taking post-like screenshot: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
 
602
- # Add a random delay between likes (2-4 seconds)
603
- delay = 2000 + random.randint(0, 2000)
604
- page.wait_for_timeout(delay)
605
 
606
- except Exception as e:
607
- status_updates.append(f"Error clicking like button: {str(e)}")
608
- else:
609
- status_updates.append(f"No like buttons found on scroll {scroll_count}")
 
 
610
 
611
- # Scroll down to load more
612
- # Use a smaller scroll distance to ensure we don't miss posts
613
- page.evaluate("window.scrollBy(0, 500);")
614
- status_updates.append("Scrolled down to load more posts")
 
615
 
616
- # Wait for new content to load
617
- page.wait_for_load_state('networkidle', timeout=5000)
618
-
619
- # Variable delay after scrolling (2-3 seconds)
620
- delay = 2000 + random.randint(0, 1000)
621
- page.wait_for_timeout(delay)
622
-
623
- scroll_count += 1
624
 
625
  except Exception as e:
626
- status_updates.append(f"Error during scroll/like cycle: {str(e)}")
627
  # Take error screenshot
628
  try:
629
  error_screenshot = f"screenshots/error_{int(time.time())}.png"
@@ -632,8 +955,8 @@ def run_instagram_liker(username, password, max_likes):
632
  except:
633
  pass
634
 
635
- # Continue to the next scroll despite errors
636
- scroll_count += 1
637
 
638
  # Final status
639
  final_message = f"Finished! Liked {likes_count} posts."
@@ -676,7 +999,7 @@ def run_instagram_liker(username, password, max_likes):
676
  # Gradio Interface
677
  def create_interface():
678
  with gr.Blocks(title="Instagram Auto-Liker") as app:
679
- gr.Markdown("# Instagram Auto-Liker (Playwright)")
680
  gr.Markdown("""
681
  Enter your Instagram credentials and the number of posts to like.
682
 
@@ -688,7 +1011,7 @@ def create_interface():
688
  with gr.Column(scale=1):
689
  username = gr.Textbox(label="Instagram Username")
690
  password = gr.Textbox(label="Instagram Password", type="password")
691
- max_likes = gr.Slider(minimum=1, maximum=100, value=10, step=1, label="Number of Posts to Like")
692
  submit_btn = gr.Button("Start Liking Posts")
693
 
694
  with gr.Column(scale=2):
 
79
  logger.warning("Playwright browser not found in expected locations")
80
  return False
81
 
82
+ def human_delay(min_seconds=1, max_seconds=5):
83
+ """Wait for a random amount of time to simulate human behavior"""
84
+ delay = min_seconds + random.random() * (max_seconds - min_seconds)
85
+ time.sleep(delay)
86
+ return delay
87
+
88
+ def human_scroll(page, min_pixels=100, max_pixels=800):
89
+ """Scroll a random amount in a human-like manner"""
90
+ # Decide scroll direction (occasionally scroll up to look more human)
91
+ direction = 1 if random.random() < 0.9 else -1
92
+
93
+ # Decide scroll amount
94
+ scroll_amount = direction * random.randint(min_pixels, max_pixels)
95
+
96
+ # Sometimes do a smooth scroll, sometimes do a quick scroll
97
+ if random.random() < 0.7:
98
+ # Smooth scroll
99
+ page.evaluate(f"""() => {{
100
+ window.scrollBy({{
101
+ top: {scroll_amount},
102
+ left: 0,
103
+ behavior: 'smooth'
104
+ }});
105
+ }}""")
106
+ else:
107
+ # Quick scroll
108
+ page.evaluate(f"window.scrollBy(0, {scroll_amount});")
109
+
110
+ # Sometimes perform multiple small scrolls instead of one big one
111
+ if random.random() < 0.3 and direction > 0:
112
+ human_delay(0.5, 1.5)
113
+ second_amount = random.randint(50, 200)
114
+ page.evaluate(f"window.scrollBy(0, {second_amount});")
115
+
116
+ return scroll_amount
117
+
118
+ def human_click(page, x, y, click_type="normal"):
119
+ """Perform a more human-like click with mouse movement and occasional double-clicks"""
120
+
121
+ # Move to a random position first (sometimes)
122
+ if random.random() < 0.6:
123
+ random_x = random.randint(100, 1000)
124
+ random_y = random.randint(100, 600)
125
+ page.mouse.move(random_x, random_y)
126
+ human_delay(0.1, 0.5)
127
+
128
+ # Move to target with slight randomness
129
+ jitter_x = x + random.randint(-5, 5)
130
+ jitter_y = y + random.randint(-3, 3)
131
+ page.mouse.move(jitter_x, jitter_y)
132
+ human_delay(0.1, 0.3)
133
+
134
+ # Click behavior
135
+ if click_type == "double" or (click_type == "normal" and random.random() < 0.1):
136
+ # Double click (rarely)
137
+ page.mouse.dblclick(jitter_x, jitter_y)
138
+ elif click_type == "slow" or (click_type == "normal" and random.random() < 0.2):
139
+ # Slow click (sometimes)
140
+ page.mouse.down()
141
+ human_delay(0.1, 0.4)
142
+ page.mouse.up()
143
+ else:
144
+ # Normal click
145
+ page.mouse.click(jitter_x, jitter_y)
146
+
147
+ # Sometimes move mouse after clicking
148
+ if random.random() < 0.4:
149
+ human_delay(0.2, 0.6)
150
+ after_x = jitter_x + random.randint(-100, 100)
151
+ after_y = jitter_y + random.randint(-80, 80)
152
+ page.mouse.move(after_x, after_y)
153
+
154
+ def handle_automation_warning(page, status_updates):
155
+ """Handle Instagram's automation warning dialog"""
156
+ try:
157
+ # Check for the warning dialog
158
+ automation_warning_selectors = [
159
+ "text=We suspect automated behavior",
160
+ "text=automated behavior on your account",
161
+ "button:has-text('Dismiss')",
162
+ "button:has-text('Ok')",
163
+ "button:has-text('Close')"
164
+ ]
165
+
166
+ for selector in automation_warning_selectors:
167
+ if page.query_selector(selector):
168
+ status_updates.append("Detected automation warning dialog")
169
+
170
+ # Take a screenshot of the warning
171
+ try:
172
+ warning_screenshot = f"screenshots/automation_warning_{int(time.time())}.png"
173
+ page.screenshot(path=warning_screenshot)
174
+ status_updates.append("Automation warning screenshot saved")
175
+ except Exception as e:
176
+ status_updates.append(f"Error taking warning screenshot: {str(e)}")
177
+
178
+ # Try to find the dismiss button using various selectors
179
+ dismiss_selectors = [
180
+ "button:has-text('Dismiss')",
181
+ "button:has-text('Ok')",
182
+ "button:has-text('Close')",
183
+ "button[type='button']"
184
+ ]
185
+
186
+ for dismiss_selector in dismiss_selectors:
187
+ try:
188
+ dismiss_button = page.query_selector(dismiss_selector)
189
+ if dismiss_button:
190
+ # Get button coordinates
191
+ coords = dismiss_button.bounding_box()
192
+ x = coords['x'] + coords['width'] / 2
193
+ y = coords['y'] + coords['height'] / 2
194
+
195
+ # Click using human-like behavior
196
+ human_delay(1, 3) # Think before dismissing
197
+ human_click(page, x, y)
198
+ status_updates.append(f"Dismissed automation warning using: {dismiss_selector}")
199
+
200
+ # Wait after dismissing
201
+ human_delay(2, 4)
202
+ return True
203
+ except Exception as e:
204
+ logger.debug(f"Dismiss button {dismiss_selector} failed: {str(e)}")
205
+
206
+ # If no buttons found, try clicking in the middle of the screen
207
+ if random.random() < 0.5:
208
+ human_delay(1, 2)
209
+ human_click(page, 640, 360) # Click in middle of screen
210
+ status_updates.append("Attempted to dismiss by clicking middle of screen")
211
+ human_delay(2, 3)
212
+
213
+ return True
214
+
215
+ return False # No warning detected
216
+ except Exception as e:
217
+ status_updates.append(f"Error handling automation warning: {str(e)}")
218
+ return False
219
+
220
  def run_instagram_liker(username, password, max_likes):
221
  """Run the Instagram auto-liker with Playwright"""
222
+ status_updates = ["Starting Instagram Auto-Liker with human-like behavior..."]
223
  image_path = save_placeholder_image("start")
224
 
225
  # Check if browsers are installed, if not install them
 
251
  status_updates.append("Launching Playwright browser...")
252
  with sync_playwright() as p:
253
  try:
254
+ # Use random viewport size to look less like automation
255
+ viewport_width = random.choice([1280, 1366, 1440, 1536, 1600, 1920])
256
+ viewport_height = random.choice([720, 768, 800, 864, 900, 1080])
257
+
258
+ # Launch chromium with specific arguments
259
  browser = p.chromium.launch(
260
  headless=True,
261
  args=[
 
267
  ]
268
  )
269
 
270
+ # Create a browser context with random user agent
271
+ user_agents = [
272
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
273
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
274
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
275
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'
276
+ ]
277
+ user_agent = random.choice(user_agents)
278
+
279
  context = browser.new_context(
280
+ user_agent=user_agent,
281
+ viewport={'width': viewport_width, 'height': viewport_height},
282
+ locale=random.choice(['en-US', 'en-GB', 'en-CA']),
283
+ timezone_id=random.choice(['America/New_York', 'America/Los_Angeles', 'Europe/London', 'Asia/Tokyo'])
284
  )
285
 
286
+ # Set random geolocation (if allowed)
287
+ if random.random() < 0.5:
288
+ try:
289
+ context.set_geolocation({
290
+ 'latitude': random.uniform(30, 50),
291
+ 'longitude': random.uniform(-120, -70)
292
+ })
293
+ except:
294
+ pass
295
+
296
  # Open a new page
297
  page = context.new_page()
298
 
299
  # Test browser by visiting Google
300
  status_updates.append("Testing browser connection...")
301
  page.goto("https://www.google.com")
302
+ human_delay(1, 3) # Wait like a human
303
  status_updates.append(f"Browser working. Title: {page.title()}")
304
 
305
  # Take a screenshot
 
311
  except Exception as e:
312
  status_updates.append(f"Error taking screenshot: {str(e)}")
313
 
314
+ # Navigate to Instagram with delay
315
  status_updates.append("Navigating to Instagram...")
316
  page.goto("https://www.instagram.com/")
317
+
318
+ # Wait for load with natural variation
319
+ wait_strategy = random.choice(['domcontentloaded', 'networkidle', 'load'])
320
+ page.wait_for_load_state(wait_strategy)
321
+ human_delay(2, 5) # Additional human-like wait
322
 
323
  # Take a screenshot to see what's happening
324
  try:
 
329
  except Exception as e:
330
  status_updates.append(f"Error taking screenshot: {str(e)}")
331
 
332
+ # Random initial interactions (to look human)
333
+ if random.random() < 0.7:
334
+ # Maybe move mouse randomly on the page
335
+ for _ in range(random.randint(1, 3)):
336
+ random_x = random.randint(100, viewport_width - 100)
337
+ random_y = random.randint(100, viewport_height - 100)
338
+ page.mouse.move(random_x, random_y)
339
+ human_delay(0.3, 1.5)
340
 
341
+ # Maybe scroll a bit before doing anything
342
+ if random.random() < 0.5:
343
+ human_scroll(page, 100, 300)
344
+ human_delay(1, 3)
 
 
 
 
345
 
346
+ # Click outside any potential popups (more natural location)
347
  try:
348
+ random_x = random.randint(50, 150)
349
+ random_y = random.randint(50, 100)
350
+ human_click(page, random_x, random_y)
351
  status_updates.append("Clicked to dismiss any initial popups")
352
  except Exception as e:
353
  status_updates.append(f"Click error: {str(e)}")
 
364
 
365
  for button_selector in cookie_buttons:
366
  try:
367
+ button = page.query_selector(button_selector)
368
+ if button:
369
+ # Get coordinates
370
+ coords = button.bounding_box()
371
+ x = coords['x'] + coords['width'] / 2
372
+ y = coords['y'] + coords['height'] / 2
373
+
374
+ # Human-like delay before clicking
375
+ human_delay(0.8, 2)
376
+
377
+ # Human-like click
378
+ human_click(page, x, y)
379
  status_updates.append(f"Clicked cookie consent button: {button_selector}")
380
+ human_delay(1, 3)
381
  break
382
  except Exception as e:
383
  logger.debug(f"Button {button_selector} not found: {str(e)}")
 
385
  except Exception as e:
386
  status_updates.append(f"Cookie dialog handling: {str(e)}")
387
 
388
+ # Interact with login form like a human
389
  status_updates.append("Looking for login form...")
390
 
391
  # Take a screenshot to see what we're working with
 
397
  except Exception as e:
398
  status_updates.append(f"Error taking screenshot: {str(e)}")
399
 
400
+ # Check for automation warning (might appear right away)
401
+ if handle_automation_warning(page, status_updates):
402
+ status_updates.append("Handled initial automation warning")
403
+
404
  # Try multiple selectors for username input
405
  username_selectors = [
406
  "input[name='username']",
 
460
  browser.close()
461
  return "\n".join(status_updates), image_path
462
 
463
+ # Enter credentials like a human would
464
  status_updates.append(f"Entering username: {username}")
465
+
466
+ # Click the username field first (human behavior)
467
+ username_coords = username_field.bounding_box()
468
+ username_x = username_coords['x'] + username_coords['width'] / 2
469
+ username_y = username_coords['y'] + username_coords['height'] / 2
470
+ human_click(page, username_x, username_y)
471
+
472
+ # Type username with human-like timing
473
+ for char in username:
474
+ username_field.type(char, delay=random.randint(100, 300))
475
+ time.sleep(random.uniform(0.01, 0.15))
476
+
477
+ # Sometimes humans pause after entering username
478
+ human_delay(0.5, 2)
479
+
480
+ # Now click password field
481
+ password_coords = password_field.bounding_box()
482
+ password_x = password_coords['x'] + password_coords['width'] / 2
483
+ password_y = password_coords['y'] + password_coords['height'] / 2
484
+ human_click(page, password_x, password_y)
485
+
486
+ # Type password with human-like timing
487
+ for char in password:
488
+ password_field.type(char, delay=random.randint(100, 300))
489
+ time.sleep(random.uniform(0.01, 0.15))
490
+
491
  status_updates.append("Credentials entered")
492
 
493
  # Take a screenshot of filled form
 
529
  browser.close()
530
  return "\n".join(status_updates), image_path
531
 
532
+ # Click login button like a human
533
  status_updates.append("Clicking login button...")
534
+ button_coords = login_button.bounding_box()
535
+ button_x = button_coords['x'] + button_coords['width'] / 2
536
+ button_y = button_coords['y'] + button_coords['height'] / 2
537
+
538
+ # Pause briefly before clicking (human behavior)
539
+ human_delay(0.5, 1.5)
540
+ human_click(page, button_x, button_y)
541
 
542
+ # Wait for navigation to complete with human-like variation
543
  status_updates.append("Waiting for login process...")
544
+ wait_time = random.uniform(3, 7)
545
+ time.sleep(wait_time)
546
 
547
  # Take post-login screenshot
548
  try:
 
553
  except Exception as e:
554
  status_updates.append(f"Error taking screenshot: {str(e)}")
555
 
556
+ # Check for automation warning after login
557
+ if handle_automation_warning(page, status_updates):
558
+ status_updates.append("Handled post-login automation warning")
559
+
560
  # Check if login was successful
561
  current_url = page.url
562
  if "/accounts/login" in current_url or "/login" in current_url:
 
591
 
592
  # Handle "Save Login Info" popup if it appears
593
  try:
594
+ human_delay(1, 3) # Wait like a human for popup to appear
595
 
596
  save_info_selectors = [
597
  "text=Save Login Info",
 
614
  break
615
 
616
  if dialog_found:
617
+ # Wait like a human would before deciding
618
+ human_delay(1, 4)
619
+
620
  # Try to click "Not Now"
621
  dismissed = False
622
  for not_now in not_now_selectors:
623
  try:
624
  button = page.query_selector(not_now)
625
  if button:
626
+ # Get button coordinates
627
+ coords = button.bounding_box()
628
+ x = coords['x'] + coords['width'] / 2
629
+ y = coords['y'] + coords['height'] / 2
630
+
631
+ # Click like a human
632
+ human_click(page, x, y)
633
  status_updates.append(f"Dismissed 'Save Login Info' popup using: {not_now}")
634
+ human_delay(1, 3)
635
  dismissed = True
636
  break
637
  except Exception as e:
 
656
 
657
  # Handle notifications popup if it appears
658
  try:
659
+ human_delay(1, 3) # Wait like a human
660
 
661
  notifications_selectors = [
662
  "text=Turn on Notifications",
 
679
  break
680
 
681
  if dialog_found:
682
+ # Wait like a human before deciding
683
+ human_delay(1, 4)
684
+
685
  # Try to click "Not Now"
686
  dismissed = False
687
  for not_now in not_now_selectors:
688
  try:
689
  button = page.query_selector(not_now)
690
  if button:
691
+ # Get button coordinates
692
+ coords = button.bounding_box()
693
+ x = coords['x'] + coords['width'] / 2
694
+ y = coords['y'] + coords['height'] / 2
695
+
696
+ # Click like a human
697
+ human_click(page, x, y)
698
  status_updates.append(f"Dismissed notifications popup using: {not_now}")
699
+ human_delay(1, 3)
700
  dismissed = True
701
  break
702
  except Exception as e:
 
719
  except Exception as e:
720
  status_updates.append(f"Error taking screenshot: {str(e)}")
721
 
722
+ # Check for automation warning again
723
+ if handle_automation_warning(page, status_updates):
724
+ status_updates.append("Handled feed automation warning")
725
+
726
  status_updates.append("Successfully navigated to Instagram feed!")
727
 
728
+ # Start liking posts with human-like behavior
729
+ likes_goal = min(max_likes, random.randint(max_likes - 3, max_likes + 3))
730
+ status_updates.append(f"Starting to like posts (target: {likes_goal})...")
731
 
732
  # Like posts
733
  likes_count = 0
734
  scroll_count = 0
735
+ max_scrolls = random.randint(25, 35)
736
 
737
  # Wait for the feed to load fully before looking for posts
738
+ # Function to find and like posts with human-like behavior
739
+ def find_and_like_post():
740
+ """Find and like a single post with human-like behavior"""
 
 
 
 
 
 
 
 
 
 
741
  try:
742
+ # Use JavaScript to find like buttons directly in the DOM
743
+ like_button_info = page.evaluate("""() => {
744
+ // Try different selectors for like buttons (not already liked)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  const selectors = [
746
+ // Generic selectors for Instagram's like buttons
747
+ "article svg[aria-label='Like']",
748
  "article section svg[aria-label='Like']",
 
749
  "svg[aria-label='Like']",
750
  "span[class*='_aamw'] svg[aria-label='Like']",
751
+ "button svg[aria-label='Like']",
752
+ // Looser selector that might catch more
753
+ "article button[type='button']:not([aria-pressed='true'])"
754
  ];
755
 
756
+ // Shuffle the selectors to add randomness
757
+ const shuffled = selectors.sort(() => 0.5 - Math.random());
758
+
759
+ // Try each selector
760
+ for (const selector of shuffled) {
761
+ const elements = Array.from(document.querySelectorAll(selector));
762
+
763
+ if (elements.length > 0) {
764
+ // Shuffle the elements to add randomness
765
+ const shuffledElements = elements.sort(() => 0.5 - Math.random());
766
+
767
+ // Choose a random element from the first few found
768
+ const randomIndex = Math.floor(Math.random() * Math.min(3, shuffledElements.length));
769
+ const element = shuffledElements[randomIndex];
770
 
771
+ // Find the actual clickable button (might be a parent)
772
+ let button = element;
773
+ if (element.tagName.toLowerCase() === 'svg') {
774
+ button = element.closest('button') || element;
775
+ }
776
+
777
+ // Get element position
778
+ const rect = button.getBoundingClientRect();
779
+
780
+ // Check if it's visible in viewport
781
+ if (rect.top >= 0 && rect.left >= 0 &&
782
+ rect.bottom <= window.innerHeight &&
783
+ rect.right <= window.innerWidth) {
784
 
785
  return {
786
  found: true,
 
787
  x: rect.x + rect.width / 2,
788
+ y: rect.y + rect.height / 2,
789
+ selector: selector,
790
+ totalFound: elements.length
791
  };
792
  }
793
  }
794
  }
795
 
796
+ return { found: false };
797
  }""")
798
 
799
+ if like_button_info['found']:
800
+ # Wait a bit before clicking (as if examining the post)
801
+ human_delay(1, 4)
802
 
803
+ # Sometimes explore the post before liking
804
+ if random.random() < 0.3:
805
+ # Move mouse around post area
806
+ explore_x = like_button_info['x'] + random.randint(-100, 100)
807
+ explore_y = like_button_info['y'] + random.randint(-100, 100)
808
+ page.mouse.move(explore_x, explore_y)
809
+ human_delay(0.5, 2)
810
+
811
+ # Take screenshot before click
812
  try:
813
+ before_like_screenshot = f"screenshots/before_like_{likes_count+1}_{int(time.time())}.png"
814
+ page.screenshot(path=before_like_screenshot)
815
+ except:
816
+ pass
817
+
818
+ # Click with human-like behavior
819
+ like_x = like_button_info['x']
820
+ like_y = like_button_info['y']
821
+
822
+ # Add slight randomness to click position
823
+ human_click(page, like_x, like_y)
824
+
825
+ # Wait for the like to register
826
+ human_delay(0.5, 2)
827
+
828
+ return True
829
+
830
+ return False
831
+ except Exception as e:
832
+ status_updates.append(f"Error finding/clicking like button: {str(e)}")
833
+ return False
834
+
835
+ # Variable to track consecutive failures
836
+ consecutive_failures = 0
837
+ last_successful_like_time = time.time()
838
+
839
+ # Main loop for scrolling and liking
840
+ while likes_count < likes_goal and scroll_count < max_scrolls:
841
+ try:
842
+ # Take screenshot occasionally to track progress
843
+ if scroll_count % 5 == 0:
844
+ try:
845
+ progress_screenshot = f"screenshots/progress_{scroll_count}_{int(time.time())}.png"
846
+ page.screenshot(path=progress_screenshot)
847
+ image_path = progress_screenshot
848
+ except:
849
+ pass
850
+
851
+ # First check for automation warnings
852
+ if handle_automation_warning(page, status_updates):
853
+ status_updates.append(f"Handled automation warning during scrolling (#{scroll_count})")
854
+ human_delay(3, 6) # Longer wait after handling warning
855
+
856
+ # Try to like a post with human-like behavior
857
+ if find_and_like_post():
858
+ likes_count += 1
859
+ consecutive_failures = 0
860
+ last_successful_like_time = time.time()
861
+ status_updates.append(f"Liked post {likes_count}/{likes_goal}")
862
+
863
+ # Take post-like screenshot occasionally
864
+ if random.random() < 0.3:
865
  try:
866
+ after_like_screenshot = f"screenshots/after_like_{likes_count}_{int(time.time())}.png"
867
+ page.screenshot(path=after_like_screenshot)
868
+ image_path = after_like_screenshot
869
+ except:
870
+ pass
871
+
872
+ # Calculate a random human-like delay between likes
873
+ # Occasionally take longer breaks to seem more human
874
+ if random.random() < 0.15: # 15% chance of a longer break
875
+ wait_time = random.uniform(5, 15)
876
+ status_updates.append(f"Taking a short break ({wait_time:.1f}s)")
877
+ time.sleep(wait_time)
878
+ else:
879
+ # Normal delay between likes (variable)
880
+ human_delay(2, 7)
881
+
882
+ # Occasionally interact with the feed in other ways to seem human
883
+ if random.random() < 0.25: # 25% chance after liking
884
+ random_action = random.choice([
885
+ "move_mouse_random",
886
+ "small_scroll_up",
887
+ "pause",
888
+ "long_hover"
889
+ ])
890
 
891
+ if random_action == "move_mouse_random":
892
+ # Move mouse to random position
893
+ random_x = random.randint(100, viewport_width - 100)
894
+ random_y = random.randint(100, viewport_height - 100)
895
+ page.mouse.move(random_x, random_y)
896
+ human_delay(0.5, 2)
897
 
898
+ elif random_action == "small_scroll_up":
899
+ # Sometimes scroll back up a bit
900
+ page.evaluate("window.scrollBy(0, -100);")
901
+ human_delay(0.5, 2)
902
+
903
+ elif random_action == "pause":
904
+ # Just pause for a moment
905
+ human_delay(1, 3)
906
+
907
+ elif random_action == "long_hover":
908
+ # Hover over something for a bit
909
+ random_x = random.randint(200, viewport_width - 200)
910
+ random_y = random.randint(200, viewport_height - 200)
911
+ page.mouse.move(random_x, random_y)
912
+ human_delay(2, 4)
913
+ else:
914
+ consecutive_failures += 1
915
+ status_updates.append(f"No new like buttons found on scroll {scroll_count}")
916
+
917
+ # If we haven't found anything to like in a while, check if we need to scroll
918
+ if consecutive_failures >= 3 or (time.time() - last_successful_like_time > 20):
919
+ # Scroll down to load more content
920
+ scroll_amount = human_scroll(page,
921
+ min_pixels=random.randint(200, 400),
922
+ max_pixels=random.randint(500, 800)
923
+ )
924
+ status_updates.append(f"Scrolled down {scroll_amount} pixels to load more posts")
925
 
926
+ # Wait for new content with variable timing
927
+ wait_time = random.uniform(2, 5)
928
+ time.sleep(wait_time)
929
 
930
+ # Reset failure counter after scrolling
931
+ consecutive_failures = 0
932
+ scroll_count += 1
933
+ else:
934
+ # Small wait before trying again
935
+ human_delay(1, 3)
936
 
937
+ # Occasionally take a longer break to seem more human
938
+ if likes_count > 0 and likes_count % 5 == 0 and random.random() < 0.7:
939
+ break_time = random.uniform(8, 20)
940
+ status_updates.append(f"Taking a longer break after {likes_count} likes ({break_time:.1f}s)")
941
+ time.sleep(break_time)
942
 
943
+ # Verify we haven't exceeded the target
944
+ if likes_count >= likes_goal:
945
+ status_updates.append(f"Reached target of {likes_goal} likes")
946
+ break
 
 
 
 
947
 
948
  except Exception as e:
949
+ status_updates.append(f"Error during like cycle: {str(e)}")
950
  # Take error screenshot
951
  try:
952
  error_screenshot = f"screenshots/error_{int(time.time())}.png"
 
955
  except:
956
  pass
957
 
958
+ # Continue despite errors after a pause
959
+ human_delay(3, 6)
960
 
961
  # Final status
962
  final_message = f"Finished! Liked {likes_count} posts."
 
999
  # Gradio Interface
1000
  def create_interface():
1001
  with gr.Blocks(title="Instagram Auto-Liker") as app:
1002
+ gr.Markdown("# Instagram Auto-Liker (Human-like)")
1003
  gr.Markdown("""
1004
  Enter your Instagram credentials and the number of posts to like.
1005
 
 
1011
  with gr.Column(scale=1):
1012
  username = gr.Textbox(label="Instagram Username")
1013
  password = gr.Textbox(label="Instagram Password", type="password")
1014
+ max_likes = gr.Slider(minimum=1, maximum=50, value=10, step=1, label="Number of Posts to Like")
1015
  submit_btn = gr.Button("Start Liking Posts")
1016
 
1017
  with gr.Column(scale=2):