Files
post2bsky/generate_tiktok_cookies.py
Guillem Hernandez Sola 5cf0b6e915 Cookies
2026-05-19 11:01:51 +02:00

295 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
generate_tiktok_cookies.py
──────────────────────────
Opens a real (headed) Chromium browser, navigates to TikTok,
and waits for you to:
1. Log in manually
2. Solve any CAPTCHA
3. Reach the TikTok home feed
Then it saves the session cookies to tiktok_cookies.json
so tiktok2bsky.py can reuse them without a browser.
Usage:
python generate_tiktok_cookies.py
python generate_tiktok_cookies.py --output my_cookies.json
python generate_tiktok_cookies.py --handle jijantesfc
"""
import argparse
import json
import logging
import os
import sys
import time
# ─────────────────────────────────────────────
# Config
# ─────────────────────────────────────────────
DEFAULT_OUTPUT_PATH = "tiktok_cookies.json"
TIKTOK_LOGIN_URL = "https://www.tiktok.com/login"
TIKTOK_HOME_URL = "https://www.tiktok.com"
POLL_INTERVAL_S = 2.0 # how often to check if login is complete
LOGIN_TIMEOUT_S = 300 # max seconds to wait for manual login (5 min)
# Selectors that indicate a successful login
LOGGED_IN_SELECTORS = [
'[data-e2e="profile-icon"]',
'[data-e2e="nav-profile"]',
'a[href*="/profile"]',
'[class*="DivAvatarContainer"]',
'[class*="avatar-wrapper"]',
'button:has-text("Upload")',
'button:has-text("Cargar")',
'[data-e2e="upload-icon"]',
]
# ─────────────────────────────────────────────
# Logging
# ─────────────────────────────────────────────
logging.basicConfig(
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
level=logging.INFO,
)
# ─────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────
def is_logged_in(page) -> bool:
"""Check if any known post-login selector is visible."""
for sel in LOGGED_IN_SELECTORS:
try:
if page.locator(sel).first.is_visible(timeout=1500):
logging.info(f"✅ Login detected via: {sel}")
return True
except Exception:
pass
return False
def wait_for_login(page, timeout_s: int = LOGIN_TIMEOUT_S) -> bool:
"""
Poll the page every POLL_INTERVAL_S seconds until a logged-in
selector appears or the timeout is reached.
"""
elapsed = 0
logging.info(
f"⏳ Waiting up to {timeout_s}s for you to log in "
"and solve any CAPTCHA..."
)
while elapsed < timeout_s:
if is_logged_in(page):
return True
time.sleep(POLL_INTERVAL_S)
elapsed += POLL_INTERVAL_S
remaining = timeout_s - elapsed
if elapsed % 30 < POLL_INTERVAL_S: # log reminder every ~30s
logging.info(
f" Still waiting... {remaining:.0f}s remaining. "
"Complete the login in the browser window."
)
return False
def normalise_cookies(raw_cookies: list) -> list:
"""
Normalise Playwright cookies to a clean JSON format
compatible with both tiktok2bsky.py and yt-dlp (Netscape-like).
Removes internal Playwright fields that cause issues.
"""
cleaned = []
for c in raw_cookies:
entry = {
"name": c.get("name", ""),
"value": c.get("value", ""),
"domain": c.get("domain", ".tiktok.com"),
"path": c.get("path", "/"),
"sameSite": c.get("sameSite", "None"),
"secure": c.get("secure", False),
"httpOnly": c.get("httpOnly", False),
}
if c.get("expires") and c["expires"] > 0:
entry["expirationDate"] = int(c["expires"])
cleaned.append(entry)
return cleaned
def save_cookies(cookies: list, output_path: str):
with open(output_path, "w", encoding="utf-8") as f:
json.dump(cookies, f, indent=2, ensure_ascii=False)
logging.info(f"💾 Saved {len(cookies)} cookies → {output_path}")
def navigate_to_profile(page, handle: str):
"""After login, optionally navigate to the target profile to warm up cookies."""
profile_url = f"https://www.tiktok.com/@{handle.lstrip('@')}"
logging.info(f"🌐 Navigating to target profile: {profile_url}")
try:
page.goto(profile_url, wait_until="domcontentloaded", timeout=30000)
time.sleep(3.0)
logging.info("✅ Profile page loaded — cookies are now profile-warmed.")
except Exception as e:
logging.warning(f"⚠️ Could not navigate to profile: {e}")
# ─────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="Generate tiktok_cookies.json by logging in manually."
)
parser.add_argument(
"--output", "-o",
default=DEFAULT_OUTPUT_PATH,
help=f"Output path for cookies JSON (default: {DEFAULT_OUTPUT_PATH})",
)
parser.add_argument(
"--handle",
default=None,
help=(
"TikTok handle to visit after login (e.g. jijantesfc). "
"Warms up the session cookies for that profile."
),
)
parser.add_argument(
"--timeout",
type=int,
default=LOGIN_TIMEOUT_S,
help=f"Seconds to wait for manual login (default: {LOGIN_TIMEOUT_S})",
)
args = parser.parse_args()
# ── Safety check: warn if output already exists ───────────────────
if os.path.exists(args.output):
logging.warning(
f"⚠️ '{args.output}' already exists and will be overwritten."
)
try:
from playwright.sync_api import sync_playwright
except ImportError:
logging.error(
"❌ playwright is not installed. Run: pip install playwright"
)
sys.exit(1)
logging.info("🚀 Launching headed Chromium browser...")
logging.info("=" * 60)
logging.info(" 👉 A browser window will open.")
logging.info(" 👉 Log in to TikTok manually.")
logging.info(" 👉 Solve any CAPTCHA or verification that appears.")
logging.info(" 👉 Once you reach the home feed, this script")
logging.info(" will detect it and save your cookies automatically.")
logging.info(" 👉 Do NOT close the browser window yourself.")
logging.info("=" * 60)
with sync_playwright() as p:
# ── Launch HEADED browser (visible window) ─────────────────────
# [[2]](#__2): Playwright headless=False for interactive login sessions
browser = p.chromium.launch(
headless=False, # ← must be False so you can interact
slow_mo=50, # slight slowdown for stability
args=[
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-blink-features=AutomationControlled",
"--window-size=1280,900",
],
)
context = browser.new_context(
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
viewport={"width": 1280, "height": 900},
locale="es-ES",
timezone_id="Europe/Madrid",
)
page = context.new_page()
# ── Navigate to TikTok login ───────────────────────────────────
logging.info(f"🌐 Opening TikTok login page: {TIKTOK_LOGIN_URL}")
try:
page.goto(TIKTOK_LOGIN_URL, wait_until="domcontentloaded",
timeout=30000)
except Exception as e:
logging.error(f"❌ Failed to open TikTok login page: {e}")
browser.close()
sys.exit(1)
# ── Wait for manual login ──────────────────────────────────────
# [[3]](#__3): Playwright storage_state / context.cookies() for session persistence
logged_in = wait_for_login(page, timeout_s=args.timeout)
if not logged_in:
logging.error(
f"❌ Login not detected within {args.timeout}s. "
"Cookies NOT saved. Please try again."
)
browser.close()
sys.exit(1)
logging.info("🎉 Login confirmed!")
# ── Optional: warm up cookies on target profile ────────────────
if args.handle:
navigate_to_profile(page, args.handle)
# ── Give TikTok a moment to set all session cookies ───────────
logging.info("⏳ Waiting 3s for all session cookies to settle...")
time.sleep(3.0)
# ── Extract and save cookies ───────────────────────────────────
raw_cookies = context.cookies()
if not raw_cookies:
logging.error("❌ No cookies found in context. Something went wrong.")
browser.close()
sys.exit(1)
tiktok_cookies = [
c for c in raw_cookies
if "tiktok.com" in c.get("domain", "")
]
logging.info(
f"🍪 Extracted {len(tiktok_cookies)} TikTok cookies "
f"(out of {len(raw_cookies)} total)."
)
normalised = normalise_cookies(tiktok_cookies)
save_cookies(normalised, args.output)
# ── Also save Playwright storage_state as backup ───────────────
storage_path = args.output.replace(".json", "_storage_state.json")
context.storage_state(path=storage_path)
logging.info(f"💾 Full storage state (backup) → {storage_path}")
browser.close()
logging.info("")
logging.info("=" * 60)
logging.info(f"✅ Done! Cookies saved to: {args.output}")
logging.info(f" Storage state saved to: {storage_path}")
logging.info("")
logging.info("Next steps:")
logging.info(f" 1. Copy '{args.output}' to your Jenkins workspace")
logging.info(" or add it as a Jenkins Secret File credential.")
logging.info(
" 2. Run tiktok2bsky.py — it will load cookies automatically."
)
logging.info(
" 3. Cookies typically last 3090 days. Re-run this script"
)
logging.info(" when the bot starts hitting CAPTCHAs again.")
logging.info("=" * 60)
if __name__ == "__main__":
main()