Spaces:
Paused
Paused
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):
|