diff --git a/main.py b/main.py index d82a24f..15f4309 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -""" +"" Instagram DM Bulk Automation — Powered by SoClose https://soclose.co -""" +""] import os import sys @@ -43,7 +43,6 @@ StaleElementReferenceException, ) - # ─── SoClose Brand Theme ──────────────────────────────────── SOCLOSE_THEME = Theme( @@ -68,7 +67,7 @@ INSTAGRAM_PASSWORD = os.getenv("INSTAGRAM_PASSWORD", "") BROWSER = os.getenv("BROWSER", "firefox").lower() MESSAGE_FILE = os.getenv("MESSAGE_FILE", "message.txt") -PROFILES_FILE = os.getenv("PROFILES_FILE", "profile_links.csv") +PROFILES_FILE = os.getenv("PROFILES_FILE", "/path/to/profile_links.csv") SENT_FILE = os.getenv("SENT_FILE", "already_send_message.csv") MAX_MESSAGES = int(os.getenv("MAX_MESSAGES", "10000")) HEADLESS = os.getenv("HEADLESS", "false").lower() == "true" @@ -112,7 +111,7 @@ def extract_username(value: str) -> str: if "instagram.com" in value: parts = value.split("instagram.com/") if len(parts) > 1: - username = parts[1].split("/")[0].split("?")[0] + username = parts[1].split("/"")[0].split("?")[0] return username # Already a plain username @@ -193,7 +192,6 @@ def random_delay(min_sec=None, max_sec=None): hi = max_sec if max_sec is not None else MAX_DELAY time.sleep(random.uniform(lo, hi)) - # ─── Browser ───────────────────────────────────────────────── @@ -219,7 +217,6 @@ def create_driver(): driver.maximize_window() return driver - # ─── Instagram Actions ─────────────────────────────────────── @@ -292,204 +289,36 @@ def login(driver): console.print("[success]Login complete.[/]") -def find_and_click_message_button(driver) -> bool: - """Find and click the Message button on a profile page.""" - selectors = [ - (By.XPATH, "//div[@role='button'][text()='Message']"), - (By.XPATH, "//div[text()='Message']/ancestor::*[@role='button']"), - (By.XPATH, "//div[text()='Message']"), - (By.XPATH, "//button[text()='Message']"), - (By.XPATH, "//div[text()='Envoyer un message']"), - (By.XPATH, "//div[text()='Envoyer message']"), - ] - - for by, selector in selectors: - try: - btn = WebDriverWait(driver, 6).until( - EC.element_to_be_clickable((by, selector)) - ) - btn.click() - return True - except ( - TimeoutException, - ElementClickInterceptedException, - StaleElementReferenceException, - NoSuchElementException, - ): - continue - - return False - - -def send_message(driver, message: str) -> bool: - """Type and send a message in the DM chat window.""" - try: - random_delay(3, 5) - - # Try multiple selectors for the message input - input_selectors = [ - (By.XPATH, "//div[@role='textbox'][@contenteditable='true']"), - ( - By.XPATH, - "//textarea[contains(@placeholder, 'Message') or contains(@placeholder, 'message')]", - ), - (By.TAG_NAME, "textarea"), - ] - - input_field = None - for by, selector in input_selectors: - try: - input_field = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((by, selector)) - ) - break - except TimeoutException: - continue - - if not input_field: - log.error("Could not find message input field") - return False - - input_field.click() - time.sleep(1) - - # Send message with line breaks preserved - lines = message.split("\n") - for i, line in enumerate(lines): - ActionChains(driver).send_keys(line).perform() - if i < len(lines) - 1: - ActionChains(driver).key_down(Keys.SHIFT).send_keys( - Keys.ENTER - ).key_up(Keys.SHIFT).perform() - time.sleep(0.3) - - # Press Enter to send - time.sleep(1) - ActionChains(driver).send_keys(Keys.RETURN).perform() - random_delay(2, 4) - - return True +# ─── Main Logic ────────────────────────────────────────────── - except Exception as e: - log.error(f"Failed to send message: {e}") - return False - -# ─── Main ──────────────────────────────────────────────────── - - -def run(): - """Main execution flow.""" +def main(): show_banner() - # Validate credentials - if not INSTAGRAM_EMAIL or not INSTAGRAM_PASSWORD: - console.print( - Panel( - "[error]Missing credentials.[/]\n\n" - "Set [bold]INSTAGRAM_EMAIL[/] and [bold]INSTAGRAM_PASSWORD[/] " - "in your [bold].env[/] file.\n" - "See [bold].env.example[/] for reference.", - title="[error]Configuration Error[/]", - border_style="red", - ) - ) - sys.exit(1) - - # Load data - message = load_message(MESSAGE_FILE) - profiles = load_profiles(PROFILES_FILE) - sent = load_sent(SENT_FILE) - - # Filter already-sent profiles - remaining = [p for p in profiles if p not in sent] - console.print(f"[info]Profiles to process:[/] {len(remaining)} / {len(profiles)}") - - if not remaining: - console.print( - "[warning]No new profiles to message. Add profiles to profile_links.csv.[/]" - ) - sys.exit(0) - - # Launch browser - console.print(f"[info]Launching {BROWSER.title()} browser...[/]") driver = create_driver() - try: login(driver) + profiles = load_profiles(PROFILES_FILE) + sent = load_sent(SENT_FILE) + message = load_message(MESSAGE_FILE) - count = 0 - - with Progress( - SpinnerColumn(style="#575ECF"), - TextColumn("[bold #575ECF]{task.description}"), - BarColumn(complete_style="#575ECF", finished_style="green"), - TaskProgressColumn(), - console=console, - ) as progress: - total = min(len(remaining), MAX_MESSAGES) - task = progress.add_task("Sending messages", total=total) - - for username in remaining: - if count >= MAX_MESSAGES: - console.print( - f"\n[warning]Reached max messages limit ({MAX_MESSAGES})[/]" - ) - break - - progress.update(task, description=f"Processing @{username}") - - # Navigate to profile - driver.get(f"https://www.instagram.com/{username}/") - random_delay(5, 10) - - # Find and click Message button - if find_and_click_message_button(driver): - random_delay(3, 6) - - if send_message(driver, message): - sent.add(username) - save_sent(SENT_FILE, sent) - count += 1 - progress.advance(task) - console.print( - f" [success]Sent to @{username} ({count}/{total})[/]" - ) - else: - console.print( - f" [error]Failed to send to @{username}[/]" - ) - else: - console.print( - f" [muted]No Message button for @{username} — skipped[/]" - ) - sent.add(username) - save_sent(SENT_FILE, sent) - progress.advance(task) - - # Human-like delay between profiles - random_delay() - - console.print() - console.print( - Panel( - f"[success]{count} messages sent successfully.[/]", - title="[bold #575ECF]Complete[/]", - border_style="#575ECF", - ) - ) + for profile in profiles: + if len(sent) >= MAX_MESSAGES: + break - except KeyboardInterrupt: - console.print("\n[warning]Interrupted. Progress saved.[/]") - except WebDriverException as e: - log.error(f"Browser error: {e}") - finally: - try: - driver.quit() - except Exception: - pass - console.print("[muted]Browser closed.[/]") + driver.get(f"https://www.instagram.com/{profile}/") + random_delay() + if find_and_click_message_button(driver): + send_message(driver, message) + sent.add(profile) + save_sent(SENT_FILE, sent) + log.info(f"Message sent to {profile}") + else: + log.warning(f"Failed to find message button for {profile}") + + finally: + driver.quit() if __name__ == "__main__": - run() + main() \ No newline at end of file