Spaces:
Paused
Paused
| import gradio as gr | |
| import time | |
| import os | |
| import subprocess | |
| import sys | |
| from PIL import Image | |
| import io | |
| import logging | |
| import traceback | |
| import random | |
| from pathlib import Path | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Create screenshots directory if it doesn't exist | |
| os.makedirs('screenshots', exist_ok=True) | |
| def download_playwright_browsers(): | |
| """Make sure Playwright and its browsers are installed properly""" | |
| try: | |
| logger.info("Installing Playwright browsers...") | |
| # First ensure the package is installed | |
| subprocess.run( | |
| [sys.executable, "-m", "pip", "install", "playwright==1.39.0"], | |
| check=True | |
| ) | |
| # Then install browsers with output printed directly to console | |
| logger.info("Running browser installation (this may take a while)...") | |
| process = subprocess.run( | |
| [sys.executable, "-m", "playwright", "install", "chromium"], | |
| check=True | |
| ) | |
| logger.info("Playwright installation completed successfully") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Error installing Playwright browsers: {e}") | |
| return False | |
| def save_placeholder_image(name): | |
| """Create a placeholder image when no screenshot is available""" | |
| timestamp = int(time.time()) | |
| filename = f"screenshots/{name}_{timestamp}.png" | |
| # Create a simple colored image | |
| img = Image.new('RGB', (400, 300), color=(73, 109, 137)) | |
| # Add some text if possible | |
| try: | |
| from PIL import ImageDraw | |
| draw = ImageDraw.Draw(img) | |
| draw.text((10, 150), f"Instagram Auto-Liker - {name}", fill=(255, 255, 255)) | |
| except Exception as e: | |
| logger.warning(f"Couldn't add text to image: {str(e)}") | |
| img.save(filename) | |
| logger.info(f"Saved placeholder image: {filename}") | |
| return filename | |
| def verify_playwright_installation(): | |
| """Verify that Playwright is properly installed by checking for browser binary""" | |
| from pathlib import Path | |
| import os | |
| # Check common installation paths | |
| potential_paths = [ | |
| Path.home() / ".cache" / "ms-playwright" / "chromium-1084" / "chrome-linux" / "chrome", | |
| Path("/home/user/.cache/ms-playwright/chromium-1084/chrome-linux/chrome") | |
| ] | |
| for path in potential_paths: | |
| if path.exists(): | |
| logger.info(f"Found Playwright browser at: {path}") | |
| return True | |
| logger.warning("Playwright browser not found in expected locations") | |
| return False | |
| def human_delay(min_seconds=1, max_seconds=5): | |
| """Wait for a random amount of time to simulate human behavior""" | |
| delay = min_seconds + random.random() * (max_seconds - min_seconds) | |
| time.sleep(delay) | |
| return delay | |
| def human_scroll(page, min_pixels=100, max_pixels=800): | |
| """Scroll a random amount in a human-like manner""" | |
| # Decide scroll direction (occasionally scroll up to look more human) | |
| direction = 1 if random.random() < 0.9 else -1 | |
| # Decide scroll amount | |
| scroll_amount = direction * random.randint(min_pixels, max_pixels) | |
| # Sometimes do a smooth scroll, sometimes do a quick scroll | |
| if random.random() < 0.7: | |
| # Smooth scroll | |
| page.evaluate(f"""() => {{ | |
| window.scrollBy({{ | |
| top: {scroll_amount}, | |
| left: 0, | |
| behavior: 'smooth' | |
| }}); | |
| }}""") | |
| else: | |
| # Quick scroll | |
| page.evaluate(f"window.scrollBy(0, {scroll_amount});") | |
| # Sometimes perform multiple small scrolls instead of one big one | |
| if random.random() < 0.3 and direction > 0: | |
| human_delay(0.5, 1.5) | |
| second_amount = random.randint(50, 200) | |
| page.evaluate(f"window.scrollBy(0, {second_amount});") | |
| return scroll_amount | |
| def human_click(page, x, y, click_type="normal"): | |
| """Perform a more human-like click with mouse movement and occasional double-clicks""" | |
| # Move to a random position first (sometimes) | |
| if random.random() < 0.6: | |
| random_x = random.randint(100, 1000) | |
| random_y = random.randint(100, 600) | |
| page.mouse.move(random_x, random_y) | |
| human_delay(0.1, 0.5) | |
| # Move to target with slight randomness | |
| jitter_x = x + random.randint(-5, 5) | |
| jitter_y = y + random.randint(-3, 3) | |
| page.mouse.move(jitter_x, jitter_y) | |
| human_delay(0.1, 0.3) | |
| # Click behavior | |
| if click_type == "double" or (click_type == "normal" and random.random() < 0.1): | |
| # Double click (rarely) | |
| page.mouse.dblclick(jitter_x, jitter_y) | |
| elif click_type == "slow" or (click_type == "normal" and random.random() < 0.2): | |
| # Slow click (sometimes) | |
| page.mouse.down() | |
| human_delay(0.1, 0.4) | |
| page.mouse.up() | |
| else: | |
| # Normal click | |
| page.mouse.click(jitter_x, jitter_y) | |
| # Sometimes move mouse after clicking | |
| if random.random() < 0.4: | |
| human_delay(0.2, 0.6) | |
| after_x = jitter_x + random.randint(-100, 100) | |
| after_y = jitter_y + random.randint(-80, 80) | |
| page.mouse.move(after_x, after_y) | |
| def handle_automation_warning(page, status_updates): | |
| """Handle Instagram's automation warning dialog""" | |
| try: | |
| # Check for the warning dialog | |
| automation_warning_selectors = [ | |
| "text=We suspect automated behavior", | |
| "text=automated behavior on your account", | |
| "button:has-text('Dismiss')", | |
| "button:has-text('Ok')", | |
| "button:has-text('Close')" | |
| ] | |
| for selector in automation_warning_selectors: | |
| if page.query_selector(selector): | |
| status_updates.append("Detected automation warning dialog") | |
| # Take a screenshot of the warning | |
| try: | |
| warning_screenshot = f"screenshots/automation_warning_{int(time.time())}.png" | |
| page.screenshot(path=warning_screenshot) | |
| status_updates.append("Automation warning screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking warning screenshot: {str(e)}") | |
| # Try to find the dismiss button using various selectors | |
| dismiss_selectors = [ | |
| "button:has-text('Dismiss')", | |
| "button:has-text('Ok')", | |
| "button:has-text('Close')", | |
| "button[type='button']" | |
| ] | |
| for dismiss_selector in dismiss_selectors: | |
| try: | |
| dismiss_button = page.query_selector(dismiss_selector) | |
| if dismiss_button: | |
| # Get button coordinates | |
| coords = dismiss_button.bounding_box() | |
| x = coords['x'] + coords['width'] / 2 | |
| y = coords['y'] + coords['height'] / 2 | |
| # Click using human-like behavior | |
| human_delay(1, 3) # Think before dismissing | |
| human_click(page, x, y) | |
| status_updates.append(f"Dismissed automation warning using: {dismiss_selector}") | |
| # Wait after dismissing | |
| human_delay(2, 4) | |
| return True | |
| except Exception as e: | |
| logger.debug(f"Dismiss button {dismiss_selector} failed: {str(e)}") | |
| # If no buttons found, try clicking in the middle of the screen | |
| if random.random() < 0.5: | |
| human_delay(1, 2) | |
| human_click(page, 640, 360) # Click in middle of screen | |
| status_updates.append("Attempted to dismiss by clicking middle of screen") | |
| human_delay(2, 3) | |
| return True | |
| return False # No warning detected | |
| except Exception as e: | |
| status_updates.append(f"Error handling automation warning: {str(e)}") | |
| return False | |
| def run_instagram_liker(username, password, max_likes): | |
| """Run the Instagram auto-liker with Playwright""" | |
| status_updates = ["Starting Instagram Auto-Liker with human-like behavior..."] | |
| image_path = save_placeholder_image("start") | |
| # Check if browsers are installed, if not install them | |
| try: | |
| status_updates.append("Verifying Playwright installation...") | |
| # First try to import | |
| try: | |
| from playwright.sync_api import sync_playwright | |
| except ImportError: | |
| status_updates.append("Playwright not installed. Installing now...") | |
| download_playwright_browsers() | |
| status_updates.append("Playwright installation completed.") | |
| # Then verify browser binary exists | |
| if not verify_playwright_installation(): | |
| status_updates.append("Browser binary not found. Installing browsers...") | |
| download_playwright_browsers() | |
| # Double check installation worked | |
| if not verify_playwright_installation(): | |
| status_updates.append("Failed to install browser binary. Please try running the script again.") | |
| return "\n".join(status_updates), image_path | |
| status_updates.append("Playwright installation verified.") | |
| from playwright.sync_api import sync_playwright | |
| status_updates.append("Launching Playwright browser...") | |
| with sync_playwright() as p: | |
| try: | |
| # Use random viewport size to look less like automation | |
| viewport_width = random.choice([1280, 1366, 1440, 1536, 1600, 1920]) | |
| viewport_height = random.choice([720, 768, 800, 864, 900, 1080]) | |
| # Launch chromium with specific arguments | |
| browser = p.chromium.launch( | |
| headless=True, | |
| args=[ | |
| '--no-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-gpu', | |
| '--disable-software-rasterizer', | |
| '--disable-setuid-sandbox' | |
| ] | |
| ) | |
| # Create a browser context with random user agent | |
| user_agents = [ | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', | |
| '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', | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', | |
| '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' | |
| ] | |
| user_agent = random.choice(user_agents) | |
| context = browser.new_context( | |
| user_agent=user_agent, | |
| viewport={'width': viewport_width, 'height': viewport_height}, | |
| locale=random.choice(['en-US', 'en-GB', 'en-CA']), | |
| timezone_id=random.choice(['America/New_York', 'America/Los_Angeles', 'Europe/London', 'Asia/Tokyo']) | |
| ) | |
| # Set random geolocation (if allowed) | |
| if random.random() < 0.5: | |
| try: | |
| context.set_geolocation({ | |
| 'latitude': random.uniform(30, 50), | |
| 'longitude': random.uniform(-120, -70) | |
| }) | |
| except: | |
| pass | |
| # Open a new page | |
| page = context.new_page() | |
| # Test browser by visiting Google | |
| status_updates.append("Testing browser connection...") | |
| page.goto("https://www.google.com") | |
| human_delay(1, 3) # Wait like a human | |
| status_updates.append(f"Browser working. Title: {page.title()}") | |
| # Take a screenshot | |
| try: | |
| test_screenshot = f"screenshots/test_pw_{int(time.time())}.png" | |
| page.screenshot(path=test_screenshot) | |
| image_path = test_screenshot | |
| status_updates.append("Browser screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Navigate to Instagram with delay | |
| status_updates.append("Navigating to Instagram...") | |
| page.goto("https://www.instagram.com/") | |
| # Wait for load with natural variation | |
| wait_strategy = random.choice(['domcontentloaded', 'networkidle', 'load']) | |
| page.wait_for_load_state(wait_strategy) | |
| human_delay(2, 5) # Additional human-like wait | |
| # Take a screenshot to see what's happening | |
| try: | |
| landing_screenshot = f"screenshots/landing_pw_{int(time.time())}.png" | |
| page.screenshot(path=landing_screenshot) | |
| image_path = landing_screenshot | |
| status_updates.append("Instagram page loaded, screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Random initial interactions (to look human) | |
| if random.random() < 0.7: | |
| # Maybe move mouse randomly on the page | |
| for _ in range(random.randint(1, 3)): | |
| random_x = random.randint(100, viewport_width - 100) | |
| random_y = random.randint(100, viewport_height - 100) | |
| page.mouse.move(random_x, random_y) | |
| human_delay(0.3, 1.5) | |
| # Maybe scroll a bit before doing anything | |
| if random.random() < 0.5: | |
| human_scroll(page, 100, 300) | |
| human_delay(1, 3) | |
| # Click outside any potential popups (more natural location) | |
| try: | |
| random_x = random.randint(50, 150) | |
| random_y = random.randint(50, 100) | |
| human_click(page, random_x, random_y) | |
| status_updates.append("Clicked to dismiss any initial popups") | |
| except Exception as e: | |
| status_updates.append(f"Click error: {str(e)}") | |
| # Handle cookie dialog if present | |
| try: | |
| cookie_buttons = [ | |
| "text=Accept", | |
| "text=Allow", | |
| "text=Only allow essential cookies", | |
| "button:has-text('Accept All')", | |
| "button:has-text('Allow essential and optional cookies')" | |
| ] | |
| for button_selector in cookie_buttons: | |
| try: | |
| button = page.query_selector(button_selector) | |
| if button: | |
| # Get coordinates | |
| coords = button.bounding_box() | |
| x = coords['x'] + coords['width'] / 2 | |
| y = coords['y'] + coords['height'] / 2 | |
| # Human-like delay before clicking | |
| human_delay(0.8, 2) | |
| # Human-like click | |
| human_click(page, x, y) | |
| status_updates.append(f"Clicked cookie consent button: {button_selector}") | |
| human_delay(1, 3) | |
| break | |
| except Exception as e: | |
| logger.debug(f"Button {button_selector} not found: {str(e)}") | |
| continue | |
| except Exception as e: | |
| status_updates.append(f"Cookie dialog handling: {str(e)}") | |
| # Interact with login form like a human | |
| status_updates.append("Looking for login form...") | |
| # Take a screenshot to see what we're working with | |
| try: | |
| form_screenshot = f"screenshots/login_form_{int(time.time())}.png" | |
| page.screenshot(path=form_screenshot) | |
| image_path = form_screenshot | |
| status_updates.append("Login form screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Check for automation warning (might appear right away) | |
| if handle_automation_warning(page, status_updates): | |
| status_updates.append("Handled initial automation warning") | |
| # Try multiple selectors for username input | |
| username_selectors = [ | |
| "input[name='username']", | |
| "input[aria-label='Phone number, username, or email']", | |
| "input[placeholder='Phone number, username, or email']", | |
| "input[placeholder='Mobile number, username or email']", | |
| "input[type='text']" | |
| ] | |
| username_field = None | |
| for selector in username_selectors: | |
| try: | |
| status_updates.append(f"Trying to find username field with selector: {selector}") | |
| field = page.query_selector(selector) | |
| if field: | |
| username_field = field | |
| status_updates.append(f"Found username field with selector: {selector}") | |
| break | |
| except Exception as e: | |
| logger.debug(f"Selector {selector} failed: {str(e)}") | |
| if not username_field: | |
| status_updates.append("Could not find username field. Instagram may have changed their interface.") | |
| # Take final screenshot before closing | |
| final_screenshot = f"screenshots/final_error_{int(time.time())}.png" | |
| page.screenshot(path=final_screenshot) | |
| image_path = final_screenshot | |
| browser.close() | |
| return "\n".join(status_updates), image_path | |
| # Try multiple selectors for password input | |
| password_selectors = [ | |
| "input[name='password']", | |
| "input[aria-label='Password']", | |
| "input[placeholder='Password']", | |
| "input[type='password']" | |
| ] | |
| password_field = None | |
| for selector in password_selectors: | |
| try: | |
| status_updates.append(f"Trying to find password field with selector: {selector}") | |
| field = page.query_selector(selector) | |
| if field: | |
| password_field = field | |
| status_updates.append(f"Found password field with selector: {selector}") | |
| break | |
| except Exception as e: | |
| logger.debug(f"Selector {selector} failed: {str(e)}") | |
| if not password_field: | |
| status_updates.append("Could not find password field. Instagram may have changed their interface.") | |
| # Take final screenshot before closing | |
| final_screenshot = f"screenshots/final_error_{int(time.time())}.png" | |
| page.screenshot(path=final_screenshot) | |
| image_path = final_screenshot | |
| browser.close() | |
| return "\n".join(status_updates), image_path | |
| # Enter credentials like a human would | |
| status_updates.append(f"Entering username: {username}") | |
| # Click the username field first (human behavior) | |
| username_coords = username_field.bounding_box() | |
| username_x = username_coords['x'] + username_coords['width'] / 2 | |
| username_y = username_coords['y'] + username_coords['height'] / 2 | |
| human_click(page, username_x, username_y) | |
| # Type username with human-like timing | |
| for char in username: | |
| username_field.type(char, delay=random.randint(100, 300)) | |
| time.sleep(random.uniform(0.01, 0.15)) | |
| # Sometimes humans pause after entering username | |
| human_delay(0.5, 2) | |
| # Now click password field | |
| password_coords = password_field.bounding_box() | |
| password_x = password_coords['x'] + password_coords['width'] / 2 | |
| password_y = password_coords['y'] + password_coords['height'] / 2 | |
| human_click(page, password_x, password_y) | |
| # Type password with human-like timing | |
| for char in password: | |
| password_field.type(char, delay=random.randint(100, 300)) | |
| time.sleep(random.uniform(0.01, 0.15)) | |
| status_updates.append("Credentials entered") | |
| # Take a screenshot of filled form | |
| try: | |
| creds_screenshot = f"screenshots/credentials_pw_{int(time.time())}.png" | |
| page.screenshot(path=creds_screenshot) | |
| image_path = creds_screenshot | |
| status_updates.append("Credentials screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Find login button | |
| login_button_selectors = [ | |
| "button[type='submit']", | |
| "button:has-text('Log in')", | |
| "button:has-text('Sign in')", | |
| "button:has-text('Log In')", | |
| "form button" | |
| ] | |
| login_button = None | |
| for selector in login_button_selectors: | |
| try: | |
| status_updates.append(f"Trying to find login button with selector: {selector}") | |
| button = page.query_selector(selector) | |
| if button: | |
| login_button = button | |
| status_updates.append(f"Found login button with selector: {selector}") | |
| break | |
| except Exception as e: | |
| logger.debug(f"Selector {selector} failed: {str(e)}") | |
| if not login_button: | |
| status_updates.append("Could not find login button. Instagram may have changed their interface.") | |
| # Take final screenshot before closing | |
| final_screenshot = f"screenshots/final_error_{int(time.time())}.png" | |
| page.screenshot(path=final_screenshot) | |
| image_path = final_screenshot | |
| browser.close() | |
| return "\n".join(status_updates), image_path | |
| # Click login button like a human | |
| status_updates.append("Clicking login button...") | |
| button_coords = login_button.bounding_box() | |
| button_x = button_coords['x'] + button_coords['width'] / 2 | |
| button_y = button_coords['y'] + button_coords['height'] / 2 | |
| # Pause briefly before clicking (human behavior) | |
| human_delay(0.5, 1.5) | |
| human_click(page, button_x, button_y) | |
| # Wait for navigation to complete with human-like variation | |
| status_updates.append("Waiting for login process...") | |
| wait_time = random.uniform(3, 7) | |
| time.sleep(wait_time) | |
| # Take post-login screenshot | |
| try: | |
| post_login_screenshot = f"screenshots/post_login_pw_{int(time.time())}.png" | |
| page.screenshot(path=post_login_screenshot) | |
| image_path = post_login_screenshot | |
| status_updates.append("Post-login screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Check for automation warning after login | |
| if handle_automation_warning(page, status_updates): | |
| status_updates.append("Handled post-login automation warning") | |
| # Check if login was successful | |
| current_url = page.url | |
| if "/accounts/login" in current_url or "/login" in current_url: | |
| # Still on login page - check for error messages | |
| error_selectors = [ | |
| "#slfErrorAlert", | |
| "p[data-testid='login-error-message']", | |
| "div[role='alert']", | |
| "p.sIKKJ", | |
| ".coreSpriteAccessUpsell + div" | |
| ] | |
| error_message = "" | |
| for selector in error_selectors: | |
| try: | |
| el = page.query_selector(selector) | |
| if el: | |
| error_message = el.text_content() | |
| break | |
| except: | |
| pass | |
| if error_message: | |
| status_updates.append(f"Login failed: {error_message}") | |
| else: | |
| status_updates.append("Login failed: Reason unknown. Check your credentials.") | |
| browser.close() | |
| return "\n".join(status_updates), image_path | |
| status_updates.append("Login successful! Now handling post-login dialogs...") | |
| # Handle "Save Login Info" popup if it appears | |
| try: | |
| human_delay(1, 3) # Wait like a human for popup to appear | |
| save_info_selectors = [ | |
| "text=Save Login Info", | |
| "text=Save Your Login Info", | |
| "text=Save Info" | |
| ] | |
| not_now_selectors = [ | |
| "text=Not Now", | |
| "button:has-text('Not Now')", | |
| "button.sqdOP.yWX7d", | |
| "button:not(:has-text('Save'))" | |
| ] | |
| # Check if any save info dialog appears | |
| dialog_found = False | |
| for selector in save_info_selectors: | |
| if page.query_selector(selector): | |
| dialog_found = True | |
| status_updates.append(f"Save Login Info dialog found with: {selector}") | |
| break | |
| if dialog_found: | |
| # Wait like a human would before deciding | |
| human_delay(1, 4) | |
| # Try to click "Not Now" | |
| dismissed = False | |
| for not_now in not_now_selectors: | |
| try: | |
| button = page.query_selector(not_now) | |
| if button: | |
| # Get button coordinates | |
| coords = button.bounding_box() | |
| x = coords['x'] + coords['width'] / 2 | |
| y = coords['y'] + coords['height'] / 2 | |
| # Click like a human | |
| human_click(page, x, y) | |
| status_updates.append(f"Dismissed 'Save Login Info' popup using: {not_now}") | |
| human_delay(1, 3) | |
| dismissed = True | |
| break | |
| except Exception as e: | |
| logger.debug(f"Not Now button {not_now} failed: {str(e)}") | |
| continue | |
| if not dismissed: | |
| status_updates.append("Found Save Login dialog but couldn't dismiss it") | |
| else: | |
| status_updates.append("No 'Save Login Info' popup detected") | |
| except Exception as e: | |
| status_updates.append(f"Error handling Save Login dialog: {str(e)}") | |
| # Take a screenshot after handling first dialog | |
| try: | |
| after_save_screenshot = f"screenshots/after_save_dialog_{int(time.time())}.png" | |
| page.screenshot(path=after_save_screenshot) | |
| image_path = after_save_screenshot | |
| status_updates.append("Screenshot after Save Info dialog") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Handle notifications popup if it appears | |
| try: | |
| human_delay(1, 3) # Wait like a human | |
| notifications_selectors = [ | |
| "text=Turn on Notifications", | |
| "text=Enable Notifications", | |
| "h2:has-text('Notifications')" | |
| ] | |
| not_now_selectors = [ | |
| "text=Not Now", | |
| "button:has-text('Not Now')", | |
| "button.sqdOP.yWX7d", | |
| "button:not(:has-text('Allow'))" | |
| ] | |
| # Check if any notifications dialog appears | |
| dialog_found = False | |
| for selector in notifications_selectors: | |
| if page.query_selector(selector): | |
| dialog_found = True | |
| status_updates.append(f"Notifications dialog found with: {selector}") | |
| break | |
| if dialog_found: | |
| # Wait like a human before deciding | |
| human_delay(1, 4) | |
| # Try to click "Not Now" | |
| dismissed = False | |
| for not_now in not_now_selectors: | |
| try: | |
| button = page.query_selector(not_now) | |
| if button: | |
| # Get button coordinates | |
| coords = button.bounding_box() | |
| x = coords['x'] + coords['width'] / 2 | |
| y = coords['y'] + coords['height'] / 2 | |
| # Click like a human | |
| human_click(page, x, y) | |
| status_updates.append(f"Dismissed notifications popup using: {not_now}") | |
| human_delay(1, 3) | |
| dismissed = True | |
| break | |
| except Exception as e: | |
| logger.debug(f"Not Now button {not_now} failed: {str(e)}") | |
| continue | |
| if not dismissed: | |
| status_updates.append("Found Notifications dialog but couldn't dismiss it") | |
| else: | |
| status_updates.append("No notifications popup detected") | |
| except Exception as e: | |
| status_updates.append(f"Error handling Notifications dialog: {str(e)}") | |
| # Take feed screenshot | |
| try: | |
| feed_screenshot = f"screenshots/feed_pw_{int(time.time())}.png" | |
| page.screenshot(path=feed_screenshot) | |
| image_path = feed_screenshot | |
| status_updates.append("Feed screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking screenshot: {str(e)}") | |
| # Check for automation warning again | |
| if handle_automation_warning(page, status_updates): | |
| status_updates.append("Handled feed automation warning") | |
| status_updates.append("Successfully navigated to Instagram feed!") | |
| # Start liking posts with human-like behavior | |
| likes_goal = min(max_likes, random.randint(max_likes - 3, max_likes + 3)) | |
| status_updates.append(f"Starting to like posts (target: {likes_goal})...") | |
| # Like posts | |
| likes_count = 0 | |
| scroll_count = 0 | |
| max_scrolls = random.randint(25, 35) | |
| # Wait for the feed to load fully before looking for posts | |
| # Function to find and like posts with human-like behavior | |
| def find_and_like_post(): | |
| """Find and like a single post with human-like behavior""" | |
| try: | |
| # Use JavaScript to find like buttons directly in the DOM | |
| like_button_info = page.evaluate("""() => { | |
| // Try different selectors for like buttons (not already liked) | |
| const selectors = [ | |
| // Generic selectors for Instagram's like buttons | |
| "article svg[aria-label='Like']", | |
| "article section svg[aria-label='Like']", | |
| "svg[aria-label='Like']", | |
| "span[class*='_aamw'] svg[aria-label='Like']", | |
| "button svg[aria-label='Like']", | |
| // Looser selector that might catch more | |
| "article button[type='button']:not([aria-pressed='true'])" | |
| ]; | |
| // Shuffle the selectors to add randomness | |
| const shuffled = selectors.sort(() => 0.5 - Math.random()); | |
| // Try each selector | |
| for (const selector of shuffled) { | |
| const elements = Array.from(document.querySelectorAll(selector)); | |
| if (elements.length > 0) { | |
| // Shuffle the elements to add randomness | |
| const shuffledElements = elements.sort(() => 0.5 - Math.random()); | |
| // Choose a random element from the first few found | |
| const randomIndex = Math.floor(Math.random() * Math.min(3, shuffledElements.length)); | |
| const element = shuffledElements[randomIndex]; | |
| // Find the actual clickable button (might be a parent) | |
| let button = element; | |
| if (element.tagName.toLowerCase() === 'svg') { | |
| button = element.closest('button') || element; | |
| } | |
| // Get element position | |
| const rect = button.getBoundingClientRect(); | |
| // Check if it's visible in viewport | |
| if (rect.top >= 0 && rect.left >= 0 && | |
| rect.bottom <= window.innerHeight && | |
| rect.right <= window.innerWidth) { | |
| return { | |
| found: true, | |
| x: rect.x + rect.width / 2, | |
| y: rect.y + rect.height / 2, | |
| selector: selector, | |
| totalFound: elements.length | |
| }; | |
| } | |
| } | |
| } | |
| return { found: false }; | |
| }""") | |
| if like_button_info['found']: | |
| # Wait a bit before clicking (as if examining the post) | |
| human_delay(1, 4) | |
| # Sometimes explore the post before liking | |
| if random.random() < 0.3: | |
| # Move mouse around post area | |
| explore_x = like_button_info['x'] + random.randint(-100, 100) | |
| explore_y = like_button_info['y'] + random.randint(-100, 100) | |
| page.mouse.move(explore_x, explore_y) | |
| human_delay(0.5, 2) | |
| # Take screenshot before click | |
| try: | |
| before_like_screenshot = f"screenshots/before_like_{likes_count+1}_{int(time.time())}.png" | |
| page.screenshot(path=before_like_screenshot) | |
| except: | |
| pass | |
| # Click with human-like behavior | |
| like_x = like_button_info['x'] | |
| like_y = like_button_info['y'] | |
| # Add slight randomness to click position | |
| human_click(page, like_x, like_y) | |
| # Wait for the like to register | |
| human_delay(0.5, 2) | |
| return True | |
| return False | |
| except Exception as e: | |
| status_updates.append(f"Error finding/clicking like button: {str(e)}") | |
| return False | |
| # Variable to track consecutive failures | |
| consecutive_failures = 0 | |
| last_successful_like_time = time.time() | |
| # Main loop for scrolling and liking | |
| while likes_count < likes_goal and scroll_count < max_scrolls: | |
| try: | |
| # Take screenshot occasionally to track progress | |
| if scroll_count % 5 == 0: | |
| try: | |
| progress_screenshot = f"screenshots/progress_{scroll_count}_{int(time.time())}.png" | |
| page.screenshot(path=progress_screenshot) | |
| image_path = progress_screenshot | |
| except: | |
| pass | |
| # First check for automation warnings | |
| if handle_automation_warning(page, status_updates): | |
| status_updates.append(f"Handled automation warning during scrolling (#{scroll_count})") | |
| human_delay(3, 6) # Longer wait after handling warning | |
| # Try to like a post with human-like behavior | |
| if find_and_like_post(): | |
| likes_count += 1 | |
| consecutive_failures = 0 | |
| last_successful_like_time = time.time() | |
| status_updates.append(f"Liked post {likes_count}/{likes_goal}") | |
| # Take post-like screenshot occasionally | |
| if random.random() < 0.3: | |
| try: | |
| after_like_screenshot = f"screenshots/after_like_{likes_count}_{int(time.time())}.png" | |
| page.screenshot(path=after_like_screenshot) | |
| image_path = after_like_screenshot | |
| except: | |
| pass | |
| # Calculate a random human-like delay between likes | |
| # Occasionally take longer breaks to seem more human | |
| if random.random() < 0.15: # 15% chance of a longer break | |
| wait_time = random.uniform(5, 15) | |
| status_updates.append(f"Taking a short break ({wait_time:.1f}s)") | |
| time.sleep(wait_time) | |
| else: | |
| # Normal delay between likes (variable) | |
| human_delay(2, 7) | |
| # Occasionally interact with the feed in other ways to seem human | |
| if random.random() < 0.25: # 25% chance after liking | |
| random_action = random.choice([ | |
| "move_mouse_random", | |
| "small_scroll_up", | |
| "pause", | |
| "long_hover" | |
| ]) | |
| if random_action == "move_mouse_random": | |
| # Move mouse to random position | |
| random_x = random.randint(100, viewport_width - 100) | |
| random_y = random.randint(100, viewport_height - 100) | |
| page.mouse.move(random_x, random_y) | |
| human_delay(0.5, 2) | |
| elif random_action == "small_scroll_up": | |
| # Sometimes scroll back up a bit | |
| page.evaluate("window.scrollBy(0, -100);") | |
| human_delay(0.5, 2) | |
| elif random_action == "pause": | |
| # Just pause for a moment | |
| human_delay(1, 3) | |
| elif random_action == "long_hover": | |
| # Hover over something for a bit | |
| random_x = random.randint(200, viewport_width - 200) | |
| random_y = random.randint(200, viewport_height - 200) | |
| page.mouse.move(random_x, random_y) | |
| human_delay(2, 4) | |
| else: | |
| consecutive_failures += 1 | |
| status_updates.append(f"No new like buttons found on scroll {scroll_count}") | |
| # If we haven't found anything to like in a while, check if we need to scroll | |
| if consecutive_failures >= 3 or (time.time() - last_successful_like_time > 20): | |
| # Scroll down to load more content | |
| scroll_amount = human_scroll(page, | |
| min_pixels=random.randint(200, 400), | |
| max_pixels=random.randint(500, 800) | |
| ) | |
| status_updates.append(f"Scrolled down {scroll_amount} pixels to load more posts") | |
| # Wait for new content with variable timing | |
| wait_time = random.uniform(2, 5) | |
| time.sleep(wait_time) | |
| # Reset failure counter after scrolling | |
| consecutive_failures = 0 | |
| scroll_count += 1 | |
| else: | |
| # Small wait before trying again | |
| human_delay(1, 3) | |
| # Occasionally take a longer break to seem more human | |
| if likes_count > 0 and likes_count % 5 == 0 and random.random() < 0.7: | |
| break_time = random.uniform(8, 20) | |
| status_updates.append(f"Taking a longer break after {likes_count} likes ({break_time:.1f}s)") | |
| time.sleep(break_time) | |
| # Verify we haven't exceeded the target | |
| if likes_count >= likes_goal: | |
| status_updates.append(f"Reached target of {likes_goal} likes") | |
| break | |
| except Exception as e: | |
| status_updates.append(f"Error during like cycle: {str(e)}") | |
| # Take error screenshot | |
| try: | |
| error_screenshot = f"screenshots/error_{int(time.time())}.png" | |
| page.screenshot(path=error_screenshot) | |
| image_path = error_screenshot | |
| except: | |
| pass | |
| # Continue despite errors after a pause | |
| human_delay(3, 6) | |
| # Final status | |
| final_message = f"Finished! Liked {likes_count} posts." | |
| status_updates.append(final_message) | |
| # Final screenshot | |
| try: | |
| final_screenshot = f"screenshots/final_{int(time.time())}.png" | |
| page.screenshot(path=final_screenshot) | |
| image_path = final_screenshot | |
| status_updates.append("Final screenshot saved") | |
| except Exception as e: | |
| status_updates.append(f"Error taking final screenshot: {str(e)}") | |
| # Close the browser | |
| browser.close() | |
| status_updates.append("Browser closed") | |
| except Exception as e: | |
| status_updates.append(f"Error during browser interaction: {str(e)}") | |
| traceback_msg = traceback.format_exc() | |
| logger.error(traceback_msg) | |
| status_updates.append("See logs for detailed error information") | |
| # Try to close browser in case of error | |
| try: | |
| browser.close() | |
| status_updates.append("Browser closed after error") | |
| except: | |
| pass | |
| except Exception as e: | |
| error_message = f"Error with Playwright: {str(e)}" | |
| logger.error(error_message) | |
| status_updates.append(error_message) | |
| logger.error(traceback.format_exc()) | |
| return "\n".join(status_updates), image_path | |
| # Gradio Interface | |
| def create_interface(): | |
| with gr.Blocks(title="Instagram Auto-Liker") as app: | |
| gr.Markdown("# Instagram Auto-Liker (Human-like)") | |
| gr.Markdown(""" | |
| Enter your Instagram credentials and the number of posts to like. | |
| **Note:** This tool is for educational purposes only. Using automation tools with Instagram | |
| may violate their terms of service. Use at your own risk. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| username = gr.Textbox(label="Instagram Username") | |
| password = gr.Textbox(label="Instagram Password", type="password") | |
| max_likes = gr.Slider(minimum=1, maximum=50, value=10, step=1, label="Number of Posts to Like") | |
| submit_btn = gr.Button("Start Liking Posts") | |
| with gr.Column(scale=2): | |
| status_output = gr.Textbox(label="Status Log", lines=15) | |
| image_output = gr.Image(label="Latest Screenshot", type="filepath") | |
| submit_btn.click( | |
| fn=run_instagram_liker, | |
| inputs=[username, password, max_likes], | |
| outputs=[status_output, image_output] | |
| ) | |
| return app | |
| # Main entry point | |
| if __name__ == "__main__": | |
| print(f"===== Application Startup at {time.strftime('%Y-%m-%d %H:%M:%S')} =====") | |
| # Make sure all dependencies are installed before starting the application | |
| print("Installing dependencies...") | |
| success = download_playwright_browsers() | |
| if not success: | |
| print("Failed to install Playwright browsers. Please check your connection and try again.") | |
| sys.exit(1) | |
| # Start the app | |
| print("Starting the application...") | |
| app = create_interface() | |
| app.launch() |