Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -7,6 +7,7 @@ from PIL import Image
|
|
7 |
import io
|
8 |
import logging
|
9 |
import traceback
|
|
|
10 |
|
11 |
# Configure logging
|
12 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
@@ -246,12 +247,377 @@ def run_instagram_liker(username, password, max_likes):
|
|
246 |
browser.close()
|
247 |
return "\n".join(status_updates), image_path
|
248 |
|
249 |
-
#
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
|
256 |
# Close the browser
|
257 |
browser.close()
|
@@ -262,6 +628,13 @@ def run_instagram_liker(username, password, max_likes):
|
|
262 |
traceback_msg = traceback.format_exc()
|
263 |
logger.error(traceback_msg)
|
264 |
status_updates.append("See logs for detailed error information")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
except Exception as e:
|
267 |
error_message = f"Error with Playwright: {str(e)}"
|
|
|
7 |
import io
|
8 |
import logging
|
9 |
import traceback
|
10 |
+
from pathlib import Path
|
11 |
|
12 |
# Configure logging
|
13 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
247 |
browser.close()
|
248 |
return "\n".join(status_updates), image_path
|
249 |
|
250 |
+
# Try multiple selectors for password input
|
251 |
+
password_selectors = [
|
252 |
+
"input[name='password']",
|
253 |
+
"input[aria-label='Password']",
|
254 |
+
"input[placeholder='Password']",
|
255 |
+
"input[type='password']"
|
256 |
+
]
|
257 |
+
|
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 = [
|
296 |
+
"button[type='submit']",
|
297 |
+
"button:has-text('Log in')",
|
298 |
+
"button:has-text('Sign in')",
|
299 |
+
"button:has-text('Log In')",
|
300 |
+
"form button"
|
301 |
+
]
|
302 |
+
|
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
|
333 |
+
try:
|
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
|
343 |
+
if "/accounts/login" in current_url or "/login" in current_url:
|
344 |
+
# Still on login page - check for error messages
|
345 |
+
error_selectors = [
|
346 |
+
"#slfErrorAlert",
|
347 |
+
"p[data-testid='login-error-message']",
|
348 |
+
"div[role='alert']",
|
349 |
+
"p.sIKKJ",
|
350 |
+
".coreSpriteAccessUpsell + div"
|
351 |
+
]
|
352 |
+
|
353 |
+
error_message = ""
|
354 |
+
for selector in error_selectors:
|
355 |
+
try:
|
356 |
+
el = page.query_selector(selector)
|
357 |
+
if el:
|
358 |
+
error_message = el.text_content()
|
359 |
+
break
|
360 |
+
except:
|
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:
|
375 |
+
page.wait_for_timeout(2000) # Wait a bit for dialog to appear
|
376 |
+
|
377 |
+
save_info_selectors = [
|
378 |
+
"text=Save Login Info",
|
379 |
+
"text=Save Your Login Info",
|
380 |
+
"text=Save Info"
|
381 |
+
]
|
382 |
+
not_now_selectors = [
|
383 |
+
"text=Not Now",
|
384 |
+
"button:has-text('Not Now')",
|
385 |
+
"button.sqdOP.yWX7d",
|
386 |
+
"button:not(:has-text('Save'))"
|
387 |
+
]
|
388 |
+
|
389 |
+
# Check if any save info dialog appears
|
390 |
+
dialog_found = False
|
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:
|
401 |
+
try:
|
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
|
409 |
+
except Exception as e:
|
410 |
+
logger.debug(f"Not Now button {not_now} failed: {str(e)}")
|
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:
|
431 |
+
page.wait_for_timeout(2000) # Wait a bit for dialog to appear
|
432 |
+
|
433 |
+
notifications_selectors = [
|
434 |
+
"text=Turn on Notifications",
|
435 |
+
"text=Enable Notifications",
|
436 |
+
"h2:has-text('Notifications')"
|
437 |
+
]
|
438 |
+
not_now_selectors = [
|
439 |
+
"text=Not Now",
|
440 |
+
"button:has-text('Not Now')",
|
441 |
+
"button.sqdOP.yWX7d",
|
442 |
+
"button:not(:has-text('Allow'))"
|
443 |
+
]
|
444 |
+
|
445 |
+
# Check if any notifications dialog appears
|
446 |
+
dialog_found = False
|
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:
|
457 |
+
try:
|
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
|
465 |
+
except Exception as e:
|
466 |
+
logger.debug(f"Not Now button {not_now} failed: {str(e)}")
|
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 |
+
# Try to find posts with multiple selectors
|
496 |
+
article_selectors = [
|
497 |
+
"article",
|
498 |
+
"div[role='presentation']",
|
499 |
+
"div[data-testid='post-content']",
|
500 |
+
"div._aac4._aac5._aac6",
|
501 |
+
"div._ab6k"
|
502 |
+
]
|
503 |
+
|
504 |
+
article_found = False
|
505 |
+
for selector in article_selectors:
|
506 |
+
try:
|
507 |
+
if page.query_selector(selector):
|
508 |
+
article_found = True
|
509 |
+
status_updates.append(f"Found posts using selector: {selector}")
|
510 |
+
break
|
511 |
+
except Exception as e:
|
512 |
+
logger.debug(f"Article selector {selector} failed: {str(e)}")
|
513 |
+
|
514 |
+
if not article_found:
|
515 |
+
status_updates.append("Could not find posts on feed. Instagram may have changed their interface.")
|
516 |
+
browser.close()
|
517 |
+
return "\n".join(status_updates), image_path
|
518 |
+
|
519 |
+
while likes_count < max_likes and scroll_count < max_scrolls:
|
520 |
+
# Try different selectors for like buttons
|
521 |
+
like_button_selectors = [
|
522 |
+
"article section svg[aria-label='Like']",
|
523 |
+
"article svg[aria-label='Like']",
|
524 |
+
"svg[aria-label='Like']",
|
525 |
+
"span[class*='_aamw'] svg[aria-label='Like']",
|
526 |
+
"article button[type='button'] svg:not([aria-label='Unlike'])"
|
527 |
+
]
|
528 |
+
|
529 |
+
like_buttons = []
|
530 |
+
used_selector = ""
|
531 |
+
for selector in like_button_selectors:
|
532 |
+
try:
|
533 |
+
buttons = page.query_selector_all(selector)
|
534 |
+
if buttons and len(buttons) > 0:
|
535 |
+
like_buttons = buttons
|
536 |
+
used_selector = selector
|
537 |
+
status_updates.append(f"Found {len(buttons)} like buttons with selector: {selector}")
|
538 |
+
break
|
539 |
+
except Exception as e:
|
540 |
+
logger.debug(f"Like button selector {selector} failed: {str(e)}")
|
541 |
+
|
542 |
+
status_updates.append(f"Found {len(like_buttons)} like buttons on scroll {scroll_count}")
|
543 |
+
|
544 |
+
if len(like_buttons) == 0 and scroll_count > 5:
|
545 |
+
status_updates.append("No more like buttons found. Stopping.")
|
546 |
+
break
|
547 |
+
|
548 |
+
# Click like buttons
|
549 |
+
for i, button in enumerate(like_buttons):
|
550 |
+
if likes_count >= max_likes:
|
551 |
+
break
|
552 |
+
|
553 |
+
try:
|
554 |
+
# Scroll to button
|
555 |
+
button.scroll_into_view_if_needed()
|
556 |
+
page.wait_for_timeout(1000)
|
557 |
+
|
558 |
+
# Take pre-click screenshot occasionally
|
559 |
+
if likes_count % 5 == 0:
|
560 |
+
try:
|
561 |
+
pre_like_screenshot = f"screenshots/pre_like_pw_{likes_count}_{int(time.time())}.png"
|
562 |
+
page.screenshot(path=pre_like_screenshot)
|
563 |
+
image_path = pre_like_screenshot
|
564 |
+
status_updates.append(f"Pre-like screenshot {likes_count} saved")
|
565 |
+
except Exception as e:
|
566 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
567 |
+
|
568 |
+
# Get parent button if needed
|
569 |
+
if "svg" in used_selector:
|
570 |
+
try:
|
571 |
+
# Find the parent button of the SVG
|
572 |
+
parent_button = button.evaluate("node => node.closest('button')")
|
573 |
+
if parent_button:
|
574 |
+
parent_button.click()
|
575 |
+
else:
|
576 |
+
button.click()
|
577 |
+
except Exception as e:
|
578 |
+
# If error finding parent, click the button directly
|
579 |
+
button.click()
|
580 |
+
else:
|
581 |
+
# Click directly if it's already a button
|
582 |
+
button.click()
|
583 |
+
|
584 |
+
likes_count += 1
|
585 |
+
status_updates.append(f"Liked post {likes_count}/{max_likes}")
|
586 |
+
|
587 |
+
# Take screenshot occasionally
|
588 |
+
if likes_count % 5 == 0:
|
589 |
+
try:
|
590 |
+
like_screenshot = f"screenshots/after_like_pw_{likes_count}_{int(time.time())}.png"
|
591 |
+
page.screenshot(path=like_screenshot)
|
592 |
+
image_path = like_screenshot
|
593 |
+
status_updates.append(f"After-like screenshot {likes_count} saved")
|
594 |
+
except Exception as e:
|
595 |
+
status_updates.append(f"Error taking screenshot: {str(e)}")
|
596 |
+
|
597 |
+
# Wait between likes to avoid rate limiting
|
598 |
+
page.wait_for_timeout(2000 + (likes_count % 3) * 1000) # Varying delay
|
599 |
+
except Exception as e:
|
600 |
+
status_updates.append(f"Error liking post {i+1}: {str(e)}")
|
601 |
+
continue
|
602 |
+
|
603 |
+
# Scroll down to load more
|
604 |
+
page.evaluate("window.scrollBy(0, 1000)")
|
605 |
+
status_updates.append("Scrolled down to load more posts")
|
606 |
+
page.wait_for_timeout(3000)
|
607 |
+
scroll_count += 1
|
608 |
+
|
609 |
+
# Final status
|
610 |
+
final_message = f"Finished! Liked {likes_count} posts."
|
611 |
+
status_updates.append(final_message)
|
612 |
+
|
613 |
+
# Final screenshot
|
614 |
+
try:
|
615 |
+
final_screenshot = f"screenshots/final_{int(time.time())}.png"
|
616 |
+
page.screenshot(path=final_screenshot)
|
617 |
+
image_path = final_screenshot
|
618 |
+
status_updates.append("Final screenshot saved")
|
619 |
+
except Exception as e:
|
620 |
+
status_updates.append(f"Error taking final screenshot: {str(e)}")
|
621 |
|
622 |
# Close the browser
|
623 |
browser.close()
|
|
|
628 |
traceback_msg = traceback.format_exc()
|
629 |
logger.error(traceback_msg)
|
630 |
status_updates.append("See logs for detailed error information")
|
631 |
+
|
632 |
+
# Try to close browser in case of error
|
633 |
+
try:
|
634 |
+
browser.close()
|
635 |
+
status_updates.append("Browser closed after error")
|
636 |
+
except:
|
637 |
+
pass
|
638 |
|
639 |
except Exception as e:
|
640 |
error_message = f"Error with Playwright: {str(e)}"
|