Spaces:
Running
Running
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() |