Instagram downloader [ULTRA PRO MAX]

🚀 Tutorial Instagram Downloader Pakai Termux (Auto Timestamp & No Overwrite!)

Buat kamu yang aktif banget di Instagram dan pengen punya arsip konten sendiri (reels, foto, story, atau hashtag), kali ini kita bakal bahas cara bikin Instagram downloader pakai Termux yang super rapi dan anti overwrite 🔥

Tutorial ini cocok buat Millennial & Gen Z yang suka ngoprek dikit tapi pengen hasil clean, otomatis, dan profesional 😎

Keyword penting hari ini: termux & instagram downloader.


📱 Kenapa Pakai Termux?

Buat yang belum tahu, Termux adalah aplikasi terminal emulator Android yang bikin HP kamu bisa jalanin perintah Linux.

Dengan termux, kamu bisa:

Install Python

Install library kayak instaloader

Jalanin script instagram downloader langsung dari HP

Simpan hasil ke penyimpanan internal (/sdcard)

Simple tapi powerful banget 💪


⚙️ Step 1 – Install & Setup Termux

Buka Termux lalu jalankan:

pkg update && pkg upgrade -y
pkg install python -y
pip install instaloader
termux-setup-storage

Kenapa termux-setup-storage penting?
Supaya script bisa akses folder /sdcard/ buat simpan hasil download.


🧠 Konsep Script Instagram Downloader Ultra v2.1

Script ini punya beberapa fitur keren:

✅ Login & session
✅ Download single post
✅ Download profile (bulk)
✅ Download story
✅ Download hashtag
✅ Auto rename pakai timestamp
✅ Auto-numbering anti overwrite
✅ Bisa pindah ke internal storage

Format nama file otomatis:

YYYYMMDD_HHMMSS_username_001.jpg
Contoh:

20250221_143052_johndoe_001.jpg 20250221_143052_johndoe_002.jpg 20250221_143053_johndoe_001.mp4
✨ Jadi:

Setiap file punya timestamp unik

Tidak ada file tertimpa

Mudah disortir berdasarkan waktu download


📂 Struktur Folder Hasil Download

downloads/
└── johndoe/
├── 20250221_143052_johndoe_001.jpg
├── 20250221_143052_johndoe_002.jpg
├── 20250221_143053_johndoe_001.mp4

Clean, rapi, dan profesional banget buat arsip konten 🔥


📝 Cara Menjalankan Script

1️⃣ Buat file baru:

nano ig_ultra.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
INSTAGRAM DOWNLOADER ULTRA v2.1
Author: Assistant
Version: 2.1
Features: Login, Session, Single Post, Profile Bulk, Story, Hashtag, File Manager
          + Timestamp Naming + Auto-Numbering Duplicate Files
"""

import os
import sys
import json
import time
import shutil
import getpass
import re
from pathlib import Path
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed

try:
    import instaloader
    from instaloader import Instaloader, Post, Profile, StoryItem, Hashtag
except ImportError:
    print("Installing required package: instaloader...")
    os.system(f"{sys.executable} -m pip install instaloader")
    import instaloader
    from instaloader import Instaloader, Post, Profile, StoryItem, Hashtag

class InstagramDownloaderUltra:
    def __init__(self):
        self.loader = Instaloader(
            dirname_pattern='downloads/{target}',
            filename_pattern='{date_utc:%Y%m%d}_{shortcode}',  # Default, akan di-override
            download_pictures=True,
            download_videos=True,
            download_video_thumbnails=True,
            download_geotags=False,
            download_comments=False,
            save_metadata=True,
            compress_json=False,
            post_metadata_txt_pattern=''
        )
        self.session_file = "session-instaloader.json"
        self.config_file = "config.json"
        self.download_dir = "downloads"
        self.internal_dir = "/sdcard/InstagramDownloads"
        self.current_user = None
        self.download_counter = {}  # Track filename counts
        
        self.ensure_directories()
        self.load_config()
        
    def ensure_directories(self):
        """Create necessary directories"""
        Path(self.download_dir).mkdir(exist_ok=True)
        Path("sessions").mkdir(exist_ok=True)
        
    def load_config(self):
        """Load configuration"""
        if os.path.exists(self.config_file):
            try:
                with open(self.config_file, 'r') as f:
                    config = json.load(f)
                    self.internal_dir = config.get('internal_dir', self.internal_dir)
            except:
                pass
                
    def save_config(self):
        """Save configuration"""
        config = {'internal_dir': self.internal_dir}
        with open(self.config_file, 'w') as f:
            json.dump(config, f, indent=4)
    
    def get_timestamp(self):
        """Generate timestamp for filename: YYYYMMDD_HHMMSS"""
        return datetime.now().strftime("%Y%m%d_%H%M%S")
    
    def get_unique_filename(self, directory, base_name, extension):
        """
        Generate unique filename with auto-numbering
        Format: timestamp_base_name.extension
        If exists: timestamp_base_name_001.extension, timestamp_base_name_002.extension, etc.
        """
        timestamp = self.get_timestamp()
        
        # Clean base_name from invalid characters
        base_name = re.sub(r'[<>:"/\\|?*]', '_', base_name)
        base_name = base_name[:50]  # Limit length
        
        # First attempt: timestamp_base_name.extension
        filename = f"{timestamp}_{base_name}.{extension}"
        filepath = os.path.join(directory, filename)
        
        if not os.path.exists(filepath):
            return filename
            
        # If exists, add numbering: timestamp_base_name_001.extension
        counter = 1
        while True:
            filename = f"{timestamp}_{base_name}_{counter:03d}.{extension}"
            filepath = os.path.join(directory, filename)
            if not os.path.exists(filepath):
                return filename
            counter += 1
            # Safety limit
            if counter > 999:
                filename = f"{timestamp}_{base_name}_{int(time.time())}.{extension}"
                return filename
    
    def rename_downloaded_files(self, target_dir, owner_username, post_date=None):
        """
        Rename downloaded files with timestamp naming convention
        Returns list of renamed files
        """
        renamed_files = []
        
        if not os.path.exists(target_dir):
            return renamed_files
            
        # Get all files in directory (excluding JSON metadata if not needed)
        files = [f for f in os.listdir(target_dir) if os.path.isfile(os.path.join(target_dir, f))]
        
        # Filter out txt metadata files (optional)
        media_files = [f for f in files if not f.endswith('.txt') and not f.endswith('.json')]
        
        for original_file in media_files:
            original_path = os.path.join(target_dir, original_file)
            
            # Get file extension
            name_parts = original_file.rsplit('.', 1)
            if len(name_parts) < 2:
                continue
            extension = name_parts[1].lower()
            
            # Determine base name
            if post_date:
                base_name = f"{owner_username}_{post_date.strftime('%Y%m%d')}"
            else:
                base_name = owner_username
            
            # Generate unique filename
            new_filename = self.get_unique_filename(target_dir, base_name, extension)
            new_path = os.path.join(target_dir, new_filename)
            
            try:
                os.rename(original_path, new_path)
                renamed_files.append({
                    'original': original_file,
                    'new': new_filename,
                    'path': new_path
                })
                print(f"   ✓ Renamed: {original_file} → {new_filename}")
            except Exception as e:
                print(f"   ✗ Error renaming {original_file}: {e}")
                
        return renamed_files
    
    def print_banner(self):
        """Display menu banner"""
        os.system('cls' if os.name == 'nt' else 'clear')
        print("╔══════════════════════════════╗")
        print("║  INSTAGRAM DOWNLOADER ULTRA  ║")
        print("║          Version 2.1         ║")
        print("╠══════════════════════════════╣")
        status = "✓ Logged in" if self.current_user else "✗ Guest"
        print(f"║ Status: {status:<19} ║")
        print("╠══════════════════════════════╣")
        print("║ 1. Login                     ║")
        print("║ 2. Load Session              ║")
        print("║ 3. Download Single Post      ║")
        print("║ 4. Download Profile (Bulk)   ║")
        print("║ 5. Download Story            ║")
        print("║ 6. Download Hashtag          ║")
        print("║ 7. Move All to Internal      ║")
        print("║ 8. Settings                  ║")
        print("║ 9. Exit                      ║")
        print("╚══════════════════════════════╝")
        print(f"\n💡 Naming: YYYYMMDD_HHMMSS_username_001.jpg")
        print()
        
    def login(self):
        """Login to Instagram"""
        print("\n[LOGIN INSTAGRAM]")
        print("-" * 40)
        username = input("Username: ").strip()
        if not username:
            print("❌ Username cannot be empty!")
            return
            
        password = getpass.getpass("Password: ")
        
        try:
            print(f"\n🔐 Logging in as {username}...")
            self.loader.login(username, password)
            self.current_user = username
            
            # Save session
            session_path = f"sessions/{username}.session"
            self.loader.save_session_to_file(session_path)
            print(f"💾 Session saved to: {session_path}")
            
            print(f"✅ Successfully logged in as {username}!")
            
        except instaloader.exceptions.BadCredentialsException:
            print("❌ Error: Invalid username or password!")
        except instaloader.exceptions.TwoFactorAuthRequiredException:
            print("⚠️  2FA Required!")
            code = input("Enter 2FA code: ")
            try:
                self.loader.two_factor_login(code)
                self.current_user = username
                print("✅ 2FA Login successful!")
            except Exception as e:
                print(f"❌ 2FA Error: {e}")
        except Exception as e:
            print(f"❌ Login Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def load_session(self):
        """Load existing session"""
        print("\n[LOAD SESSION]")
        print("-" * 40)
        
        sessions = [f for f in os.listdir("sessions") if f.endswith('.session')]
        if not sessions:
            print("❌ No saved sessions found!")
            print("💡 Please login first (Option 1)")
            input("\nPress Enter to continue...")
            return
            
        print("Available sessions:")
        for i, session in enumerate(sessions, 1):
            user = session.replace('.session', '')
            print(f"  {i}. {user}")
            
        choice = input("\nSelect session (number) or type username: ").strip()
        
        try:
            if choice.isdigit():
                idx = int(choice) - 1
                if 0 <= idx < len(sessions):
                    username = sessions[idx].replace('.session', '')
                else:
                    print("❌ Invalid selection!")
                    return
            else:
                username = choice
                
            session_path = f"sessions/{username}.session"
            if not os.path.exists(session_path):
                print(f"❌ Session for {username} not found!")
                return
                
            print(f"\n📂 Loading session for {username}...")
            self.loader.load_session_from_file(username, session_path)
            self.current_user = username
            print("✅ Session loaded successfully!")
            
            # Test session validity
            try:
                profile = instaloader.Profile.from_username(self.loader.context, username)
                print(f"✓ Verified: {profile.full_name} (@{profile.username})")
                print(f"  Followers: {profile.followers:,} | Following: {profile.followees:,}")
            except Exception as e:
                print(f"⚠️  Session may be expired: {e}")
                
        except Exception as e:
            print(f"❌ Error loading session: {e}")
            
        input("\nPress Enter to continue...")
        
    def download_single_post(self):
        """Download single post by URL or shortcode"""
        print("\n[DOWNLOAD SINGLE POST]")
        print("-" * 40)
        
        url = input("Enter Post URL or Shortcode: ").strip()
        if not url:
            print("❌ Input cannot be empty!")
            return
            
        # Extract shortcode from URL
        shortcode = url
        if '/' in url:
            parts = url.split('/')
            for i, part in enumerate(parts):
                if part in ['p', 'reel', 'tv'] and i + 1 < len(parts):
                    shortcode = parts[i + 1]
                    break
                    
        shortcode = shortcode.replace('/', '').strip()
        
        try:
            print(f"\n🔍 Fetching post: {shortcode}...")
            post = instaloader.Post.from_shortcode(self.loader.context, shortcode)
            
            print(f"📸 Post by: {post.owner_username}")
            print(f"❤️  Likes: {post.likes:,} | 💬 Comments: {post.comments:,}")
            print(f"📅 Date: {post.date_utc.strftime('%Y-%m-%d %H:%M:%S')}")
            
            # Create specific folder
            target_dir = f"{self.download_dir}/{post.owner_username}"
            Path(target_dir).mkdir(parents=True, exist_ok=True)
            
            print(f"\n⬇️  Downloading to: {target_dir}/")
            
            # Temporary download with default naming
            temp_dir = os.path.join(target_dir, f"temp_{int(time.time())}")
            Path(temp_dir).mkdir(parents=True, exist_ok=True)
            
            # Download post
            self.loader.download_post(post, target=temp_dir)
            
            print(f"\n📝 Renaming files with timestamp...")
            # Rename files with timestamp and auto-numbering
            renamed = self.rename_downloaded_files(temp_dir, post.owner_username, post.date_utc)
            
            # Move files from temp to target
            for file_info in renamed:
                src = file_info['path']
                dst = os.path.join(target_dir, file_info['new'])
                shutil.move(src, dst)
                
            # Remove temp directory
            shutil.rmtree(temp_dir)
            
            # Move JSON metadata if exists
            json_files = [f for f in os.listdir(target_dir) if f.endswith('.json') and f.startswith('temp_')]
            for jf in json_files:
                os.remove(os.path.join(target_dir, jf))
            
            print(f"\n✅ Download completed!")
            print(f"📁 Saved in: {target_dir}/")
            print(f"📄 {len(renamed)} file(s) saved with timestamp naming")
            
        except instaloader.exceptions.PostChangedException:
            print("❌ Post has been changed or deleted!")
        except instaloader.exceptions.BadResponseException:
            print("❌ Post not found or private (and not following)!")
        except Exception as e:
            print(f"❌ Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def download_profile(self):
        """Download profile posts in bulk"""
        print("\n[DOWNLOAD PROFILE (BULK)]")
        print("-" * 40)
        
        username = input("Enter Instagram Username: ").strip()
        if not username:
            print("❌ Username cannot be empty!")
            return
            
        try:
            print(f"\n🔍 Fetching profile: @{username}...")
            profile = instaloader.Profile.from_username(self.loader.context, username)
            
            print(f"\n👤 Profile Info:")
            print(f"   Name: {profile.full_name}")
            print(f"   Posts: {profile.mediacount:,}")
            print(f"   Private: {'Yes 🔒' if profile.is_private else 'No 🌐'}")
            
            if profile.is_private and not profile.followed_by_viewer:
                print("\n❌ This profile is private and you don't follow them!")
                input("\nPress Enter to continue...")
                return
                
            # Options
            print("\n⚙️  Download Options:")
            print("   1. All posts")
            print("   2. Recent posts only (last 12)")
            print("   3. Posts from specific date range")
            print("   4. Only videos (IGTV/Reels)")
            print("   5. Only photos")
            
            choice = input("\nSelect option (1-5): ").strip()
            
            target_dir = f"{self.download_dir}/{profile.username}"
            Path(target_dir).mkdir(parents=True, exist_ok=True)
            
            posts = profile.get_posts()
            
            if choice == "2":
                posts = list(posts)[:12]
                print(f"\n⬇️  Downloading recent {len(posts)} posts...")
            elif choice == "3":
                start_date = input("Start date (YYYY-MM-DD): ").strip()
                end_date = input("End date (YYYY-MM-DD): ").strip()
                try:
                    start = datetime.strptime(start_date, "%Y-%m-%d")
                    end = datetime.strptime(end_date, "%Y-%m-%d")
                    posts = [p for p in posts if start <= p.date_utc <= end]
                    print(f"\n⬇️  Downloading {len(posts)} posts from {start_date} to {end_date}...")
                except:
                    print("❌ Invalid date format!")
                    return
            elif choice == "4":
                posts = [p for p in posts if p.is_video]
                print(f"\n⬇️  Downloading {len(posts)} videos...")
            elif choice == "5":
                posts = [p for p in posts if not p.is_video]
                print(f"\n⬇️  Downloading {len(posts)} photos...")
            else:
                print(f"\n⬇️  Downloading all {profile.mediacount} posts...")
                
            # Download with progress
            total_downloaded = 0
            for i, post in enumerate(posts, 1):
                try:
                    print(f"\n[{i}] Downloading post from {post.date_utc.strftime('%Y-%m-%d')}...")
                    
                    # Create temp directory for this post
                    temp_dir = os.path.join(target_dir, f"temp_{post.shortcode}")
                    Path(temp_dir).mkdir(parents=True, exist_ok=True)
                    
                    # Download
                    self.loader.download_post(post, target=temp_dir)
                    
                    # Rename files
                    renamed = self.rename_downloaded_files(temp_dir, profile.username, post.date_utc)
                    
                    # Move to target
                    for file_info in renamed:
                        src = file_info['path']
                        dst = os.path.join(target_dir, file_info['new'])
                        shutil.move(src, dst)
                        
                    # Cleanup temp
                    shutil.rmtree(temp_dir)
                    total_downloaded += len(renamed)
                    
                    # Rate limiting
                    if i % 10 == 0:
                        print("⏳ Pausing to avoid rate limit...")
                        time.sleep(2)
                        
                except Exception as e:
                    print(f"   ⚠️  Error: {e}")
                    continue
                    
            print(f"\n✅ Downloaded {total_downloaded} media files!")
            print(f"📁 Saved in: {target_dir}/")
            
        except Exception as e:
            print(f"❌ Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def download_story(self):
        """Download stories"""
        print("\n[DOWNLOAD STORY]")
        print("-" * 40)
        
        if not self.current_user:
            print("⚠️  Warning: Not logged in. Can only download public stories.")
            proceed = input("Continue anyway? (y/n): ").lower()
            if proceed != 'y':
                return
                
        username = input("Enter Username (or 'self' for your own stories): ").strip()
        if not username:
            print("❌ Username cannot be empty!")
            return
            
        try:
            if username.lower() == 'self':
                if not self.current_user:
                    print("❌ Please login first!")
                    return
                profile = instaloader.Profile.from_username(self.loader.context, self.current_user)
            else:
                profile = instaloader.Profile.from_username(self.loader.context, username)
                
            target_dir = f"{self.download_dir}/{profile.username}_stories"
            Path(target_dir).mkdir(parents=True, exist_ok=True)
            
            print(f"\n🔍 Fetching stories from @{profile.username}...")
            
            # Get stories
            stories = self.loader.get_stories([profile.userid])
            
            count = 0
            for story in stories:
                for item in story.get_items():
                    try:
                        print(f"\n📥 Downloading story {count+1}...")
                        
                        # Determine extension
                        if item.is_video:
                            ext = "mp4"
                        else:
                            ext = "jpg"
                            
                        # Generate unique filename
                        base_name = f"{profile.username}_story"
                        filename = self.get_unique_filename(target_dir, base_name, ext)
                        filepath = os.path.join(target_dir, filename)
                        
                        # Download story item
                        self.loader.download_storyitem(item, target=target_dir)
                        
                        # Rename the downloaded file
                        # Find the file that was just downloaded
                        downloaded_files = [f for f in os.listdir(target_dir) 
                                          if f.startswith(str(item.date_utc.strftime('%Y%m%d')))]
                        
                        for df in downloaded_files:
                            old_path = os.path.join(target_dir, df)
                            if os.path.isfile(old_path) and not df.startswith('20'):  # Not already renamed
                                new_path = os.path.join(target_dir, filename)
                                # If filename taken, get new one
                                if os.path.exists(new_path):
                                    filename = self.get_unique_filename(target_dir, base_name, ext)
                                    new_path = os.path.join(target_dir, filename)
                                os.rename(old_path, new_path)
                                print(f"   ✓ Saved as: {filename}")
                                break
                                
                        count += 1
                    except Exception as e:
                        print(f"   ⚠️  Error: {e}")
                        
            if count > 0:
                print(f"\n✅ Downloaded {count} stories!")
                print(f"📁 Saved in: {target_dir}/")
            else:
                print("\nℹ️  No stories available")
                
        except Exception as e:
            print(f"❌ Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def download_hashtag(self):
        """Download posts by hashtag"""
        print("\n[DOWNLOAD HASHTAG]")
        print("-" * 40)
        
        hashtag = input("Enter Hashtag (without #): ").strip().replace('#', '')
        if not hashtag:
            print("❌ Hashtag cannot be empty!")
            return
            
        try:
            print(f"\n🔍 Searching #{hashtag}...")
            tag = instaloader.Hashtag.from_name(self.loader.context, hashtag)
            
            print(f"\n🏷️  Hashtag: #{tag.name} | Posts: {tag.mediacount:,}")
            
            limit = input("How many posts to download? (default 50): ").strip()
            limit = int(limit) if limit.isdigit() else 50
            
            target_dir = f"{self.download_dir}/#{tag.name}"
            Path(target_dir).mkdir(parents=True, exist_ok=True)
            
            print(f"\n⬇️  Downloading up to {limit} posts...")
            
            posts = tag.get_posts()
            count = 0
            total_media = 0
            
            for post in posts:
                if count >= limit:
                    break
                try:
                    print(f"\n[{count+1}/{limit}] From @{post.owner_username}...")
                    
                    temp_dir = os.path.join(target_dir, f"temp_{post.shortcode}")
                    Path(temp_dir).mkdir(parents=True, exist_ok=True)
                    
                    self.loader.download_post(post, target=temp_dir)
                    
                    renamed = self.rename_downloaded_files(temp_dir, f"{tag.name}_{post.owner_username}", post.date_utc)
                    
                    for file_info in renamed:
                        src = file_info['path']
                        dst = os.path.join(target_dir, file_info['new'])
                        shutil.move(src, dst)
                        
                    shutil.rmtree(temp_dir)
                    total_media += len(renamed)
                    count += 1
                    
                    if count % 10 == 0:
                        print("⏳ Pausing...")
                        time.sleep(2)
                        
                except Exception as e:
                    print(f"   ⚠️  Error: {e}")
                    continue
                    
            print(f"\n✅ Downloaded {count} posts ({total_media} media files) with #{hashtag}!")
            print(f"📁 Saved in: {target_dir}/")
            
        except Exception as e:
            print(f"❌ Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def move_to_internal(self):
        """Move all downloads to internal storage"""
        print("\n[MOVE TO INTERNAL STORAGE]")
        print("-" * 40)
        
        is_android = os.path.exists('/sdcard')
        default_path = "/sdcard/InstagramDownloads" if is_android else os.path.expanduser("~/InstagramDownloads")
            
        print(f"Current path: {self.internal_dir}")
        new_path = input(f"Destination (Enter for {default_path}): ").strip()
        if new_path:
            self.internal_dir = new_path
        else:
            self.internal_dir = default_path
            
        self.save_config()
        
        if not os.path.exists(self.internal_dir):
            Path(self.internal_dir).mkdir(parents=True, exist_ok=True)
            
        try:
            items = [f for f in os.listdir(self.download_dir) 
                    if os.path.isdir(os.path.join(self.download_dir, f)) and not f.startswith('.')]
            
            if not items:
                print("ℹ️  No folders to move!")
                return
                
            print(f"\n📦 Found {len(items)} folders to move...")
            
            moved = 0
            for item in items:
                src = os.path.join(self.download_dir, item)
                dst = os.path.join(self.internal_dir, item)
                
                try:
                    if os.path.exists(dst):
                        # Merge folders with auto-numbering for conflicts
                        for root, dirs, files in os.walk(src):
                            rel_path = os.path.relpath(root, src)
                            dst_root = os.path.join(dst, rel_path)
                            Path(dst_root).mkdir(parents=True, exist_ok=True)
                            
                            for file in files:
                                src_file = os.path.join(root, file)
                                # Check if file exists in destination
                                dst_file = os.path.join(dst_root, file)
                                if os.path.exists(dst_file):
                                    # Add numbering
                                    name, ext = file.rsplit('.', 1)
                                    counter = 1
                                    while os.path.exists(os.path.join(dst_root, f"{name}_{counter:03d}.{ext}")):
                                        counter += 1
                                    dst_file = os.path.join(dst_root, f"{name}_{counter:03d}.{ext}")
                                shutil.copy2(src_file, dst_file)
                        shutil.rmtree(src)
                    else:
                        shutil.copytree(src, dst)
                        shutil.rmtree(src)
                    moved += 1
                    print(f"   ✓ Moved: {item}")
                except Exception as e:
                    print(f"   ✗ Error: {e}")
                    
            print(f"\n✅ Moved {moved}/{len(items)} folders!")
            print(f"📁 Destination: {self.internal_dir}")
            
        except Exception as e:
            print(f"❌ Error: {e}")
            
        input("\nPress Enter to continue...")
        
    def settings(self):
        """Settings menu"""
        print("\n[SETTINGS]")
        print("-" * 40)
        print(f"1. Change internal storage path")
        print(f"2. Clear all sessions")
        print(f"3. View download statistics")
        print(f"4. Back")
        
        choice = input("\nSelect: ").strip()
        
        if choice == "1":
            new_path = input("New internal path: ").strip()
            if new_path:
                self.internal_dir = new_path
                self.save_config()
                print("✅ Path updated!")
        elif choice == "2":
            confirm = input("Delete all saved sessions? (yes/no): ").lower()
            if confirm == "yes":
                sessions = [f for f in os.listdir("sessions") if f.endswith('.session')]
                for s in sessions:
                    os.remove(os.path.join("sessions", s))
                print("✅ Sessions cleared!")
        elif choice == "3":
            self.show_statistics()
            
        input("\nPress Enter to continue...")
        
    def show_statistics(self):
        """Show download statistics"""
        print("\n[DOWNLOAD STATISTICS]")
        print("-" * 40)
        
        total_folders = 0
        total_files = 0
        total_size = 0
        
        for root, dirs, files in os.walk(self.download_dir):
            total_folders = len(dirs)
            for file in files:
                filepath = os.path.join(root, file)
                total_files += 1
                total_size += os.path.getsize(filepath)
                
        print(f"Total folders: {total_folders}")
        print(f"Total files: {total_files}")
        print(f"Total size: {total_size / (1024*1024):.2f} MB")
        print(f"Download directory: {os.path.abspath(self.download_dir)}")
        
    def run(self):
        """Main loop"""
        while True:
            self.print_banner()
            choice = input("Select option (1-9): ").strip()
            
            if choice == '1':
                self.login()
            elif choice == '2':
                self.load_session()
            elif choice == '3':
                self.download_single_post()
            elif choice == '4':
                self.download_profile()
            elif choice == '5':
                self.download_story()
            elif choice == '6':
                self.download_hashtag()
            elif choice == '7':
                self.move_to_internal()
            elif choice == '8':
                self.settings()
            elif choice == '9':
                print("\n👋 Thank you for using Instagram Downloader Ultra!")
                sys.exit(0)
            else:
                print("\n❌ Invalid option!")
                time.sleep(1)

if __name__ == "__main__":
    try:
        app = InstagramDownloaderUltra()
        app.run()
    except KeyboardInterrupt:
        print("\n\n👋 Goodbye!")
        sys.exit(0)
    except Exception as e:
        print(f"\n💥 Error: {e}")
        sys.exit(1)
        

2️⃣ Paste script Instagram Downloader Ultra v2.1
3️⃣ Simpan → CTRL + X → Y → Enter
4️⃣ Jalankan:

python ig_ultra.py

Nanti akan muncul menu seperti ini:

  1. Login
  2. Load Session
  3. Download Single Post
  4. Download Profile
  5. Download Story
  6. Download Hashtag
  7. Move All to Internal
  8. Settings
  9. Exit

🔐 Fitur Login & Session

Login sekali, session otomatis disimpan di folder:

sessions/username.session

Jadi nggak perlu login berulang-ulang tiap buka script 😌

Kalau akun pakai 2FA?
Script sudah support input kode verifikasi juga 🔥


📸 Download Single Post

Masukkan:

URL post

atau shortcode

Script otomatis:

Ambil data post

Download ke folder sementara

Rename pakai timestamp

Pindahkan ke folder utama

Hapus folder temp

Smart file management banget 👌


👤 Download Profile (Bulk Mode)

Pilihan download:

Semua post

12 post terbaru

Rentang tanggal tertentu

Video saja

Foto saja

Cocok buat:

Backup akun

Arsip konten brand

Simpan referensi konten

Script juga punya delay anti rate limit tiap 10 post ⏳


🔥 Download Story

Bisa download:

Story akun publik

Story akun sendiri (kalau login)

Format file:

YYYYMMDD_HHMMSS_username_story_001.jpg

Auto rename juga, jadi aman 💯


🏷️ Download Hashtag

Masukkan hashtag tanpa tanda #

Contoh:

travel

Script akan:

Ambil post dari hashtag

Batasi jumlah (default 50)

Rename otomatis

Simpan ke folder:

downloads/#travel/

Mantap buat riset konten 🔎


📦 Move ke Penyimpanan Internal

Option 7 akan memindahkan semua folder ke:

/sdcard/InstagramDownloads

Keunggulannya:

Bisa langsung terlihat di Galeri

Bisa dipindah ke laptop

Bisa upload ulang

Bisa dijadikan backup


⚙️ Settings Menu

Di Option 8 kamu bisa:

Ganti path internal storage

Hapus semua session login

Lihat statistik download (total file & ukuran)

Cocok buat yang suka monitoring storage HP 📊


💎 Kenapa Sistem Ini Lebih Aman?

✨ Timestamp unik per detik
✨ Auto-numbering sampai 999
✨ Tidak ada file tertimpa
✨ Sorting otomatis berdasarkan waktu
✨ Folder per username

Ini bikin instagram downloader kamu terasa seperti aplikasi profesional 😎


⚠️ Tips Penting Biar Aman

Jangan spam download ratusan post sekaligus

Gunakan akun sendiri untuk login

Hindari akun private yang tidak kamu follow

Gunakan untuk arsip pribadi & edukasi


🎯 Siapa yang Cocok Pakai Ini?

✔️ Content creator
✔️ Social media manager
✔️ Admin brand
✔️ Pelajar IT yang suka ngoprek
✔️ Kamu yang pengen backup konten sendiri


🚀 Kesimpulan

Dengan kombinasi termux + instagram downloader berbasis Python, kamu bisa bikin sistem download Instagram yang:

Lebih fleksibel dari aplikasi biasa

Tanpa watermark

Tanpa iklan

Tanpa overwrite file

Super rapi & profesional

Dan semua itu cuma dari HP

Tinggalkan komentar atau share artikel ini ke teman kamu yang suka ngulik Termux juga 😎

Jangan lupa save & bookmark ya! 🚀

Lebih lamaTerbaru

Posting Komentar