diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b8278 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environments +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ + +# Instaloader Session Files +session-* +*.session + +# Downloaded Instagram Content (in specific folder) +downloads/ +instagram_content/ +*.json.xz +*.txt.xz + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Credentials (if any config files are added later) +config.ini +.env +credentials.json + +# Logs +*.log + +# OS +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index bd9bf0b..8b309cc 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,1074 @@ -### INSTA-OSINT 📋 -is a tool to find as much information as possible on Instagram accounts, such as username, full username, post target, account type, number of followers, number of followings and so on. +# 📸 Instagram OSINT Tool v2.0 - +> Advanced Open Source Intelligence gathering tool for Instagram with interactive menus, analytics engine, batch processing, and multi-format exports. -This tool is an update from OsintIG on my first account -***DO NOT COPY OR RECODE THIS TOOL WITHOUT PERMISSION!!!*** -
-***DILARANG RECODE TOOL INI TANPA IZIN DARI ADMINN!!!!*** +--- -
-:zap: Version 1.1.0 : -- INSTA-OSINT V1.1.0 -
+## 🌟 Overview -### Note 📍 -to use this tool you have to prepare a `dummy account`, don't use a real account if you don't want it to expire -do not use for illegal actions because the admin will not be responsible +Instagram OSINT is a comprehensive intelligence gathering tool that combines session persistence, advanced analytics, database storage, and beautiful **interactive terminal UI** to analyze Instagram profiles efficiently. Whether you're a researcher, security analyst, or enthusiast, this tool provides powerful insights into Instagram account patterns and behaviors. -### Instalation on Linux (.deb) -``` -sudo apt update && sudo apt upgrade -sudo apt install git -sudo apt install python3 -``` +**Current Version:** 2.0.0 +**Last Updated:** December 2025 +**Interface:** Fully Interactive Menu System + +--- + +## 📺 Terminal UI - Screenshots + +### 1. Login Screen +Beautiful ASCII art banner with secure password input (hidden characters) + +![Login Screen](screenshots/01_login_screen.png) + +--- + +### 2. Two-Factor Authentication +Seamless 2FA support for protected accounts + +![2FA Prompt](screenshots/02_2fa_prompt.png) + +--- + +### 3. Features Configuration Menu +Choose what you want to do - downloads, analytics, database storage + +![Features Menu](screenshots/03_features_config.png) + +--- + +### 4. Export Format Selection +Multiple export options - JSON, CSV, HTML or all combined + +![Export Format](screenshots/04_export_format.png) + +--- + +### 5. Data Limits Configuration +Control how much data to fetch for performance optimization + +![Data Limits](screenshots/05_data_limits.png) + +--- + +### 6. Configuration Summary +Review your selections before processing begins + +![Config Summary](screenshots/06_config_summary.png) + +--- + +### 7. Profile Processing +Real-time progress indicators and colorful status messages + +![Processing](screenshots/07_processing.png) + +--- + +### 8. HTML Report Output +Beautiful interactive reports with all profile data + +![HTML Report](screenshots/08_html_report.png) + +--- + +## ✨ Key Features + +### 🎯 Interactive Menu System +- **No Command-Line Arguments Needed** - Everything is menu-driven +- **Step-by-Step Guidance** - Clear prompts for every decision +- **Visual Menus** - Colorful, numbered options +- **Input Validation** - Prevents errors with helpful messages +- **Confirmation Prompts** - Review settings before execution + +### 🔐 Authentication & Security +- **Hidden Password Input** - Passwords never shown on screen +- **Two-Factor Authentication Support** - Handle 2FA seamlessly +- **Session Persistence** - Save sessions to avoid re-authentication +- **Smart Session Management** - Auto-load previous sessions + +### 📊 Analytics Engine +- **Engagement Rate Calculation** - Followers per post metric +- **Follower Ratio Analysis** - Followers vs Following comparison +- **Risk Score Detection** - Identify suspicious accounts (0-100 scale) +- **Profile Classification** - Detect Business, Influencer, Bot, Regular accounts +- **Growth Tracking** - Historical data and trend analysis + +### 📤 Export Capabilities +- **JSON Export** - Complete structured data with metadata +- **CSV Export** - Spreadsheet-ready format (profile, followers, following) +- **HTML Reports** - Interactive visual reports with styling +- **Multi-format Exports** - Export to multiple formats simultaneously + +### 💾 Database Backend (SQLite) +- Store profile snapshots with timestamps +- Track follower/following changes over time +- Cache analytics results for quick queries +- Generate growth statistics and trends +- Historical profile comparisons + +### 🔄 Batch Processing +- Process multiple profiles in single run +- Enter targets interactively or from file +- Compare profiles side-by-side +- Find mutual followers/following between accounts +- Rate-limited to respect Instagram API + +### 🎨 Beautiful Terminal UI +- Colorful ASCII banner and messages +- Color-coded status indicators (✓ ✗ !) +- Loading animations and progress indicators +- Organized, readable output format +- Professional terminal interface + +--- + +## 📋 Table of Contents -### Instalation on Termux +1. [Installation](#-installation) +2. [Quick Start](#-quick-start) +3. [Interactive Menu Guide](#-interactive-menu-guide) +4. [Features in Detail](#-features-in-detail) +5. [Export Formats](#-export-formats) +6. [Analytics Explained](#-analytics-explained) +7. [Database Features](#-database-features) +8. [Module Architecture](#-module-architecture) +9. [Legal & Disclaimer](#-legal--disclaimer) + +--- + +## 🚀 Installation + +### For Desktop/Laptop (Linux, macOS, Windows) + +#### Prerequisites +- Python 3.8+ +- pip package manager +- Instagram account (for authentication) + +#### Setup + +```bash +# Clone repository +git clone https://github.com/Adamo08/INSTA-OSINT.git +cd INSTA-OSINT + +# Create virtual environment (recommended) +python3 -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate + +# Install dependencies +pip install -r requirements.txt ``` + +--- + +### 📱 For Mobile (Android via Termux) + +Yes! This tool works perfectly on Android phones using **Termux**! + +#### Step 1: Install Termux +Download Termux from [F-Droid](https://f-droid.org/packages/com.termux/) (recommended) or Google Play Store + +#### Step 2: Setup Termux Environment + +```bash +# Update packages pkg update && pkg upgrade -pkg install git -pkg install python3 -``` -### Use Tool +# Install required packages +pkg install python git + +# Install pip +pip install --upgrade pip ``` -git clone https://github.com/HunxByts/INSTA-OSINT.git + +#### Step 3: Clone and Setup + +```bash +# Get storage permissions (optional, for saving files) +termux-setup-storage + +# Clone repository +git clone https://github.com/Adamo08/INSTA-OSINT.git cd INSTA-OSINT -pip3 install -r requirements.txt + +# Install dependencies +pip install -r requirements.txt +``` + +#### Step 4: Run the Tool + +```bash +python instaOSINT.py +``` + +#### 📱 Mobile Tips +- Use landscape mode for better visibility +- Swipe keyboard to navigate terminal +- Files are saved in `/storage/emulated/0/` if using termux-setup-storage +- Session files persist between runs (no need to login every time) +- Works on mobile data or WiFi +- Can run in background with Termux:Boot addon + +#### Termux Keyboard Shortcuts +- `Volume Down + C` = Ctrl+C (cancel/exit) +- `Volume Down + L` = Clear screen +- `Volume Down + Z` = Suspend process + +--- + +### Dependencies +- `instaloader` - Instagram scraping library +- Built-in Python libraries (no external dependencies for UI) + +--- + +## ⚡ Quick Start + +### Simply Run the Tool +```bash python3 instaOSINT.py ``` +**That's it!** The tool will guide you through everything with interactive menus. + +### What Happens Next? + +1. **🔐 Login Screen** - Enter your Instagram credentials (password is hidden) +2. **📋 Main Menu** - Choose what you want to do: + - Single Profile Analysis + - Batch Profile Analysis + - Compare Profiles + - View Database History +3. **⚙️ Configuration Menus** - Select features, export formats, and limits +4. **📊 Summary** - Review your choices +5. **🚀 Processing** - Watch real-time progress +6. **✅ Results** - Get exported files and analytics + +--- + +## 🎮 Interactive Menu Guide + +### Main Menu Options + +When you run the tool, you'll see: + -p mypassword \ + -t targetusername \ + --analyze \ + +**1. Single Profile Analysis** - Analyze one Instagram account + - Download posts & highlights + - Calculate analytics + - Export in multiple formats + +**2. Batch Profile Analysis** - Process multiple accounts + - Enter usernames one by one + - Or provide them via file + - Process sequentially with rate limiting + +**3. Compare Profiles** - Find connections between accounts + - Mutual followers analysis + - Profile similarity scoring + - Network mapping + +**4. View Database History** - Check stored data + - Query past analyses + - View growth statistics + - Export historical reports + +**5. Exit** - Close the tool + +### Features Configuration Menu + +After selecting a profile, configure what to do: + +**1. Download Posts & Highlights** ⚠️ May take time + - Downloads all media from profile + - Saves to local directory + +**2. Run Analytics** 📊 Calculate metrics + - Engagement rate + - Follower ratio + - Risk scoring + - Profile classification + +**3. Save to Database** 💾 Track history + - Store profile snapshot + - Enable growth tracking + - Historical comparisons + +**4. All Features** - Enable everything above + +**5. None (Quick Mode)** - Basic info only + - Fast execution + - Just profile metadata + - No downloads or analytics + +### Export Format Menu + +Choose how to save your data: + +**1. JSON only** - Structured data format +**2. CSV only** - Spreadsheet format (3 files) +**3. HTML only** - Visual report in browser +**4. JSON + CSV** - Both formats +**5. JSON + HTML** - Structured + Visual +**6. All formats** - JSON + CSV + HTML (recommended) + +### Data Limits Menu + +Control performance by limiting data fetched: + +**1. No limits** - Fetch everything (slow for large accounts) +**2. Limit to 100 each** - Quick analysis +**3. Limit to 500 each** - Balanced +**4. Limit to 1000 each** - Comprehensive +**5. Custom limits** - Enter your own numbers + +--- + +## 🎯 Step-by-Step Walkthrough + +### Example: Analyzing a Single Profile + +1. **Start the tool:** + ```bash + python3 instaOSINT.py + ``` + +2. **Login** (first time or if session expired): + - Enter your Instagram username + - Enter password (hidden input - you won't see characters) + - If 2FA is enabled, enter the 6-digit code + +3. **Main Menu appears:** + - Select **[1] Single Profile Analysis** + +4. **Enter target:** + - Type the username you want to analyze + - Example: `targetusername` + +5. **Features Configuration:** + - Select **[2] Run Analytics** for metrics + - Or **[4] All Features** for complete analysis + +6. **Export Format:** + - Select **[6] All formats** for complete export + +7. **Data Limits:** + - Select **[3] Limit to 500 each** for balanced performance + +8. **Configuration Summary:** + - Review your selections + - Confirm to proceed (y/n) + +9. **Processing:** + - Watch real-time progress indicators + - Colored status messages show each step + - ✓ marks successful completion + +10. **Results:** + - Files exported to current directory + - Open HTML report in browser + - Check JSON for structured data + - Import CSV into spreadsheet + +--- + +## 🔄 Batch Processing Example + +Process multiple accounts efficiently: + +1. **Create targets file:** + ```bash + cat > targets.txt << EOF + username1 + username2 + username3 + EOF + ``` + +2. **Run tool:** + ```bash + python3 instaOSINT.py + ``` + +3. **Select [2] Batch Profile Analysis** + +4. **Choose input method:** + - Select "Load from file" + - Enter filename: `targets.txt` + +5. **Configure options:** + - Features: **[2] Run Analytics** + - Export: **[1] JSON only** + - Limits: **[2] Limit to 100 each** (for speed) + +6. **Watch processing:** + - Progress: [1/3], [2/3], [3/3] + - Each profile processed sequentially + - 2-second delay between profiles (rate limiting) + +7. **Results:** + - 3 JSON files created (one per profile) + - Summary statistics displayed + - All data in database if enabled + +--- + +## 💡 Usage Tips + +### Best Practices + +**For Quick Analysis:** +- Use **Quick Mode** (Feature option 5) +- Set **Limit to 100** on followers/following +- Export **JSON only** + +**For Comprehensive Research:** +- Enable **All Features** +- Use **No Limits** (option 1) +- Export **All formats** +- Enable **Database storage** + +**For Large Accounts (100K+ followers):** +- Use **Custom Limits** with reasonable numbers +- Skip downloads (**Quick Mode**) +- Process during off-peak hours + +**For Regular Monitoring:** +- Enable **Database storage** +- Run weekly/monthly +- Use **Quick Mode** for speed +- Track growth via database queries + +### Session Management + +**First Run:** +- Requires username + password +- 2FA if enabled +- Session saved automatically + +**Subsequent Runs:** +- Session auto-loaded +- No password needed +- No 2FA prompt +- Instant authentication ✓ + +**Clear Sessions:** +```bash +rm session-* +``` + +--- + +## 🎯 Features in Detail + +### Session Persistence +Automatically saves authentication sessions to `session-{username}` files. + +**Benefits:** +- No need to re-enter credentials +- Skip 2FA on subsequent runs +- Faster authentication +- Secure token storage + +**How it works:** +``` +First Run: Username + Password → Authenticate → Save Session +Second Run: Load Session → Authenticate Instantly ✓ +``` + +### Two-Factor Authentication +Seamlessly handles 2FA protected accounts. + +**Process:** +1. Script detects 2FA requirement +2. Prompts for 6-digit code +3. Accepts code from authenticator app or SMS +4. Saves session with 2FA tokens +5. Future runs skip 2FA entirely + +### Security Features +- **Hidden Password Input** - Uses getpass module, no echo +- **Session Encryption** - Tokens stored securely +- **No Password Storage** - Never saves plain-text passwords +- **Local Session Files** - No cloud storage + +### Error Handling +Graceful error handling for: +- Non-existent profiles +- Private accounts (limited data) +- Rate limiting (automatic delays) +- Network errors +- Invalid credentials + +--- + +## 💾 Database Features + +### Automatic Database Creation +First run creates `osint_data.db` with optimized schema. + +### Tables + +#### profiles +Snapshots of profile metadata: +- Username, user ID, full name +- Bio, external URL +- Privacy settings, account type +- Follower/following/post counts +- Timestamps + +#### followers +Historical follower records: +- Profile being followed +- Follower details (username, ID, name) +- Timestamp of recording + +#### profile_history +Growth tracking over time: +- Follower count snapshot +- Following count snapshot +- Post count snapshot +- Recorded timestamp + +#### analytics_cache +Cached analytics results: +- Engagement metrics +- Risk scores +- Profile classification +- Calculation timestamp + +### Growth Tracking Example + +Run the same profile analysis periodically: + +```bash +# Day 1 +python3 instaOSINT.py -u myuser -p mypass -t target --db + +# Day 7 (one week later) +python3 instaOSINT.py -u myuser -p mypass -t target --db +``` + +Database automatically tracks changes. Query growth: +```python +from database import OsintDatabase +db = OsintDatabase() +growth = db.get_growth_stats('target') +print(f"Follower change: +{growth['followers_change']}") +``` + +📸 **SCREENSHOT**: Show osint_data.db file in file explorer and terminal output showing growth statistics query result + +--- + +## 📤 Export Formats + +### JSON Export +**File:** `{username}_data_YYYYMMDD_HHMMSS.json` + +**Sample Structure:** +```json +{ + "export_date": "2025-12-30T12:34:56.789000", + "profile": { + "username": "targetuser", + "full_name": "Target User Name", + "user_id": 123456789, + "biography": "User bio", + "followers_count": 5000, + "following_count": 1200, + "posts_count": 250 + }, + "followers": [ + {"username": "follower1", "user_id": 111111111, "full_name": "Follower Name"} + ], + "following": [...], + "analytics": { + "engagement_rate": 2.5, + "follower_ratio": 4.17, + "risk_score": 12, + "profile_type": "Influencer" + } +} +``` + +📸 **SCREENSHOT**: Open JSON file in text editor and show formatted output (capture first 50 lines) + +### CSV Export +**Files Generated:** +- `{username}_profile_TIMESTAMP.csv` - Profile metadata +- `{username}_followers_TIMESTAMP.csv` - Followers list +- `{username}_following_TIMESTAMP.csv` - Following list + +📸 **SCREENSHOT**: Show file explorer with all three CSV files generated + +### HTML Report +**File:** `{username}_report_YYYYMMDD_HHMMSS.html` + +**Features:** +- Professional styling with Instagram branding +- Profile information card +- Analytics dashboard +- Responsive design +- Searchable tables +- Print-friendly layout + +📸 **SCREENSHOT**: Open HTML report in web browser (Firefox/Chrome) showing full page with profile info, analytics section, and tables + +--- + +## 📊 Analytics Explained + +### Engagement Rate +``` +Formula: Followers / Posts +``` + +**Example:** 1000 followers / 100 posts = 10 followers per post + +### Follower Ratio +``` +Formula: Followers / Following +``` + +**Example:** 1000 followers / 200 following = 5.0 ratio + +### Risk Score (0-100) +Calculated risk of account being suspicious, bot, or fake. + +**Factors:** +- Very high following count (following 2x+ followers) +- No posts but many followers (bot indicator) +- Extremely low engagement +- Private with unusual follower growth + +### Profile Type Classification +- **Business Account** - Has business category set +- **Influencer** - 10,000+ followers +- **Private Account** - Privacy enabled +- **Regular Account** - Standard user +- **Inactive/Potential Bot** - 0 posts despite followers + +### Example Analytics Output + +``` +📊 ANALYTICS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✓ Profile Type : Influencer +✓ Engagement Rate : 2.5 +✓ Follower Ratio : 4.17 +✓ Risk Score : 15/100 (Low Risk) +✓ Posts per Follower : 0.05 +✓ Following % : 24.0% +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +📸 **SCREENSHOT**: Run analysis with `--analyze` and capture the colorful analytics output section + +--- + +## 🏗️ Module Architecture + +### processor.py +Core OSINT operations including authentication, profile fetching, batch processing, and export coordination. + +### analyzer.py +Analytics calculations including engagement metrics, risk scoring, profile classification, and comparative analysis. + +### exporter.py +Multi-format export supporting JSON, CSV, and HTML report generation with professional formatting. + +### database.py +SQLite backend for persistent storage, historical tracking, and growth statistics. + +### cli.py +Command-line interface with argument parsing, validation, and comprehensive help documentation. + +### utils.py +Helper functions for colored output, formatting, animations, and status messages. + +--- + +## 💡 Examples + +### Example 1: Analyze Single Influencer + +```bash +python3 instaOSINT.py \ + -u myusername \ + -p mypassword \ + -t cristiano \ + --analyze \ + -o json,html +``` + +**Note:** When running interactively, passwords are **hidden** for security (masked input). + +📸 **SCREENSHOT**: Run this command and capture console showing analysis results with colorful output + +### Example 2: Batch Processing from File + +```bash +cat > targets.txt << EOF +username1 +username2 +username3 +EOF + +python3 instaOSINT.py \ + -u myusername \ + -p mypassword \ + -f targets.txt \ + --analyze \ + --db +``` + +📸 **SCREENSHOT**: Capture the batch processing progress showing all three profiles being processed + +### Example 3: Track Growth Over Time + +```bash +# Day 1 +python3 instaOSINT.py -u myuser -p mypass -t target --db + +# Day 8 +python3 instaOSINT.py -u myuser -p mypass -t target --db + +# Query results +python3 << 'EOF' +from database import OsintDatabase +db = OsintDatabase() +growth = db.get_growth_stats('target') +print(growth) +EOF +``` + +📸 **SCREENSHOT**: Show the Python query output displaying growth statistics + +### Example 4: Multi-Format Export + +```bash +python3 instaOSINT.py \ + -u myusername \ + -p mypassword \ + -t targetusername \ + -o json,csv,html \ + --analyze +``` + +📸 **SCREENSHOT**: Show file explorer with all exported files (JSON, 3 CSVs, HTML) + +--- + +## 🔧 CLI Reference + +### Full Help +```bash +python3 instaOSINT.py --help +``` + +### Essential Arguments + +```bash +# Authentication +-u, --username USERNAME Your Instagram username +-p, --password PASSWORD Your Instagram password +--no-session Don't use saved sessions + +# Target Selection +-t, --target TARGET Single target username +-f, --file FILE File with target list + +# Output Options +-o, --output OUTPUT Export formats: json,csv,html +-d, --dir DIR Output directory + +# Features +--db Store in database +--analyze Calculate analytics +--compare Compare profiles +--no-download Skip downloads + +# Performance +--limit-followers N Max followers to fetch +--limit-following N Max following to fetch +--quiet Minimize output +``` + +📸 **SCREENSHOT**: Capture the help output from `python3 instaOSINT.py --help` + +--- + +## 🎨 Terminal Output Examples + +### Login & Authentication +``` +📱 Instagram OSINT Tool v2.0 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[+] LOGIN YOUR INSTAGRAM ACCOUNT + +Enter Instagram username: myusername +Enter Instagram password: •••••••• + +[✓] Session loaded successfully! +[✓] Login successful! +``` + +📸 **SCREENSHOT**: Capture the colorful login screen with ASCII art banner + +### Profile Analysis Output +``` +📊 PROCESSING targetusername +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[+] Full Name: Target User Name +[+] ID: 123456789 +[+] Followers: 5,000 +[+] Following: 1,200 +[+] Posts: 250 + +[✓] Fetched 5000 followers +[✓] Fetched 1200 following + +📊 ANALYTICS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +[+] Profile Type: Influencer +[+] Engagement Rate: 2.5 +[+] Follower Ratio: 4.17 +[+] Risk Score: 15/100 (Low Risk) + +[✓] Exported to: targetuser_data_20251230_123456.json +``` + +📸 **SCREENSHOT**: Run a profile analysis and capture full colored output + +### Batch Processing Progress +``` +🔄 BATCH PROCESSING 3 PROFILES +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1/3] Processing username1... +✓ Completed in 15s + +[2/3] Processing username2... +✓ Completed in 12s + +[3/3] Processing username3... +✓ Completed in 18s + +✓ Batch complete! Total: 45s +✓ All data saved to database +``` + +📸 **SCREENSHOT**: Run batch processing and capture the progress output + +--- + +## 📋 Requirements + +### System Requirements +- Python 3.8 or higher +- 50MB disk space minimum +- Internet connection +- Instagram account + +### Python Dependencies +``` +instaloader>=4.12.0 +requests>=2.28.0 +``` + +--- + +## ⚙️ Configuration + +### Session Files +Sessions stored as: `session-{username}` + +**Clear sessions:** +```bash +rm session-* +``` + +### Database File +Auto-created as: `osint_data.db` + +**Reset database:** +```bash +rm osint_data.db +``` + +### Output Directory +```bash +python3 instaOSINT.py -u user -p pass -t target -d /custom/path +``` + +--- + +## 🔒 Security & Best Practices + +### Password Security +✅ **Interactive Mode:** +- Passwords are **masked** during input (not visible on screen) +- Uses Python's `getpass` module for secure input +- Characters replaced with dots/asterisks +- No password echoing to terminal + +✅ **CLI Mode:** +- Pass credentials via command-line arguments +- Credentials not stored in shell history (use space prefix on some shells) +- Session tokens stored locally only + +### Credentials Handling +- Never commit passwords to version control +- Use environment variables for automation +- Session tokens are local only +- Use your own account + +### Rate Limiting +- Tool adds automatic delays between requests +- Don't run multiple instances simultaneously +- Respect Instagram's Terms of Service +- Monitor for IP blocks + +### Data Privacy +- Content stored locally +- Database contains snapshots +- Export files are not encrypted +- Clean up old files regularly + +--- + +## ⚠️ Legal & Disclaimer + +**This tool is for educational and authorized research purposes only.** + +### Important +1. **Terms of Service**: Using this may violate Instagram's ToS +2. **Legal Compliance**: Follow all applicable laws +3. **No Liability**: Authors not responsible for consequences +4. **Authorization Only**: Analyze accounts you have permission for + +### Responsible Use +- ✅ Analyze your own accounts +- ✅ Authorized competitor research +- ✅ Permission-based security audits +- ❌ Don't scrape private accounts +- ❌ No harassment or stalking +- ❌ Don't violate ToS + +--- + +## 🆘 Troubleshooting + +### Login Issues +- Check username/password +- Try `--no-session` flag +- Verify 2FA code if required + +### Profile Not Found +- Verify username spelling +- Check if account deleted +- Private accounts have limited data + +### Database Errors +- Close other instances +- Delete `osint_data.db` to reset + +### Rate Limiting +- Wait 30-60 minutes +- Reduce batch sizes +- Use `--limit-followers` option + +--- + +## 🤝 Contributing + +Contributions welcome! Please: +1. Fork the repository +2. Create feature branch +3. Make improvements +4. Submit pull request + +### Ideas for Contribution +- Additional export formats (XML, PDF) +- Advanced analytics and predictions +- Web UI dashboard +- Performance improvements +- Bug fixes + +--- + +## 📝 Changelog + +### v2.0.0 (Current - December 2025) +**Major Rewrite** +- ✨ Complete modular refactoring (6 specialized modules) +- ✨ Advanced CLI argument parsing +- ✨ Batch processing with file input +- ✨ SQLite database backend +- ✨ Multi-format exports (JSON, CSV, HTML) +- ✨ Professional analytics engine +- ✨ Enhanced terminal UI +- ✨ Profile comparison and analysis +- 🐛 Improved error handling +- 🐛 Better rate limiting + +### v1.0.0 (Original) +- Basic profile analysis +- Follower/following retrieval +- JSON export +- Post downloading +- 2FA support + +--- + +## 📮 Support + +### Having Issues? +1. Check [Troubleshooting](#-troubleshooting) +2. Review GitHub issues +3. Create new issue with details + +### Questions? +See examples and documentation above. -### Result insta-osint tool +--- - +## 📄 License - +Original author: **HUNX04** +v2.0 modernization and features - +Respect original creator's terms. +--- +## 📞 Contact +**Original Author:** HUNX04 +**Repository:** [INSTA-OSINT](https://github.com/HunxByts/INSTA-OSINT) +--- +## ⭐ Support -### Feature INSTA-OSINT +If useful, please: +- ⭐ Star the repository +- 🐛 Report bugs +- 💡 Suggest features +- 📤 Share with others +- 🤝 Contribute code -| Featur | Status | -|:-----------:|:-------:| -| Username | ✔️ | -| UserFull | ✔️ | -| TypeAccount | ✔️ | -| TargPost | ✔️ | -| Follwers | ✔️ | -| Followings | ✔️ | -| id | ✔️ | -| Bio | ✔️ | -| Url | ✔️ | -| AccountPrv | ✔️ | -| AccountBis | ✔️ | -| ProfPic | ✔️ | -| IGTV | ✔️ | -| Higlights | ✔️ | -| ShowFollwrs | ✔️ | -| ShowFollwng | ✔️ | +--- +**Made with ❤️ for the OSINT Community** -
-:zap: Author : -- HunxByts -
+**Last Updated:** December 30, 2025 +**Version:** 2.0.0 +**Status:** Production Ready diff --git a/analyzer.py b/analyzer.py new file mode 100644 index 0000000..d27dc8b --- /dev/null +++ b/analyzer.py @@ -0,0 +1,113 @@ +"""Analytics engine for Instagram profiles""" + +class ProfileAnalytics: + """Analyze Instagram profile metrics""" + + def __init__(self, profile): + self.profile = profile + + def get_engagement_rate(self): + """Calculate estimated engagement rate (followers / posts)""" + if self.profile.mediacount == 0: + return 0 + return round(self.profile.followers / self.profile.mediacount, 2) + + def get_follower_following_ratio(self): + """Calculate follower to following ratio""" + if self.profile.followees == 0: + return self.profile.followers + return round(self.profile.followers / self.profile.followees, 2) + + def get_profile_risk_score(self): + """Calculate risk score (higher = more suspicious) + Based on: low engagement, many following, few followers, no posts""" + risk = 0 + + # High following, low followers = risky + if self.profile.followees > self.profile.followers * 2: + risk += 30 + + # No posts but many followers = suspicious + if self.profile.mediacount == 0 and self.profile.followers > 100: + risk += 25 + + # Low engagement + if self.profile.mediacount > 0: + engagement = self.get_engagement_rate() + if engagement < 1: + risk += 20 + + # Likely bot indicators + if self.profile.is_private == False and self.profile.is_business_account == False and self.profile.followers > 1000: + if self.profile.mediacount == 0: + risk += 25 + + return min(risk, 100) + + def get_profile_type(self): + """Classify profile type""" + if self.profile.is_business_account: + return "Business Account" + if self.profile.is_private: + return "Private Account" + if self.profile.followers > 10000: + return "Influencer" + if self.profile.mediacount == 0: + return "Inactive/Potential Bot" + return "Regular Account" + + def get_analytics_summary(self): + """Get complete analytics summary""" + return { + "engagement_rate": self.get_engagement_rate(), + "follower_ratio": self.get_follower_following_ratio(), + "risk_score": self.get_profile_risk_score(), + "profile_type": self.get_profile_type(), + "posts_per_follower": round(self.profile.mediacount / max(self.profile.followers, 1), 4), + "following_percentage": round((self.profile.followees / max(self.profile.followers, 1)) * 100, 2) if self.profile.followers > 0 else 0 + } + + +class ComparativeAnalytics: + """Compare multiple profiles""" + + @staticmethod + def compare_profiles(profile_list): + """Compare multiple profiles and return insights""" + if not profile_list: + return {} + + analytics_list = [ProfileAnalytics(p) for p in profile_list] + + total_followers = sum(p.profile.followers for p in profile_list) + total_following = sum(p.profile.followees for p in profile_list) + total_posts = sum(p.profile.mediacount for p in profile_list) + + return { + "profile_count": len(profile_list), + "total_followers": total_followers, + "total_following": total_following, + "total_posts": total_posts, + "avg_followers": round(total_followers / len(profile_list), 0), + "avg_following": round(total_following / len(profile_list), 0), + "avg_posts": round(total_posts / len(profile_list), 0), + "profiles": [a.get_analytics_summary() for a in analytics_list] + } + + @staticmethod + def find_mutual_followers(followers_list1, followers_list2): + """Find mutual followers between two follower lists""" + usernames1 = {f["username"] for f in followers_list1} + usernames2 = {f["username"] for f in followers_list2} + + mutual = usernames1.intersection(usernames2) + + # Reconstruct mutual follower objects + mutual_followers = [f for f in followers_list1 if f["username"] in mutual] + + return { + "mutual_count": len(mutual), + "mutual_followers": mutual_followers, + "percentage_of_first": round((len(mutual) / len(followers_list1)) * 100, 2) if followers_list1 else 0, + "percentage_of_second": round((len(mutual) / len(followers_list2)) * 100, 2) if followers_list2 else 0 + } diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..e3e6c71 --- /dev/null +++ b/cli.py @@ -0,0 +1,82 @@ +"""CLI argument parser and configuration""" + +import argparse + +def get_args(): + """Parse command line arguments""" + parser = argparse.ArgumentParser( + description='Instagram OSINT Tool - Gather intelligence on Instagram profiles', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Interactive mode + python instaOSINT.py + + # Specify account and target + python instaOSINT.py -u myusername -p mypassword -t targetusername + + # Batch mode with multiple targets + python instaOSINT.py -u myusername -p mypassword -f targets.txt + + # Export to specific format + python instaOSINT.py -u myusername -p mypassword -t targetusername -o html,json,csv + + # Store data in database and analyze + python instaOSINT.py -u myusername -p mypassword -t targetusername --db --analyze + """ + ) + + # Authentication arguments + auth_group = parser.add_argument_group('Authentication') + auth_group.add_argument('-u', '--username', help='Instagram username') + auth_group.add_argument('-p', '--password', help='Instagram password') + auth_group.add_argument('--no-session', action='store_true', help='Do not use saved session') + + # Target arguments + target_group = parser.add_argument_group('Target') + target_group.add_argument('-t', '--target', help='Single target username') + target_group.add_argument('-f', '--file', help='File with list of target usernames (one per line)') + + # Output arguments + output_group = parser.add_argument_group('Output') + output_group.add_argument('-o', '--output', default='json', + help='Export format(s): json, csv, html (comma-separated, default: json)') + output_group.add_argument('-d', '--dir', default='.', help='Output directory (default: current)') + + # Feature arguments + feature_group = parser.add_argument_group('Features') + feature_group.add_argument('--db', action='store_true', help='Store data in database') + feature_group.add_argument('--analyze', action='store_true', help='Run analytics and calculate metrics') + feature_group.add_argument('--compare', action='store_true', help='Compare multiple profiles') + feature_group.add_argument('--no-download', action='store_true', help='Skip downloading posts/highlights') + + # Performance arguments + perf_group = parser.add_argument_group('Performance') + perf_group.add_argument('--limit-followers', type=int, help='Limit followers to fetch (default: all)') + perf_group.add_argument('--limit-following', type=int, help='Limit following to fetch (default: all)') + perf_group.add_argument('--quiet', action='store_true', help='Minimize console output') + + return parser.parse_args() + +def validate_args(args): + """Validate parsed arguments""" + errors = [] + + if not args.target and not args.file: + errors.append("Either --target or --file must be specified") + + if args.target and args.file: + errors.append("Cannot specify both --target and --file") + + valid_formats = ['json', 'csv', 'html'] + formats = [f.strip().lower() for f in args.output.split(',')] + invalid = [f for f in formats if f not in valid_formats] + if invalid: + errors.append(f"Invalid output format(s): {', '.join(invalid)}") + + if errors: + for error in errors: + print(f"Error: {error}") + return False + + return True diff --git a/database.py b/database.py new file mode 100644 index 0000000..8347c75 --- /dev/null +++ b/database.py @@ -0,0 +1,239 @@ +"""Database management for Instagram OSINT""" + +import sqlite3 +from datetime import datetime +import json + +class OsintDatabase: + """SQLite database for storing OSINT data""" + + def __init__(self, db_name="osint_data.db"): + self.db_name = db_name + self.init_database() + + def init_database(self): + """Initialize database with required tables""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + # Profiles table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE, + user_id INTEGER, + full_name TEXT, + biography TEXT, + external_url TEXT, + is_private BOOLEAN, + is_business BOOLEAN, + business_category TEXT, + followers INTEGER, + following INTEGER, + posts INTEGER, + profile_pic_url TEXT, + first_seen TIMESTAMP, + last_updated TIMESTAMP + ) + ''') + + # Followers table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS followers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + profile_username TEXT, + follower_username TEXT, + follower_id INTEGER, + follower_full_name TEXT, + recorded_at TIMESTAMP, + FOREIGN KEY(profile_username) REFERENCES profiles(username) + ) + ''') + + # Historical data table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS profile_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT, + followers_count INTEGER, + following_count INTEGER, + posts_count INTEGER, + recorded_at TIMESTAMP, + FOREIGN KEY(username) REFERENCES profiles(username) + ) + ''') + + # Analytics cache table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS analytics_cache ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT, + engagement_rate REAL, + follower_ratio REAL, + risk_score INTEGER, + profile_type TEXT, + recorded_at TIMESTAMP, + FOREIGN KEY(username) REFERENCES profiles(username) + ) + ''') + + conn.commit() + conn.close() + + def save_profile(self, profile): + """Save profile data to database""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + try: + cursor.execute(''' + INSERT OR REPLACE INTO profiles + (username, user_id, full_name, biography, external_url, is_private, is_business, + business_category, followers, following, posts, profile_pic_url, last_updated) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + profile.username, + profile.userid, + profile.full_name, + profile.biography, + profile.external_url, + profile.is_private, + profile.is_business_account, + profile.business_category_name, + profile.followers, + profile.followees, + profile.mediacount, + profile.profile_pic_url, + datetime.now() + )) + + # Also record in history + cursor.execute(''' + INSERT INTO profile_history + (username, followers_count, following_count, posts_count, recorded_at) + VALUES (?, ?, ?, ?, ?) + ''', ( + profile.username, + profile.followers, + profile.followees, + profile.mediacount, + datetime.now() + )) + + conn.commit() + return True + except Exception as e: + print(f"Error saving profile: {e}") + return False + finally: + conn.close() + + def save_followers(self, profile_username, followers_list): + """Save followers to database""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + try: + for follower in followers_list: + cursor.execute(''' + INSERT INTO followers + (profile_username, follower_username, follower_id, follower_full_name, recorded_at) + VALUES (?, ?, ?, ?, ?) + ''', ( + profile_username, + follower.get("username"), + follower.get("user_id"), + follower.get("full_name"), + datetime.now() + )) + + conn.commit() + return True + except Exception as e: + print(f"Error saving followers: {e}") + return False + finally: + conn.close() + + def save_analytics(self, username, analytics_data): + """Save analytics results""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + try: + cursor.execute(''' + INSERT INTO analytics_cache + (username, engagement_rate, follower_ratio, risk_score, profile_type, recorded_at) + VALUES (?, ?, ?, ?, ?, ?) + ''', ( + username, + analytics_data.get("engagement_rate"), + analytics_data.get("follower_ratio"), + analytics_data.get("risk_score"), + analytics_data.get("profile_type"), + datetime.now() + )) + + conn.commit() + return True + except Exception as e: + print(f"Error saving analytics: {e}") + return False + finally: + conn.close() + + def get_profile_history(self, username): + """Get historical data for a profile""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + try: + cursor.execute(''' + SELECT followers_count, following_count, posts_count, recorded_at + FROM profile_history + WHERE username = ? + ORDER BY recorded_at + ''', (username,)) + + results = cursor.fetchall() + return [ + { + "followers": r[0], + "following": r[1], + "posts": r[2], + "recorded_at": r[3] + } + for r in results + ] + finally: + conn.close() + + def get_growth_stats(self, username): + """Calculate growth stats from history""" + history = self.get_profile_history(username) + + if len(history) < 2: + return None + + first = history[0] + last = history[-1] + + return { + "followers_change": last["followers"] - first["followers"], + "following_change": last["following"] - first["following"], + "posts_change": last["posts"] - first["posts"], + "data_points": len(history), + "first_recorded": first["recorded_at"], + "last_recorded": last["recorded_at"] + } + + def get_all_profiles(self): + """Get all stored profiles""" + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + try: + cursor.execute('SELECT username FROM profiles') + return [row[0] for row in cursor.fetchall()] + finally: + conn.close() diff --git a/exporter.py b/exporter.py new file mode 100644 index 0000000..e728781 --- /dev/null +++ b/exporter.py @@ -0,0 +1,244 @@ +"""Export functionality for multiple formats""" + +import json +import csv +from datetime import datetime +from utils import Gr, Re, Wh, Ye + +class Exporter: + """Handle exports in multiple formats""" + + @staticmethod + def export_json(profile, followers_list, followees_list, username, analytics=None): + """Export to JSON format""" + data = { + "export_date": datetime.now().isoformat(), + "profile": { + "username": profile.username, + "full_name": profile.full_name, + "user_id": profile.userid, + "biography": profile.biography, + "external_url": profile.external_url, + "is_private": profile.is_private, + "is_business_account": profile.is_business_account, + "business_category": profile.business_category_name, + "followers_count": profile.followers, + "following_count": profile.followees, + "posts_count": profile.mediacount, + "profile_pic_url": profile.profile_pic_url + }, + "followers": followers_list, + "following": followees_list + } + + if analytics: + data["analytics"] = analytics + + filename = f"{username}_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + with open(filename, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + return filename + + @staticmethod + def export_csv(profile, followers_list, followees_list, username): + """Export to CSV format""" + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + + # Profile CSV + profile_file = f"{username}_profile_{timestamp}.csv" + with open(profile_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Metric', 'Value']) + writer.writerow(['Username', profile.username]) + writer.writerow(['Full Name', profile.full_name]) + writer.writerow(['User ID', profile.userid]) + writer.writerow(['Biography', profile.biography]) + writer.writerow(['External URL', profile.external_url]) + writer.writerow(['Is Private', profile.is_private]) + writer.writerow(['Is Business', profile.is_business_account]) + writer.writerow(['Followers', profile.followers]) + writer.writerow(['Following', profile.followees]) + writer.writerow(['Posts', profile.mediacount]) + + # Followers CSV + followers_file = f"{username}_followers_{timestamp}.csv" + with open(followers_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['username', 'user_id', 'full_name']) + writer.writeheader() + writer.writerows(followers_list) + + # Following CSV + following_file = f"{username}_following_{timestamp}.csv" + with open(following_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['username', 'user_id', 'full_name']) + writer.writeheader() + writer.writerows(followees_list) + + return profile_file, followers_file, following_file + + @staticmethod + def export_html(profile, followers_list, followees_list, username, analytics=None): + """Export to HTML report format""" + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f"{username}_report_{timestamp}.html" + + html_content = f""" + + + + Instagram OSINT Report - {username} + + + +
+

📊 Instagram OSINT Report

+ +

Profile Information

+
+
+ Username: {profile.username} +
+
+ User ID: {profile.userid} +
+
+ Full Name: {profile.full_name} +
+
+ Followers: {profile.followers:,} +
+
+ Following: {profile.followees:,} +
+
+ Posts: {profile.mediacount} +
+
+ Private: {"Yes" if profile.is_private else "No"} +
+
+ Business Account: {"Yes" if profile.is_business_account else "No"} +
+
+ +
+
+ Biography:
{profile.biography or "N/A"} +
+
+ External URL:
{profile.external_url or "N/A"} +
+
+ """ + + if analytics: + html_content += f""" +
+

📈 Analytics

+
+
+ Engagement Rate: {analytics.get('engagement_rate', 'N/A')} +
+
+ Follower Ratio: {analytics.get('follower_ratio', 'N/A')} +
+
+ Profile Type: {analytics.get('profile_type', 'N/A')} +
+
+ Risk Score: {analytics.get('risk_score', 'N/A')}/100 +
+
+
+ """ + + html_content += f""" +

Followers ({len(followers_list)})

+ + + + + + + + + + """ + + for follower in followers_list[:100]: # Limit to first 100 for performance + html_content += f""" + + + + + + """ + + if len(followers_list) > 100: + html_content += f""" + + + + """ + + html_content += f""" + +
UsernameUser IDFull Name
{follower.get('username', 'N/A')}{follower.get('user_id', 'N/A')}{follower.get('full_name', 'N/A')}
... and {len(followers_list) - 100} more followers
+ +

Following ({len(followees_list)})

+ + + + + + + + + + """ + + for followee in followees_list[:100]: + html_content += f""" + + + + + + """ + + if len(followees_list) > 100: + html_content += f""" + + + + """ + + html_content += f""" + +
UsernameUser IDFull Name
{followee.get('username', 'N/A')}{followee.get('user_id', 'N/A')}{followee.get('full_name', 'N/A')}
... and {len(followees_list) - 100} more following
+ +

Report generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

+
+ + + """ + + with open(filename, 'w', encoding='utf-8') as f: + f.write(html_content) + + return filename diff --git a/instaOSINT.py b/instaOSINT.py index 6760e16..a0c8043 100644 --- a/instaOSINT.py +++ b/instaOSINT.py @@ -2,127 +2,358 @@ # << CODE BY HUNX04 # << MAU RECODE ??? IZIN DULU LAH , MINIMAL TAG AKUN GITHUB MIMIN YANG MENGARAH KE AKUN INI, LEBIH ENAKNYA SIH FORK # << KALAU DI ATAS TIDAK DI IKUTI MAKA AKAN MENDAPATKAN DOSA KARENA MIMIN GAK IKHLAS DUNIA AKHIRAT SAMPAI 7 TURUNAN -# “Wahai orang-orang yang beriman! Janganlah kamu saling memakan harta sesamamu dengan jalan yang batil,” (QS. An Nisaa': 29). Rasulullah SAW juga melarang umatnya untuk mengambil hak orang lain tanpa izin. +# "Wahai orang-orang yang beriman! Janganlah kamu saling memakan harta sesamamu dengan jalan yang batil," (QS. An Nisaa': 29). Rasulullah SAW juga melarang umatnya untuk mengambil hak orang lain tanpa izin. -import instaloader #MUDULE -import time -from sys import stderr import sys import os +import time +import getpass +from cli import get_args, validate_args +from processor import InstagramOSINT +from utils import print_logo, print_info, print_warning, print_error, print_success, print_header, Wh, Gr, Ye, Cy +from menu import (display_main_menu, get_menu_choice, display_features_menu, + get_features_config, display_export_menu, get_export_format, + get_target_input, get_multiple_targets, get_limit_options, + confirm_action, display_summary) +from database import OsintDatabase +def interactive_mode(): + """Interactive CLI mode with menu system""" + print_logo() + print_header("AUTHENTICATION") + + # Get credentials + username = input(f"\n {Wh}[{Gr}>{Wh}] {Cy}Instagram username: {Ye}") + password = getpass.getpass(f" {Wh}[{Gr}>{Wh}] {Cy}Instagram password: {Ye}") + + os.system('clear') + print_logo() + + # Initialize OSINT processor + osint = InstagramOSINT(username, password) + + # Authenticate + print_info("Authenticating...") + if not osint.authenticate(): + print_error("Authentication failed!") + sys.exit(1) + + print_success("Authentication successful!") + time.sleep(1) + + # Main menu loop + while True: + os.system('clear') + print_logo() + display_main_menu() + + choice = get_menu_choice("Select an option", ['1', '2', '3', '4', '5']) + + if choice == '5': + print_success("Goodbye!") + sys.exit(0) + + elif choice == '1': + # Single profile analysis + handle_single_profile(osint) + + elif choice == '2': + # Batch analysis + handle_batch_profiles(osint) + + elif choice == '3': + # Compare profiles + handle_compare_profiles(osint) + + elif choice == '4': + # View database + handle_view_database() + + # Ask if user wants to continue + if not confirm_action("Continue with another operation?"): + print_success("Goodbye!") + sys.exit(0) +def handle_single_profile(osint): + """Handle single profile analysis""" + os.system('clear') + print_logo() + + # Get target + target = get_target_input() + + # Get features configuration + features_config = get_features_config() + + # Get export format + export_format = get_export_format() + + # Get limits + follower_limit, following_limit = get_limit_options() + + # Build options + options = { + 'analyze': features_config['analyze'], + 'db': features_config['db'], + 'no_download': features_config['no_download'], + 'limit_followers': follower_limit, + 'limit_following': following_limit + } + + # Show summary + config_summary = {**options, 'export_format': export_format, 'download': features_config['download']} + display_summary(config_summary) + + if not confirm_action("Proceed with analysis?"): + print_warning("Analysis cancelled") + return + + # Process + print_header("PROCESSING") + print_info(f"Analyzing profile: {target}") + + results = osint.process_profile(target, options) + + if results: + # Export results + print_info("Exporting results...") + exported = osint.export_results(results, target, export_format) + print_success(f"✓ Exported {len(exported)} file(s)") + for file in exported: + print(f" {Wh}• {Gr}{file}") + else: + print_error("Failed to process target") -Bl='\033[30m' # VARIABLE COLOR -Re='\033[1;31m' -Gr='\033[1;32m' -Ye='\033[1;33m' -Blu='\033[1;34m' -Mage='\033[1;35m' -Cy='\033[1;36m' -Wh='\033[1;37m' - - -try: -#LOGO GW - stderr.writelines(f"""{Gr} - - {Gr}██╗███╗ ██╗███████╗████████╗ █████╗ {Re}██████╗ ███████╗██╗███╗ ██╗████████╗ - {Gr}██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗ {Re}██╔═══██╗██╔════╝██║████╗ ██║╚══██╔══╝ - {Gr}██║██╔██╗ ██║███████╗ ██║ ███████║{Wh}█████╗{Re}██║ ██║███████╗██║██╔██╗ ██║ ██║ - {Gr}██║██║╚██╗██║╚════██║ ██║ ██╔══██║{Wh}╚════╝{Re}██║ ██║╚════██║██║██║╚██╗██║ ██║ - {Gr}██║██║ ╚████║███████║ ██║ ██║ ██║ {Re}╚██████╔╝███████║██║██║ ╚████║ ██║ - {Gr}╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ {Re}╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝ ╚═╝ - {Wh} <----- {Gr}O S I N T {Wh}I N S T A G R A M {Wh}B Y {Gr}H U N B Y T S {Wh}-----> - """) - print(f"\n {Wh}[ {Gr}! {Wh}] LOGIN YOUR INSTAGRAM ACCOUNT") - US = input(f"\n [ {Gr}+ {Wh}] INPUT USERNAME INSTAGRAM ACCOUNT : {Re}") - PW = input(f" {Wh}[ {Gr}+ {Wh}] INPUT PASSWORD INSTAGRAM ACCOUNT : {Re}") +def handle_batch_profiles(osint): + """Handle batch profile analysis""" + os.system('clear') + print_logo() + + # Get targets + targets = get_multiple_targets() + + if not targets: + print_warning("No targets provided") + return + + print_info(f"Total targets: {len(targets)}") + + # Get features configuration + features_config = get_features_config() + + # Get export format + export_format = get_export_format() + + # Get limits + follower_limit, following_limit = get_limit_options() + + # Build options + options = { + 'analyze': features_config['analyze'], + 'db': features_config['db'], + 'no_download': features_config['no_download'], + 'limit_followers': follower_limit, + 'limit_following': following_limit + } + + # Show summary + config_summary = {**options, 'export_format': export_format, 'download': features_config['download']} + display_summary(config_summary) + + if not confirm_action(f"Proceed with {len(targets)} profiles?"): + print_warning("Analysis cancelled") + return + + # Process batch + print_header("BATCH PROCESSING") + + for idx, target in enumerate(targets, 1): + print_info(f"Processing [{idx}/{len(targets)}]: {target}") + + results = osint.process_profile(target, options) + + if results: + exported = osint.export_results(results, target, export_format) + print_success(f"✓ Completed {target}") + else: + print_error(f"✗ Failed {target}") + + if idx < len(targets): + time.sleep(2) # Rate limiting + + print_success(f"✓ Batch processing complete! Processed {len(targets)} profiles") +def handle_compare_profiles(osint): + """Handle profile comparison""" os.system('clear') + print_logo() + + print_info("Profile comparison requires at least 2 profiles") + targets = get_multiple_targets() + + if len(targets) < 2: + print_warning("Need at least 2 profiles to compare") + return + + print_info(f"Comparing {len(targets)} profiles") + + # Get options + options = { + 'analyze': True, + 'db': True, + 'no_download': True, + 'limit_followers': 100, # Limit for comparison + 'limit_following': 100 + } + + if not confirm_action(f"Compare {len(targets)} profiles?"): + print_warning("Comparison cancelled") + return + + # Process all profiles + print_header("COMPARING PROFILES") + all_results = [] + + for idx, target in enumerate(targets, 1): + print_info(f"Fetching [{idx}/{len(targets)}]: {target}") + results = osint.process_profile(target, options) + if results: + all_results.append(results) + + if len(all_results) >= 2: + print_header("COMPARISON RESULTS") + + # Display comparison + for result in all_results: + profile = result['profile'] + analytics = result.get('analytics', {}) + + print(f"\n {Cy}Profile: {Gr}{profile.username}") + print(f" {Wh}├─ Followers: {Ye}{profile.followers:,}") + print(f" {Wh}├─ Following: {Ye}{profile.followees:,}") + print(f" {Wh}├─ Posts: {Ye}{profile.mediacount}") + + if analytics: + print(f" {Wh}├─ Engagement Rate: {Ye}{analytics.get('engagement_rate', 0)}") + print(f" {Wh}├─ Follower Ratio: {Ye}{analytics.get('follower_ratio', 0)}") + print(f" {Wh}└─ Risk Score: {Ye}{analytics.get('risk_score', 0)}/100") + + print_success(f"\n✓ Comparison complete!") + else: + print_error("Failed to fetch enough profiles for comparison") - def osintig(US,PW): - stderr.writelines(f"""{Gr} +def handle_view_database(): + """View database statistics""" + os.system('clear') + print_logo() + print_header("DATABASE VIEWER") + + db = OsintDatabase() + profiles = db.get_all_profiles() + + if not profiles: + print_warning("No profiles in database yet") + return + + print_info(f"Total profiles stored: {len(profiles)}") + print() + + for profile in profiles[:10]: # Show first 10 + print(f" {Wh}• {Gr}{profile}") + + # Try to get history + history = db.get_profile_history(profile) + if len(history) > 1: + growth = db.get_growth_stats(profile) + if growth: + print(f" {Wh}├─ Data points: {Ye}{growth['data_points']}") + print(f" {Wh}├─ Follower change: {Ye}{growth['followers_change']:+,}") + print(f" {Wh}└─ Following change: {Ye}{growth['following_change']:+,}") + + if len(profiles) > 10: + print(f"\n {Wh}... and {Ye}{len(profiles) - 10}{Wh} more profiles") + + print() - {Gr}██╗███╗ ██╗███████╗████████╗ █████╗ {Re}██████╗ ███████╗██╗███╗ ██╗████████╗ - {Gr}██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗ {Re}██╔═══██╗██╔════╝██║████╗ ██║╚══██╔══╝ - {Gr}██║██╔██╗ ██║███████╗ ██║ ███████║{Wh}█████╗{Re}██║ ██║███████╗██║██╔██╗ ██║ ██║ - {Gr}██║██║╚██╗██║╚════██║ ██║ ██╔══██║{Wh}╚════╝{Re}██║ ██║╚════██║██║██║╚██╗██║ ██║ - {Gr}██║██║ ╚████║███████║ ██║ ██║ ██║ {Re}╚██████╔╝███████║██║██║ ╚████║ ██║ - {Gr}╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ {Re}╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝ ╚═╝ - {Wh} <----- {Gr}O S I N T {Wh}I N S T A G R A M {Wh}B Y {Gr}H U N B Y T S {Wh}-----> - """) - IG_INSTA = instaloader.Instaloader() - IG_INSTA.login(US, PW) - if not IG_INSTA.context.is_logged_in: - IG_INSTA.context.log(f"\n {Wh}[ {Gr}+ {Wh}] {Re}Login failed!, you account wrong") - else: - IG_INSTA.context.log(f"\n {Wh}[ {Gr}+ {Wh}] {Gr}Login Success!") - time.sleep(3) - user_input = input(f"\n {Wh}[ {Gr}+ {Wh}] INPUT USERNAME TARGET : {Re}") - profile = instaloader.Profile.from_username(IG_INSTA.context, user_input) +def cli_mode(args): + """Command-line argument mode""" + # Prepare options + options = { + 'analyze': args.analyze, + 'db': args.db, + 'no_download': args.no_download, + 'limit_followers': args.limit_followers, + 'limit_following': args.limit_following + } + + # Initialize OSINT processor + osint = InstagramOSINT( + args.username, + args.password, + use_session=not args.no_session, + quiet=args.quiet + ) + + # Authenticate + if not osint.authenticate(): + if not args.quiet: + print_error("Authentication failed!") + sys.exit(1) + + # Get target list + targets = [] + if args.target: + targets = [args.target] + elif args.file: + try: + with open(args.file, 'r') as f: + targets = [line.strip() for line in f if line.strip()] + except FileNotFoundError: + if not args.quiet: + print_error(f"File not found: {args.file}") + sys.exit(1) + + # Process targets + if args.compare and len(targets) > 1: + # Comparison mode + all_results = osint.batch_process(targets, options) + + if all_results: + # Export each profile + for result in all_results: + profile = result['profile'] + osint.export_results(result, profile.username, args.output) + else: + # Single or batch mode + for target in targets: + result = osint.process_profile(target, options) + if result: + osint.export_results(result, target, args.output) + time.sleep(2) # Rate limiting - print(f"\n {Wh}============================== {Gr}INFORMATION ACCOUNT {Wh}=============================") - print(f"\n {Wh}Username :{Gr} {profile.username}") - print(f" {Wh}Full Name :{Gr} {profile.full_name}") - print(f" {Wh}Id :{Gr} {profile.userid}") - print(f" {Wh}Bio :{Gr} {profile.biography}") - print(f" {Wh}Url Profile :{Gr} {profile.external_url}") - print(f" {Wh}Account Private :{Gr} {profile.is_private}") - print(f" {Wh}Account Business :{Gr} {profile.is_business_account}") - print(f" {Wh}Account business type :{Gr} {profile.business_category_name}") - print(f" {Wh}Followers :{Gr} {profile.followers}") - print(f" {Wh}Following :{Gr} {profile.followees}") - print(f" {Wh}Total Post :{Gr} {profile.mediacount}") - time.sleep(0.3) - print(f"\n {Wh}[ {Gr}+ {Wh}]{Wh} Download profile picture : ") - profile_pic = profile.profile_pic_url - print(profile_pic) - print() - print(f" {Wh}[ {Gr}+ {Wh}]{Wh} Download {Gr}target post : ") - for post in profile.get_posts(): - IG_INSTA.download_post(post, target=profile.username) - print() - print(f" {Wh}[ {Gr}+ {Wh}]{Wh} Download {Gr}igtv post : ") - for post in IG_INSTA.get_feed_posts(): - IG_INSTA.download_post(post, target=profile.username) - print() - print(f" {Wh}[ {Gr}+ {Wh}]{Wh} Download {Gr}highlights post : ") - for highlight in IG_INSTA.get_highlights(profile): - for post in highlight.get_items(): - IG_INSTA.download_storyitem(post, target=profile.username) - print() - def animasi(): - for i in range(10): - sys.stdout.write(f'\r {Wh}LOADING {Gr}|') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}/') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}-') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}\\') - animasi() - print(f"\n {Wh}[ {Gr}+ {Wh}]{Wh} Show {Gr}target followers : ") - followers = profile.get_followers() - for follower in followers: - print(f" {Gr}USERNAME : {Wh}{follower.username} {Gr}| ID : {Wh}{follower.userid}") +def main(): + """Main entry point""" + try: + # Check if arguments provided + if len(sys.argv) > 1: + args = get_args() + + if not validate_args(args): + sys.exit(1) + + cli_mode(args) + else: + interactive_mode() + + except KeyboardInterrupt: + print(f"\n[!] Program interrupted by user") + sys.exit(0) + except Exception as e: + print_error(f"Unexpected error: {str(e)}") + sys.exit(1) - def animasi(): - for i in range(10): - sys.stdout.write(f'\r {Wh}LOADING {Gr}|') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}/') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}-') - time.sleep(0.1) - sys.stdout.write(f'\r {Wh}LOADING {Gr}\\') - animasi() - print(f"\n {Wh}[ {Gr}+ {Wh}]{Wh} Show {Gr}target followings : ") - followees = profile.get_followees() - for followee in followees: - print(f" {Gr}USERNAME : {Wh}{followee.username} {Gr}| ID : {Wh}{followee.userid}") - osintig(US,PW) -except KeyboardInterrupt: - print(f" {Wh}[ {Ye}! {Wh}] {Ye}PROGRAM STOPPED...") +if __name__ == "__main__": + main() diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..bbea242 --- /dev/null +++ b/menu.py @@ -0,0 +1,167 @@ +"""Interactive menu system for Instagram OSINT""" + +from utils import print_header, print_info, print_success, print_error, print_warning, Wh, Gr, Ye, Cy, Re + +def display_main_menu(): + """Display main menu options""" + print_header("MAIN MENU") + print(f""" + {Wh}[{Gr}1{Wh}] {Cy}Single Profile Analysis + {Wh}[{Gr}2{Wh}] {Cy}Batch Profile Analysis (Multiple Targets) + {Wh}[{Gr}3{Wh}] {Cy}Compare Profiles + {Wh}[{Gr}4{Wh}] {Cy}View Database History + {Wh}[{Gr}5{Wh}] {Re}Exit + """) + +def get_menu_choice(prompt, valid_choices): + """Get user menu choice with validation""" + while True: + choice = input(f"\n {Wh}[{Gr}?{Wh}] {prompt}: {Ye}").strip() + if choice in valid_choices: + return choice + print_error(f"Invalid choice. Please select from {', '.join(valid_choices)}") + +def display_features_menu(): + """Display features configuration menu""" + print_header("FEATURES CONFIGURATION") + print(f""" + {Wh}Select features to enable: + + {Wh}[{Gr}1{Wh}] {Cy}Download Posts & Highlights {Ye}(may take time) + {Wh}[{Gr}2{Wh}] {Cy}Run Analytics {Ye}(engagement, risk scoring) + {Wh}[{Gr}3{Wh}] {Cy}Save to Database {Ye}(historical tracking) + {Wh}[{Gr}4{Wh}] {Cy}All Features + {Wh}[{Gr}5{Wh}] {Cy}None (Quick mode - basic info only) + """) + +def get_features_config(): + """Get features configuration from user""" + display_features_menu() # Show the menu before asking + choice = get_menu_choice("Choose features option", ['1', '2', '3', '4', '5']) + + config = { + 'download': False, + 'analyze': False, + 'db': False, + 'no_download': True + } + + if choice == '1': + config['download'] = True + config['no_download'] = False + elif choice == '2': + config['analyze'] = True + elif choice == '3': + config['db'] = True + elif choice == '4': + config['download'] = True + config['analyze'] = True + config['db'] = True + config['no_download'] = False + # Choice 5 keeps defaults (quick mode) + + return config + +def display_export_menu(): + """Display export format menu""" + print_header("EXPORT FORMATS") + print(f""" + {Wh}Select export format(s): + + {Wh}[{Gr}1{Wh}] {Cy}JSON only {Ye}(structured data) + {Wh}[{Gr}2{Wh}] {Cy}CSV only {Ye}(spreadsheet format) + {Wh}[{Gr}3{Wh}] {Cy}HTML only {Ye}(visual report) + {Wh}[{Gr}4{Wh}] {Cy}JSON + CSV + {Wh}[{Gr}5{Wh}] {Cy}JSON + HTML + {Wh}[{Gr}6{Wh}] {Cy}All formats {Ye}(JSON + CSV + HTML) + """) + +def get_export_format(): + """Get export format choice from user""" + display_export_menu() # Show the menu before asking + choice = get_menu_choice("Choose export format", ['1', '2', '3', '4', '5', '6']) + + format_map = { + '1': 'json', + '2': 'csv', + '3': 'html', + '4': 'json,csv', + '5': 'json,html', + '6': 'json,csv,html' + } + + return format_map[choice] + +def get_target_input(): + """Get single target username from user""" + print_info("Enter target username to analyze") + target = input(f"\n {Wh}[{Gr}>{Wh}] {Cy}Target username: {Ye}").strip() + return target + +def get_multiple_targets(): + """Get multiple target usernames from user""" + print_info("Enter target usernames (one per line, empty line to finish)") + targets = [] + count = 1 + + while True: + target = input(f" {Wh}[{Gr}{count}{Wh}] {Cy}Target: {Ye}").strip() + if not target: + break + targets.append(target) + count += 1 + + return targets + +def get_limit_options(): + """Get follower/following limit options""" + print_header("DATA LIMITS") + print(f""" + {Wh}Set data fetching limits (for performance): + + {Wh}[{Gr}1{Wh}] {Cy}No limits {Ye}(fetch all - may be slow) + {Wh}[{Gr}2{Wh}] {Cy}Limit to 100 each + {Wh}[{Gr}3{Wh}] {Cy}Limit to 500 each + {Wh}[{Gr}4{Wh}] {Cy}Limit to 1000 each + {Wh}[{Gr}5{Wh}] {Cy}Custom limits + """) + + choice = get_menu_choice("Choose limit option", ['1', '2', '3', '4', '5']) + + if choice == '1': + return None, None + elif choice == '2': + return 100, 100 + elif choice == '3': + return 500, 500 + elif choice == '4': + return 1000, 1000 + elif choice == '5': + try: + followers = int(input(f" {Wh}[{Gr}?{Wh}] {Cy}Followers limit: {Ye}")) + following = int(input(f" {Wh}[{Gr}?{Wh}] {Cy}Following limit: {Ye}")) + return followers, following + except ValueError: + print_warning("Invalid number, using no limits") + return None, None + +def confirm_action(message): + """Ask user to confirm action""" + response = input(f"\n {Wh}[{Ye}?{Wh}] {message} {Ye}(y/n): {Wh}").strip().lower() + return response in ['y', 'yes'] + +def display_summary(config): + """Display configuration summary before processing""" + print_header("CONFIGURATION SUMMARY") + print(f"\n {Cy}Features:") + print(f" {Wh}• Download Posts: {Gr if config.get('download') else Re}{'Yes' if config.get('download') else 'No'}") + print(f" {Wh}• Run Analytics: {Gr if config.get('analyze') else Re}{'Yes' if config.get('analyze') else 'No'}") + print(f" {Wh}• Save to Database: {Gr if config.get('db') else Re}{'Yes' if config.get('db') else 'No'}") + + if config.get('limit_followers') or config.get('limit_following'): + print(f"\n {Cy}Limits:") + print(f" {Wh}• Followers: {Ye}{config.get('limit_followers', 'All')}") + print(f" {Wh}• Following: {Ye}{config.get('limit_following', 'All')}") + + print(f"\n {Cy}Export Format: {Ye}{config.get('export_format', 'json')}") + print() diff --git a/processor.py b/processor.py new file mode 100644 index 0000000..27b40a2 --- /dev/null +++ b/processor.py @@ -0,0 +1,286 @@ +"""Core processor for Instagram OSINT operations""" + +import instaloader +import time +import os +from utils import print_info, print_warning, print_error, print_success, print_header, loading_animation +from analyzer import ProfileAnalytics, ComparativeAnalytics +from exporter import Exporter +from database import OsintDatabase + +SESSION_FILE = "session-{username}" +DOWNLOAD_DIR = "downloads" # Directory for Instagram content + +class InstagramOSINT: + """Main processor for Instagram OSINT""" + + def __init__(self, username=None, password=None, use_session=True, quiet=False): + self.username = username + self.password = password + self.use_session = use_session + self.quiet = quiet + self.loader = instaloader.Instaloader(dirname_pattern=DOWNLOAD_DIR + "/{target}") + self.is_logged_in = False + + # Create download directory if it doesn't exist + os.makedirs(DOWNLOAD_DIR, exist_ok=True) + + def load_session(self): + """Load saved session if available""" + if not self.use_session or not self.username: + return False + + session_file = SESSION_FILE.format(username=self.username) + try: + self.loader.load_session_from_file(self.username, session_file) + self.is_logged_in = True + if not self.quiet: + print_success("Session loaded successfully!") + return True + except FileNotFoundError: + return False + + def login(self, username, password): + """Handle login with 2FA support""" + self.username = username + self.password = password + + try: + self.loader.login(username, password) + self.is_logged_in = True + self.save_session() + if not self.quiet: + print_success("Login successful!") + return True + except instaloader.exceptions.TwoFactorAuthRequiredException: + if not self.quiet: + print_warning("Two-factor authentication required!") + two_factor_code = input("Enter your 2FA code: ") + self.loader.two_factor_login(two_factor_code) + self.is_logged_in = True + self.save_session() + if not self.quiet: + print_success("Login successful with 2FA!") + return True + except Exception as e: + if not self.quiet: + print_error(f"Login failed: {str(e)}") + return False + + def save_session(self): + """Save current session""" + if self.username: + session_file = SESSION_FILE.format(username=self.username) + self.loader.save_session_to_file(session_file) + + def authenticate(self): + """Authenticate using session or login""" + if self.use_session and self.load_session(): + return True + + if self.username and self.password: + return self.login(self.username, self.password) + + return False + + def fetch_profile(self, target_username): + """Fetch profile data""" + if not self.is_logged_in: + print_error("Not logged in") + return None + + try: + profile = instaloader.Profile.from_username(self.loader.context, target_username) + return profile + except instaloader.exceptions.ProfileNotExistsException: + print_error(f"Profile '{target_username}' does not exist") + return None + except Exception as e: + print_error(f"Error fetching profile: {str(e)}") + return None + + def fetch_followers(self, profile, limit=None): + """Fetch followers list""" + try: + followers = [] + count = 0 + for follower in profile.get_followers(): + followers.append({ + "username": follower.username, + "user_id": follower.userid, + "full_name": follower.full_name + }) + count += 1 + if limit and count >= limit: + break + + if not self.quiet: + print_success(f"Fetched {len(followers)} followers") + return followers + except Exception as e: + print_error(f"Error fetching followers: {str(e)}") + return [] + + def fetch_following(self, profile, limit=None): + """Fetch following list""" + try: + following = [] + count = 0 + for followee in profile.get_followees(): + following.append({ + "username": followee.username, + "user_id": followee.userid, + "full_name": followee.full_name + }) + count += 1 + if limit and count >= limit: + break + + if not self.quiet: + print_success(f"Fetched {len(following)} following") + return following + except Exception as e: + print_error(f"Error fetching following: {str(e)}") + return [] + + def download_posts(self, profile): + """Download profile posts""" + if not self.is_logged_in: + return + + try: + count = 0 + for post in profile.get_posts(): + self.loader.download_post(post, target=profile.username) + count += 1 + + if not self.quiet and count > 0: + print_success(f"Downloaded {count} posts") + except Exception as e: + if not self.quiet: + print_warning(f"Error downloading posts: {str(e)}") + + def download_highlights(self, profile): + """Download profile highlights""" + if not self.is_logged_in: + return + + try: + count = 0 + for highlight in self.loader.get_highlights(profile): + for item in highlight.get_items(): + self.loader.download_storyitem(item, target=profile.username) + count += 1 + + if not self.quiet and count > 0: + print_success(f"Downloaded {count} highlight items") + except Exception as e: + if not self.quiet: + print_warning(f"Error downloading highlights: {str(e)}") + + def process_profile(self, target_username, options=None): + """Complete profile processing pipeline""" + options = options or {} + + if not self.quiet: + print_header(f"PROCESSING {target_username}") + + # Fetch profile + profile = self.fetch_profile(target_username) + if not profile: + return None + + if not self.quiet: + print_info(f"Full Name: {profile.full_name}") + print_info(f"ID: {profile.userid}") + print_info(f"Followers: {profile.followers}") + print_info(f"Following: {profile.followees}") + print_info(f"Posts: {profile.mediacount}") + + # Fetch followers and following + followers = self.fetch_followers(profile, options.get('limit_followers')) + following = self.fetch_following(profile, options.get('limit_following')) + + # Calculate analytics + analytics = None + if options.get('analyze'): + analytics = ProfileAnalytics(profile).get_analytics_summary() + if not self.quiet and analytics: + print_header("ANALYTICS") + print_info(f"Profile Type: {analytics.get('profile_type')}") + print_info(f"Engagement Rate: {analytics.get('engagement_rate')}") + print_info(f"Follower Ratio: {analytics.get('follower_ratio')}") + print_info(f"Risk Score: {analytics.get('risk_score')}/100") + + # Store in database + if options.get('db'): + db = OsintDatabase() + db.save_profile(profile) + db.save_followers(profile.username, followers) + if analytics: + db.save_analytics(profile.username, analytics) + if not self.quiet: + print_success("Data saved to database") + + # Download content + if not options.get('no_download'): + self.download_posts(profile) + self.download_highlights(profile) + + # Export data + results = { + "profile": profile, + "followers": followers, + "following": following, + "analytics": analytics + } + + return results + + def batch_process(self, target_list, options=None): + """Process multiple targets""" + options = options or {} + results = [] + + for target in target_list: + result = self.process_profile(target, options) + if result: + results.append(result) + time.sleep(2) # Rate limiting + + return results + + def export_results(self, results, target_username, formats='json'): + """Export results in specified formats""" + profile = results['profile'] + followers = results['followers'] + following = results['following'] + analytics = results['analytics'] + + format_list = [f.strip().lower() for f in formats.split(',')] + exported_files = [] + + for fmt in format_list: + try: + if fmt == 'json': + file = Exporter.export_json(profile, followers, following, target_username, analytics) + exported_files.append(file) + if not self.quiet: + print_success(f"Exported to JSON: {file}") + + elif fmt == 'csv': + files = Exporter.export_csv(profile, followers, following, target_username) + exported_files.extend(files) + if not self.quiet: + for file in files: + print_success(f"Exported to CSV: {file}") + + elif fmt == 'html': + file = Exporter.export_html(profile, followers, following, target_username, analytics) + exported_files.append(file) + if not self.quiet: + print_success(f"Exported to HTML: {file}") + except Exception as e: + print_error(f"Error exporting to {fmt}: {str(e)}") + + return exported_files diff --git a/screenshots/01_login_screen.png b/screenshots/01_login_screen.png new file mode 100644 index 0000000..e150941 Binary files /dev/null and b/screenshots/01_login_screen.png differ diff --git a/screenshots/02_2fa_prompt.png b/screenshots/02_2fa_prompt.png new file mode 100644 index 0000000..bc76e6d Binary files /dev/null and b/screenshots/02_2fa_prompt.png differ diff --git a/screenshots/03_features_config.png b/screenshots/03_features_config.png new file mode 100644 index 0000000..cad6ea9 Binary files /dev/null and b/screenshots/03_features_config.png differ diff --git a/screenshots/04_export_format.png b/screenshots/04_export_format.png new file mode 100644 index 0000000..b7b284f Binary files /dev/null and b/screenshots/04_export_format.png differ diff --git a/screenshots/05_data_limits.png b/screenshots/05_data_limits.png new file mode 100644 index 0000000..1665ef8 Binary files /dev/null and b/screenshots/05_data_limits.png differ diff --git a/screenshots/06_config_summary.png b/screenshots/06_config_summary.png new file mode 100644 index 0000000..3db5539 Binary files /dev/null and b/screenshots/06_config_summary.png differ diff --git a/screenshots/07_processing.png b/screenshots/07_processing.png new file mode 100644 index 0000000..ce4310d Binary files /dev/null and b/screenshots/07_processing.png differ diff --git a/screenshots/08_html_report.png b/screenshots/08_html_report.png new file mode 100644 index 0000000..4771c4d Binary files /dev/null and b/screenshots/08_html_report.png differ diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..bda04d0 --- /dev/null +++ b/ui.py @@ -0,0 +1,251 @@ +"""Advanced terminal UI components""" + +import time +import getpass +from typing import List, Dict, Tuple +from utils import ( + Cy, Gr, Re, Ye, Wh, BrGr, BrCy, BrYe, BrRe, + Bold, Underline, Dim, Reset, CHECK, CROSS, INFO, ARROW +) + +class TerminalUI: + """Advanced terminal UI components""" + + @staticmethod + def print_header_banner(title, subtitle=""): + """Print an impressive header banner""" + width = 80 + print(f"\n{Cy}{Bold}{'█' * width}{Reset}") + print(f"{Cy}█{Reset} {BrCy}{Bold}{title.center(width - 4)}{Reset} {Cy}█{Reset}") + + if subtitle: + print(f"{Cy}█{Reset} {Dim}{subtitle.center(width - 4)}{Reset} {Cy}█{Reset}") + + print(f"{Cy}{Bold}{'█' * width}{Reset}\n") + + @staticmethod + def print_profile_table(profile_data: Dict): + """Print profile data in a nice table format""" + print(f"\n{BrCy}{Bold} 📊 PROFILE DATA{Reset}\n") + + display_order = [ + ("Username", "username"), + ("Full Name", "full_name"), + ("User ID", "userid"), + ("Bio", "biography"), + ("Followers", "followers"), + ("Following", "followees"), + ("Posts", "mediacount"), + ("Private", "is_private"), + ("Business", "is_business_account"), + ] + + for label, key in display_order: + value = profile_data.get(key, "N/A") + + # Format value + if isinstance(value, bool): + value_str = f"{BrGr}Yes{Reset}" if value else f"{Re}No{Reset}" + elif isinstance(value, int) and key in ["followers", "followees", "mediacount"]: + value_str = f"{BrCy}{value:,}{Reset}" + else: + value_str = str(value) if value else f"{Dim}N/A{Reset}" + + label_width = 20 + print(f" {ARROW} {label:<{label_width}} {Wh}{value_str}{Reset}") + + @staticmethod + def print_stats_grid(stats: Dict[str, Tuple[str, str]]): + """Print statistics in a grid layout + + stats = { + "Engagement": ("2.5", "posts/follower"), + "Follower Ratio": ("1.5", "followers/following") + } + """ + print(f"\n{BrCy}{Bold} 📈 ANALYTICS{Reset}\n") + + for label, (value, unit) in stats.items(): + print(f" {CHECK} {Bold}{label:<25}{Reset} {BrGr}{value:>12}{Reset} {Dim}{unit}{Reset}") + + @staticmethod + def print_followers_table(followers: List[Dict], limit=20): + """Print followers in a formatted table""" + print(f"\n{BrCy}{Bold} 👥 FOLLOWERS ({len(followers)}){Reset}\n") + + # Header + header = ["#", "Username", "User ID", "Full Name"] + col_widths = [4, 20, 12, 25] + + header_line = " " + " │ ".join(f"{h:^{w}}" for h, w in zip(header, col_widths)) + print(f"{BrCy}{Bold}{header_line}{Reset}") + + sep = " " + "─┼─".join("─" * w for w in col_widths) + print(f"{Cy}{sep}{Reset}") + + # Data rows + for i, follower in enumerate(followers[:limit], 1): + row_data = [ + str(i), + follower.get("username", "N/A")[:20], + str(follower.get("user_id", ""))[:12], + follower.get("full_name", "N/A")[:25] + ] + row_line = " " + " │ ".join(f"{d:<{w}}" for d, w in zip(row_data, col_widths)) + print(f"{Wh}{row_line}{Reset}") + + if len(followers) > limit: + remaining = len(followers) - limit + print(f"{Dim} ... and {remaining} more followers{Reset}\n") + else: + print() + + @staticmethod + def print_risk_assessment(risk_score: int, profile_type: str): + """Print risk assessment with color coding""" + print(f"\n{BrCy}{Bold} ⚠️ RISK ASSESSMENT{Reset}\n") + + # Risk level + if risk_score < 25: + risk_color = BrGr + risk_level = "LOW RISK" + elif risk_score < 50: + risk_color = BrYe + risk_level = "MEDIUM RISK" + elif risk_score < 75: + risk_color = Ye + risk_level = "HIGH RISK" + else: + risk_color = BrRe + risk_level = "CRITICAL RISK" + + # Risk bar + filled = int(risk_score / 10) + bar = "█" * filled + "░" * (10 - filled) + + print(f" Risk Score: {risk_color}{bar}{Reset} {risk_score}/100") + print(f" Level: {risk_color}{Bold}{risk_level}{Reset}") + print(f" Type: {BrCy}{profile_type}{Reset}\n") + + @staticmethod + def print_comparison(profile1: Dict, profile2: Dict): + """Print side-by-side profile comparison""" + print(f"\n{BrCy}{Bold} 🔄 PROFILE COMPARISON{Reset}\n") + + keys = ["followers", "followees", "mediacount"] + + for key in keys: + label = key.replace("_", " ").title() + val1 = profile1.get(key, 0) + val2 = profile2.get(key, 0) + + val1_str = f"{val1:,}" if isinstance(val1, int) else str(val1) + val2_str = f"{val2:,}" if isinstance(val2, int) else str(val2) + + # Determine which is larger + if isinstance(val1, int) and isinstance(val2, int): + diff = val1 - val2 + if diff > 0: + diff_str = f"{BrGr}+{diff:,}{Reset}" + elif diff < 0: + diff_str = f"{BrRe}{diff:,}{Reset}" + else: + diff_str = f"{Dim}Equal{Reset}" + else: + diff_str = "" + + print(f" {label:<15} {BrCy}{val1_str:>12}{Reset} vs {BrCy}{val2_str:>12}{Reset} {diff_str}") + + print() + + @staticmethod + def print_step_progress(current: int, total: int, label: str): + """Print step-by-step progress""" + bar = "▰" * current + "▱" * (total - current) + percent = int((current / total) * 100) + + print(f" {BrCy}{bar}{Reset} {BrGr}{percent:>3}%{Reset} - {label}") + + @staticmethod + def print_export_summary(exports: List[Tuple[str, str]]): + """Print export file summary""" + print(f"\n{BrGr}{Bold} ✓ EXPORT COMPLETE{Reset}\n") + + for file_type, filename in exports: + icon = "📄" if file_type == "JSON" else ("📊" if file_type == "CSV" else "🌐") + print(f" {icon} {Bold}{file_type:<8}{Reset} {Cy}{filename}{Reset}") + + print() + + @staticmethod + def print_database_summary(profile_count: int, follower_count: int): + """Print database operation summary""" + print(f"\n{BrGr}{Bold} ✓ DATABASE SAVED{Reset}\n") + print(f" {CHECK} Stored {BrCy}{profile_count}{Reset} profile snapshot(s)") + print(f" {CHECK} Stored {BrCy}{follower_count:,}{Reset} follower record(s)") + print() + + @staticmethod + def print_loading_phase(phase: int, total_phases: int, phase_name: str): + """Print loading phase with animation""" + bar = "▰" * phase + "▱" * (total_phases - phase) + print(f"\r {Cy}{bar}{Reset} {BrCy}[{phase}/{total_phases}]{Reset} {phase_name:<40}", end="", flush=True) + + @staticmethod + def print_success_box(message: str): + """Print success in a box""" + width = 78 + print(f"\n {BrGr}╔{'═' * width}╗{Reset}") + print(f" {BrGr}║{Reset} {BrGr}{Bold}{message.center(width)}{Reset} {BrGr}║{Reset}") + print(f" {BrGr}╚{'═' * width}╝{Reset}\n") + + @staticmethod + def print_error_box(message: str): + """Print error in a box""" + width = 78 + print(f"\n {BrRe}╔{'═' * width}╗{Reset}") + print(f" {BrRe}║{Reset} {BrRe}{Bold}{message.center(width)}{Reset} {BrRe}║{Reset}") + print(f" {BrRe}╚{'═' * width}╝{Reset}\n") + +class InteractivePrompt: + """Interactive command-line prompts""" + + @staticmethod + def get_credentials(): + """Get credentials with styling - password hidden for security""" + print(f"\n{BrCy}{Bold} 🔐 INSTAGRAM CREDENTIALS{Reset}\n") + + username = input(f" {ARROW} Username: {BrCy}") + print(Reset, end="") + + # Use getpass to hide password input + password = getpass.getpass(f" {ARROW} Password: {BrCy}") + print(Reset, end="") + + return username, password + + @staticmethod + def get_target(): + """Get target username""" + print(f"\n{BrCy}{Bold} 🎯 TARGET SELECTION{Reset}\n") + target = input(f" {ARROW} Target username: {BrCy}") + print(Reset, end="") + return target + + @staticmethod + def confirm(message: str) -> bool: + """Get confirmation""" + response = input(f" {ARROW} {message} {BrCy}(y/n): {Reset}").lower().strip() + return response in ['y', 'yes'] + + @staticmethod + def select_options(options: List[str], title: str = "Select options") -> List[bool]: + """Multi-select options""" + print(f"\n{BrCy}{Bold} ⚙️ {title}{Reset}\n") + + selections = [] + for i, option in enumerate(options, 1): + response = input(f" {i}. {option}? {BrCy}(y/n): {Reset}").lower().strip() + selections.append(response in ['y', 'yes']) + + return selections diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..e9ef91e --- /dev/null +++ b/utils.py @@ -0,0 +1,240 @@ +"""Utility functions for Instagram OSINT""" + +import sys +import time +from typing import List + +# Color variables +Bl = '\033[30m' +Re = '\033[1;31m' +Gr = '\033[1;32m' +Ye = '\033[1;33m' +Blu = '\033[1;34m' +Mage = '\033[1;35m' +Cy = '\033[1;36m' +Wh = '\033[1;37m' + +# Reset color +Reset = '\033[0m' + +# Bright colors +BrRe = '\033[91m' +BrGr = '\033[92m' +BrYe = '\033[93m' +BrBlu = '\033[94m' +BrMa = '\033[95m' +BrCy = '\033[96m' + +# Background colors +BgGr = '\033[42m' +BgRe = '\033[41m' +BgYe = '\033[43m' +BgBlu = '\033[44m' + +# Formatting +Bold = '\033[1m' +Dim = '\033[2m' +Italic = '\033[3m' +Underline = '\033[4m' + +# Symbols +CHECK = f"{Gr}✓{Reset}" +CROSS = f"{Re}✗{Reset}" +INFO = f"{Blu}ℹ{Reset}" +WARN = f"{Ye}⚠{Reset}" +STAR = f"{Ye}★{Reset}" +ARROW = f"{Cy}→{Reset}" +BULLET = f"{Gr}●{Reset}" +LINE = "─" * 80 + +def print_logo(): + """Print the application logo""" + logo = f"""{Gr} + + {Gr}██╗███╗ ██╗███████╗████████╗ █████╗ {Re}██████╗ ███████╗██╗███╗ ██╗████████╗ + {Gr}██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗ {Re}██╔═══██╗██╔════╝██║████╗ ██║╚══██╔══╝ + {Gr}██║██╔██╗ ██║███████╗ ██║ ███████║{Wh}█████╗{Re}██║ ██║███████╗██║██╔██╗ ██║ ██║ + {Gr}██║██║╚██╗██║╚════██║ ██║ ██╔══██║{Wh}╚════╝{Re}██║ ██║╚════██║██║██║╚██╗██║ ██║ + {Gr}██║██║ ╚████║███████║ ██║ ██║ ██║ {Re}╚██████╔╝███████║██║██║ ╚████║ ██║ + {Gr}╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ {Re}╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝ ╚═╝ + + {Wh}{Bold}INSTAGRAM OSINT INTELLIGENCE GATHERING TOOL{Reset} + {Cy}{Dim}Advanced Profile Analysis & Data Collection{Reset} + """ + print(logo) + +def print_section(title, subtitle=""): + """Print a formatted section header""" + print(f"\n{Cy}{Bold}{LINE}{Reset}") + print(f"{Cy}{Bold} {title.upper()}{Reset}") + if subtitle: + print(f"{Dim}{Cy} {subtitle}{Reset}") + print(f"{Cy}{Bold}{LINE}{Reset}\n") + +def print_info(message, prefix=""): + """Print info message""" + icon = INFO if not prefix else prefix + print(f" {icon} {Wh}{message}{Reset}") + +def print_success(message, prefix=""): + """Print success message""" + icon = CHECK if not prefix else prefix + print(f" {icon} {BrGr}{message}{Reset}") + +def print_warning(message, prefix=""): + """Print warning message""" + icon = WARN if not prefix else prefix + print(f" {icon} {BrYe}{message}{Reset}") + +def print_error(message, prefix=""): + """Print error message""" + icon = CROSS if not prefix else prefix + print(f" {icon} {BrRe}{message}{Reset}") + +def print_highlight(message): + """Print highlighted message""" + print(f" {STAR} {Bold}{BrCy}{message}{Reset}") + +def print_header(title): + """Print section header""" + print(f"\n{BgBlu}{Wh}{Bold} {title.upper()} {Reset}\n") + +def print_subheader(title): + """Print subsection header""" + print(f"\n{Cy}{Underline}{title}{Reset}\n") + +def print_data(key, value, color=Wh): + """Print key-value pair""" + print(f" {ARROW} {Cyan}{Bold}{key}{Reset}: {color}{value}{Reset}") + +def print_table_row(columns: List[str], widths: List[int], is_header=False, color=Wh): + """Print a formatted table row""" + row = " │ ".join( + f"{col:<{width}}" if i == 0 else f"{col:^{width}}" + for i, (col, width) in enumerate(zip(columns, widths)) + ) + + if is_header: + print(f" {BgBlu}{Wh}{Bold}{row}{Reset}") + separator = " ├─" + "─┼─".join("─" * w for w in widths) + "─┤" + print(f" {Cy}{separator}{Reset}") + else: + print(f" {color}{row}{Reset}") + +def loading_animation(message="", duration=3): + """Show loading animation""" + frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + start = time.time() + + while time.time() - start < duration: + for frame in frames: + sys.stdout.write(f"\r {Cy}{frame}{Reset} {message}") + sys.stdout.flush() + time.sleep(0.08) + +def progress_bar(current, total, width=50, label="", color=Gr): + """Display a progress bar""" + if total == 0: + return + + percent = current / total + filled = int(width * percent) + bar = "█" * filled + "░" * (width - filled) + + percentage = f"{int(percent * 100):>3}%" + count_str = f"[{current}/{total}]" + + if label: + print(f" {label}") + + print(f" {color}{bar}{Reset} {percentage} {count_str}") + +def spinner(duration=3, label=""): + """Show a spinner""" + frames = ["◐", "◓", "◑", "◒"] + start = time.time() + frame_idx = 0 + + while time.time() - start < duration: + sys.stdout.write(f"\r {Cy}{frames[frame_idx % len(frames)]}{Reset} {label}") + sys.stdout.flush() + time.sleep(0.2) + frame_idx += 1 + + sys.stdout.write(f"\r {CHECK} {label}\n") + sys.stdout.flush() + +def print_box(title, content, color=Cy): + """Print content in a box""" + border_top = f" {color}╔════════════════════════════════════════════════════════════════════════════════╗{Reset}" + border_mid = f" {color}║{Reset}" + border_bot = f" {color}╚════════════════════════════════════════════════════════════════════════════════╝{Reset}" + + print(border_top) + if title: + title_str = f" {Bold}{title}{Reset} " + print(f"{border_mid} {title_str:<78} {border_mid}") + print(f" {color}╠════════════════════════════════════════════════════════════════════════════════╣{Reset}") + + for line in content.split('\n'): + padding = 78 - len(line) + print(f"{border_mid} {line:<{padding}} {border_mid}") + + print(border_bot) + +def print_stat(label, value, unit="", color=BrCy): + """Print a statistic with formatting""" + print(f" {BULLET} {Bold}{label}{Reset}: {color}{value:,}{Reset} {Dim}{unit}{Reset}") + +def print_menu(options: List[str], title="Select an option"): + """Print an interactive menu""" + print(f"\n{Cy}{Bold}{title}{Reset}") + for i, option in enumerate(options, 1): + print(f" {BrCy}{i}{Reset} {Wh}{option}{Reset}") + + while True: + try: + choice = input(f"\n {Gr}Enter your choice (1-{len(options)}): {Reset}") + choice = int(choice) + if 1 <= choice <= len(options): + return choice + else: + print_warning("Invalid choice. Please try again.") + except ValueError: + print_warning("Please enter a valid number.") + +def clear_screen(): + """Clear terminal screen""" + import os + os.system('clear' if sys.platform != 'win32' else 'cls') + +def print_divider(char="=", length=80): + """Print a divider line""" + print(f" {Cy}{char * length}{Reset}") + +def print_profile_card(profile_data: dict): + """Print a formatted profile card""" + print(f"\n{BgBlu}{Wh}{Bold} PROFILE INFORMATION {Reset}\n") + + for key, value in profile_data.items(): + key_display = key.replace('_', ' ').title() + if isinstance(value, bool): + value = f"{BrGr}Yes{Reset}" if value else f"{Re}No{Reset}" + else: + value = str(value) if value else f"{Dim}N/A{Reset}" + print(f" {Cy}▸{Reset} {Bold}{key_display:<25}{Reset} {Wh}{value}{Reset}") + +def print_comparison_header(title1, title2): + """Print header for comparison""" + print(f"\n{Cy}{Bold}{LINE}{Reset}") + print(f" {BrCy}{title1:<35}{Reset} │ {BrCy}{title2:<35}{Reset}") + print(f"{Cy}{Bold}{LINE}{Reset}\n") + +def format_large_number(num): + """Format large numbers for display""" + if num >= 1_000_000: + return f"{num / 1_000_000:.1f}M" + elif num >= 1_000: + return f"{num / 1_000:.1f}K" + return str(num) +