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