diff --git a/README.md b/README.md index e86d9fe..5350ce6 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ A powerful and free open-source Instagram automation tool for managing your digi - **Error Handling**: Automatic retry on failures - **Configurable Settings**: Customize to your needs - **Activity Logging**: Track your operations +- **Exclude Users**: Exclude users from the like removing, so specific liked posts do not get removed! ## 🚀 Quick Start Guide @@ -137,7 +138,7 @@ The installer handles all dependencies automatically: ### Quick Usage Guide 1. **Installation** - - Windows: Run `run.bat` + - Windows: Run `run.bat` -> I didn't update this - macOS/Linux: Execute `run.sh` 2. **Setup**: Add Instagram Account, then provide your login credentials, and then start the unliking process 3. **Operation**: Follow the interactive dashboard @@ -185,5 +186,6 @@ This tool is for educational purposes only. Use responsibly and in accordance wi ---
-Made with â¤ī¸ by u/TahaGorme +Made with â¤ī¸ TahaGorme +Added feature of excluding instagram accounts by me
diff --git a/config.json b/config.json index 829d894..04864c0 100644 --- a/config.json +++ b/config.json @@ -1,16 +1,18 @@ { "delay": { - "min": 20, - "max": 100 + "min": 5.0, + "max": 15.0 }, "break": { - "min": 900, - "max": 3600, - "probability": 0.01 + "min": 300.0, + "max": 900.0, + "probability": 0.001 }, "accounts": {}, + "excluded_users": [], "log_level": "INFO", "max_retries": 3, "retry_delay": 60, + "auto_update": true, "python_min_version": "3.7.0" -} +} \ No newline at end of file diff --git a/instagram_unliker.py b/instagram_unliker.py index eafb27d..f9c8405 100644 --- a/instagram_unliker.py +++ b/instagram_unliker.py @@ -11,7 +11,7 @@ import threading from datetime import datetime, timedelta from pathlib import Path -from typing import Optional, Dict, Any, List, Tuple +from typing import Optional, Dict, Any, List, Tuple, Set from getpass import getpass import webbrowser import signal @@ -31,35 +31,36 @@ # Global variables for configuration CONFIG = { "delay": { - "min": 60, # 1 minutes minimum - "max": 300, # 5 minutes maximum + "min": 20, + "max": 100, }, "break": { - "min": 900, # 15 minutes - "max": 3600, # 1 hour - "probability": 0.1 # 10% chance of taking a break + "min": 900, + "max": 3600, + "probability": 0.01 }, "accounts": {}, + "excluded_users": [], # NEW: List of usernames to exclude "log_level": "INFO", "max_retries": 3, - "retry_delay": 60, # 1 minute + "retry_delay": 60, "auto_update": True, - "python_min_version": "3.7.0" # Minimum required Python version + "python_min_version": "3.7.0" } class ConsoleColors: - HEADER = '\033[95m' # Pink - BLUE = '\033[94m' # Blue - GREEN = '\033[92m' # Green - YELLOW = '\033[93m' # Yellow - RED = '\033[91m' # Red - PURPLE = '\033[95m' # Purple - CYAN = '\033[96m' # Cyan - WHITE = '\033[97m' # White - MAGENTA = '\033[35m' # Magenta - BOLD = '\033[1m' # Bold - UNDERLINE = '\033[4m'# Underline - RESET = '\033[0m' # Reset all formatting + HEADER = '\033[95m' + BLUE = '\033[94m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + PURPLE = '\033[95m' + CYAN = '\033[96m' + WHITE = '\033[97m' + MAGENTA = '\033[35m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + RESET = '\033[0m' class InstagramUnliker: def __init__(self): @@ -70,22 +71,23 @@ def __init__(self): self.accounts_dir = Path("accounts") self.logs_dir = Path("logs") self.running = True + self.excluded_users: Set[str] = set() - # Create necessary directories self._create_required_directories() - - # Check and setup Python environment self._ensure_python_environment() self._setup_signal_handlers() self.setup_logging() - - # Load configuration self.check_and_create_config() + self._load_excluded_users() + + def _load_excluded_users(self): + """Load excluded users from config""" + self.excluded_users = set(CONFIG.get('excluded_users', [])) + logging.info(f"Loaded {len(self.excluded_users)} excluded users") def _ensure_python_environment(self): """Ensure Python and pip are properly installed""" try: - # Check if pip is installed import pip except ImportError: logging.warning("pip is not installed. Installing pip...") @@ -94,15 +96,10 @@ def _ensure_python_environment(self): def _install_pip(self): """Install pip if not present""" try: - # Download get-pip.py import urllib.request urllib.request.urlretrieve("https://bootstrap.pypa.io/get-pip.py", "get-pip.py") - - # Install pip subprocess.check_call([sys.executable, "get-pip.py"]) logging.info("Successfully installed pip") - - # Clean up os.remove("get-pip.py") except Exception as e: logging.error(f"Failed to install pip: {str(e)}") @@ -118,7 +115,7 @@ def _handle_shutdown(self, signum, frame): """Handle shutdown signals gracefully""" print(f"\n{ConsoleColors.YELLOW}[!] Received shutdown signal. Cleaning up...{ConsoleColors.RESET}") self.running = False - time.sleep(1) # Give time for cleanup + time.sleep(1) sys.exit(0) def setup_logging(self): @@ -126,16 +123,14 @@ def setup_logging(self): self.logs_dir = Path("logs") self.logs_dir.mkdir(exist_ok=True) - # Set up rotating file handler log_file = self.logs_dir / "unliker.log" file_handler = RotatingFileHandler( log_file, - maxBytes=5*1024*1024, # 5MB per file - backupCount=5, # Keep 5 backup files + maxBytes=5*1024*1024, + backupCount=5, encoding='utf-8' ) - # Set up formatters with more detailed information file_formatter = logging.Formatter( '%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' @@ -146,58 +141,27 @@ def setup_logging(self): ) file_handler.setFormatter(file_formatter) - - # Console handler with color support console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(console_formatter) - # Root logger configuration root_logger = logging.getLogger() root_logger.setLevel(logging.INFO) - - # Remove existing handlers and add new ones root_logger.handlers.clear() root_logger.addHandler(file_handler) root_logger.addHandler(console_handler) - # Register cleanup on exit atexit.register(self._cleanup_logs) - logging.info("Logging system initialized") def _cleanup_logs(self): """Cleanup function that runs on program exit""" try: logging.info("Performing final cleanup...") - # Save any pending configurations self.save_config() - - # Close all handlers for handler in logging.getLogger().handlers: handler.close() - except Exception as e: print(f"Error during cleanup: {str(e)}") - - def _log_system_info(self): - """Log detailed system information""" - logging.info("-" * 50) - logging.info("System Information:") - logging.info(f"OS: {platform.system()} {platform.release()}") - logging.info(f"Python: {sys.version}") - logging.info(f"Platform: {platform.platform()}") - logging.info(f"Working Directory: {os.getcwd()}") - logging.info("-" * 50) - - def _cleanup_old_logs(self, days=5): - """Clean up log files older than specified days""" - try: - cutoff = datetime.now() - timedelta(days=days) - for log_file in self.logs_dir.glob("*.log"): - if datetime.fromtimestamp(log_file.stat().st_mtime) < cutoff: - log_file.unlink() - except Exception as e: - logging.warning(f"Failed to cleanup old logs: {str(e)}") def _create_required_directories(self): """Create necessary directories if they don't exist""" @@ -221,12 +185,10 @@ def check_python_version(self) -> bool: def install_requirements(self) -> bool: """Install required Python packages with detailed error handling""" try: - # First uninstall ensta if it exists subprocess.run([sys.executable, "-m", "pip", "uninstall", "-y", "ensta"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - # Install specific version of ensta result = subprocess.run([sys.executable, "-m", "pip", "install", "--no-cache-dir", "ensta==5.2.9"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -255,7 +217,6 @@ def check_and_create_config(self): try: with open(self.config_file, 'r') as f: loaded_config = json.load(f) - # Update global CONFIG with loaded values for key, value in loaded_config.items(): if key in CONFIG: CONFIG[key] = value @@ -351,11 +312,69 @@ def remove_account(self): except Exception as e: print(f"{ConsoleColors.RED}[✗] Error: {str(e)}{ConsoleColors.RESET}") + def manage_excluded_users(self): + """Manage excluded users list""" + while True: + print(f"\n{ConsoleColors.CYAN}đŸšĢ Manage Excluded Users{ConsoleColors.RESET}") + print("=" * 50) + + if self.excluded_users: + print(f"\n{ConsoleColors.YELLOW}Currently Excluded ({len(self.excluded_users)} users):{ConsoleColors.RESET}") + for i, user in enumerate(sorted(self.excluded_users), 1): + print(f" {i}. @{user}") + else: + print(f"\n{ConsoleColors.YELLOW}No users excluded yet{ConsoleColors.RESET}") + + print(f"\n{ConsoleColors.CYAN}Options:{ConsoleColors.RESET}") + print(f" 1. Add user to exclude list") + print(f" 2. Remove user from exclude list") + print(f" 3. Clear all excluded users") + print(f" 0. Back to main menu") + + choice = input(f"\n{ConsoleColors.BOLD}Select option: {ConsoleColors.RESET}").strip() + + if choice == "1": + username = input(f"{ConsoleColors.BOLD}Enter username to exclude: {ConsoleColors.RESET}").strip().lower() + if username: + self.excluded_users.add(username) + CONFIG['excluded_users'] = list(self.excluded_users) + self.save_config() + print(f"{ConsoleColors.GREEN}✓ Added @{username} to exclude list{ConsoleColors.RESET}") + + elif choice == "2": + if not self.excluded_users: + print(f"{ConsoleColors.YELLOW}No users to remove{ConsoleColors.RESET}") + continue + + username = input(f"{ConsoleColors.BOLD}Enter username to remove: {ConsoleColors.RESET}").strip().lower() + if username in self.excluded_users: + self.excluded_users.remove(username) + CONFIG['excluded_users'] = list(self.excluded_users) + self.save_config() + print(f"{ConsoleColors.GREEN}✓ Removed @{username} from exclude list{ConsoleColors.RESET}") + else: + print(f"{ConsoleColors.YELLOW}User not found in exclude list{ConsoleColors.RESET}") + + elif choice == "3": + if self.excluded_users: + confirm = input(f"{ConsoleColors.YELLOW}Clear all excluded users? (y/N): {ConsoleColors.RESET}").lower() + if confirm == 'y': + self.excluded_users.clear() + CONFIG['excluded_users'] = [] + self.save_config() + print(f"{ConsoleColors.GREEN}✓ Cleared all excluded users{ConsoleColors.RESET}") + + elif choice == "0": + break + else: + print(f"{ConsoleColors.RED}Invalid option{ConsoleColors.RESET}") + + time.sleep(1) + def list_accounts(self) -> List[str]: """List all configured accounts""" if not self.accounts_dir.exists(): return [] - return [f.stem for f in self.accounts_dir.glob("*.json")] def save_config(self): @@ -367,9 +386,9 @@ def save_config(self): print(f"{ConsoleColors.RED}[✗] Failed to save configuration: {str(e)}{ConsoleColors.RESET}") def unlike_posts(self, username: str): - """Unlike posts using JSON file""" + """Unlike posts using JSON file with exclude list support""" account_file = self.accounts_dir / f"{username}.json" - progress_bar = None # Initialize progress_bar at the beginning of the method + progress_bar = None if not account_file.exists(): error_msg = f"Account file not found for {username}" @@ -382,7 +401,8 @@ def unlike_posts(self, username: str): account_data = json.load(f) print(f"\n{ConsoleColors.CYAN}Starting to unlike posts for @{username}...{ConsoleColors.RESET}") - print(f"{ConsoleColors.YELLOW}This will run in the background. You can close anytime.{ConsoleColors.RESET}") + if self.excluded_users: + print(f"{ConsoleColors.YELLOW}â„šī¸ Excluding {len(self.excluded_users)} users from unliking{ConsoleColors.RESET}") try: from ensta import Web @@ -397,7 +417,6 @@ def unlike_posts(self, username: str): print(f"→ Please check your username and password.{ConsoleColors.RESET}") return - # Try to load liked posts from JSON file if not os.path.exists('liked_posts.json'): error_msg = "liked_posts.json file not found" logging.error(error_msg) @@ -413,12 +432,28 @@ def unlike_posts(self, username: str): logging.warning(error_msg) print(f"{ConsoleColors.YELLOW}[!] {error_msg}!{ConsoleColors.RESET}") return - - total_posts = len(liked_data['likes_media_likes']) - first_likes_media_likes = total_posts + + # Filter out excluded users + original_count = len(liked_data['likes_media_likes']) + filtered_posts = [ + post for post in liked_data['likes_media_likes'] + if post['title'].lower() not in self.excluded_users + ] + excluded_count = original_count - len(filtered_posts) + + if excluded_count > 0: + print(f"{ConsoleColors.YELLOW}đŸšĢ Skipped {excluded_count} posts from excluded users{ConsoleColors.RESET}") + + liked_data['likes_media_likes'] = filtered_posts + total_posts = len(filtered_posts) unliked_count = 0 + skipped_count = 0 - print(f"{ConsoleColors.BLUE}Found {total_posts} liked posts{ConsoleColors.RESET}") + if total_posts == 0: + print(f"{ConsoleColors.YELLOW}No posts to unlike after filtering{ConsoleColors.RESET}") + return + + print(f"{ConsoleColors.BLUE}Found {total_posts} posts to unlike{ConsoleColors.RESET}") progress_bar = tqdm( total=total_posts, @@ -433,9 +468,17 @@ def unlike_posts(self, username: str): time.sleep(actual_delay) post = liked_data['likes_media_likes'].pop(0) + post_username = post['title'].lower() + + # Double-check exclusion (safety) + if post_username in self.excluded_users: + skipped_count += 1 + progress_bar.update(1) + continue + media_id = instagram_code_to_media_id(post['string_list_data'][0]['href']) - # Unlike with retry mechanism and detailed error logging + # Unlike with retry mechanism for retry in range(CONFIG['max_retries']): try: client.unlike(media_id) @@ -444,7 +487,6 @@ def unlike_posts(self, username: str): error_msg = f"Failed to unlike post (attempt {retry + 1}/{CONFIG['max_retries']}): {str(e)}" logging.warning(error_msg) if retry < CONFIG['max_retries'] - 1: - print(f"{ConsoleColors.YELLOW}[!] {error_msg}. Retrying...{ConsoleColors.RESET}") time.sleep(CONFIG['retry_delay']) else: raise Exception(error_msg) @@ -460,16 +502,15 @@ def unlike_posts(self, username: str): # Random break if random.random() < CONFIG['break']['probability']: break_time = random.uniform(CONFIG['break']['min'], CONFIG['break']['max']) - print(f"\n{ConsoleColors.BLUE}[*] Taking a break for {break_time/60:.1f} minutes...{ConsoleColors.RESET}") + progress_bar.write(f"{ConsoleColors.BLUE}[*] Taking a break for {break_time/60:.1f} minutes...{ConsoleColors.RESET}") time.sleep(break_time) except Exception as e: error_msg = f"Failed to unlike post: {str(e)}" - logging.error(error_msg, exc_info=True) # Include full traceback in logs - print(f"{ConsoleColors.RED}[✗] {error_msg}") - print(f"→ Taking a 5-minute cooldown...{ConsoleColors.RESET}") + logging.error(error_msg, exc_info=True) + progress_bar.write(f"{ConsoleColors.RED}[✗] {error_msg}{ConsoleColors.RESET}") account_data['last_error'] = error_msg - time.sleep(300) # 5 minute cooldown on error + time.sleep(300) # 5 minute cooldown finally: if progress_bar is not None: @@ -481,67 +522,57 @@ def unlike_posts(self, username: str): json.dump(account_data, f, indent=4) print(f"\n{ConsoleColors.GREEN}[✓] Unliking complete for {username}{ConsoleColors.RESET}") - print(f"{ConsoleColors.BLUE}[*] Total unliked: {account_data['total_unliked']}{ConsoleColors.RESET}") + print(f"{ConsoleColors.BLUE}[*] Total unliked: {unliked_count}{ConsoleColors.RESET}") + if skipped_count > 0: + print(f"{ConsoleColors.YELLOW}[*] Skipped (excluded): {skipped_count}{ConsoleColors.RESET}") except json.JSONDecodeError as e: - error_msg = f"Invalid JSON format in account file: {str(e)}" + error_msg = f"Invalid JSON format: {str(e)}" logging.error(error_msg) - print(f"{ConsoleColors.RED}[✗] {error_msg}") - print("→ Try removing and re-adding your account.{ConsoleColors.RESET}") + print(f"{ConsoleColors.RED}[✗] {error_msg}{ConsoleColors.RESET}") except Exception as e: error_msg = f"Unexpected error: {str(e)}" - logging.error(error_msg, exc_info=True) # Include full traceback in logs - print(f"\n{ConsoleColors.RED}[✗] {error_msg}") - print("→ The error has been logged. Please check the logs for details.{ConsoleColors.RESET}") - try: - account_data['last_error'] = error_msg - with open(account_file, 'w') as f: - json.dump(account_data, f, indent=4) - except: - logging.error("Failed to save error information to account file", exc_info=True) + logging.error(error_msg, exc_info=True) + print(f"\n{ConsoleColors.RED}[✗] {error_msg}{ConsoleColors.RESET}") def center_text_in_box(text, box_width=48): """Center text in a box line, accounting for color codes""" - # Get visible length excluding color codes visible_length = get_visible_length(text) - # Calculate padding needed for centering - padding = (box_width - 2 - visible_length) // 2 # -2 for the box edges + padding = (box_width - 2 - visible_length) // 2 return f"║{' ' * padding}{text}{' ' * (box_width - 2 - visible_length - padding)}║" def show_menu(self): """Display interactive menu with improved UI""" while True: - # Clear screen for better visibility - # print("\033[H\033[J" if platform.system() != "Windows" else os.system('cls')) - - # Display decorated header print(f"\n{ConsoleColors.CYAN}{ConsoleColors.BOLD}╔{'═' * 46}╗") print(InstagramUnliker.center_text_in_box(f"{ConsoleColors.BOLD}Instagram Mass Unliker{ConsoleColors.RESET}{ConsoleColors.CYAN}{ConsoleColors.BOLD}")) print(InstagramUnliker.center_text_in_box(f"{ConsoleColors.BOLD}Erase your digital footprint{ConsoleColors.RESET}{ConsoleColors.CYAN}{ConsoleColors.BOLD}")) - print(f"╚{'═' * 46}╝{ConsoleColors.RESET}") - # Display account status if any exist + print(f"╚{'═' * 46}╝{ConsoleColors.RESET}") + accounts = self.list_accounts() if accounts: print(f"\n{ConsoleColors.BLUE}Connected Accounts: {ConsoleColors.GREEN}{len(accounts)}{ConsoleColors.RESET}") - for acc in accounts[:3]: # Show up to 3 accounts + for acc in accounts[:3]: print(f" {ConsoleColors.WHITE}â€ĸ{ConsoleColors.RESET} @{acc}") if len(accounts) > 3: print(f" {ConsoleColors.WHITE}â€ĸ{ConsoleColors.RESET} ...and {len(accounts) - 3} more") else: print(f"\n{ConsoleColors.YELLOW}No accounts connected yet{ConsoleColors.RESET}") + + if self.excluded_users: + print(f"{ConsoleColors.YELLOW}đŸšĢ Excluding {len(self.excluded_users)} users{ConsoleColors.RESET}") - # Main menu options with better visual hierarchy print(f"\n{ConsoleColors.CYAN}Available Actions:{ConsoleColors.RESET}") print(f"╭{'─' * 40}╮") print(menu_line("1", "Add Instagram Account")) print(menu_line("2", "Remove Account")) print(menu_line("3", "Start Unliking")) - print(menu_line("4", "View Stats")) - print(menu_line("5", "Settings")) + print(menu_line("4", "Manage Excluded Users")) + print(menu_line("5", "View Stats")) + print(menu_line("6", "Settings")) print(menu_line("0", "Exit")) print(f"╰{'─' * 40}╯") - # Input prompt with better styling try: print(f"\n{ConsoleColors.WHITE}╭─ Enter your choice{ConsoleColors.RESET}") choice = input(f"{ConsoleColors.WHITE}╰─▸{ConsoleColors.RESET} ").strip() @@ -553,8 +584,10 @@ def show_menu(self): elif choice == "3": self._start_unliking_menu() elif choice == "4": - self.show_statistics() + self.manage_excluded_users() elif choice == "5": + self.show_statistics() + elif choice == "6": self.show_settings() elif choice == "0": print(f"\n{ConsoleColors.GREEN}✨ Thanks for using Instagram Unliker!") @@ -572,15 +605,6 @@ def show_menu(self): print(f"\n{ConsoleColors.RED}✗ Error: {str(e)}{ConsoleColors.RESET}") time.sleep(2) - def _display_header(self): - """Display program header with improved UI""" - print(f"\n{ConsoleColors.CYAN}{ConsoleColors.BOLD}") - print("╔══════════════════════════════════════════╗") - print("║ Instagram Unliker ║") - print("║ Easily Unlike Your Old Posts ║") - print("╚══════════════════════════════════════════╝") - print(f"{ConsoleColors.RESET}") - def _start_unliking_menu(self): """Display account selection menu for unliking""" accounts = self.list_accounts() @@ -592,16 +616,15 @@ def _start_unliking_menu(self): print("-" * 30) for i, acc in enumerate(accounts, 1): - # Get account status account_file = self.accounts_dir / f"{acc}.json" status = "Ready" if account_file.exists(): with open(account_file) as f: data = json.load(f) if data.get('last_error'): - status = f"Error: {data['last_error']}" + status = f"Error" elif data.get('last_run'): - status = f"Last run: {datetime.fromisoformat(data['last_run']).strftime('%Y-%m-%d %H:%M')}" + status = f"Last: {datetime.fromisoformat(data['last_run']).strftime('%Y-%m-%d %H:%M')}" print(f"{ConsoleColors.BOLD}{i}{ConsoleColors.RESET}. [{acc}] - {status}") @@ -627,7 +650,7 @@ def show_statistics(self): accounts = self.list_accounts() if not accounts: print(f"{ConsoleColors.YELLOW}No accounts added yet{ConsoleColors.RESET}") - input(f"\n{ConsoleColors.BOLD}Press Enter to continue...{ConsoleColors.RESET}") # Added pause + input(f"\n{ConsoleColors.BOLD}Press Enter to continue...{ConsoleColors.RESET}") return print(f"\n{ConsoleColors.CYAN}📊 Statistics{ConsoleColors.RESET}") @@ -650,40 +673,33 @@ def show_statistics(self): print(f"{ConsoleColors.RED}Could not read data for {username}{ConsoleColors.RESET}") print(f"\n{ConsoleColors.GREEN}🎉 Total unliked: {total_unliked} posts{ConsoleColors.RESET}") - input(f"\n{ConsoleColors.BOLD}Press Enter to continue...{ConsoleColors.RESET}") # Added pause + if self.excluded_users: + print(f"{ConsoleColors.YELLOW}đŸšĢ Excluding: {len(self.excluded_users)} users{ConsoleColors.RESET}") + input(f"\n{ConsoleColors.BOLD}Press Enter to continue...{ConsoleColors.RESET}") def show_settings(self): """Display and modify settings with enhanced UI""" while True: - # Clear previous content - print("\033[H\033[J" if platform.system() != "Windows" else os.system('cls')) - - # Header print(f"\n{ConsoleColors.CYAN}{ConsoleColors.BOLD}╔══════════════════════════════════╗") print(f"║ Settings Menu ║") print(f"╚══════════════════════════════════╝{ConsoleColors.RESET}") - # Delay Settings print(f"\n{ConsoleColors.YELLOW}▸ Delay Settings{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}1.{ConsoleColors.RESET} Minimum Delay : {ConsoleColors.GREEN}{CONFIG['delay']['min']}{ConsoleColors.RESET} seconds") print(f" {ConsoleColors.BOLD}2.{ConsoleColors.RESET} Maximum Delay : {ConsoleColors.GREEN}{CONFIG['delay']['max']}{ConsoleColors.RESET} seconds") - # Break Settings print(f"\n{ConsoleColors.YELLOW}▸ Break Settings{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}3.{ConsoleColors.RESET} Break Probability : {ConsoleColors.GREEN}{CONFIG['break']['probability'] * 100}%{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}4.{ConsoleColors.RESET} Minimum Break : {ConsoleColors.GREEN}{CONFIG['break']['min'] / 60:.1f}{ConsoleColors.RESET} minutes") print(f" {ConsoleColors.BOLD}5.{ConsoleColors.RESET} Maximum Break : {ConsoleColors.GREEN}{CONFIG['break']['max'] / 60:.1f}{ConsoleColors.RESET} minutes") - # Retry Settings print(f"\n{ConsoleColors.YELLOW}▸ Retry Settings{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}6.{ConsoleColors.RESET} Maximum Retries : {ConsoleColors.GREEN}{CONFIG['max_retries']}{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}7.{ConsoleColors.RESET} Retry Delay : {ConsoleColors.GREEN}{CONFIG['retry_delay']}{ConsoleColors.RESET} seconds") - # Navigation print(f"\n{ConsoleColors.CYAN}▸ Navigation{ConsoleColors.RESET}") print(f" {ConsoleColors.BOLD}0.{ConsoleColors.RESET} Save and Return") - # Input try: print(f"\n{ConsoleColors.WHITE}╭─{ConsoleColors.RESET}") choice = input(f"{ConsoleColors.WHITE}╰─▸{ConsoleColors.RESET} ").strip() @@ -693,73 +709,50 @@ def show_settings(self): time.sleep(1) break - # Process the choice and get new value try: if choice in ["1", "2", "3", "4", "5", "6", "7"]: print(f"{ConsoleColors.WHITE}╭─{ConsoleColors.RESET}") if choice == "1": - prompt = "Enter new minimum delay (seconds)" - new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ Enter new minimum delay (seconds): {ConsoleColors.RESET}")) CONFIG['delay']['min'] = new_value - elif choice == "2": - prompt = "Enter new maximum delay (seconds)" - new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ Enter new maximum delay (seconds): {ConsoleColors.RESET}")) CONFIG['delay']['max'] = new_value - elif choice == "3": - prompt = "Enter new break probability (0-1)" - new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ Enter new break probability (0-1): {ConsoleColors.RESET}")) if 0 <= new_value <= 1: CONFIG['break']['probability'] = new_value else: raise ValueError("Probability must be between 0 and 1") - elif choice == "4": - prompt = "Enter new minimum break time (minutes)" - new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ Enter new minimum break time (minutes): {ConsoleColors.RESET}")) CONFIG['break']['min'] = new_value * 60 - elif choice == "5": - prompt = "Enter new maximum break time (minutes)" - new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = float(input(f"{ConsoleColors.WHITE}╰─▸ Enter new maximum break time (minutes): {ConsoleColors.RESET}")) CONFIG['break']['max'] = new_value * 60 - elif choice == "6": - prompt = "Enter new maximum retries" - new_value = int(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = int(input(f"{ConsoleColors.WHITE}╰─▸ Enter new maximum retries: {ConsoleColors.RESET}")) CONFIG['max_retries'] = new_value - elif choice == "7": - prompt = "Enter new retry delay (seconds)" - new_value = int(input(f"{ConsoleColors.WHITE}╰─▸ {prompt}: {ConsoleColors.RESET}")) + new_value = int(input(f"{ConsoleColors.WHITE}╰─▸ Enter new retry delay (seconds): {ConsoleColors.RESET}")) CONFIG['retry_delay'] = new_value - # Save after each change self.save_config() print(f"\n{ConsoleColors.GREEN}✓ Setting updated successfully!{ConsoleColors.RESET}") time.sleep(1) - else: - print(f"\n{ConsoleColors.RED}✗ Invalid choice. Please try again.{ConsoleColors.RESET}") + print(f"\n{ConsoleColors.RED}✗ Invalid choice{ConsoleColors.RESET}") time.sleep(1) - except ValueError as e: print(f"\n{ConsoleColors.RED}✗ Invalid input: {str(e)}{ConsoleColors.RESET}") time.sleep(2) - except KeyboardInterrupt: - print(f"\n{ConsoleColors.YELLOW}⚠ Settings menu closed.{ConsoleColors.RESET}") break - except Exception as e: - print(f"\n{ConsoleColors.RED}✗ Error: {str(e)}{ConsoleColors.RESET}") - time.sleep(2) def check_system_requirements(self) -> bool: """Check if system meets all requirements""" try: - # First ensure psutil is installed try: import psutil except ImportError: @@ -767,42 +760,11 @@ def check_system_requirements(self) -> bool: if not self.install_requirements(): return False - # Check operating system os_name = platform.system() logging.info(f"Operating System: {os_name}") - - # Check system architecture - arch = platform.architecture()[0] - logging.info(f"System Architecture: {arch}") - - # Check available memory - if os_name == "Windows": - memory = psutil.virtual_memory() - available_mb = memory.available / (1024 * 1024) - logging.info(f"Available Memory: {available_mb:.2f} MB") - - if available_mb < 500: # Less than 500MB available - logging.warning("Low memory available. Performance may be affected.") - print(f"{ConsoleColors.YELLOW}[!] Warning: Low memory available. Performance may be affected.{ConsoleColors.RESET}") - - # Check disk space - target_dir = Path.cwd() - try: - free_space = shutil.disk_usage(target_dir).free / (1024 * 1024) # MB - logging.info(f"Free Disk Space: {free_space:.2f} MB") - - if free_space < 100: # Less than 100MB free - logging.warning("Low disk space available.") - print(f"{ConsoleColors.YELLOW}[!] Warning: Low disk space. Please free up some space.{ConsoleColors.RESET}") - except PermissionError: - logging.warning("Could not check disk space due to permissions") - print(f"{ConsoleColors.YELLOW}[!] Warning: Could not check disk space due to permissions{ConsoleColors.RESET}") - return True - except Exception as e: logging.error(f"Error checking system requirements: {str(e)}") - print(f"{ConsoleColors.RED}[✗] Error checking system requirements: {str(e)}{ConsoleColors.RESET}") return False def check_dependencies(self) -> bool: @@ -810,76 +772,28 @@ def check_dependencies(self) -> bool: try: import importlib.util - # Try to find the ensta module ensta_spec = importlib.util.find_spec("ensta") if ensta_spec is None: - error_msg = "ensta library not found in Python path" - logging.error(error_msg) - print(f"{ConsoleColors.RED}[✗] {error_msg}") + logging.error("ensta library not found") + print(f"{ConsoleColors.RED}[✗] ensta library not found") print("→ Attempting to reinstall...{ConsoleColors.RESET}") self.install_requirements() return False - # Try to import and validate ensta try: import ensta logging.info("Successfully imported ensta") return True except Exception as e: - error_msg = f"Error importing ensta: {str(e)}" - logging.error(error_msg, exc_info=True) - print(f"{ConsoleColors.RED}[✗] {error_msg}") + logging.error(f"Error importing ensta: {str(e)}", exc_info=True) + print(f"{ConsoleColors.RED}[✗] Error importing ensta") print("→ Attempting to fix by reinstalling...{ConsoleColors.RESET}") self.install_requirements() return False - except Exception as e: - error_msg = f"Dependency check failed: {str(e)}" - logging.error(error_msg, exc_info=True) - print(f"{ConsoleColors.RED}[✗] {error_msg}") - print("→ Please try installing dependencies manually:{ConsoleColors.RESET}") - print("pip install psutil tqdm colorama requests ensta==5.2.9") + logging.error(f"Dependency check failed: {str(e)}", exc_info=True) return False -def ensure_python_installed(): - """Check if Python is installed and install if needed""" - try: - # Check Python version - version = sys.version_info - if version.major >= 3 and version.minor >= 7: - return True - - print("Python 3.7 or higher is required.") - - # For Windows - if platform.system() == "Windows": - print("Downloading Python installer...") - # Download Python installer - with tempfile.NamedTemporaryFile(delete=False, suffix='.exe') as f: - url = "https://www.python.org/ftp/python/3.9.7/python-3.9.7-amd64.exe" - urllib.request.urlretrieve(url, f.name) - installer = f.name - - print("Installing Python...") - # Install Python silently - subprocess.run([installer, "/quiet", "InstallAllUsers=1", "PrependPath=1"]) - os.unlink(installer) - print("Python installed successfully. Please restart the application.") - sys.exit(0) - - # For Linux/Mac - else: - print("Please install Python 3.7 or higher:") - if platform.system() == "Linux": - print("Run: sudo apt-get install python3") - else: # Mac - print("Visit: https://www.python.org/downloads/") - sys.exit(1) - - except Exception as e: - print(f"Error checking/installing Python: {str(e)}") - sys.exit(1) - def instagram_code_to_media_id(code): """Convert Instagram shortcode to media ID""" charmap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' @@ -888,7 +802,6 @@ def instagram_code_to_media_id(code): def get_visible_length(text): """Calculate the visible length of text by removing ANSI color codes""" - # Pattern to match ANSI escape sequences import re ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') return len(ansi_escape.sub('', text)) @@ -897,44 +810,31 @@ def menu_line(number, text, box_width=40): """Create a properly aligned menu line with consistent formatting""" prefix = f"│ {ConsoleColors.BOLD}{number}.{ConsoleColors.RESET} {ConsoleColors.WHITE}" content = f"{text}{ConsoleColors.RESET}" - - # Calculate visible content length to determine proper padding visible_length = get_visible_length(f"{prefix}{content}") - padding = box_width - visible_length + 1 # -2 for the box edges - + padding = box_width - visible_length + 1 return f"{prefix}{content}{' ' * padding}│{ConsoleColors.RESET}" def main(): """Main entry point with improved initialization and dependency checking""" try: - # Display welcome message print("\nWelcome to Instagram Mass Unliker!") print("Checking system requirements...") - # Create instance and check dependencies first unliker = InstagramUnliker() if not unliker.check_dependencies(): print("Error: Failed to install required dependencies.") - print("Please try installing them manually:") - print("pip install psutil tqdm colorama requests ensta") sys.exit(1) - # Continue with other checks if not unliker.check_system_requirements(): print("Error: System requirements not met.") - print("Please check the logs for more information.") sys.exit(1) - # Handle Ctrl+C gracefully signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) - # Perform initial setup if not unliker.check_python_version(): sys.exit(1) unliker.check_and_create_config() - - # Show interactive menu unliker.show_menu() except KeyboardInterrupt: diff --git a/run.sh b/run.sh index ec1dfb1..7ed642a 100644 --- a/run.sh +++ b/run.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -# Color codes RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' @@ -9,7 +8,6 @@ MAGENTA='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' -# Detect OS if [[ "$OSTYPE" == "darwin"* ]]; then OS="macOS" elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then @@ -20,11 +18,8 @@ fi # Function to check for root/sudo check_root() { - if [ "$OS" != "Windows" ] && [ "$EUID" -ne 0 ]; then - echo -e "${RED}[!] Error: This script requires root privileges${NC}" - echo -e "${YELLOW}[i] Please run with sudo${NC}" - exit 1 - fi + # Root check removed - not needed for user-level operations + return 0 } # Function to install package manager @@ -51,35 +46,43 @@ install_package() { echo -e "${BLUE}[*] Checking $package installation...${NC}" if ! command -v $package &> /dev/null; then - echo -e "${YELLOW}[+] Installing $package...${NC}" + echo -e "${YELLOW}[+] $package not found. Installing...${NC}" + if [ "$OS" = "macOS" ]; then - brew install $package || return 1 + brew install $package || { + echo -e "${YELLOW}[!] Could not install $package. Please install manually with: brew install $package${NC}" + return 1 + } elif [ "$OS" = "Linux" ]; then - if command -v apt-get &> /dev/null; then - apt-get update && apt-get install -y $package || return 1 - elif command -v dnf &> /dev/null; then - dnf install -y $package || return 1 - fi + echo -e "${YELLOW}[!] Please install $package manually${NC}" + return 1 fi fi + echo -e "${GREEN}[✓] $package check passed${NC}" return 0 } # Header -echo -e "${BLUE}[*]=======================================${NC}" -echo -e "${YELLOW}[+] Instagram Mass Unliker - Setup Utility${NC}" -echo -e "${BLUE}[*]=======================================${NC}" +clear +echo -e "${BLUE}" +echo "╔════════════════════════════════════════════════╗" +echo "║ Instagram Mass Unliker - Setup Utility ║" +echo "║ Fast, Secure, Undetectable ║" +echo "╚════════════════════════════════════════════════╝" +echo -e "${NC}" echo -# Check root privileges -check_root +# Check root privileges (skip on Windows) +if [ "$OS" != "Windows" ]; then + check_root +fi # Install package manager install_package_manager # Install required packages -for package in git python3 python3-pip ffmpeg; do +for package in git python3 python3-pip; do install_package $package || { echo -e "${RED}[!] Failed to install $package${NC}" exit 1 @@ -115,25 +118,39 @@ if [ $? -ne 0 ]; then exit 1 fi +echo -e "${GREEN}[✓] Virtual environment activated${NC}" + +# Upgrade pip +echo -e "${BLUE}[*] Upgrading pip...${NC}" +python -m pip install --upgrade pip --quiet + # Install Python dependencies echo -e "${BLUE}[*] Installing Python dependencies...${NC}" -python -m pip install --upgrade pip -pip install ensta==5.2.9 tqdm==4.67.1 colorama==0.4.6 requests==2.32.3 || { +pip install --no-cache-dir ensta==5.2.9 tqdm==4.67.1 colorama==0.4.6 requests==2.32.3 psutil || { echo -e "${RED}[!] Failed to install Python dependencies${NC}" exit 1 } -echo -e "${GREEN}[✓] Virtual environment setup complete${NC}" +echo -e "${GREEN}[✓] All dependencies installed successfully${NC}" +echo # Run the main script -echo -e "${BLUE}[*] Starting Instagram Mass Unliker...${NC}" +echo -e "${CYAN}[*] Starting Instagram Mass Unliker...${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo + python instagram_unliker.py -if [ $? -ne 0 ]; then - echo -e "${RED}[!] Program exited with errors${NC}" - exit 1 -fi + +exit_code=$? # Deactivate virtual environment deactivate -echo -e "${GREEN}[✓] Program completed successfully${NC}" \ No newline at end of file +if [ $exit_code -ne 0 ]; then + echo -e "${RED}[!] Program exited with errors${NC}" + exit 1 +fi + +echo +echo -e "${GREEN}[✓] Program completed successfully${NC}" +echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" \ No newline at end of file