first commit
This commit is contained in:
133
README.md
Normal file
133
README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Social Media Collection Downloader
|
||||
|
||||
Automatically downloads TikTok collections and Instagram saved posts, then unsaves them.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Chrome, Firefox, or Edge (for cookie extraction or headless automation)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium
|
||||
|
||||
# 2. Run the interactive setup wizard (recommended)
|
||||
python setup.py
|
||||
|
||||
# 3. Or run directly if config already exists
|
||||
python downloader.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Your Cookies (Required for Private Content)
|
||||
|
||||
TikTok and Instagram require you to be logged in. The app needs your session cookies.
|
||||
|
||||
### Option A — Export from browser (recommended)
|
||||
1. Install the **"Get cookies.txt LOCALLY"** extension:
|
||||
- [Chrome](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
|
||||
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
|
||||
2. Log into tiktok.com and/or instagram.com
|
||||
3. Click the extension icon → choose **JSON** format → Export
|
||||
4. Save the files somewhere safe (e.g. `config/tiktok_cookies.json`)
|
||||
5. Enter the paths during `setup.py`
|
||||
|
||||
### Option B — Auto-read from browser
|
||||
During setup, choose option `2` (browser auto-read) and enter your browser name (`chrome`, `firefox`, `edge`).
|
||||
> ⚠ Your browser must be **closed** (or using a separate profile) when this runs.
|
||||
|
||||
---
|
||||
|
||||
## Finding Your Collection URLs
|
||||
|
||||
### TikTok
|
||||
1. Go to your profile → tap **Collections** (bookmark icon)
|
||||
2. Open the collection you want to download
|
||||
3. Copy the URL from your browser — it looks like:
|
||||
`https://www.tiktok.com/@yourhandle/collection/MyVideos-1234567890`
|
||||
|
||||
### Instagram
|
||||
1. Go to your profile → tap the **bookmark icon** (Saved)
|
||||
2. Open the collection you want to download
|
||||
3. Copy the URL — it looks like:
|
||||
`https://www.instagram.com/yourhandle/saved/my-collection/1234567890/`
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `config/config.json` directly, or use `setup.py` to regenerate it.
|
||||
|
||||
See `config/config.example.json` for all available options.
|
||||
|
||||
Key options:
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
| `enabled` | Enable/disable a platform |
|
||||
| `cookies_file` | Path to exported cookies JSON |
|
||||
| `cookies_from_browser` | Auto-read from `chrome`/`firefox`/`edge` |
|
||||
| `collections` | List of collection URLs to process |
|
||||
| `unsave_after_download` | Remove posts after downloading |
|
||||
| `headless` | Hide browser window during unsaving |
|
||||
| `delay_between_downloads` | Seconds between each download (avoid rate limits) |
|
||||
| `delay_between_unsaves` | Seconds between each unsave click |
|
||||
|
||||
---
|
||||
|
||||
## Downloaded Files
|
||||
|
||||
Files are saved to:
|
||||
```
|
||||
downloads/
|
||||
tiktok/
|
||||
Username - Video Title [video_id].mp4
|
||||
Username - Video Title [video_id].info.json
|
||||
instagram/
|
||||
Username - Post Title [post_id].mp4
|
||||
Username - Post Title [post_id].jpg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scheduling (run automatically)
|
||||
|
||||
### Windows Task Scheduler
|
||||
Create a task that runs:
|
||||
```
|
||||
python C:\path\to\social-dl\downloader.py
|
||||
```
|
||||
|
||||
### macOS / Linux (cron)
|
||||
```bash
|
||||
# Run every day at 9am
|
||||
0 9 * * * cd /path/to/social-dl && python downloader.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**yt-dlp says "login required"**
|
||||
→ Your cookies are missing or expired. Re-export them from your browser.
|
||||
|
||||
**Unsave doesn't work / button not found**
|
||||
→ TikTok/Instagram may have updated their UI. Set `"headless": false` to watch the browser and inspect what's happening. Open an issue with a screenshot.
|
||||
|
||||
**Rate limited / banned temporarily**
|
||||
→ Increase `delay_between_downloads` to `5` or more.
|
||||
|
||||
**Instagram downloads fail**
|
||||
→ Instagram heavily restricts scraping. Use fresh cookies from a recently-logged-in session. Try `cookies_from_browser` with your browser open to Instagram.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Downloads are skipped if the file already exists (`--no-overwrites`)
|
||||
- Logs are saved to `logs/session_YYYYMMDD_HHMMSS.log`
|
||||
- The unsave step opens a browser window (unless `headless: true`) — this is normal
|
||||
- This tool uses your own account cookies; it does not share or store credentials anywhere
|
||||
26
config/config.example.json
Normal file
26
config/config.example.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"tiktok": {
|
||||
"enabled": true,
|
||||
"cookies_file": "/path/to/tiktok_cookies.json",
|
||||
"cookies_from_browser": null,
|
||||
"collections": [
|
||||
"https://www.tiktok.com/@yourhandle/collection/MyCollection-1234567890"
|
||||
],
|
||||
"unsave_after_download": true,
|
||||
"headless": false,
|
||||
"delay_between_downloads": 2,
|
||||
"delay_between_unsaves": 2
|
||||
},
|
||||
"instagram": {
|
||||
"enabled": true,
|
||||
"cookies_file": "/path/to/instagram_cookies.json",
|
||||
"cookies_from_browser": null,
|
||||
"collections": [
|
||||
"https://www.instagram.com/yourhandle/saved/collection-name/1234567890/"
|
||||
],
|
||||
"unsave_after_download": true,
|
||||
"headless": false,
|
||||
"delay_between_downloads": 3,
|
||||
"delay_between_unsaves": 3
|
||||
}
|
||||
}
|
||||
17
config/config.json
Normal file
17
config/config.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"tiktok": {
|
||||
"enabled": true,
|
||||
"cookies_from_browser": "firefox",
|
||||
"cookies_file": null,
|
||||
"collections": [
|
||||
"https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824"
|
||||
],
|
||||
"unsave_after_download": true,
|
||||
"headless": false,
|
||||
"delay_between_downloads": 5,
|
||||
"delay_between_unsaves": 3
|
||||
},
|
||||
"instagram": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
350
downloader.py
Normal file
350
downloader.py
Normal file
@@ -0,0 +1,350 @@
|
||||
"""
|
||||
Social Media Collection Downloader
|
||||
Downloads TikTok collections and Instagram saved posts, then removes them.
|
||||
Requires: yt-dlp, playwright, browser cookies exported via browser extension.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# ── Logging ──────────────────────────────────────────────────────────────────
|
||||
|
||||
LOG_DIR = Path(__file__).parent / "logs"
|
||||
LOG_DIR.mkdir(exist_ok=True)
|
||||
log_file = LOG_DIR / f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
||||
|
||||
import io
|
||||
_stream_handler = logging.StreamHandler(io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace"))
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler(log_file, encoding="utf-8"),
|
||||
_stream_handler,
|
||||
],
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# ── Config ────────────────────────────────────────────────────────────────────
|
||||
|
||||
CONFIG_PATH = Path(__file__).parent / "config" / "config.json"
|
||||
DOWNLOADS_DIR = Path(__file__).parent / "downloads"
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
if not CONFIG_PATH.exists():
|
||||
log.error(f"Config not found at {CONFIG_PATH}. Run: python setup.py")
|
||||
sys.exit(1)
|
||||
with open(CONFIG_PATH) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
# ── yt-dlp helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
def build_ytdlp_cmd(url: str, output_dir: Path, cookies_file: Optional[str] = None, cookies_from_browser: Optional[str] = None) -> list:
|
||||
cmd = [
|
||||
"yt-dlp",
|
||||
"--no-warnings",
|
||||
"--quiet",
|
||||
"--progress",
|
||||
"-o", str(output_dir / "%(uploader)s - %(title).80s [%(id)s].%(ext)s"),
|
||||
"--write-info-json",
|
||||
"--no-overwrites",
|
||||
"--retries", "3",
|
||||
"--fragment-retries", "3",
|
||||
"--concurrent-fragments", "4",
|
||||
]
|
||||
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cmd += ["--cookies", cookies_file]
|
||||
elif cookies_from_browser:
|
||||
cmd += ["--cookies-from-browser", cookies_from_browser]
|
||||
|
||||
cmd.append(url)
|
||||
return cmd
|
||||
|
||||
|
||||
def download_url(url: str, output_dir: Path, cookies_file: Optional[str] = None, cookies_from_browser: Optional[str] = None) -> bool:
|
||||
cmd = build_ytdlp_cmd(url, output_dir, cookies_file, cookies_from_browser)
|
||||
log.info(f"Downloading: {url}")
|
||||
result = subprocess.run(cmd, capture_output=False, text=True)
|
||||
if result.returncode == 0:
|
||||
log.info(f"[OK] Downloaded: {url}")
|
||||
return True
|
||||
else:
|
||||
log.error(f"[FAIL] Failed: {url}")
|
||||
return False
|
||||
|
||||
|
||||
# ── TikTok ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_tiktok_collection_urls(collection_url: str, cookies_file: Optional[str], cookies_from_browser: Optional[str]) -> list[str]:
|
||||
"""Use yt-dlp to extract all video URLs from a TikTok collection/playlist."""
|
||||
cmd = [
|
||||
"yt-dlp",
|
||||
"--flat-playlist",
|
||||
"--print", "url",
|
||||
"--no-warnings",
|
||||
"--quiet",
|
||||
]
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cmd += ["--cookies", cookies_file]
|
||||
elif cookies_from_browser:
|
||||
cmd += ["--cookies-from-browser", cookies_from_browser]
|
||||
cmd.append(collection_url)
|
||||
|
||||
log.info(f"Fetching TikTok collection URLs from: {collection_url}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
urls = [line.strip() for line in result.stdout.splitlines() if line.strip().startswith("http")]
|
||||
log.info(f"Found {len(urls)} videos in collection")
|
||||
return urls
|
||||
|
||||
|
||||
def download_tiktok_collection(config: dict) -> list[str]:
|
||||
"""Download all videos from configured TikTok collections. Returns list of downloaded URLs."""
|
||||
tk_cfg = config.get("tiktok", {})
|
||||
if not tk_cfg.get("enabled", False):
|
||||
log.info("TikTok disabled in config, skipping.")
|
||||
return []
|
||||
|
||||
collections = tk_cfg.get("collections", [])
|
||||
if not collections:
|
||||
log.warning("No TikTok collections configured.")
|
||||
return []
|
||||
|
||||
cookies_file = tk_cfg.get("cookies_file")
|
||||
cookies_from_browser = tk_cfg.get("cookies_from_browser") # e.g. "chrome", "firefox"
|
||||
output_dir = DOWNLOADS_DIR / "tiktok"
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
downloaded_urls = []
|
||||
for collection_url in collections:
|
||||
urls = get_tiktok_collection_urls(collection_url, cookies_file, cookies_from_browser)
|
||||
for url in urls:
|
||||
success = download_url(url, output_dir, cookies_file, cookies_from_browser)
|
||||
if success:
|
||||
downloaded_urls.append(url)
|
||||
time.sleep(tk_cfg.get("delay_between_downloads", 2))
|
||||
|
||||
return downloaded_urls
|
||||
|
||||
|
||||
# ── Instagram ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_instagram_saved_urls(collection_url: str, cookies_file: Optional[str], cookies_from_browser: Optional[str]) -> list[str]:
|
||||
"""Use yt-dlp to extract all post URLs from an Instagram saved collection."""
|
||||
cmd = [
|
||||
"yt-dlp",
|
||||
"--flat-playlist",
|
||||
"--print", "url",
|
||||
"--no-warnings",
|
||||
"--quiet",
|
||||
]
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cmd += ["--cookies", cookies_file]
|
||||
elif cookies_from_browser:
|
||||
cmd += ["--cookies-from-browser", cookies_from_browser]
|
||||
cmd.append(collection_url)
|
||||
|
||||
log.info(f"Fetching Instagram saved URLs from: {collection_url}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
urls = [line.strip() for line in result.stdout.splitlines() if line.strip().startswith("http")]
|
||||
log.info(f"Found {len(urls)} posts in saved collection")
|
||||
return urls
|
||||
|
||||
|
||||
def download_instagram_collection(config: dict) -> list[str]:
|
||||
"""Download all posts from configured Instagram saved collections."""
|
||||
ig_cfg = config.get("instagram", {})
|
||||
if not ig_cfg.get("enabled", False):
|
||||
log.info("Instagram disabled in config, skipping.")
|
||||
return []
|
||||
|
||||
collections = ig_cfg.get("collections", [])
|
||||
if not collections:
|
||||
log.warning("No Instagram collections configured.")
|
||||
return []
|
||||
|
||||
cookies_file = ig_cfg.get("cookies_file")
|
||||
cookies_from_browser = ig_cfg.get("cookies_from_browser")
|
||||
output_dir = DOWNLOADS_DIR / "instagram"
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
downloaded_urls = []
|
||||
for collection_url in collections:
|
||||
urls = get_instagram_saved_urls(collection_url, cookies_file, cookies_from_browser)
|
||||
for url in urls:
|
||||
success = download_url(url, output_dir, cookies_file, cookies_from_browser)
|
||||
if success:
|
||||
downloaded_urls.append(url)
|
||||
time.sleep(ig_cfg.get("delay_between_downloads", 3))
|
||||
|
||||
return downloaded_urls
|
||||
|
||||
|
||||
# ── Unsave / Remove ───────────────────────────────────────────────────────────
|
||||
|
||||
def unsave_tiktok_videos(urls: list[str], config: dict):
|
||||
"""Use Playwright to unsave/unlike downloaded TikTok videos."""
|
||||
if not urls:
|
||||
return
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
log.error("Playwright not installed. Run: pip install playwright && playwright install chromium")
|
||||
return
|
||||
|
||||
tk_cfg = config.get("tiktok", {})
|
||||
cookies_file = tk_cfg.get("cookies_file")
|
||||
|
||||
log.info(f"Unsaving {len(urls)} TikTok videos...")
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=tk_cfg.get("headless", False))
|
||||
context = browser.new_context()
|
||||
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
with open(cookies_file) as f:
|
||||
raw = json.load(f)
|
||||
pw_cookies = []
|
||||
for c in raw:
|
||||
if "tiktok.com" in c.get("domain", ""):
|
||||
pw_cookies.append({
|
||||
"name": c["name"],
|
||||
"value": c["value"],
|
||||
"domain": c["domain"],
|
||||
"path": c.get("path", "/"),
|
||||
"httpOnly": c.get("httpOnly", False),
|
||||
"secure": c.get("secure", False),
|
||||
})
|
||||
context.add_cookies(pw_cookies)
|
||||
|
||||
page = context.new_page()
|
||||
|
||||
for url in urls:
|
||||
try:
|
||||
log.info(f"Unsaving: {url}")
|
||||
page.goto(url, wait_until="networkidle", timeout=30000)
|
||||
time.sleep(2)
|
||||
|
||||
# Try clicking bookmark/save button (TikTok uses aria-label)
|
||||
bookmark = page.query_selector('[data-e2e="bookmark-icon"], [aria-label*="Add to Favorites"], [aria-label*="Save"]')
|
||||
if bookmark:
|
||||
bookmark.click()
|
||||
time.sleep(1)
|
||||
log.info(f"[OK] Unsaved: {url}")
|
||||
else:
|
||||
log.warning(f"[WARN] Could not find bookmark button for: {url}")
|
||||
|
||||
time.sleep(tk_cfg.get("delay_between_unsaves", 2))
|
||||
except Exception as e:
|
||||
log.error(f"Error unsaving {url}: {e}")
|
||||
|
||||
browser.close()
|
||||
|
||||
|
||||
def unsave_instagram_posts(urls: list[str], config: dict):
|
||||
"""Use Playwright to unsave downloaded Instagram posts."""
|
||||
if not urls:
|
||||
return
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
log.error("Playwright not installed. Run: pip install playwright && playwright install chromium")
|
||||
return
|
||||
|
||||
ig_cfg = config.get("instagram", {})
|
||||
cookies_file = ig_cfg.get("cookies_file")
|
||||
|
||||
log.info(f"Unsaving {len(urls)} Instagram posts...")
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=ig_cfg.get("headless", False))
|
||||
context = browser.new_context(
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
with open(cookies_file) as f:
|
||||
raw = json.load(f)
|
||||
pw_cookies = []
|
||||
for c in raw:
|
||||
if "instagram.com" in c.get("domain", ""):
|
||||
pw_cookies.append({
|
||||
"name": c["name"],
|
||||
"value": c["value"],
|
||||
"domain": c["domain"],
|
||||
"path": c.get("path", "/"),
|
||||
"httpOnly": c.get("httpOnly", False),
|
||||
"secure": c.get("secure", False),
|
||||
})
|
||||
context.add_cookies(pw_cookies)
|
||||
|
||||
page = context.new_page()
|
||||
|
||||
for url in urls:
|
||||
try:
|
||||
log.info(f"Unsaving: {url}")
|
||||
page.goto(url, wait_until="networkidle", timeout=30000)
|
||||
time.sleep(2)
|
||||
|
||||
# Instagram save button - look for bookmark SVG button
|
||||
save_btn = page.query_selector('svg[aria-label="Remove"]')
|
||||
if not save_btn:
|
||||
save_btn = page.query_selector('[aria-label="Unsave"]')
|
||||
if not save_btn:
|
||||
# Try finding bookmark icon that's currently "saved" (filled state)
|
||||
save_btn = page.query_selector('button svg[aria-label*="Save"]')
|
||||
|
||||
if save_btn:
|
||||
save_btn.click()
|
||||
time.sleep(1)
|
||||
log.info(f"[OK] Unsaved: {url}")
|
||||
else:
|
||||
log.warning(f"[WARN] Could not find save button for: {url}")
|
||||
|
||||
time.sleep(ig_cfg.get("delay_between_unsaves", 3))
|
||||
except Exception as e:
|
||||
log.error(f"Error unsaving {url}: {e}")
|
||||
|
||||
browser.close()
|
||||
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
log.info("=" * 60)
|
||||
log.info("Social Media Collection Downloader — Starting")
|
||||
log.info("=" * 60)
|
||||
|
||||
config = load_config()
|
||||
|
||||
# Download TikTok
|
||||
tiktok_downloaded = download_tiktok_collection(config)
|
||||
log.info(f"TikTok: downloaded {len(tiktok_downloaded)} videos")
|
||||
|
||||
# Download Instagram
|
||||
instagram_downloaded = download_instagram_collection(config)
|
||||
log.info(f"Instagram: downloaded {len(instagram_downloaded)} posts")
|
||||
|
||||
# Unsave TikTok videos
|
||||
if config.get("tiktok", {}).get("unsave_after_download", True):
|
||||
unsave_tiktok_videos(tiktok_downloaded, config)
|
||||
|
||||
# Unsave Instagram posts
|
||||
if config.get("instagram", {}).get("unsave_after_download", True):
|
||||
unsave_instagram_posts(instagram_downloaded, config)
|
||||
|
||||
log.info("=" * 60)
|
||||
log.info(f"Done. TikTok: {len(tiktok_downloaded)} | Instagram: {len(instagram_downloaded)}")
|
||||
log.info(f"Log saved to: {log_file}")
|
||||
log.info("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
downloads/tiktok/wbr.marina - #fyp [7630954438395563296].mp4
Normal file
BIN
downloads/tiktok/wbr.marina - #fyp [7630954438395563296].mp4
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
downloads/tiktok/wbr.marina - #fyp [7632413865589869857].mp4
Normal file
BIN
downloads/tiktok/wbr.marina - #fyp [7632413865589869857].mp4
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
7
logs/session_20260509_195901.log
Normal file
7
logs/session_20260509_195901.log
Normal file
@@ -0,0 +1,7 @@
|
||||
2026-05-09 19:59:01,573 [INFO] ============================================================
|
||||
2026-05-09 19:59:01,573 [INFO] Social Media Collection Downloader — Starting
|
||||
2026-05-09 19:59:01,573 [INFO] ============================================================
|
||||
2026-05-09 19:59:01,578 [INFO] Fetching TikTok collection URLs from: https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824
|
||||
2026-05-09 19:59:04,174 [INFO] Found 24 videos in collection
|
||||
2026-05-09 19:59:04,174 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
|
||||
2026-05-09 19:59:12,895 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
|
||||
112
logs/session_20260509_200029.log
Normal file
112
logs/session_20260509_200029.log
Normal file
@@ -0,0 +1,112 @@
|
||||
2026-05-09 20:00:29,709 [INFO] ============================================================
|
||||
2026-05-09 20:00:29,709 [INFO] Social Media Collection Downloader — Starting
|
||||
2026-05-09 20:00:29,709 [INFO] ============================================================
|
||||
2026-05-09 20:00:29,710 [INFO] Fetching TikTok collection URLs from: https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824
|
||||
2026-05-09 20:00:32,465 [INFO] Found 24 videos in collection
|
||||
2026-05-09 20:00:32,465 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
|
||||
2026-05-09 20:00:35,099 [INFO] [OK] Downloaded: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
|
||||
2026-05-09 20:00:40,100 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
|
||||
2026-05-09 20:00:42,958 [INFO] [OK] Downloaded: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
|
||||
2026-05-09 20:00:47,959 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7634839871621926146
|
||||
2026-05-09 20:00:51,768 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7634839871621926146
|
||||
2026-05-09 20:00:56,769 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7628766610232331542
|
||||
2026-05-09 20:01:00,315 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7628766610232331542
|
||||
2026-05-09 20:01:05,316 [INFO] Downloading: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
|
||||
2026-05-09 20:01:08,993 [INFO] [OK] Downloaded: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
|
||||
2026-05-09 20:01:13,994 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7632954468690169110
|
||||
2026-05-09 20:01:18,790 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7632954468690169110
|
||||
2026-05-09 20:01:23,795 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
|
||||
2026-05-09 20:01:29,037 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
|
||||
2026-05-09 20:01:34,038 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7632319995040697623
|
||||
2026-05-09 20:01:38,456 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7632319995040697623
|
||||
2026-05-09 20:01:43,457 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
|
||||
2026-05-09 20:01:47,424 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
|
||||
2026-05-09 20:01:52,425 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
|
||||
2026-05-09 20:01:55,467 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
|
||||
2026-05-09 20:02:00,468 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
|
||||
2026-05-09 20:02:03,161 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
|
||||
2026-05-09 20:02:08,162 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631369508929735958
|
||||
2026-05-09 20:02:11,298 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631369508929735958
|
||||
2026-05-09 20:02:16,299 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631369724319878422
|
||||
2026-05-09 20:02:19,751 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631369724319878422
|
||||
2026-05-09 20:02:24,752 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631086293740555542
|
||||
2026-05-09 20:02:28,535 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631086293740555542
|
||||
2026-05-09 20:02:33,538 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631088690676256022
|
||||
2026-05-09 20:02:36,885 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631088690676256022
|
||||
2026-05-09 20:02:41,886 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7630387450480708886
|
||||
2026-05-09 20:02:45,551 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7630387450480708886
|
||||
2026-05-09 20:02:50,552 [INFO] Downloading: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
|
||||
2026-05-09 20:02:53,297 [INFO] [OK] Downloaded: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
|
||||
2026-05-09 20:02:58,297 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7627690853955489027
|
||||
2026-05-09 20:03:01,688 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7627690853955489027
|
||||
2026-05-09 20:03:06,689 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7625324579522710806
|
||||
2026-05-09 20:03:09,988 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7625324579522710806
|
||||
2026-05-09 20:03:14,989 [INFO] Downloading: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
|
||||
2026-05-09 20:03:17,678 [INFO] [OK] Downloaded: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
|
||||
2026-05-09 20:03:22,679 [INFO] Downloading: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
|
||||
2026-05-09 20:03:26,337 [INFO] [OK] Downloaded: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
|
||||
2026-05-09 20:03:31,337 [INFO] Downloading: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
|
||||
2026-05-09 20:03:34,215 [INFO] [OK] Downloaded: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
|
||||
2026-05-09 20:03:39,216 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7626716959744331030
|
||||
2026-05-09 20:03:42,528 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7626716959744331030
|
||||
2026-05-09 20:03:47,529 [INFO] Downloading: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
|
||||
2026-05-09 20:03:51,154 [INFO] [OK] Downloaded: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
|
||||
2026-05-09 20:03:56,155 [INFO] TikTok: downloaded 24 videos
|
||||
2026-05-09 20:03:56,155 [INFO] Instagram disabled in config, skipping.
|
||||
2026-05-09 20:03:56,155 [INFO] Instagram: downloaded 0 posts
|
||||
2026-05-09 20:03:56,479 [INFO] Unsaving 24 TikTok videos...
|
||||
2026-05-09 20:03:58,160 [INFO] Unsaving: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
|
||||
2026-05-09 20:04:07,384 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
|
||||
2026-05-09 20:04:10,386 [INFO] Unsaving: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
|
||||
2026-05-09 20:04:15,427 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
|
||||
2026-05-09 20:04:18,428 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7634839871621926146
|
||||
2026-05-09 20:04:23,982 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7634839871621926146
|
||||
2026-05-09 20:04:27,077 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7628766610232331542
|
||||
2026-05-09 20:04:34,892 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7628766610232331542
|
||||
2026-05-09 20:04:37,892 [INFO] Unsaving: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
|
||||
2026-05-09 20:04:42,765 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
|
||||
2026-05-09 20:04:45,765 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7632954468690169110
|
||||
2026-05-09 20:04:51,523 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7632954468690169110
|
||||
2026-05-09 20:04:54,524 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
|
||||
2026-05-09 20:05:02,892 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
|
||||
2026-05-09 20:05:05,892 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7632319995040697623
|
||||
2026-05-09 20:05:12,095 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7632319995040697623: Page.goto: Target page, context or browser has been closed
|
||||
Call log:
|
||||
- navigating to "https://www.tiktok.com/@anniieerose/video/7632319995040697623", waiting until "networkidle"
|
||||
|
||||
2026-05-09 20:05:12,096 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
|
||||
2026-05-09 20:05:12,101 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7631687184290024736: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,105 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
|
||||
2026-05-09 20:05:12,126 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7632413865589869857: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,129 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
|
||||
2026-05-09 20:05:12,133 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7631687398547590432: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,138 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631369508929735958
|
||||
2026-05-09 20:05:12,143 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631369508929735958: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,144 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631369724319878422
|
||||
2026-05-09 20:05:12,160 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631369724319878422: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,160 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631086293740555542
|
||||
2026-05-09 20:05:12,163 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631086293740555542: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,164 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631088690676256022
|
||||
2026-05-09 20:05:12,200 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631088690676256022: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,206 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7630387450480708886
|
||||
2026-05-09 20:05:12,211 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7630387450480708886: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,211 [INFO] Unsaving: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
|
||||
2026-05-09 20:05:12,219 [ERROR] Error unsaving https://www.tiktok.com/@hannahxgda/video/7628933958373215521: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,220 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7627690853955489027
|
||||
2026-05-09 20:05:12,224 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7627690853955489027: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,224 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7625324579522710806
|
||||
2026-05-09 20:05:12,229 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7625324579522710806: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,230 [INFO] Unsaving: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
|
||||
2026-05-09 20:05:12,239 [ERROR] Error unsaving https://www.tiktok.com/@romy_ronm/video/7612331993858116886: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,241 [INFO] Unsaving: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
|
||||
2026-05-09 20:05:12,244 [ERROR] Error unsaving https://www.tiktok.com/@romy_ronm/video/7626392072790035745: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,247 [INFO] Unsaving: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
|
||||
2026-05-09 20:05:12,264 [ERROR] Error unsaving https://www.tiktok.com/@latenightfinds603/video/7626247102334586143: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,269 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7626716959744331030
|
||||
2026-05-09 20:05:12,277 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7626716959744331030: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:12,277 [INFO] Unsaving: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
|
||||
2026-05-09 20:05:12,281 [ERROR] Error unsaving https://www.tiktok.com/@twinsonice_official/video/7625674301684894998: Page.goto: Target page, context or browser has been closed
|
||||
2026-05-09 20:05:13,047 [INFO] ============================================================
|
||||
2026-05-09 20:05:13,047 [INFO] Done. TikTok: 24 | Instagram: 0
|
||||
2026-05-09 20:05:13,051 [INFO] Log saved to: C:\Users\timoh\Documents\New project\logs\session_20260509_200029.log
|
||||
2026-05-09 20:05:13,051 [INFO] ============================================================
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
yt-dlp>=2024.1.1
|
||||
playwright>=1.40.0
|
||||
164
setup.py
Normal file
164
setup.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Setup wizard for Social Media Collection Downloader.
|
||||
Run this first to configure your collections, cookies, and options.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_DIR = Path(__file__).parent / "config"
|
||||
CONFIG_PATH = CONFIG_DIR / "config.json"
|
||||
CONFIG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def check_dependency(cmd: str, name: str, install_hint: str) -> bool:
|
||||
try:
|
||||
subprocess.run([cmd, "--version"], capture_output=True, check=True)
|
||||
print(f" ✓ {name} found")
|
||||
return True
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
print(f" ✗ {name} not found — {install_hint}")
|
||||
return False
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
print("\n── Checking dependencies ──────────────────────────────────")
|
||||
ok = True
|
||||
ok &= check_dependency("yt-dlp", "yt-dlp", "pip install yt-dlp")
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
print(" ✓ Playwright found")
|
||||
except ImportError:
|
||||
print(" ✗ Playwright not found — pip install playwright && playwright install chromium")
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
|
||||
def prompt(question: str, default: str = "") -> str:
|
||||
if default:
|
||||
val = input(f" {question} [{default}]: ").strip()
|
||||
return val if val else default
|
||||
else:
|
||||
return input(f" {question}: ").strip()
|
||||
|
||||
|
||||
def prompt_bool(question: str, default: bool = True) -> bool:
|
||||
d = "Y/n" if default else "y/N"
|
||||
val = input(f" {question} [{d}]: ").strip().lower()
|
||||
if not val:
|
||||
return default
|
||||
return val in ("y", "yes", "1", "true")
|
||||
|
||||
|
||||
def setup_platform(platform: str) -> dict:
|
||||
print(f"\n── {platform} Setup ─────────────────────────────────────────")
|
||||
cfg: dict = {}
|
||||
|
||||
enabled = prompt_bool(f"Enable {platform}?", True)
|
||||
cfg["enabled"] = enabled
|
||||
if not enabled:
|
||||
return cfg
|
||||
|
||||
print(f"""
|
||||
How to get your cookies:
|
||||
1. Install "Get cookies.txt LOCALLY" browser extension
|
||||
2. Log into {platform.lower()}.com
|
||||
3. Click the extension and export cookies as JSON (or Netscape .txt)
|
||||
4. Save the file anywhere and enter the path below
|
||||
|
||||
OR: let yt-dlp read cookies directly from your browser (Chrome/Firefox)
|
||||
(your browser must be closed or the profile must be accessible)
|
||||
""")
|
||||
|
||||
method = prompt("Cookie method — (1) cookies file (2) browser auto-read", "1")
|
||||
|
||||
if method == "2":
|
||||
browser = prompt("Browser name (chrome / firefox / edge / safari)", "chrome")
|
||||
cfg["cookies_from_browser"] = browser
|
||||
cfg["cookies_file"] = None
|
||||
else:
|
||||
cookies_path = prompt("Path to cookies file (JSON or Netscape txt)", "")
|
||||
cfg["cookies_file"] = cookies_path if cookies_path else None
|
||||
cfg["cookies_from_browser"] = None
|
||||
|
||||
print(f"\n Enter your {platform} collection/saved URLs.")
|
||||
print(" TikTok: Go to your profile → Collections → open a collection → copy URL")
|
||||
print(" Instagram: Go to profile → Saved → open a collection → copy URL")
|
||||
print(" (Enter one per line, blank line when done)\n")
|
||||
|
||||
collections = []
|
||||
while True:
|
||||
url = input(" Collection URL (or press Enter to finish): ").strip()
|
||||
if not url:
|
||||
break
|
||||
if url.startswith("http"):
|
||||
collections.append(url)
|
||||
print(f" Added: {url}")
|
||||
else:
|
||||
print(" ⚠ That doesn't look like a URL, skipping.")
|
||||
|
||||
cfg["collections"] = collections
|
||||
|
||||
cfg["unsave_after_download"] = prompt_bool("Unsave/remove posts after downloading?", True)
|
||||
cfg["headless"] = prompt_bool("Run browser in headless mode (invisible) for unsaving?", False)
|
||||
|
||||
delay_dl = prompt("Seconds to wait between downloads (avoid rate limits)", "2")
|
||||
cfg["delay_between_downloads"] = int(delay_dl) if delay_dl.isdigit() else 2
|
||||
|
||||
delay_un = prompt("Seconds to wait between unsaves", "3")
|
||||
cfg["delay_between_unsaves"] = int(delay_un) if delay_un.isdigit() else 3
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def save_config(config: dict):
|
||||
with open(CONFIG_PATH, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
print(f"\n ✓ Config saved to {CONFIG_PATH}")
|
||||
|
||||
|
||||
def print_banner():
|
||||
print("""
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ Social Media Collection Downloader — Setup ║
|
||||
║ TikTok & Instagram → Download → Auto-unsave ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
print_banner()
|
||||
deps_ok = check_dependencies()
|
||||
if not deps_ok:
|
||||
print("\n Please install missing dependencies first, then re-run setup.py")
|
||||
print("\n Quick install:")
|
||||
print(" pip install yt-dlp playwright")
|
||||
print(" playwright install chromium\n")
|
||||
sys.exit(1)
|
||||
|
||||
config = {}
|
||||
config["tiktok"] = setup_platform("TikTok")
|
||||
config["instagram"] = setup_platform("Instagram")
|
||||
|
||||
print("\n── Summary ─────────────────────────────────────────────────")
|
||||
for platform in ["tiktok", "instagram"]:
|
||||
p = config[platform]
|
||||
status = "✓ Enabled" if p.get("enabled") else "✗ Disabled"
|
||||
n = len(p.get("collections", []))
|
||||
print(f" {platform.capitalize()}: {status} | {n} collection(s)")
|
||||
|
||||
if prompt_bool("\n Save this config and run now?", True):
|
||||
save_config(config)
|
||||
print("\n Running downloader...\n")
|
||||
subprocess.run([sys.executable, str(Path(__file__).parent / "downloader.py")])
|
||||
else:
|
||||
save_config(config)
|
||||
print("\n Config saved. Run later with:")
|
||||
print(" python downloader.py\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user