Spaces:
Running
Running
Update app.py
Browse files
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
|
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 |
-
#
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
context = browser.new_context(
|
130 |
-
user_agent=
|
131 |
-
viewport={'width':
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
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 |
-
#
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
168 |
|
169 |
-
#
|
170 |
-
|
171 |
-
|
172 |
-
|
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 |
-
#
|
179 |
try:
|
180 |
-
|
|
|
|
|
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 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
status_updates.append(f"Clicked cookie consent button: {button_selector}")
|
200 |
-
|
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 |
-
#
|
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 |
-
|
282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
|
328 |
-
# Wait for navigation to complete
|
329 |
status_updates.append("Waiting for login process...")
|
330 |
-
|
|
|
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 |
-
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
405 |
status_updates.append(f"Dismissed 'Save Login Info' popup using: {not_now}")
|
406 |
-
|
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 |
-
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
461 |
status_updates.append(f"Dismissed notifications popup using: {not_now}")
|
462 |
-
|
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 |
-
|
|
|
489 |
|
490 |
# Like posts
|
491 |
likes_count = 0
|
492 |
scroll_count = 0
|
493 |
-
max_scrolls =
|
494 |
|
495 |
# Wait for the feed to load fully before looking for posts
|
496 |
-
|
497 |
-
|
498 |
-
|
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 |
-
|
511 |
-
|
512 |
-
|
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 |
-
"
|
|
|
|
|
547 |
];
|
548 |
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
557 |
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
574 |
}""")
|
575 |
|
576 |
-
if
|
577 |
-
|
|
|
578 |
|
579 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
580 |
try:
|
581 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
try:
|
583 |
-
|
584 |
-
page.screenshot(path=
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
|
591 |
-
|
592 |
-
|
|
|
|
|
|
|
|
|
593 |
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
601 |
|
602 |
-
#
|
603 |
-
|
604 |
-
|
605 |
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
|
|
|
|
610 |
|
611 |
-
#
|
612 |
-
|
613 |
-
|
614 |
-
|
|
|
615 |
|
616 |
-
#
|
617 |
-
|
618 |
-
|
619 |
-
|
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
|
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
|
636 |
-
|
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 (
|
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=
|
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):
|