Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -7,9 +7,8 @@ from PIL import Image
|
|
7 |
import io
|
8 |
import logging
|
9 |
import traceback
|
|
|
10 |
from pathlib import Path
|
11 |
-
import threading
|
12 |
-
import queue
|
13 |
|
14 |
# Configure logging
|
15 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
@@ -18,10 +17,6 @@ logger = logging.getLogger(__name__)
|
|
18 |
# Create screenshots directory if it doesn't exist
|
19 |
os.makedirs('screenshots', exist_ok=True)
|
20 |
|
21 |
-
# Global queues for real-time updates
|
22 |
-
status_queue = queue.Queue()
|
23 |
-
image_queue = queue.Queue()
|
24 |
-
|
25 |
def download_playwright_browsers():
|
26 |
"""Make sure Playwright and its browsers are installed properly"""
|
27 |
try:
|
@@ -84,76 +79,38 @@ def verify_playwright_installation():
|
|
84 |
logger.warning("Playwright browser not found in expected locations")
|
85 |
return False
|
86 |
|
87 |
-
def update_ui(status_text, image_path=None):
|
88 |
-
"""Add updates to queue for real-time UI updates"""
|
89 |
-
status_queue.put(status_text)
|
90 |
-
if image_path:
|
91 |
-
image_queue.put(image_path)
|
92 |
-
|
93 |
-
def run_instagram_liker_thread(username, password, max_likes):
|
94 |
-
"""Thread function to run the Instagram auto-liker to allow real-time updates"""
|
95 |
-
result = run_instagram_liker(username, password, max_likes)
|
96 |
-
# Put final results in queue
|
97 |
-
status_queue.put(None) # Signal completion
|
98 |
-
image_queue.put(None) # Signal completion
|
99 |
-
return result
|
100 |
-
|
101 |
def run_instagram_liker(username, password, max_likes):
|
102 |
"""Run the Instagram auto-liker with Playwright"""
|
103 |
-
|
104 |
-
update_ui(all_status_updates[-1])
|
105 |
-
|
106 |
image_path = save_placeholder_image("start")
|
107 |
-
update_ui(None, image_path)
|
108 |
-
|
109 |
-
# Track all screenshots for gallery view
|
110 |
-
all_screenshots = [image_path]
|
111 |
|
112 |
# Check if browsers are installed, if not install them
|
113 |
try:
|
114 |
-
|
115 |
-
all_status_updates.append(status_text)
|
116 |
-
update_ui(status_text)
|
117 |
|
118 |
# First try to import
|
119 |
try:
|
120 |
from playwright.sync_api import sync_playwright
|
121 |
except ImportError:
|
122 |
-
|
123 |
-
all_status_updates.append(status_text)
|
124 |
-
update_ui(status_text)
|
125 |
-
|
126 |
download_playwright_browsers()
|
127 |
-
|
128 |
-
status_text = "Playwright installation completed."
|
129 |
-
all_status_updates.append(status_text)
|
130 |
-
update_ui(status_text)
|
131 |
|
132 |
# Then verify browser binary exists
|
133 |
if not verify_playwright_installation():
|
134 |
-
|
135 |
-
all_status_updates.append(status_text)
|
136 |
-
update_ui(status_text)
|
137 |
-
|
138 |
download_playwright_browsers()
|
139 |
|
140 |
# Double check installation worked
|
141 |
if not verify_playwright_installation():
|
142 |
-
|
143 |
-
|
144 |
-
update_ui(status_text)
|
145 |
-
return "\n".join(all_status_updates), image_path, all_screenshots
|
146 |
|
147 |
-
|
148 |
-
all_status_updates.append(status_text)
|
149 |
-
update_ui(status_text)
|
150 |
|
151 |
from playwright.sync_api import sync_playwright
|
152 |
|
153 |
-
|
154 |
-
all_status_updates.append(status_text)
|
155 |
-
update_ui(status_text)
|
156 |
-
|
157 |
with sync_playwright() as p:
|
158 |
try:
|
159 |
# Launch chromium with specific arguments to ensure it works in containerized environments
|
@@ -178,36 +135,21 @@ def run_instagram_liker(username, password, max_likes):
|
|
178 |
page = context.new_page()
|
179 |
|
180 |
# Test browser by visiting Google
|
181 |
-
|
182 |
-
all_status_updates.append(status_text)
|
183 |
-
update_ui(status_text)
|
184 |
-
|
185 |
page.goto("https://www.google.com")
|
186 |
-
|
187 |
-
status_text = f"Browser working. Title: {page.title()}"
|
188 |
-
all_status_updates.append(status_text)
|
189 |
-
update_ui(status_text)
|
190 |
|
191 |
# Take a screenshot
|
192 |
try:
|
193 |
test_screenshot = f"screenshots/test_pw_{int(time.time())}.png"
|
194 |
page.screenshot(path=test_screenshot)
|
195 |
image_path = test_screenshot
|
196 |
-
|
197 |
-
|
198 |
-
status_text = "Browser screenshot saved"
|
199 |
-
all_status_updates.append(status_text)
|
200 |
-
update_ui(status_text, image_path)
|
201 |
except Exception as e:
|
202 |
-
|
203 |
-
all_status_updates.append(status_text)
|
204 |
-
update_ui(status_text)
|
205 |
|
206 |
# Navigate to Instagram
|
207 |
-
|
208 |
-
all_status_updates.append(status_text)
|
209 |
-
update_ui(status_text)
|
210 |
-
|
211 |
page.goto("https://www.instagram.com/")
|
212 |
page.wait_for_load_state('networkidle')
|
213 |
|
@@ -216,21 +158,12 @@ def run_instagram_liker(username, password, max_likes):
|
|
216 |
landing_screenshot = f"screenshots/landing_pw_{int(time.time())}.png"
|
217 |
page.screenshot(path=landing_screenshot)
|
218 |
image_path = landing_screenshot
|
219 |
-
|
220 |
-
|
221 |
-
status_text = "Instagram page loaded, screenshot saved"
|
222 |
-
all_status_updates.append(status_text)
|
223 |
-
update_ui(status_text, image_path)
|
224 |
except Exception as e:
|
225 |
-
|
226 |
-
all_status_updates.append(status_text)
|
227 |
-
update_ui(status_text)
|
228 |
|
229 |
# Introduce delay to let the page fully load
|
230 |
-
|
231 |
-
all_status_updates.append(status_text)
|
232 |
-
update_ui(status_text)
|
233 |
-
|
234 |
page.wait_for_timeout(5000)
|
235 |
|
236 |
# Take another screenshot after waiting
|
@@ -238,26 +171,16 @@ def run_instagram_liker(username, password, max_likes):
|
|
238 |
loaded_screenshot = f"screenshots/fully_loaded_{int(time.time())}.png"
|
239 |
page.screenshot(path=loaded_screenshot)
|
240 |
image_path = loaded_screenshot
|
241 |
-
|
242 |
-
|
243 |
-
status_text = "Page fully loaded, screenshot saved"
|
244 |
-
all_status_updates.append(status_text)
|
245 |
-
update_ui(status_text, image_path)
|
246 |
except Exception as e:
|
247 |
-
|
248 |
-
all_status_updates.append(status_text)
|
249 |
-
update_ui(status_text)
|
250 |
|
251 |
# Try clicking outside any potential popups
|
252 |
try:
|
253 |
page.mouse.click(10, 10)
|
254 |
-
|
255 |
-
all_status_updates.append(status_text)
|
256 |
-
update_ui(status_text)
|
257 |
except Exception as e:
|
258 |
-
|
259 |
-
all_status_updates.append(status_text)
|
260 |
-
update_ui(status_text)
|
261 |
|
262 |
# Handle cookie dialog if present
|
263 |
try:
|
@@ -273,38 +196,26 @@ def run_instagram_liker(username, password, max_likes):
|
|
273 |
try:
|
274 |
if page.query_selector(button_selector):
|
275 |
page.click(button_selector)
|
276 |
-
|
277 |
-
all_status_updates.append(status_text)
|
278 |
-
update_ui(status_text)
|
279 |
page.wait_for_timeout(2000)
|
280 |
break
|
281 |
except Exception as e:
|
282 |
logger.debug(f"Button {button_selector} not found: {str(e)}")
|
283 |
continue
|
284 |
except Exception as e:
|
285 |
-
|
286 |
-
all_status_updates.append(status_text)
|
287 |
-
update_ui(status_text)
|
288 |
|
289 |
# Look for the login form
|
290 |
-
|
291 |
-
all_status_updates.append(status_text)
|
292 |
-
update_ui(status_text)
|
293 |
|
294 |
# Take a screenshot to see what we're working with
|
295 |
try:
|
296 |
form_screenshot = f"screenshots/login_form_{int(time.time())}.png"
|
297 |
page.screenshot(path=form_screenshot)
|
298 |
image_path = form_screenshot
|
299 |
-
|
300 |
-
|
301 |
-
status_text = "Login form screenshot saved"
|
302 |
-
all_status_updates.append(status_text)
|
303 |
-
update_ui(status_text, image_path)
|
304 |
except Exception as e:
|
305 |
-
|
306 |
-
all_status_updates.append(status_text)
|
307 |
-
update_ui(status_text)
|
308 |
|
309 |
# Try multiple selectors for username input
|
310 |
username_selectors = [
|
@@ -318,35 +229,23 @@ def run_instagram_liker(username, password, max_likes):
|
|
318 |
username_field = None
|
319 |
for selector in username_selectors:
|
320 |
try:
|
321 |
-
|
322 |
-
all_status_updates.append(status_text)
|
323 |
-
update_ui(status_text)
|
324 |
-
|
325 |
field = page.query_selector(selector)
|
326 |
if field:
|
327 |
username_field = field
|
328 |
-
|
329 |
-
status_text = f"Found username field with selector: {selector}"
|
330 |
-
all_status_updates.append(status_text)
|
331 |
-
update_ui(status_text)
|
332 |
break
|
333 |
except Exception as e:
|
334 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
335 |
|
336 |
if not username_field:
|
337 |
-
|
338 |
-
all_status_updates.append(status_text)
|
339 |
-
update_ui(status_text)
|
340 |
-
|
341 |
# Take final screenshot before closing
|
342 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
343 |
page.screenshot(path=final_screenshot)
|
344 |
image_path = final_screenshot
|
345 |
-
all_screenshots.append(image_path)
|
346 |
-
update_ui(None, image_path)
|
347 |
-
|
348 |
browser.close()
|
349 |
-
return "\n".join(
|
350 |
|
351 |
# Try multiple selectors for password input
|
352 |
password_selectors = [
|
@@ -359,62 +258,38 @@ def run_instagram_liker(username, password, max_likes):
|
|
359 |
password_field = None
|
360 |
for selector in password_selectors:
|
361 |
try:
|
362 |
-
|
363 |
-
all_status_updates.append(status_text)
|
364 |
-
update_ui(status_text)
|
365 |
-
|
366 |
field = page.query_selector(selector)
|
367 |
if field:
|
368 |
password_field = field
|
369 |
-
|
370 |
-
status_text = f"Found password field with selector: {selector}"
|
371 |
-
all_status_updates.append(status_text)
|
372 |
-
update_ui(status_text)
|
373 |
break
|
374 |
except Exception as e:
|
375 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
376 |
|
377 |
if not password_field:
|
378 |
-
|
379 |
-
all_status_updates.append(status_text)
|
380 |
-
update_ui(status_text)
|
381 |
-
|
382 |
# Take final screenshot before closing
|
383 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
384 |
page.screenshot(path=final_screenshot)
|
385 |
image_path = final_screenshot
|
386 |
-
all_screenshots.append(image_path)
|
387 |
-
update_ui(None, image_path)
|
388 |
-
|
389 |
browser.close()
|
390 |
-
return "\n".join(
|
391 |
|
392 |
# Enter credentials
|
393 |
-
|
394 |
-
all_status_updates.append(status_text)
|
395 |
-
update_ui(status_text)
|
396 |
-
|
397 |
username_field.fill(username)
|
398 |
password_field.fill(password)
|
399 |
-
|
400 |
-
status_text = "Credentials entered"
|
401 |
-
all_status_updates.append(status_text)
|
402 |
-
update_ui(status_text)
|
403 |
|
404 |
# Take a screenshot of filled form
|
405 |
try:
|
406 |
creds_screenshot = f"screenshots/credentials_pw_{int(time.time())}.png"
|
407 |
page.screenshot(path=creds_screenshot)
|
408 |
image_path = creds_screenshot
|
409 |
-
|
410 |
-
|
411 |
-
status_text = "Credentials screenshot saved"
|
412 |
-
all_status_updates.append(status_text)
|
413 |
-
update_ui(status_text, image_path)
|
414 |
except Exception as e:
|
415 |
-
|
416 |
-
all_status_updates.append(status_text)
|
417 |
-
update_ui(status_text)
|
418 |
|
419 |
# Find login button
|
420 |
login_button_selectors = [
|
@@ -428,48 +303,30 @@ def run_instagram_liker(username, password, max_likes):
|
|
428 |
login_button = None
|
429 |
for selector in login_button_selectors:
|
430 |
try:
|
431 |
-
|
432 |
-
all_status_updates.append(status_text)
|
433 |
-
update_ui(status_text)
|
434 |
-
|
435 |
button = page.query_selector(selector)
|
436 |
if button:
|
437 |
login_button = button
|
438 |
-
|
439 |
-
status_text = f"Found login button with selector: {selector}"
|
440 |
-
all_status_updates.append(status_text)
|
441 |
-
update_ui(status_text)
|
442 |
break
|
443 |
except Exception as e:
|
444 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
445 |
|
446 |
if not login_button:
|
447 |
-
|
448 |
-
all_status_updates.append(status_text)
|
449 |
-
update_ui(status_text)
|
450 |
-
|
451 |
# Take final screenshot before closing
|
452 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
453 |
page.screenshot(path=final_screenshot)
|
454 |
image_path = final_screenshot
|
455 |
-
all_screenshots.append(image_path)
|
456 |
-
update_ui(None, image_path)
|
457 |
-
|
458 |
browser.close()
|
459 |
-
return "\n".join(
|
460 |
|
461 |
# Click login button
|
462 |
-
|
463 |
-
all_status_updates.append(status_text)
|
464 |
-
update_ui(status_text)
|
465 |
-
|
466 |
login_button.click()
|
467 |
|
468 |
# Wait for navigation to complete
|
469 |
-
|
470 |
-
all_status_updates.append(status_text)
|
471 |
-
update_ui(status_text)
|
472 |
-
|
473 |
page.wait_for_timeout(5000)
|
474 |
|
475 |
# Take post-login screenshot
|
@@ -477,15 +334,9 @@ def run_instagram_liker(username, password, max_likes):
|
|
477 |
post_login_screenshot = f"screenshots/post_login_pw_{int(time.time())}.png"
|
478 |
page.screenshot(path=post_login_screenshot)
|
479 |
image_path = post_login_screenshot
|
480 |
-
|
481 |
-
|
482 |
-
status_text = "Post-login screenshot saved"
|
483 |
-
all_status_updates.append(status_text)
|
484 |
-
update_ui(status_text, image_path)
|
485 |
except Exception as e:
|
486 |
-
|
487 |
-
all_status_updates.append(status_text)
|
488 |
-
update_ui(status_text)
|
489 |
|
490 |
# Check if login was successful
|
491 |
current_url = page.url
|
@@ -510,20 +361,14 @@ def run_instagram_liker(username, password, max_likes):
|
|
510 |
pass
|
511 |
|
512 |
if error_message:
|
513 |
-
|
514 |
-
all_status_updates.append(status_text)
|
515 |
-
update_ui(status_text)
|
516 |
else:
|
517 |
-
|
518 |
-
all_status_updates.append(status_text)
|
519 |
-
update_ui(status_text)
|
520 |
|
521 |
browser.close()
|
522 |
-
return "\n".join(
|
523 |
|
524 |
-
|
525 |
-
all_status_updates.append(status_text)
|
526 |
-
update_ui(status_text)
|
527 |
|
528 |
# Handle "Save Login Info" popup if it appears
|
529 |
try:
|
@@ -546,20 +391,10 @@ def run_instagram_liker(username, password, max_likes):
|
|
546 |
for selector in save_info_selectors:
|
547 |
if page.query_selector(selector):
|
548 |
dialog_found = True
|
549 |
-
|
550 |
-
status_text = f"Save Login Info dialog found with: {selector}"
|
551 |
-
all_status_updates.append(status_text)
|
552 |
-
update_ui(status_text)
|
553 |
break
|
554 |
|
555 |
if dialog_found:
|
556 |
-
# Take screenshot of dialog
|
557 |
-
dialog_screenshot = f"screenshots/save_info_dialog_{int(time.time())}.png"
|
558 |
-
page.screenshot(path=dialog_screenshot)
|
559 |
-
image_path = dialog_screenshot
|
560 |
-
all_screenshots.append(image_path)
|
561 |
-
update_ui(None, image_path)
|
562 |
-
|
563 |
# Try to click "Not Now"
|
564 |
dismissed = False
|
565 |
for not_now in not_now_selectors:
|
@@ -567,11 +402,7 @@ def run_instagram_liker(username, password, max_likes):
|
|
567 |
button = page.query_selector(not_now)
|
568 |
if button:
|
569 |
button.click()
|
570 |
-
|
571 |
-
status_text = f"Dismissed 'Save Login Info' popup using: {not_now}"
|
572 |
-
all_status_updates.append(status_text)
|
573 |
-
update_ui(status_text)
|
574 |
-
|
575 |
page.wait_for_timeout(2000)
|
576 |
dismissed = True
|
577 |
break
|
@@ -580,32 +411,20 @@ def run_instagram_liker(username, password, max_likes):
|
|
580 |
continue
|
581 |
|
582 |
if not dismissed:
|
583 |
-
|
584 |
-
all_status_updates.append(status_text)
|
585 |
-
update_ui(status_text)
|
586 |
else:
|
587 |
-
|
588 |
-
all_status_updates.append(status_text)
|
589 |
-
update_ui(status_text)
|
590 |
except Exception as e:
|
591 |
-
|
592 |
-
all_status_updates.append(status_text)
|
593 |
-
update_ui(status_text)
|
594 |
|
595 |
# Take a screenshot after handling first dialog
|
596 |
try:
|
597 |
after_save_screenshot = f"screenshots/after_save_dialog_{int(time.time())}.png"
|
598 |
page.screenshot(path=after_save_screenshot)
|
599 |
image_path = after_save_screenshot
|
600 |
-
|
601 |
-
|
602 |
-
status_text = "Screenshot after Save Info dialog"
|
603 |
-
all_status_updates.append(status_text)
|
604 |
-
update_ui(status_text, image_path)
|
605 |
except Exception as e:
|
606 |
-
|
607 |
-
all_status_updates.append(status_text)
|
608 |
-
update_ui(status_text)
|
609 |
|
610 |
# Handle notifications popup if it appears
|
611 |
try:
|
@@ -628,20 +447,10 @@ def run_instagram_liker(username, password, max_likes):
|
|
628 |
for selector in notifications_selectors:
|
629 |
if page.query_selector(selector):
|
630 |
dialog_found = True
|
631 |
-
|
632 |
-
status_text = f"Notifications dialog found with: {selector}"
|
633 |
-
all_status_updates.append(status_text)
|
634 |
-
update_ui(status_text)
|
635 |
break
|
636 |
|
637 |
if dialog_found:
|
638 |
-
# Take screenshot of dialog
|
639 |
-
notif_screenshot = f"screenshots/notifications_dialog_{int(time.time())}.png"
|
640 |
-
page.screenshot(path=notif_screenshot)
|
641 |
-
image_path = notif_screenshot
|
642 |
-
all_screenshots.append(image_path)
|
643 |
-
update_ui(None, image_path)
|
644 |
-
|
645 |
# Try to click "Not Now"
|
646 |
dismissed = False
|
647 |
for not_now in not_now_selectors:
|
@@ -649,11 +458,7 @@ def run_instagram_liker(username, password, max_likes):
|
|
649 |
button = page.query_selector(not_now)
|
650 |
if button:
|
651 |
button.click()
|
652 |
-
|
653 |
-
status_text = f"Dismissed notifications popup using: {not_now}"
|
654 |
-
all_status_updates.append(status_text)
|
655 |
-
update_ui(status_text)
|
656 |
-
|
657 |
page.wait_for_timeout(2000)
|
658 |
dismissed = True
|
659 |
break
|
@@ -662,47 +467,34 @@ def run_instagram_liker(username, password, max_likes):
|
|
662 |
continue
|
663 |
|
664 |
if not dismissed:
|
665 |
-
|
666 |
-
all_status_updates.append(status_text)
|
667 |
-
update_ui(status_text)
|
668 |
else:
|
669 |
-
|
670 |
-
all_status_updates.append(status_text)
|
671 |
-
update_ui(status_text)
|
672 |
except Exception as e:
|
673 |
-
|
674 |
-
all_status_updates.append(status_text)
|
675 |
-
update_ui(status_text)
|
676 |
|
677 |
# Take feed screenshot
|
678 |
try:
|
679 |
feed_screenshot = f"screenshots/feed_pw_{int(time.time())}.png"
|
680 |
page.screenshot(path=feed_screenshot)
|
681 |
image_path = feed_screenshot
|
682 |
-
|
683 |
-
|
684 |
-
status_text = "Feed screenshot saved"
|
685 |
-
all_status_updates.append(status_text)
|
686 |
-
update_ui(status_text, image_path)
|
687 |
except Exception as e:
|
688 |
-
|
689 |
-
all_status_updates.append(status_text)
|
690 |
-
update_ui(status_text)
|
691 |
|
692 |
-
|
693 |
-
all_status_updates.append(status_text)
|
694 |
-
update_ui(status_text)
|
695 |
|
696 |
# Start liking posts
|
697 |
-
|
698 |
-
all_status_updates.append(status_text)
|
699 |
-
update_ui(status_text)
|
700 |
|
701 |
# Like posts
|
702 |
likes_count = 0
|
703 |
scroll_count = 0
|
704 |
max_scrolls = 30
|
705 |
|
|
|
|
|
|
|
706 |
# Try to find posts with multiple selectors
|
707 |
article_selectors = [
|
708 |
"article",
|
@@ -717,299 +509,171 @@ def run_instagram_liker(username, password, max_likes):
|
|
717 |
try:
|
718 |
if page.query_selector(selector):
|
719 |
article_found = True
|
720 |
-
|
721 |
-
status_text = f"Found posts using selector: {selector}"
|
722 |
-
all_status_updates.append(status_text)
|
723 |
-
update_ui(status_text)
|
724 |
break
|
725 |
except Exception as e:
|
726 |
logger.debug(f"Article selector {selector} failed: {str(e)}")
|
727 |
|
728 |
if not article_found:
|
729 |
-
|
730 |
-
all_status_updates.append(status_text)
|
731 |
-
update_ui(status_text)
|
732 |
-
|
733 |
browser.close()
|
734 |
-
return "\n".join(
|
735 |
|
736 |
-
#
|
737 |
while likes_count < max_likes and scroll_count < max_scrolls:
|
738 |
-
# Take screenshot of current view
|
739 |
try:
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
update_ui(status_text)
|
749 |
-
|
750 |
-
# Try different selectors for like buttons
|
751 |
-
like_button_selectors = [
|
752 |
-
"article svg[aria-label='Like']", # Main selector
|
753 |
-
"section svg[aria-label='Like']",
|
754 |
-
"span[role='button'] svg[aria-label='Like']",
|
755 |
-
"div[role='button'] svg[aria-label='Like']"
|
756 |
-
]
|
757 |
-
|
758 |
-
# Get all like buttons in the current view
|
759 |
-
all_like_buttons = []
|
760 |
-
used_selector = ""
|
761 |
-
|
762 |
-
for selector in like_button_selectors:
|
763 |
-
try:
|
764 |
-
buttons = page.query_selector_all(selector)
|
765 |
-
if buttons and len(buttons) > 0:
|
766 |
-
all_like_buttons = buttons
|
767 |
-
used_selector = selector
|
768 |
-
|
769 |
-
status_text = f"Found {len(buttons)} like buttons with selector: {selector}"
|
770 |
-
all_status_updates.append(status_text)
|
771 |
-
update_ui(status_text)
|
772 |
-
break
|
773 |
-
except Exception as e:
|
774 |
-
logger.debug(f"Like button selector {selector} failed: {str(e)}")
|
775 |
-
|
776 |
-
status_text = f"Found {len(all_like_buttons)} like buttons on scroll {scroll_count}"
|
777 |
-
all_status_updates.append(status_text)
|
778 |
-
update_ui(status_text)
|
779 |
-
|
780 |
-
if len(all_like_buttons) == 0 and scroll_count > 5:
|
781 |
-
status_text = "No more like buttons found. Stopping."
|
782 |
-
all_status_updates.append(status_text)
|
783 |
-
update_ui(status_text)
|
784 |
-
break
|
785 |
-
|
786 |
-
# Process each like button - NEW IMPROVED METHOD
|
787 |
-
for i, button in enumerate(all_like_buttons):
|
788 |
-
if likes_count >= max_likes:
|
789 |
-
break
|
790 |
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
|
|
|
|
|
|
|
|
|
|
800 |
|
801 |
-
|
802 |
-
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
811 |
}
|
812 |
-
|
813 |
-
}""")
|
814 |
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
# Scroll the article into view - critical for stability
|
822 |
-
page.evaluate("""(article) => {
|
823 |
-
article.scrollIntoView({
|
824 |
-
behavior: 'smooth',
|
825 |
-
block: 'center',
|
826 |
-
inline: 'center'
|
827 |
-
});
|
828 |
-
}""", article)
|
829 |
-
|
830 |
-
# Wait for scroll to complete
|
831 |
-
page.wait_for_timeout(1000)
|
832 |
-
|
833 |
-
# FIND THE LIKE BUTTON AGAIN after scrolling - very important
|
834 |
-
# This ensures we're working with an attached element
|
835 |
-
like_button = article.query_selector(used_selector)
|
836 |
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
|
|
|
|
|
|
|
|
841 |
|
842 |
-
#
|
843 |
-
|
844 |
-
like_button = article.query_selector(alt_selector)
|
845 |
-
if like_button:
|
846 |
-
status_text = f"Found alternative like button with {alt_selector}"
|
847 |
-
all_status_updates.append(status_text)
|
848 |
-
update_ui(status_text)
|
849 |
-
break
|
850 |
-
|
851 |
-
if not like_button:
|
852 |
-
status_text = f"Could not find like button for post {i+1} after scrolling, skipping"
|
853 |
-
all_status_updates.append(status_text)
|
854 |
-
update_ui(status_text)
|
855 |
-
continue
|
856 |
-
|
857 |
-
# Take one more screenshot right before clicking
|
858 |
-
ready_screenshot = f"screenshots/ready_to_like_{likes_count+1}_{int(time.time())}.png"
|
859 |
-
page.screenshot(path=ready_screenshot)
|
860 |
-
all_screenshots.append(ready_screenshot)
|
861 |
-
|
862 |
-
# IMPORTANT: Find the actual clickable button element
|
863 |
-
# SVG itself is not clickable, need to find parent button
|
864 |
-
clickable = like_button.evaluate("""(node) => {
|
865 |
-
// If already a button, use it
|
866 |
-
if (node.tagName === 'BUTTON') return node;
|
867 |
|
868 |
-
|
869 |
-
|
870 |
-
while (current && current.tagName !== 'BUTTON') {
|
871 |
-
current = current.parentElement;
|
872 |
-
// Safety check
|
873 |
-
if (!current || current.tagName === 'BODY') return null;
|
874 |
-
}
|
875 |
-
return current;
|
876 |
-
}""")
|
877 |
-
|
878 |
-
if not clickable:
|
879 |
-
status_text = f"Couldn't find clickable button for post {i+1}, trying direct click"
|
880 |
-
all_status_updates.append(status_text)
|
881 |
-
update_ui(status_text)
|
882 |
|
883 |
-
#
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
# Take error screenshot
|
912 |
-
try:
|
913 |
-
error_screenshot = f"screenshots/error_like_{int(time.time())}.png"
|
914 |
-
page.screenshot(path=error_screenshot)
|
915 |
-
all_screenshots.append(error_screenshot)
|
916 |
-
update_ui(None, error_screenshot)
|
917 |
-
except:
|
918 |
-
pass
|
919 |
-
|
920 |
-
continue
|
921 |
-
|
922 |
-
# If we've reached our target, break out
|
923 |
-
if likes_count >= max_likes:
|
924 |
-
break
|
925 |
-
|
926 |
-
# If we haven't found any like buttons or couldn't like any posts, scroll down
|
927 |
-
status_text = "Scrolling down to load more posts..."
|
928 |
-
all_status_updates.append(status_text)
|
929 |
-
update_ui(status_text)
|
930 |
-
|
931 |
-
# IMPROVED SCROLLING: Find the last post and scroll to it
|
932 |
-
try:
|
933 |
-
page.evaluate("""() => {
|
934 |
-
// Scroll smoothly to improve content loading
|
935 |
-
window.scrollBy({
|
936 |
-
top: 800,
|
937 |
-
behavior: 'smooth'
|
938 |
-
});
|
939 |
-
}""")
|
940 |
|
941 |
-
|
942 |
-
page.wait_for_timeout(3000)
|
943 |
|
944 |
-
status_text = f"Scrolled down to load more posts (scroll {scroll_count + 1})"
|
945 |
-
all_status_updates.append(status_text)
|
946 |
-
update_ui(status_text)
|
947 |
except Exception as e:
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
953 |
|
954 |
-
# Final status
|
955 |
final_message = f"Finished! Liked {likes_count} posts."
|
956 |
-
|
957 |
-
update_ui(final_message)
|
958 |
|
959 |
# Final screenshot
|
960 |
try:
|
961 |
final_screenshot = f"screenshots/final_{int(time.time())}.png"
|
962 |
page.screenshot(path=final_screenshot)
|
963 |
image_path = final_screenshot
|
964 |
-
|
965 |
-
|
966 |
-
status_text = "Final screenshot saved"
|
967 |
-
all_status_updates.append(status_text)
|
968 |
-
update_ui(status_text, image_path)
|
969 |
except Exception as e:
|
970 |
-
|
971 |
-
all_status_updates.append(status_text)
|
972 |
-
update_ui(status_text)
|
973 |
|
974 |
# Close the browser
|
975 |
browser.close()
|
976 |
-
|
977 |
-
status_text = "Browser closed"
|
978 |
-
all_status_updates.append(status_text)
|
979 |
-
update_ui(status_text)
|
980 |
|
981 |
except Exception as e:
|
982 |
-
|
983 |
-
all_status_updates.append(status_text)
|
984 |
-
update_ui(status_text)
|
985 |
-
|
986 |
traceback_msg = traceback.format_exc()
|
987 |
logger.error(traceback_msg)
|
988 |
-
|
989 |
-
status_text = "See logs for detailed error information"
|
990 |
-
all_status_updates.append(status_text)
|
991 |
-
update_ui(status_text)
|
992 |
|
993 |
# Try to close browser in case of error
|
994 |
try:
|
995 |
browser.close()
|
996 |
-
|
997 |
-
all_status_updates.append(status_text)
|
998 |
-
update_ui(status_text)
|
999 |
except:
|
1000 |
pass
|
1001 |
|
1002 |
except Exception as e:
|
1003 |
error_message = f"Error with Playwright: {str(e)}"
|
1004 |
logger.error(error_message)
|
1005 |
-
|
1006 |
-
update_ui(error_message)
|
1007 |
-
|
1008 |
logger.error(traceback.format_exc())
|
1009 |
|
1010 |
-
return "\n".join(
|
1011 |
|
1012 |
-
# Gradio Interface
|
1013 |
def create_interface():
|
1014 |
with gr.Blocks(title="Instagram Auto-Liker") as app:
|
1015 |
gr.Markdown("# Instagram Auto-Liker (Playwright)")
|
@@ -1029,85 +693,12 @@ def create_interface():
|
|
1029 |
|
1030 |
with gr.Column(scale=2):
|
1031 |
status_output = gr.Textbox(label="Status Log", lines=15)
|
1032 |
-
|
1033 |
-
# Gallery view of all screenshots
|
1034 |
-
with gr.Row():
|
1035 |
-
# Main current image
|
1036 |
-
image_output = gr.Image(label="Current Screenshot", type="filepath")
|
1037 |
-
|
1038 |
-
# Add gallery for all screenshots
|
1039 |
-
gallery = gr.Gallery(label="All Screenshots", show_label=True, elem_id="screenshot_gallery")
|
1040 |
-
|
1041 |
-
# Function to handle real-time updates
|
1042 |
-
def process_updates(username, password, max_likes):
|
1043 |
-
# Clear status queue
|
1044 |
-
while not status_queue.empty():
|
1045 |
-
try:
|
1046 |
-
status_queue.get_nowait()
|
1047 |
-
except:
|
1048 |
-
pass
|
1049 |
-
|
1050 |
-
# Clear image queue
|
1051 |
-
while not image_queue.empty():
|
1052 |
-
try:
|
1053 |
-
image_queue.get_nowait()
|
1054 |
-
except:
|
1055 |
-
pass
|
1056 |
-
|
1057 |
-
# Start worker thread
|
1058 |
-
thread = threading.Thread(
|
1059 |
-
target=run_instagram_liker_thread,
|
1060 |
-
args=(username, password, max_likes)
|
1061 |
-
)
|
1062 |
-
thread.daemon = True
|
1063 |
-
thread.start()
|
1064 |
-
|
1065 |
-
# Initial values
|
1066 |
-
status_text = "Starting Instagram Auto-Liker..."
|
1067 |
-
image_path = None
|
1068 |
-
all_images = []
|
1069 |
-
|
1070 |
-
# Function to update UI in real-time
|
1071 |
-
def get_updates():
|
1072 |
-
nonlocal status_text, image_path, all_images
|
1073 |
-
|
1074 |
-
# Check for new status updates
|
1075 |
-
try:
|
1076 |
-
while not status_queue.empty():
|
1077 |
-
new_status = status_queue.get_nowait()
|
1078 |
-
if new_status is None: # End signal
|
1079 |
-
return None, image_path, all_images
|
1080 |
-
status_text += "\n" + new_status
|
1081 |
-
except:
|
1082 |
-
pass
|
1083 |
-
|
1084 |
-
# Check for new images
|
1085 |
-
try:
|
1086 |
-
while not image_queue.empty():
|
1087 |
-
new_image = image_queue.get_nowait()
|
1088 |
-
if new_image is None: # End signal
|
1089 |
-
return status_text, image_path, all_images
|
1090 |
-
image_path = new_image
|
1091 |
-
if new_image not in all_images:
|
1092 |
-
all_images.append(new_image)
|
1093 |
-
except:
|
1094 |
-
pass
|
1095 |
-
|
1096 |
-
return status_text, image_path, all_images
|
1097 |
-
|
1098 |
-
return get_updates
|
1099 |
|
1100 |
-
# Connect the button to start processing
|
1101 |
submit_btn.click(
|
1102 |
-
fn=
|
1103 |
inputs=[username, password, max_likes],
|
1104 |
-
outputs=[status_output, image_output
|
1105 |
-
queue=False,
|
1106 |
-
).then(
|
1107 |
-
lambda: gr.Textbox(interactive=False),
|
1108 |
-
None,
|
1109 |
-
[status_output],
|
1110 |
-
queue=False,
|
1111 |
)
|
1112 |
|
1113 |
return app
|
|
|
7 |
import io
|
8 |
import logging
|
9 |
import traceback
|
10 |
+
import random
|
11 |
from pathlib import Path
|
|
|
|
|
12 |
|
13 |
# Configure logging
|
14 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
17 |
# Create screenshots directory if it doesn't exist
|
18 |
os.makedirs('screenshots', exist_ok=True)
|
19 |
|
|
|
|
|
|
|
|
|
20 |
def download_playwright_browsers():
|
21 |
"""Make sure Playwright and its browsers are installed properly"""
|
22 |
try:
|
|
|
79 |
logger.warning("Playwright browser not found in expected locations")
|
80 |
return False
|
81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
def run_instagram_liker(username, password, max_likes):
|
83 |
"""Run the Instagram auto-liker with Playwright"""
|
84 |
+
status_updates = ["Starting Instagram Auto-Liker with Playwright..."]
|
|
|
|
|
85 |
image_path = save_placeholder_image("start")
|
|
|
|
|
|
|
|
|
86 |
|
87 |
# Check if browsers are installed, if not install them
|
88 |
try:
|
89 |
+
status_updates.append("Verifying Playwright installation...")
|
|
|
|
|
90 |
|
91 |
# First try to import
|
92 |
try:
|
93 |
from playwright.sync_api import sync_playwright
|
94 |
except ImportError:
|
95 |
+
status_updates.append("Playwright not installed. Installing now...")
|
|
|
|
|
|
|
96 |
download_playwright_browsers()
|
97 |
+
status_updates.append("Playwright installation completed.")
|
|
|
|
|
|
|
98 |
|
99 |
# Then verify browser binary exists
|
100 |
if not verify_playwright_installation():
|
101 |
+
status_updates.append("Browser binary not found. Installing browsers...")
|
|
|
|
|
|
|
102 |
download_playwright_browsers()
|
103 |
|
104 |
# Double check installation worked
|
105 |
if not verify_playwright_installation():
|
106 |
+
status_updates.append("Failed to install browser binary. Please try running the script again.")
|
107 |
+
return "\n".join(status_updates), image_path
|
|
|
|
|
108 |
|
109 |
+
status_updates.append("Playwright installation verified.")
|
|
|
|
|
110 |
|
111 |
from playwright.sync_api import sync_playwright
|
112 |
|
113 |
+
status_updates.append("Launching Playwright browser...")
|
|
|
|
|
|
|
114 |
with sync_playwright() as p:
|
115 |
try:
|
116 |
# Launch chromium with specific arguments to ensure it works in containerized environments
|
|
|
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
|
143 |
try:
|
144 |
test_screenshot = f"screenshots/test_pw_{int(time.time())}.png"
|
145 |
page.screenshot(path=test_screenshot)
|
146 |
image_path = test_screenshot
|
147 |
+
status_updates.append("Browser screenshot saved")
|
|
|
|
|
|
|
|
|
148 |
except Exception as e:
|
149 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
150 |
|
151 |
# Navigate to Instagram
|
152 |
+
status_updates.append("Navigating to Instagram...")
|
|
|
|
|
|
|
153 |
page.goto("https://www.instagram.com/")
|
154 |
page.wait_for_load_state('networkidle')
|
155 |
|
|
|
158 |
landing_screenshot = f"screenshots/landing_pw_{int(time.time())}.png"
|
159 |
page.screenshot(path=landing_screenshot)
|
160 |
image_path = landing_screenshot
|
161 |
+
status_updates.append("Instagram page loaded, screenshot saved")
|
|
|
|
|
|
|
|
|
162 |
except Exception as e:
|
163 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
164 |
|
165 |
# Introduce delay to let the page fully load
|
166 |
+
status_updates.append("Waiting for page to fully load...")
|
|
|
|
|
|
|
167 |
page.wait_for_timeout(5000)
|
168 |
|
169 |
# Take another screenshot after waiting
|
|
|
171 |
loaded_screenshot = f"screenshots/fully_loaded_{int(time.time())}.png"
|
172 |
page.screenshot(path=loaded_screenshot)
|
173 |
image_path = loaded_screenshot
|
174 |
+
status_updates.append("Page fully loaded, screenshot saved")
|
|
|
|
|
|
|
|
|
175 |
except Exception as e:
|
176 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
177 |
|
178 |
# Try clicking outside any potential popups
|
179 |
try:
|
180 |
page.mouse.click(10, 10)
|
181 |
+
status_updates.append("Clicked to dismiss any initial popups")
|
|
|
|
|
182 |
except Exception as e:
|
183 |
+
status_updates.append(f"Click error: {str(e)}")
|
|
|
|
|
184 |
|
185 |
# Handle cookie dialog if present
|
186 |
try:
|
|
|
196 |
try:
|
197 |
if page.query_selector(button_selector):
|
198 |
page.click(button_selector)
|
199 |
+
status_updates.append(f"Clicked cookie consent button: {button_selector}")
|
|
|
|
|
200 |
page.wait_for_timeout(2000)
|
201 |
break
|
202 |
except Exception as e:
|
203 |
logger.debug(f"Button {button_selector} not found: {str(e)}")
|
204 |
continue
|
205 |
except Exception as e:
|
206 |
+
status_updates.append(f"Cookie dialog handling: {str(e)}")
|
|
|
|
|
207 |
|
208 |
# Look for the login form
|
209 |
+
status_updates.append("Looking for login form...")
|
|
|
|
|
210 |
|
211 |
# Take a screenshot to see what we're working with
|
212 |
try:
|
213 |
form_screenshot = f"screenshots/login_form_{int(time.time())}.png"
|
214 |
page.screenshot(path=form_screenshot)
|
215 |
image_path = form_screenshot
|
216 |
+
status_updates.append("Login form screenshot saved")
|
|
|
|
|
|
|
|
|
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 = [
|
|
|
229 |
username_field = None
|
230 |
for selector in username_selectors:
|
231 |
try:
|
232 |
+
status_updates.append(f"Trying to find username field with selector: {selector}")
|
|
|
|
|
|
|
233 |
field = page.query_selector(selector)
|
234 |
if field:
|
235 |
username_field = field
|
236 |
+
status_updates.append(f"Found username field with selector: {selector}")
|
|
|
|
|
|
|
237 |
break
|
238 |
except Exception as e:
|
239 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
240 |
|
241 |
if not username_field:
|
242 |
+
status_updates.append("Could not find username field. Instagram may have changed their interface.")
|
|
|
|
|
|
|
243 |
# Take final screenshot before closing
|
244 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
245 |
page.screenshot(path=final_screenshot)
|
246 |
image_path = final_screenshot
|
|
|
|
|
|
|
247 |
browser.close()
|
248 |
+
return "\n".join(status_updates), image_path
|
249 |
|
250 |
# Try multiple selectors for password input
|
251 |
password_selectors = [
|
|
|
258 |
password_field = None
|
259 |
for selector in password_selectors:
|
260 |
try:
|
261 |
+
status_updates.append(f"Trying to find password field with selector: {selector}")
|
|
|
|
|
|
|
262 |
field = page.query_selector(selector)
|
263 |
if field:
|
264 |
password_field = field
|
265 |
+
status_updates.append(f"Found password field with selector: {selector}")
|
|
|
|
|
|
|
266 |
break
|
267 |
except Exception as e:
|
268 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
269 |
|
270 |
if not password_field:
|
271 |
+
status_updates.append("Could not find password field. Instagram may have changed their interface.")
|
|
|
|
|
|
|
272 |
# Take final screenshot before closing
|
273 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
274 |
page.screenshot(path=final_screenshot)
|
275 |
image_path = final_screenshot
|
|
|
|
|
|
|
276 |
browser.close()
|
277 |
+
return "\n".join(status_updates), image_path
|
278 |
|
279 |
# Enter credentials
|
280 |
+
status_updates.append(f"Entering username: {username}")
|
|
|
|
|
|
|
281 |
username_field.fill(username)
|
282 |
password_field.fill(password)
|
283 |
+
status_updates.append("Credentials entered")
|
|
|
|
|
|
|
284 |
|
285 |
# Take a screenshot of filled form
|
286 |
try:
|
287 |
creds_screenshot = f"screenshots/credentials_pw_{int(time.time())}.png"
|
288 |
page.screenshot(path=creds_screenshot)
|
289 |
image_path = creds_screenshot
|
290 |
+
status_updates.append("Credentials screenshot saved")
|
|
|
|
|
|
|
|
|
291 |
except Exception as e:
|
292 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
293 |
|
294 |
# Find login button
|
295 |
login_button_selectors = [
|
|
|
303 |
login_button = None
|
304 |
for selector in login_button_selectors:
|
305 |
try:
|
306 |
+
status_updates.append(f"Trying to find login button with selector: {selector}")
|
|
|
|
|
|
|
307 |
button = page.query_selector(selector)
|
308 |
if button:
|
309 |
login_button = button
|
310 |
+
status_updates.append(f"Found login button with selector: {selector}")
|
|
|
|
|
|
|
311 |
break
|
312 |
except Exception as e:
|
313 |
logger.debug(f"Selector {selector} failed: {str(e)}")
|
314 |
|
315 |
if not login_button:
|
316 |
+
status_updates.append("Could not find login button. Instagram may have changed their interface.")
|
|
|
|
|
|
|
317 |
# Take final screenshot before closing
|
318 |
final_screenshot = f"screenshots/final_error_{int(time.time())}.png"
|
319 |
page.screenshot(path=final_screenshot)
|
320 |
image_path = final_screenshot
|
|
|
|
|
|
|
321 |
browser.close()
|
322 |
+
return "\n".join(status_updates), image_path
|
323 |
|
324 |
# Click login button
|
325 |
+
status_updates.append("Clicking login button...")
|
|
|
|
|
|
|
326 |
login_button.click()
|
327 |
|
328 |
# Wait for navigation to complete
|
329 |
+
status_updates.append("Waiting for login process...")
|
|
|
|
|
|
|
330 |
page.wait_for_timeout(5000)
|
331 |
|
332 |
# Take post-login screenshot
|
|
|
334 |
post_login_screenshot = f"screenshots/post_login_pw_{int(time.time())}.png"
|
335 |
page.screenshot(path=post_login_screenshot)
|
336 |
image_path = post_login_screenshot
|
337 |
+
status_updates.append("Post-login screenshot saved")
|
|
|
|
|
|
|
|
|
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
|
|
|
361 |
pass
|
362 |
|
363 |
if error_message:
|
364 |
+
status_updates.append(f"Login failed: {error_message}")
|
|
|
|
|
365 |
else:
|
366 |
+
status_updates.append("Login failed: Reason unknown. Check your credentials.")
|
|
|
|
|
367 |
|
368 |
browser.close()
|
369 |
+
return "\n".join(status_updates), image_path
|
370 |
|
371 |
+
status_updates.append("Login successful! Now handling post-login dialogs...")
|
|
|
|
|
372 |
|
373 |
# Handle "Save Login Info" popup if it appears
|
374 |
try:
|
|
|
391 |
for selector in save_info_selectors:
|
392 |
if page.query_selector(selector):
|
393 |
dialog_found = True
|
394 |
+
status_updates.append(f"Save Login Info dialog found with: {selector}")
|
|
|
|
|
|
|
395 |
break
|
396 |
|
397 |
if dialog_found:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
# Try to click "Not Now"
|
399 |
dismissed = False
|
400 |
for not_now in not_now_selectors:
|
|
|
402 |
button = page.query_selector(not_now)
|
403 |
if button:
|
404 |
button.click()
|
405 |
+
status_updates.append(f"Dismissed 'Save Login Info' popup using: {not_now}")
|
|
|
|
|
|
|
|
|
406 |
page.wait_for_timeout(2000)
|
407 |
dismissed = True
|
408 |
break
|
|
|
411 |
continue
|
412 |
|
413 |
if not dismissed:
|
414 |
+
status_updates.append("Found Save Login dialog but couldn't dismiss it")
|
|
|
|
|
415 |
else:
|
416 |
+
status_updates.append("No 'Save Login Info' popup detected")
|
|
|
|
|
417 |
except Exception as e:
|
418 |
+
status_updates.append(f"Error handling Save Login dialog: {str(e)}")
|
|
|
|
|
419 |
|
420 |
# Take a screenshot after handling first dialog
|
421 |
try:
|
422 |
after_save_screenshot = f"screenshots/after_save_dialog_{int(time.time())}.png"
|
423 |
page.screenshot(path=after_save_screenshot)
|
424 |
image_path = after_save_screenshot
|
425 |
+
status_updates.append("Screenshot after Save Info dialog")
|
|
|
|
|
|
|
|
|
426 |
except Exception as e:
|
427 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
428 |
|
429 |
# Handle notifications popup if it appears
|
430 |
try:
|
|
|
447 |
for selector in notifications_selectors:
|
448 |
if page.query_selector(selector):
|
449 |
dialog_found = True
|
450 |
+
status_updates.append(f"Notifications dialog found with: {selector}")
|
|
|
|
|
|
|
451 |
break
|
452 |
|
453 |
if dialog_found:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
# Try to click "Not Now"
|
455 |
dismissed = False
|
456 |
for not_now in not_now_selectors:
|
|
|
458 |
button = page.query_selector(not_now)
|
459 |
if button:
|
460 |
button.click()
|
461 |
+
status_updates.append(f"Dismissed notifications popup using: {not_now}")
|
|
|
|
|
|
|
|
|
462 |
page.wait_for_timeout(2000)
|
463 |
dismissed = True
|
464 |
break
|
|
|
467 |
continue
|
468 |
|
469 |
if not dismissed:
|
470 |
+
status_updates.append("Found Notifications dialog but couldn't dismiss it")
|
|
|
|
|
471 |
else:
|
472 |
+
status_updates.append("No notifications popup detected")
|
|
|
|
|
473 |
except Exception as e:
|
474 |
+
status_updates.append(f"Error handling Notifications dialog: {str(e)}")
|
|
|
|
|
475 |
|
476 |
# Take feed screenshot
|
477 |
try:
|
478 |
feed_screenshot = f"screenshots/feed_pw_{int(time.time())}.png"
|
479 |
page.screenshot(path=feed_screenshot)
|
480 |
image_path = feed_screenshot
|
481 |
+
status_updates.append("Feed screenshot saved")
|
|
|
|
|
|
|
|
|
482 |
except Exception as e:
|
483 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
|
|
|
|
484 |
|
485 |
+
status_updates.append("Successfully navigated to Instagram feed!")
|
|
|
|
|
486 |
|
487 |
# Start liking posts
|
488 |
+
status_updates.append(f"Starting to like posts (target: {max_likes})...")
|
|
|
|
|
489 |
|
490 |
# Like posts
|
491 |
likes_count = 0
|
492 |
scroll_count = 0
|
493 |
max_scrolls = 30
|
494 |
|
495 |
+
# Wait for the feed to load fully before looking for posts
|
496 |
+
page.wait_for_timeout(3000)
|
497 |
+
|
498 |
# Try to find posts with multiple selectors
|
499 |
article_selectors = [
|
500 |
"article",
|
|
|
509 |
try:
|
510 |
if page.query_selector(selector):
|
511 |
article_found = True
|
512 |
+
status_updates.append(f"Found posts using selector: {selector}")
|
|
|
|
|
|
|
513 |
break
|
514 |
except Exception as e:
|
515 |
logger.debug(f"Article selector {selector} failed: {str(e)}")
|
516 |
|
517 |
if not article_found:
|
518 |
+
status_updates.append("Could not find posts on feed. Instagram may have changed their interface.")
|
|
|
|
|
|
|
519 |
browser.close()
|
520 |
+
return "\n".join(status_updates), image_path
|
521 |
|
522 |
+
# Like one post at a time with full scroll stabilization
|
523 |
while likes_count < max_likes and scroll_count < max_scrolls:
|
|
|
524 |
try:
|
525 |
+
# Take a screenshot before each scroll to aid debugging
|
526 |
+
if scroll_count % 5 == 0:
|
527 |
+
try:
|
528 |
+
scroll_screenshot = f"screenshots/scroll_{scroll_count}_{int(time.time())}.png"
|
529 |
+
page.screenshot(path=scroll_screenshot)
|
530 |
+
image_path = scroll_screenshot
|
531 |
+
except Exception as e:
|
532 |
+
status_updates.append(f"Error taking scroll screenshot: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
533 |
|
534 |
+
# Wait for the page to be stable after scrolling
|
535 |
+
page.wait_for_timeout(2000)
|
536 |
+
|
537 |
+
# IMPORTANT FIX: Evaluate the selector directly in the page context to get fresh elements
|
538 |
+
# This addresses the "Element is not attached to the DOM" issue
|
539 |
+
like_buttons = page.evaluate("""() => {
|
540 |
+
// Try different selectors to find like buttons that haven't been clicked yet
|
541 |
+
const selectors = [
|
542 |
+
"article section svg[aria-label='Like']",
|
543 |
+
"article svg[aria-label='Like']",
|
544 |
+
"svg[aria-label='Like']",
|
545 |
+
"span[class*='_aamw'] svg[aria-label='Like']",
|
546 |
+
"article button[type='button'] svg:not([aria-label='Unlike'])"
|
547 |
+
];
|
548 |
|
549 |
+
for (const selector of selectors) {
|
550 |
+
const buttons = Array.from(document.querySelectorAll(selector));
|
551 |
+
if (buttons.length > 0) {
|
552 |
+
// We need to find the actual button elements (parents of the SVGs)
|
553 |
+
const buttonElements = buttons.map(svg => {
|
554 |
+
// Find closest button parent
|
555 |
+
return svg.closest('button');
|
556 |
+
}).filter(btn => btn !== null);
|
557 |
+
|
558 |
+
if (buttonElements.length > 0) {
|
559 |
+
// Return info about the first valid button
|
560 |
+
const button = buttonElements[0];
|
561 |
+
const rect = button.getBoundingClientRect();
|
562 |
+
|
563 |
+
return {
|
564 |
+
found: true,
|
565 |
+
count: buttonElements.length,
|
566 |
+
x: rect.x + rect.width / 2,
|
567 |
+
y: rect.y + rect.height / 2
|
568 |
+
};
|
569 |
+
}
|
570 |
}
|
571 |
+
}
|
|
|
572 |
|
573 |
+
return { found: false, count: 0 };
|
574 |
+
}""")
|
575 |
+
|
576 |
+
if like_buttons['found']:
|
577 |
+
status_updates.append(f"Found {like_buttons['count']} like buttons on scroll {scroll_count}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
578 |
|
579 |
+
# Click the first like button directly by coordinates
|
580 |
+
try:
|
581 |
+
# Take pre-click screenshot
|
582 |
+
try:
|
583 |
+
pre_like_screenshot = f"screenshots/pre_like_{likes_count}_{int(time.time())}.png"
|
584 |
+
page.screenshot(path=pre_like_screenshot)
|
585 |
+
except Exception as e:
|
586 |
+
status_updates.append(f"Error taking pre-like screenshot: {str(e)}")
|
587 |
|
588 |
+
# Click by coordinates rather than on the element
|
589 |
+
page.mouse.click(like_buttons['x'], like_buttons['y'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
|
591 |
+
likes_count += 1
|
592 |
+
status_updates.append(f"Liked post {likes_count}/{max_likes}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
593 |
|
594 |
+
# Take post-click screenshot
|
595 |
+
try:
|
596 |
+
post_like_screenshot = f"screenshots/post_like_{likes_count}_{int(time.time())}.png"
|
597 |
+
page.screenshot(path=post_like_screenshot)
|
598 |
+
image_path = post_like_screenshot
|
599 |
+
except Exception as e:
|
600 |
+
status_updates.append(f"Error taking post-like screenshot: {str(e)}")
|
601 |
+
|
602 |
+
# Add a random delay between likes (2-4 seconds)
|
603 |
+
delay = 2000 + random.randint(0, 2000)
|
604 |
+
page.wait_for_timeout(delay)
|
605 |
+
|
606 |
+
except Exception as e:
|
607 |
+
status_updates.append(f"Error clicking like button: {str(e)}")
|
608 |
+
else:
|
609 |
+
status_updates.append(f"No like buttons found on scroll {scroll_count}")
|
610 |
+
|
611 |
+
# Scroll down to load more
|
612 |
+
# Use a smaller scroll distance to ensure we don't miss posts
|
613 |
+
page.evaluate("window.scrollBy(0, 500);")
|
614 |
+
status_updates.append("Scrolled down to load more posts")
|
615 |
+
|
616 |
+
# Wait for new content to load
|
617 |
+
page.wait_for_load_state('networkidle', timeout=5000)
|
618 |
+
|
619 |
+
# Variable delay after scrolling (2-3 seconds)
|
620 |
+
delay = 2000 + random.randint(0, 1000)
|
621 |
+
page.wait_for_timeout(delay)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
622 |
|
623 |
+
scroll_count += 1
|
|
|
624 |
|
|
|
|
|
|
|
625 |
except Exception as e:
|
626 |
+
status_updates.append(f"Error during scroll/like cycle: {str(e)}")
|
627 |
+
# Take error screenshot
|
628 |
+
try:
|
629 |
+
error_screenshot = f"screenshots/error_{int(time.time())}.png"
|
630 |
+
page.screenshot(path=error_screenshot)
|
631 |
+
image_path = error_screenshot
|
632 |
+
except:
|
633 |
+
pass
|
634 |
+
|
635 |
+
# Continue to the next scroll despite errors
|
636 |
+
scroll_count += 1
|
637 |
|
638 |
+
# Final status
|
639 |
final_message = f"Finished! Liked {likes_count} posts."
|
640 |
+
status_updates.append(final_message)
|
|
|
641 |
|
642 |
# Final screenshot
|
643 |
try:
|
644 |
final_screenshot = f"screenshots/final_{int(time.time())}.png"
|
645 |
page.screenshot(path=final_screenshot)
|
646 |
image_path = final_screenshot
|
647 |
+
status_updates.append("Final screenshot saved")
|
|
|
|
|
|
|
|
|
648 |
except Exception as e:
|
649 |
+
status_updates.append(f"Error taking final screenshot: {str(e)}")
|
|
|
|
|
650 |
|
651 |
# Close the browser
|
652 |
browser.close()
|
653 |
+
status_updates.append("Browser closed")
|
|
|
|
|
|
|
654 |
|
655 |
except Exception as e:
|
656 |
+
status_updates.append(f"Error during browser interaction: {str(e)}")
|
|
|
|
|
|
|
657 |
traceback_msg = traceback.format_exc()
|
658 |
logger.error(traceback_msg)
|
659 |
+
status_updates.append("See logs for detailed error information")
|
|
|
|
|
|
|
660 |
|
661 |
# Try to close browser in case of error
|
662 |
try:
|
663 |
browser.close()
|
664 |
+
status_updates.append("Browser closed after error")
|
|
|
|
|
665 |
except:
|
666 |
pass
|
667 |
|
668 |
except Exception as e:
|
669 |
error_message = f"Error with Playwright: {str(e)}"
|
670 |
logger.error(error_message)
|
671 |
+
status_updates.append(error_message)
|
|
|
|
|
672 |
logger.error(traceback.format_exc())
|
673 |
|
674 |
+
return "\n".join(status_updates), image_path
|
675 |
|
676 |
+
# Gradio Interface
|
677 |
def create_interface():
|
678 |
with gr.Blocks(title="Instagram Auto-Liker") as app:
|
679 |
gr.Markdown("# Instagram Auto-Liker (Playwright)")
|
|
|
693 |
|
694 |
with gr.Column(scale=2):
|
695 |
status_output = gr.Textbox(label="Status Log", lines=15)
|
696 |
+
image_output = gr.Image(label="Latest Screenshot", type="filepath")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
|
|
|
698 |
submit_btn.click(
|
699 |
+
fn=run_instagram_liker,
|
700 |
inputs=[username, password, max_likes],
|
701 |
+
outputs=[status_output, image_output]
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
)
|
703 |
|
704 |
return app
|