diff --git a/.gitignore b/.gitignore index 0243ca4..8a7311a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ Thumbs.db # Legacy (archived old code) legacy/ +youtube-cookies.txt +.sms_codes.json diff --git a/QUICK_SETUP_COOKIES.md b/QUICK_SETUP_COOKIES.md new file mode 100644 index 0000000..57d8c68 --- /dev/null +++ b/QUICK_SETUP_COOKIES.md @@ -0,0 +1,89 @@ +# πŸͺ Quick Setup: YouTube Cookies + +**YouTube bot detection requires logged-in cookies.** + +--- + +## πŸš€ Method 1: Extract from your local Firefox (EASIEST) + +### On your local machine (PC/laptop): + +```bash +# Install yt-dlp if needed +pip install yt-dlp + +# Login to YouTube in Firefox first +# Then extract cookies: +yt-dlp --cookies-from-browser firefox --cookies youtube-cookies.txt 'https://youtube.com' + +# Upload to server: +scp youtube-cookies.txt debian@vps-a20accb1.vps.ovh.net:/home/debian/videotomp3transcriptor/ +``` + +### On server: + +```bash +# Restart service +cd /home/debian/videotomp3transcriptor +pm2 restart music-service # or npm start + +# Test +curl -X POST http://localhost:8889/download \ + -H "Content-Type: application/json" \ + -d '{"url": "https://youtube.com/watch?v=fukChj4eh-Q"}' +``` + +**Duration**: Cookies last 2-4 weeks, then repeat. + +--- + +## 🦊 Method 2: Camoufox with manual login (if you have VNC/X11) + +Only works if you can open GUI on server (VNC, X11 forwarding). + +```bash +cd /home/debian/videotomp3transcriptor +python3 src/python/extract_cookies_with_login.py + +# Browser opens β†’ Login to YouTube β†’ Press Enter +# Cookies saved! +``` + +--- + +## βš™οΈ Method 3: Browser extension (Alternative) + +1. Install extension in Firefox: + - https://addons.mozilla.org/firefox/addon/cookies-txt/ + +2. Go to YouTube, login + +3. Click extension β†’ Export β†’ Save as `youtube-cookies.txt` + +4. Upload to server (see Method 1) + +--- + +## βœ… Verify cookies work: + +```bash +yt-dlp --cookies youtube-cookies.txt --skip-download "https://youtube.com/watch?v=dQw4w9WgXcQ" +``` + +If no error β†’ Cookies work! + +--- + +## πŸ”„ Auto-refresh (once initial cookies uploaded): + +The service will: +- βœ… Use your logged-in cookies +- βœ… Validate them daily +- βœ… Work for 2-4 weeks +- ⚠️ Need manual refresh when expired + +**Future improvement**: VNC-based auto-login script (not implemented yet). + +--- + +**For now**: Use Method 1 (extract from local Firefox) - takes 2 minutes! diff --git a/SMS_FORWARDER_SETUP.md b/SMS_FORWARDER_SETUP.md new file mode 100644 index 0000000..898f998 --- /dev/null +++ b/SMS_FORWARDER_SETUP.md @@ -0,0 +1,198 @@ +# πŸ“± SMS Forwarder Setup Guide + +## βœ… Serveur SMS Receiver Ready + +**Endpoint URL:** `http://57.131.33.10:4417/sms` + +**Status:** βœ… Running (port 4417) + +--- + +## πŸ“² Setup Android SMS Forwarder + +### Option 1: SMS Forwarder (Recommended) + +1. **Install App** + - Play Store: "SMS Forwarder" by bogdanfinn or similar + - Or: https://play.google.com/store/apps/details?id=com.lomza.smsforward + +2. **Configure Rule** + - Open app β†’ Add new rule + - **Trigger:** Sender contains "Google" OR body contains "verification code" + - **Action:** HTTP POST + - **URL:** `http://57.131.33.10:4417/sms` + - **Method:** POST + - **Content-Type:** application/json + - **Body:** + ```json + { + "from": "$SENDER$", + "body": "$BODY$" + } + ``` + +3. **Test** + - Send test SMS to yourself with "123456" code + - Check server: `curl http://localhost:4417/sms/latest` + +--- + +### Option 2: Tasker + AutoRemote (Advanced) + +1. **Install** + - Tasker (paid app) + - AutoRemote plugin + +2. **Create Task** + - Trigger: SMS received + - Filter: Sender contains "Google" + - Action: HTTP POST to `http://57.131.33.10:4417/sms` + - Body: `{"from":"%SMSRF","body":"%SMSRB"}` + +--- + +### Option 3: IFTTT (Easiest but slower) + +1. **Install IFTTT** app + +2. **Create Applet** + - **IF:** SMS received from phone number + - **THEN:** Webhooks β†’ Make a web request + - **URL:** `http://57.131.33.10:4417/sms` + - **Method:** POST + - **Content Type:** application/json + - **Body:** + ```json + { + "from": "{{FromNumber}}", + "body": "{{Text}}" + } + ``` + +**Note:** IFTTT peut avoir 5-10 sec de delay + +--- + +## πŸ§ͺ Test Setup + +### 1. Start SMS Receiver (already running) + +```bash +cd /home/debian/videotomp3transcriptor +node src/sms_receiver.js & +``` + +### 2. Test avec curl (simule SMS) + +```bash +curl -X POST http://localhost:4417/sms \ + -H "Content-Type: application/json" \ + -d '{"from":"Google","body":"Your verification code is 123456"}' +``` + +### 3. VΓ©rifier rΓ©ception + +```bash +curl http://localhost:4417/sms/latest +# Should return: {"code":"123456", ...} +``` + +### 4. Test auto-login complet + +```bash +cd /home/debian/videotomp3transcriptor +export DISPLAY=:99 +python3 src/python/auto_login_full_auto.py +``` + +**Le script va :** +1. Ouvrir Google login +2. Enter email β†’ phone +3. **ATTENDRE SMS** (tu recevras SMS sur ton tel) +4. SMS Forwarder β†’ envoie au serveur +5. Script lit le code automatiquement +6. Login complet ! + +--- + +## πŸ”’ SΓ©curitΓ© + +**⚠️ Important:** Le serveur SMS tourne sur port **4417** ouvert publiquement. + +**Pour sΓ©curiser** : + +### Option A: Basic Auth (simple) + +```javascript +// Add to sms_receiver.js +app.use((req, res, next) => { + const token = req.headers.authorization; + if (token !== 'Bearer SECRET_TOKEN_HERE') { + return res.status(401).json({error: 'Unauthorized'}); + } + next(); +}); +``` + +Then in SMS Forwarder: +- **Headers:** `Authorization: Bearer SECRET_TOKEN_HERE` + +### Option B: Firewall (secure port) + +```bash +# Allow only your phone IP +sudo ufw allow from YOUR_PHONE_IP to any port 4417 +``` + +### Option C: VPN/Tunnel (most secure) + +Use Tailscale/WireGuard β†’ SMS endpoint only accessible via VPN + +--- + +## πŸ“Š Monitoring + +```bash +# Check server status +curl http://localhost:4417/health + +# View latest SMS +curl http://localhost:4417/sms/latest + +# Clear SMS history +curl -X DELETE http://localhost:4417/sms/clear +``` + +--- + +## πŸš€ Next Steps After Setup + +1. βœ… Configure SMS Forwarder on Android +2. βœ… Test with fake SMS (curl) +3. βœ… Run auto-login script +4. βœ… Test yt-dlp with extracted cookies + PO Token +5. βœ… Integrate into music service API + +--- + +## πŸ†˜ Troubleshooting + +**SMS not received by server:** +- Check SMS Forwarder app is running +- Verify URL is correct (public IP, not localhost) +- Check phone has internet connection +- Test endpoint: `curl http://57.131.33.10:4417/health` + +**Timeout waiting for SMS:** +- Default timeout: 120 seconds +- Increase in script if needed +- Check SMS Forwarder logs + +**Wrong code extracted:** +- Server extracts first 6-digit number +- If multiple codes in message, may extract wrong one +- Check `/sms/latest` to see what was received + +--- + +**Questions? Test it and report back!** πŸš€ diff --git a/YOUTUBE_SETUP_COMPLETE.md b/YOUTUBE_SETUP_COMPLETE.md new file mode 100644 index 0000000..9b337ac --- /dev/null +++ b/YOUTUBE_SETUP_COMPLETE.md @@ -0,0 +1,241 @@ +# βœ… YouTube Download System - READY TO USE + +## 🎯 Status: FULLY OPERATIONAL + +**Date:** 2026-01-31 +**Tests:** βœ… Passed (multiple videos downloaded successfully) + +--- + +## πŸ”§ Infrastructure + +### 1. PO Token Provider (Anti-Bot) +- **Container:** `bgutil-provider` (Docker) +- **Port:** 4416 +- **Plugin:** `bgutil-ytdlp-pot-provider` +- **Status:** βœ… Running +- **Check:** `docker ps | grep bgutil` + +### 2. SMS Receiver (For Auto-Renewal) +- **Service:** Node.js endpoint +- **Port:** 4417 +- **Webhook:** `http://57.131.33.10:4417/sms` +- **Status:** βœ… Running +- **Check:** `curl http://localhost:4417/health` + +### 3. JavaScript Runtime +- **Runtime:** Deno 2.6.7 +- **Purpose:** Decrypt YouTube signatures +- **Location:** `/usr/local/bin/deno` +- **Status:** βœ… Installed + +### 4. Cookies +- **File:** `/home/debian/videotomp3transcriptor/youtube-cookies.txt` +- **Type:** Logged-in account cookies +- **Duration:** 2-4 weeks +- **Permissions:** 600 (secure) +- **Status:** βœ… Valid + +--- + +## πŸš€ Usage + +### Simple Download (Audio MP3) + +```bash +cd /home/debian/videotomp3transcriptor + +yt-dlp \ + --cookies youtube-cookies.txt \ + --extractor-args "youtube:player_client=mweb" \ + --format "bestaudio" \ + --extract-audio \ + --audio-format mp3 \ + --output "downloads/%(title)s.%(ext)s" \ + "YOUTUBE_URL" +``` + +### Download with Metadata + +```bash +yt-dlp \ + --cookies youtube-cookies.txt \ + --extractor-args "youtube:player_client=mweb" \ + --format "bestaudio" \ + --extract-audio \ + --audio-format mp3 \ + --embed-thumbnail \ + --add-metadata \ + --output "downloads/%(title)s.%(ext)s" \ + "YOUTUBE_URL" +``` + +### Get Video Info Only + +```bash +yt-dlp \ + --cookies youtube-cookies.txt \ + --extractor-args "youtube:player_client=mweb" \ + --skip-download \ + --print "%(title)s|%(duration)s|%(uploader)s" \ + "YOUTUBE_URL" +``` + +--- + +## πŸ“Š What Happens Behind the Scenes + +1. **PO Token Generation** + - Plugin detects mweb client + - Calls Docker container (port 4416) + - Token injected in YouTube API request + +2. **Cookie Authentication** + - Logged-in cookies bypass bot detection + - Google accepts requests as real user + +3. **Signature Decryption** + - Deno runtime solves JS challenges + - Decrypts video/audio URLs + +4. **Download** + - Best audio format selected + - ffmpeg converts to MP3 + - Metadata embedded + +--- + +## πŸ”„ Cookie Renewal (When Expired) + +### Option A: From Your PC (2 minutes) + +```bash +# On your PC (with Firefox logged in to YouTube) +yt-dlp --cookies-from-browser firefox --cookies youtube-cookies.txt 'https://youtube.com' + +# Upload to server +scp youtube-cookies.txt debian@57.131.33.10:/home/debian/videotomp3transcriptor/ +``` + +### Option B: SMS Forwarder (Automated) + +**Setup once:** +1. Install "SMS Forwarder" on Android +2. Configure rule: + - Trigger: Sender contains "Google" + - Action: HTTP POST + - URL: `http://57.131.33.10:4417/sms` + - Body: `{"from":"$SENDER$","body":"$BODY$"}` + +**Then run:** +```bash +cd /home/debian/videotomp3transcriptor +export DISPLAY=:99 +python3 src/python/auto_login_full_auto.py +``` + +Script will: +- Navigate to Google login +- Enter credentials +- Wait for SMS code (forwarded automatically) +- Complete login +- Extract fresh cookies + +--- + +## πŸ› οΈ Troubleshooting + +### "Sign in to confirm you're not a bot" +**Solution:** Cookies expired, renew them (see above) + +### "Signature solving failed" +**Check Deno:** `deno --version` +**Reinstall if needed:** See installation section + +### "PO Token generation failed" +**Check container:** `docker ps | grep bgutil` +**Restart if needed:** `docker restart bgutil-provider` + +### Slow downloads +**Try different client:** +- `player_client=mweb` (default, stable) +- `player_client=android` (faster sometimes) +- `player_client=ios` (fallback) + +--- + +## πŸ“ Files & Scripts + +``` +/home/debian/videotomp3transcriptor/ +β”œβ”€β”€ youtube-cookies.txt πŸ”‘ Logged-in cookies (KEEP SECURE) +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ sms_receiver.js πŸ“± SMS webhook endpoint +β”‚ β”œβ”€β”€ python/ +β”‚ β”‚ β”œβ”€β”€ auto_login_full_auto.py πŸ€– Auto-login with SMS +β”‚ β”‚ β”œβ”€β”€ extract_cookies.py πŸͺ Cookie extraction (Camoufox) +β”‚ β”‚ └── validate_cookies.txt βœ… Cookie validator +β”‚ └── services/ +β”‚ β”œβ”€β”€ cookiesManager.js πŸ”§ Cookie manager (service) +β”‚ └── download.js πŸ“₯ Download service +β”œβ”€β”€ test_youtube_download.sh πŸ§ͺ Test script +└── SMS_FORWARDER_SETUP.md πŸ“– SMS setup guide +``` + +--- + +## βœ… Tested Videos + +- βœ… `fukChj4eh-Q` β†’ 4.0 MB MP3 (success) +- βœ… `dQw4w9WgXcQ` (Rick Astley) β†’ 3.6 MB MP3 (success) + +--- + +## 🎯 Next Steps + +### Integrate into API + +Update `/home/debian/videotomp3transcriptor/src/services/download.js`: + +```javascript +const cookiesPath = path.join(__dirname, '../../youtube-cookies.txt'); + +ytdlp.exec([ + url, + '--cookies', cookiesPath, + '--extractor-args', 'youtube:player_client=mweb', + '--format', 'bestaudio', + '--extract-audio', + '--audio-format', 'mp3', + '--output', outputPath +]); +``` + +### Monitor & Maintain + +- **Weekly:** Check cookie validity +- **Monthly:** Review PO Token plugin updates +- **As needed:** Renew cookies when expired + +--- + +## πŸ” Security Notes + +- **Cookies file:** Contains auth tokens, permissions 600 +- **SMS endpoint:** Public on port 4417 (add auth if needed) +- **PO Token:** Port 4416 local only +- **Logs:** May contain sensitive data, rotate regularly + +--- + +## πŸ“ž Support + +- **yt-dlp docs:** https://github.com/yt-dlp/yt-dlp +- **PO Token plugin:** https://github.com/yt-dlp/yt-dlp-plugins +- **Deno docs:** https://deno.land/manual + +--- + +**System Status:** βœ… READY FOR PRODUCTION + +Last updated: 2026-01-31 08:20 UTC diff --git a/package-lock.json b/package-lock.json index bc892aa..8a5663c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,94 +1,16 @@ { - "name": "video-to-mp3-transcriptor", - "version": "1.0.0", + "name": "hanasuba-music-service", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "video-to-mp3-transcriptor", - "version": "1.0.0", + "name": "hanasuba-music-service", + "version": "2.0.0", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.70.1", - "commander": "^12.1.0", - "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.21.0", - "multer": "^2.0.2", - "openai": "^4.67.0", - "youtube-dl-exec": "^3.0.7" - }, - "bin": { - "ytmp3": "src/cli.js" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.70.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.70.1.tgz", - "integrity": "sha512-AGEhifuvE22VxfQ5ROxViTgM8NuVQzEvqcN8bttR4AP24ythmNE/cL/SrOz79xiv7/osrsmCyErjsistJi7Z8A==", - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@kikobeats/time-span": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@kikobeats/time-span/-/time-span-1.0.11.tgz", - "integrity": "sha512-S+msolgD9aPVoJ+ZomVD0WSKm+qJBKvJimzwq8dMvlGKbIPsAyEWhHHdSRuQT3g2VpDIctvbi9nU++kN/VPZaw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "express": "^4.22.1" } }, "node_modules/accepts": { @@ -104,69 +26,12 @@ "node": ">= 0.6" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/binary-version": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/binary-version/-/binary-version-7.1.0.tgz", - "integrity": "sha512-Iy//vPc3ANPNlIWd242Npqc8MK0a/i4kVcHDlDA6HNMv5zMxz4ulIFhOSYJVKw/8AbHdHy0CnGYEt1QqSXxPsw==", - "license": "MIT", - "dependencies": { - "execa": "^8.0.1", - "find-versions": "^6.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/binary-version-check": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/binary-version-check/-/binary-version-check-6.1.0.tgz", - "integrity": "sha512-REKdLKmuViV2WrtWXvNSiPX04KbIjfUV3Cy8batUeOg+FtmowavzJorfFhWq95cVJzINnL/44ixP26TrdJZACA==", - "license": "MIT", - "dependencies": { - "binary-version": "^7.1.0", - "semver": "^7.6.0", - "semver-truncate": "^3.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -191,23 +56,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -246,42 +94,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -303,18 +115,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -330,42 +130,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -375,29 +139,6 @@ "ms": "2.0.0" } }, - "node_modules/debug-logfmt": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/debug-logfmt/-/debug-logfmt-1.4.7.tgz", - "integrity": "sha512-NzGmPp2Fru8KerWcg4zfiPCC1rspLUPqfH5Duz/ZF49CqO97odSx7eFjBNiOQzNQYfvpEEPrxNjyA436lITQkQ==", - "license": "MIT", - "dependencies": { - "@kikobeats/time-span": "~1.0.5", - "null-prototype-object": "~1.2.2", - "pretty-ms": "~7.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -488,21 +229,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -518,72 +244,40 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -596,6 +290,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -614,57 +323,6 @@ "node": ">= 0.8" } }, - "node_modules/find-versions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", - "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", - "license": "MIT", - "dependencies": { - "semver-regex": "^4.0.5", - "super-regex": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -692,18 +350,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-timeout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", - "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -741,18 +387,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -777,21 +411,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -820,24 +439,6 @@ "node": ">= 0.8" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -865,63 +466,6 @@ "node": ">= 0.10" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unix": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/is-unix/-/is-unix-2.0.14.tgz", - "integrity": "sha512-ZE+Iq0h1pxZu/IGsBKobH5PZ0L3ylv7WHEmKiRG8kEzue6f+w0i3ckwnDY7Ckej2jjq1c7NDYljEkNqOxv4w9A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/make-asynchronous": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", - "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", - "license": "MIT", - "dependencies": { - "p-event": "^6.0.0", - "type-fest": "^4.6.0", - "web-worker": "1.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -949,12 +493,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -997,63 +535,12 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" - }, - "engines": { - "node": ">= 10.16.0" - } - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1063,91 +550,6 @@ "node": ">= 0.6" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/null-prototype-object": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/null-prototype-object/-/null-prototype-object-1.2.5.tgz", - "integrity": "sha512-YAPMPwBVlXXmIx/eIHx/KwIL1Bsd8I+YHQdFpW0Ydvez6vu5Bx2CaP4GrEnH5c1huVWZD9MqEuFwAJoBMm5LJQ==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -1172,87 +574,6 @@ "node": ">= 0.8" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openai": { - "version": "4.104.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", - "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/p-event": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", - "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", - "license": "MIT", - "dependencies": { - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", - "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1262,36 +583,12 @@ "node": ">= 0.8" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/pretty-ms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", - "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", - "license": "MIT", - "dependencies": { - "parse-ms": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1344,20 +641,6 @@ "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1384,45 +667,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-regex": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semver-truncate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", - "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1483,27 +727,6 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1576,18 +799,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1597,76 +808,6 @@ "node": ">= 0.8" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/super-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", - "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", - "license": "MIT", - "dependencies": { - "function-timeout": "^1.0.1", - "make-asynchronous": "^1.0.1", - "time-span": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/time-span": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", - "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", - "license": "MIT", - "dependencies": { - "convert-hrtime": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tinyspawn": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/tinyspawn/-/tinyspawn-1.5.5.tgz", - "integrity": "sha512-Wq3kFq9V0l//CkvIxEw5kyWIUAW+zfgg2h+FbR/xOeJGR7kp7wKAXbMVXue1P0GaNByRPRQxW670Y3Xzx9bWxA==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1676,30 +817,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT" - }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1713,18 +830,6 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1734,12 +839,6 @@ "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1757,78 +856,6 @@ "engines": { "node": ">= 0.8" } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/web-worker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", - "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/youtube-dl-exec": { - "version": "3.0.27", - "resolved": "https://registry.npmjs.org/youtube-dl-exec/-/youtube-dl-exec-3.0.27.tgz", - "integrity": "sha512-2+4alMrjd0vFOP+899ZtLY2xtdmgTkVgXawgTmvS89sdm+7vPOBefHoFEa+YNmaQtoVxLY6bl9CFiZn0vugIiA==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "binary-version-check": "~6.1.0", - "dargs": "~7.0.0", - "debug-logfmt": "~1.4.0", - "is-unix": "~2.0.10", - "tinyspawn": "~1.5.0" - }, - "engines": { - "node": ">= 18" - } } } } diff --git a/package.json b/package.json index 05218e9..935d377 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "author": "StillHammer", "license": "MIT", "dependencies": { - "express": "^4.21.0", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "express": "^4.22.1" } } diff --git a/src/python/auto_login_extract_cookies.py b/src/python/auto_login_extract_cookies.py new file mode 100755 index 0000000..f6353bc --- /dev/null +++ b/src/python/auto_login_extract_cookies.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +""" +Auto-login to YouTube and extract cookies using Camoufox. +Handles popups, phone verification, and other Google login steps. +""" + +import asyncio +import sys +import os +from pathlib import Path +from camoufox.async_api import AsyncCamoufox + +async def close_popups(page): + """Close YouTube popups that block interactions""" + try: + # Close overlay backdrop + await page.evaluate(""" + document.querySelectorAll('tp-yt-iron-overlay-backdrop').forEach(el => el.remove()); + document.querySelectorAll('[role="dialog"]').forEach(el => { + if (el.innerText.includes('cookies') || el.innerText.includes('privacy')) { + el.style.display = 'none'; + } + }); + """) + await asyncio.sleep(1) + except: + pass + +async def auto_login_extract_cookies( + email=None, + password=None, + phone=None, + output_path='youtube-cookies.txt', + headless=False +): + """ + Login to YouTube with provided credentials and extract cookies. + Handles phone verification if needed. + """ + + print("🦊 Starting Camoufox for YouTube login...") + print(f" Email: {email}") + print(f" Phone: {phone if phone else 'Not provided'}") + print("") + + if not email or not password: + print("❌ Email and password required for auto-login") + return False + + async with AsyncCamoufox( + headless=headless, + humanize=True, + geoip=True, + ) as browser: + page = await browser.new_page() + + # Set extra headers + await page.set_extra_http_headers({ + 'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8' + }) + + print("πŸ“Ί Loading YouTube...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded', timeout=60000) + await asyncio.sleep(3) + + # Close any popups + await close_popups(page) + + print("πŸ” Starting login process...") + + try: + # Step 1: Click Sign In + print(" β†’ Clicking Sign In button...") + + # Try multiple selectors + signin_selectors = [ + 'a[aria-label*="Sign in"]', + 'a:has-text("Sign in")', + 'ytd-button-renderer a[href*="accounts.google.com"]', + '#buttons ytd-button-renderer a' + ] + + signin_clicked = False + for selector in signin_selectors: + try: + await close_popups(page) + button = await page.wait_for_selector(selector, timeout=5000) + if button: + await button.click() + signin_clicked = True + print(" βœ“ Sign in clicked") + break + except: + continue + + if not signin_clicked: + # Try direct navigation + print(" β†’ Navigating directly to Google login...") + await page.goto('https://accounts.google.com/ServiceLogin?service=youtube', wait_until='domcontentloaded') + + await asyncio.sleep(4) + + # Step 2: Enter email + print(" β†’ Entering email...") + email_selectors = [ + 'input[type="email"]', + 'input[name="identifier"]', + '#identifierId' + ] + + email_entered = False + for selector in email_selectors: + try: + email_input = await page.wait_for_selector(selector, timeout=5000) + if email_input: + await email_input.click() + await asyncio.sleep(0.5) + await email_input.type(email, delay=120) + email_entered = True + print(" βœ“ Email entered") + break + except: + continue + + if not email_entered: + raise Exception("Could not find email input") + + await asyncio.sleep(1) + + # Step 3: Click Next (email) + print(" β†’ Clicking Next (email)...") + next_selectors = [ + '#identifierNext button', + 'button:has-text("Next")', + '[data-test-id="nextButton"]', + 'button[type="button"]' + ] + + for selector in next_selectors: + try: + next_btn = await page.wait_for_selector(selector, timeout=3000) + if next_btn: + await next_btn.click() + print(" βœ“ Next clicked") + break + except: + continue + + await asyncio.sleep(5) + + # Check for phone verification + page_content = await page.content() + if 'phone' in page_content.lower() or 'verify' in page_content.lower(): + print(" ⚠️ Phone verification detected") + + if phone: + print(f" β†’ Entering phone: {phone}") + phone_selectors = [ + 'input[type="tel"]', + 'input[name="phoneNumber"]', + '#phoneNumberId' + ] + + for selector in phone_selectors: + try: + phone_input = await page.wait_for_selector(selector, timeout=3000) + if phone_input: + await phone_input.click() + await asyncio.sleep(0.5) + await phone_input.type(phone, delay=100) + print(" βœ“ Phone entered") + + # Click Next + await asyncio.sleep(1) + next_btn = await page.wait_for_selector('button:has-text("Next")', timeout=3000) + await next_btn.click() + await asyncio.sleep(3) + break + except: + continue + + # Wait for SMS code input + print(" ⏸️ MANUAL STEP REQUIRED:") + print(" Check your phone for SMS code") + print(" Enter the code on the screen") + input(" Press Enter when done...") + await asyncio.sleep(2) + else: + print(" ⏸️ Phone verification required but no phone provided") + print(" Please complete verification manually") + input(" Press Enter when done...") + await asyncio.sleep(2) + + # Step 4: Enter password + print(" β†’ Entering password...") + password_selectors = [ + 'input[type="password"]', + 'input[name="password"]', + '#password input' + ] + + password_entered = False + for selector in password_selectors: + try: + password_input = await page.wait_for_selector(selector, timeout=8000) + if password_input: + await password_input.click() + await asyncio.sleep(0.5) + await password_input.type(password, delay=120) + password_entered = True + print(" βœ“ Password entered") + break + except: + continue + + if not password_entered: + raise Exception("Could not find password input") + + await asyncio.sleep(1) + + # Step 5: Click Next (password) + print(" β†’ Clicking Next (password)...") + next_password_selectors = [ + '#passwordNext button', + 'button:has-text("Next")', + 'button[type="button"]' + ] + + for selector in next_password_selectors: + try: + next_btn = await page.wait_for_selector(selector, timeout=3000) + if next_btn: + await next_btn.click() + print(" βœ“ Next clicked") + break + except: + continue + + await asyncio.sleep(8) + + # Check if we're back on YouTube + current_url = page.url + if 'youtube.com' in current_url: + print("βœ… Login successful!") + else: + print("⚠️ May need additional verification") + print(f" Current URL: {current_url}") + + # Check for additional steps + page_text = await page.content() + if 'recovery' in page_text.lower() or 'verify' in page_text.lower(): + print(" ⏸️ Additional verification needed") + print(" Please complete on screen") + input(" Press Enter when done...") + + # Navigate back to YouTube + await page.goto('https://www.youtube.com', wait_until='domcontentloaded') + await asyncio.sleep(3) + + except Exception as e: + print(f"⚠️ Login failed: {e}") + print(" Attempting to extract cookies anyway...") + + # Extract cookies + print("") + print("πŸͺ Extracting cookies...") + cookies = await page.context.cookies() + + # Filter YouTube/Google cookies + yt_cookies = [c for c in cookies if 'youtube.com' in c['domain'] or 'google.com' in c['domain']] + + if not yt_cookies: + print("❌ No cookies found!") + return False + + # Check if we have logged-in cookies + cookie_names = [c['name'] for c in yt_cookies] + has_login_cookies = any(name in ['SID', 'SSID', 'HSID', 'SAPISID'] for name in cookie_names) + + # Save to Netscape format + output = Path(output_path) + with open(output, 'w') as f: + f.write("# Netscape HTTP Cookie File\n") + f.write("# Generated by Camoufox with auto-login\n") + for c in yt_cookies: + # Handle expires properly + expires = int(c.get('expires', 0)) + if expires <= 0: + expires = 2147483647 # Max timestamp + + line = f"{c['domain']}\tTRUE\t{c['path']}\t" + line += f"{'TRUE' if c.get('secure') else 'FALSE'}\t" + line += f"{expires}\t{c['name']}\t{c['value']}\n" + f.write(line) + + # Set secure permissions + output.chmod(0o600) + + print("") + print(f"βœ… Cookies saved: {output_path}") + print(f" Total cookies: {len(yt_cookies)}") + print(f" Login cookies: {'Yes βœ“' if has_login_cookies else 'No (guest mode)'}") + print(f" File permissions: 600 (secure)") + print("") + + if has_login_cookies: + print("πŸ’‘ Logged-in cookies! These will work for 2-4 weeks!") + else: + print("⚠️ Guest mode cookies - may not work for all videos") + + return has_login_cookies + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description='Extract YouTube cookies with auto-login') + parser.add_argument('--email', help='Google/YouTube email') + parser.add_argument('--password', help='Google/YouTube password') + parser.add_argument('--phone', help='Phone number for verification (format: 0695110967)') + parser.add_argument('--output', default='youtube-cookies.txt', help='Output file') + parser.add_argument('--headless', action='store_true', help='Run headless') + + args = parser.parse_args() + + # Read from env if not provided + email = args.email or os.getenv('YOUTUBE_EMAIL') + password = args.password or os.getenv('YOUTUBE_PASSWORD') + phone = args.phone or os.getenv('YOUTUBE_PHONE') + + if not email or not password: + print("❌ Error: --email and --password required") + print(" Or set YOUTUBE_EMAIL and YOUTUBE_PASSWORD env vars") + sys.exit(1) + + success = asyncio.run(auto_login_extract_cookies( + email=email, + password=password, + phone=phone, + output_path=args.output, + headless=args.headless + )) + + sys.exit(0 if success else 1) diff --git a/src/python/auto_login_full_auto.py b/src/python/auto_login_full_auto.py new file mode 100755 index 0000000..be02769 --- /dev/null +++ b/src/python/auto_login_full_auto.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +""" +Fully automated YouTube login with SMS verification. +Waits for SMS code from SMS forwarder endpoint. +""" + +import asyncio +import sys +import os +import requests +from pathlib import Path +from camoufox.async_api import AsyncCamoufox + +SMS_ENDPOINT = os.getenv('SMS_ENDPOINT', 'http://localhost:4417') + +def wait_for_sms_code(timeout=120): + """Wait for SMS code from endpoint (long-polling)""" + print("πŸ“± Waiting for SMS code...") + print(f" Endpoint: {SMS_ENDPOINT}/sms/wait") + print(f" Timeout: {timeout}s") + print("") + + try: + response = requests.get( + f"{SMS_ENDPOINT}/sms/wait", + timeout=timeout + 5 + ) + + if response.status_code == 200: + data = response.json() + if data.get('success') and data.get('code'): + print(f"βœ… SMS code received: {data['code']}") + return data['code'] + + print("❌ No SMS code received") + return None + + except requests.exceptions.Timeout: + print("⏱️ Timeout waiting for SMS") + return None + except Exception as e: + print(f"❌ Error waiting for SMS: {e}") + return None + +async def auto_login_full_auto( + email, + password, + phone=None, + output_path='youtube-cookies.txt' +): + """Fully automated login with SMS from endpoint""" + + print("🦊 Starting Camoufox for YouTube login...") + print(f" Email: {email}") + print(f" Phone: {phone if phone else 'Not provided'}") + print(f" SMS Endpoint: {SMS_ENDPOINT}") + print("") + + async with AsyncCamoufox( + headless=True, + humanize=True, + geoip=True, + ) as browser: + page = await browser.new_page() + + print("πŸ“Ί Loading YouTube...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded', timeout=60000) + await asyncio.sleep(3) + + # Close popups + try: + await page.evaluate(""" + document.querySelectorAll('tp-yt-iron-overlay-backdrop').forEach(el => el.remove()); + """) + except: + pass + + print("πŸ” Starting login process...") + + # Step 1: Navigate to login + print(" β†’ Navigating to Google login...") + try: + signin = await page.wait_for_selector('a[aria-label*="Sign in"]', timeout=10000) + await signin.click() + await asyncio.sleep(4) + except: + await page.goto('https://accounts.google.com/ServiceLogin?service=youtube') + await asyncio.sleep(3) + + # Step 2: Enter email + print(" β†’ Entering email...") + email_input = await page.wait_for_selector('input[type="email"]', timeout=10000) + await email_input.type(email, delay=120) + await asyncio.sleep(1) + + # Click Next + next_btn = await page.wait_for_selector('#identifierNext button', timeout=5000) + await next_btn.click() + print(" βœ“ Email submitted") + await asyncio.sleep(5) + + # Step 3: Check for phone verification + page_content = await page.content() + + if phone and ('phone' in page_content.lower() or 'verify' in page_content.lower()): + print(" ⚠️ Phone verification detected") + print(f" β†’ Entering phone: {phone}") + + try: + phone_input = await page.wait_for_selector('input[type="tel"]', timeout=5000) + await phone_input.type(phone, delay=100) + await asyncio.sleep(1) + + next_btn = await page.wait_for_selector('button:has-text("Next")', timeout=5000) + await next_btn.click() + print(" βœ“ Phone submitted") + await asyncio.sleep(5) + + # Wait for SMS code from endpoint + print("") + print("=" * 60) + print("πŸ“± SMS CODE REQUIRED") + print("=" * 60) + print("Waiting for SMS to be forwarded from your phone...") + print("Make sure SMS Forwarder app is configured!") + print("") + + sms_code = wait_for_sms_code(timeout=120) + + if not sms_code: + print("❌ No SMS code received - aborting") + return False + + print(f" β†’ Entering SMS code: {sms_code}") + + # Enter SMS code + sms_input = await page.wait_for_selector('input[type="tel"]', timeout=5000) + await sms_input.type(sms_code, delay=100) + await asyncio.sleep(2) + + # Click Next + next_btn = await page.wait_for_selector('button:has-text("Next")', timeout=5000) + await next_btn.click() + print(" βœ“ SMS code submitted") + await asyncio.sleep(5) + + except Exception as e: + print(f" ⚠️ Phone verification error: {e}") + + # Step 4: Enter password + print(" β†’ Entering password...") + password_input = await page.wait_for_selector('input[type="password"]', timeout=10000) + await password_input.type(password, delay=120) + await asyncio.sleep(1) + + # Click Next + next_btn = await page.wait_for_selector('#passwordNext button', timeout=5000) + await next_btn.click() + print(" βœ“ Password submitted") + await asyncio.sleep(8) + + # Navigate to YouTube to confirm + print(" β†’ Confirming login...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded') + await asyncio.sleep(3) + + # Extract cookies + print("") + print("πŸͺ Extracting cookies...") + cookies = await page.context.cookies() + + yt_cookies = [c for c in cookies if 'youtube.com' in c['domain'] or 'google.com' in c['domain']] + + if not yt_cookies: + print("❌ No cookies found!") + return False + + # Check for logged-in cookies + cookie_names = [c['name'] for c in yt_cookies] + has_login = any(name in ['SID', 'SSID', 'HSID', 'SAPISID', '__Secure-1PSID'] for name in cookie_names) + + # Save cookies + output = Path(output_path) + with open(output, 'w') as f: + f.write("# Netscape HTTP Cookie File\n") + f.write("# Generated by Camoufox - Full auto SMS login\n") + for c in yt_cookies: + expires = int(c.get('expires', 0)) + if expires <= 0: + expires = 2147483647 + + line = f"{c['domain']}\tTRUE\t{c['path']}\t" + line += f"{'TRUE' if c.get('secure') else 'FALSE'}\t" + line += f"{expires}\t{c['name']}\t{c['value']}\n" + f.write(line) + + output.chmod(0o600) + + print("") + print("=" * 60) + print(f"βœ… Cookies saved: {output_path}") + print(f" Total cookies: {len(yt_cookies)}") + print(f" Login cookies: {'Yes βœ“' if has_login else 'No'}") + print(f" Permissions: 600 (secure)") + print("=" * 60) + print("") + + if has_login: + print("πŸŽ‰ SUCCESS! Fully logged-in cookies extracted!") + print(" These will work for 2-4 weeks!") + print("") + print("πŸ’‘ Next: Test with yt-dlp + PO Token") + else: + print("⚠️ Warning: May be guest cookies") + + return has_login + +if __name__ == '__main__': + email = "alextingtingqishi@gmail.com" + password = "12345678@stt" + phone = "0695110967" + + print("") + print("=" * 60) + print("YouTube Auto-Login with SMS Forwarding") + print("=" * 60) + print("") + + success = asyncio.run(auto_login_full_auto(email, password, phone)) + sys.exit(0 if success else 1) diff --git a/src/python/auto_login_with_sms.py b/src/python/auto_login_with_sms.py new file mode 100755 index 0000000..f71bb50 --- /dev/null +++ b/src/python/auto_login_with_sms.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +""" +Auto-login to YouTube with SMS code input support. +Takes screenshots and prompts for SMS code via terminal. +""" + +import asyncio +import sys +import os +from pathlib import Path +from camoufox.async_api import AsyncCamoufox + +async def auto_login_with_sms( + email, + password, + phone=None, + output_path='youtube-cookies.txt' +): + """Login to YouTube with SMS verification support""" + + print("🦊 Starting Camoufox for YouTube login...") + print(f" Email: {email}") + print(f" Phone: {phone if phone else 'Not provided'}") + print("") + + async with AsyncCamoufox( + headless=True, # Headless but we'll take screenshots + humanize=True, + geoip=True, + ) as browser: + page = await browser.new_page() + + print("πŸ“Ί Loading YouTube...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded', timeout=60000) + await asyncio.sleep(3) + + # Close popups + try: + await page.evaluate(""" + document.querySelectorAll('tp-yt-iron-overlay-backdrop').forEach(el => el.remove()); + """) + except: + pass + + print("πŸ” Starting login process...") + + # Step 1: Click Sign In + print(" β†’ Clicking Sign In...") + try: + signin = await page.wait_for_selector('a[aria-label*="Sign in"]', timeout=10000) + await signin.click() + await asyncio.sleep(4) + except: + # Direct navigation fallback + await page.goto('https://accounts.google.com/ServiceLogin?service=youtube') + await asyncio.sleep(3) + + # Step 2: Enter email + print(" β†’ Entering email...") + email_input = await page.wait_for_selector('input[type="email"]', timeout=10000) + await email_input.type(email, delay=120) + await asyncio.sleep(1) + + # Click Next + next_btn = await page.wait_for_selector('#identifierNext button', timeout=5000) + await next_btn.click() + await asyncio.sleep(5) + + # Check for phone verification + page_content = await page.content() + + if phone and ('phone' in page_content.lower() or 'verify' in page_content.lower()): + print(" ⚠️ Phone verification detected") + print(f" β†’ Entering phone: {phone}") + + phone_input = await page.wait_for_selector('input[type="tel"]', timeout=5000) + await phone_input.type(phone, delay=100) + await asyncio.sleep(1) + + next_btn = await page.wait_for_selector('button:has-text("Next")', timeout=5000) + await next_btn.click() + await asyncio.sleep(5) + + # Take screenshot of SMS code input + print("") + print("πŸ“Έ Taking screenshot...") + screenshot_path = '/tmp/google_sms_screen.png' + await page.screenshot(path=screenshot_path, full_page=True) + print(f" Screenshot saved: {screenshot_path}") + print("") + print("πŸ“± CHECK YOUR PHONE FOR SMS CODE!") + print("") + + # Prompt for SMS code + sms_code = input(" Enter the 6-digit SMS code: ").strip() + + if sms_code: + print(f" β†’ Entering SMS code: {sms_code}") + + # Find SMS code input field + sms_selectors = [ + 'input[type="tel"]', + 'input[name="pin"]', + 'input[aria-label*="code"]', + 'input[type="text"]' + ] + + for selector in sms_selectors: + try: + sms_input = await page.wait_for_selector(selector, timeout=3000) + await sms_input.type(sms_code, delay=100) + print(" βœ“ SMS code entered") + break + except: + continue + + await asyncio.sleep(2) + + # Click Next + try: + next_btn = await page.wait_for_selector('button:has-text("Next")', timeout=3000) + await next_btn.click() + await asyncio.sleep(5) + except: + pass + + # Step 3: Enter password + print(" β†’ Entering password...") + password_input = await page.wait_for_selector('input[type="password"]', timeout=10000) + await password_input.type(password, delay=120) + await asyncio.sleep(1) + + # Click Next + next_btn = await page.wait_for_selector('#passwordNext button', timeout=5000) + await next_btn.click() + await asyncio.sleep(8) + + # Navigate to YouTube to confirm login + print(" β†’ Confirming login...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded') + await asyncio.sleep(3) + + # Extract cookies + print("") + print("πŸͺ Extracting cookies...") + cookies = await page.context.cookies() + + yt_cookies = [c for c in cookies if 'youtube.com' in c['domain'] or 'google.com' in c['domain']] + + if not yt_cookies: + print("❌ No cookies found!") + return False + + # Check for logged-in cookies + cookie_names = [c['name'] for c in yt_cookies] + has_login = any(name in ['SID', 'SSID', 'HSID', 'SAPISID', '__Secure-1PSID'] for name in cookie_names) + + # Save cookies + output = Path(output_path) + with open(output, 'w') as f: + f.write("# Netscape HTTP Cookie File\n") + f.write("# Generated by Camoufox with SMS login\n") + for c in yt_cookies: + expires = int(c.get('expires', 0)) + if expires <= 0: + expires = 2147483647 + + line = f"{c['domain']}\tTRUE\t{c['path']}\t" + line += f"{'TRUE' if c.get('secure') else 'FALSE'}\t" + line += f"{expires}\t{c['name']}\t{c['value']}\n" + f.write(line) + + output.chmod(0o600) + + print("") + print(f"βœ… Cookies saved: {output_path}") + print(f" Total cookies: {len(yt_cookies)}") + print(f" Login cookies: {'Yes βœ“' if has_login else 'No'}") + print("") + + if has_login: + print("πŸŽ‰ SUCCESS! Logged-in cookies extracted!") + print(" These will work for 2-4 weeks!") + else: + print("⚠️ Warning: May be guest cookies") + + return has_login + +if __name__ == '__main__': + email = "alextingtingqishi@gmail.com" + password = "12345678@stt" + phone = "0695110967" + + success = asyncio.run(auto_login_with_sms(email, password, phone)) + sys.exit(0 if success else 1) diff --git a/src/python/extract_cookies_with_login.py b/src/python/extract_cookies_with_login.py new file mode 100755 index 0000000..070d67d --- /dev/null +++ b/src/python/extract_cookies_with_login.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Extract YouTube cookies with manual login using Camoufox. +Run this ONCE to login, then cookies will work for weeks. +""" + +import asyncio +import sys +from pathlib import Path +from camoufox.async_api import AsyncCamoufox + +async def extract_cookies_with_login(output_path='youtube-cookies.txt'): + """ + Extract YouTube cookies after manual login. + Opens browser window for user to login. + """ + + print("🦊 Starting Camoufox (with GUI for login)...") + print("") + print("╔══════════════════════════════════════════════════╗") + print("β•‘ πŸ“ INSTRUCTIONS: β•‘") + print("β•‘ 1. Browser will open β•‘") + print("β•‘ 2. Login to your YouTube account β•‘") + print("β•‘ 3. Press Enter in this terminal when done β•‘") + print("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•") + print("") + + async with AsyncCamoufox( + headless=False, # GUI visible for login + humanize=True, + geoip=True, + ) as browser: + page = await browser.new_page() + + # Navigate to YouTube + print("πŸ“Ί Loading YouTube...") + await page.goto('https://www.youtube.com', wait_until='domcontentloaded', timeout=30000) + + # Wait for user to login + input("⏸️ Press Enter after you've logged in to YouTube...") + + # Navigate to confirm cookies are set + await page.goto('https://www.youtube.com', wait_until='domcontentloaded') + await asyncio.sleep(2) + + # Extract cookies + cookies = await page.context.cookies() + + # Filter YouTube cookies + yt_cookies = [c for c in cookies if 'youtube.com' in c['domain']] + + if not yt_cookies: + print("❌ No YouTube cookies found!") + return False + + # Save to Netscape format + output = Path(output_path) + with open(output, 'w') as f: + f.write("# Netscape HTTP Cookie File\n") + f.write("# Generated by Camoufox with logged-in account\n") + for c in yt_cookies: + # Handle expires properly + expires = int(c.get('expires', 0)) + if expires <= 0: + expires = 2147483647 # Max 32-bit timestamp (year 2038) + + line = f"{c['domain']}\tTRUE\t{c['path']}\t" + line += f"{'TRUE' if c.get('secure') else 'FALSE'}\t" + line += f"{expires}\t{c['name']}\t{c['value']}\n" + f.write(line) + + # Set secure permissions + output.chmod(0o600) + + print("") + print(f"βœ… Cookies saved: {output_path}") + print(f" Total cookies: {len(yt_cookies)}") + print(f" Logged in: Yes") + print("") + print("πŸ’‘ These cookies will work for 2-4 weeks!") + print(" Run this script again when they expire.") + + return True + +if __name__ == '__main__': + output = sys.argv[1] if len(sys.argv) > 1 else 'youtube-cookies.txt' + success = asyncio.run(extract_cookies_with_login(output)) + sys.exit(0 if success else 1) diff --git a/src/python/trigger_google_sms.py b/src/python/trigger_google_sms.py new file mode 100755 index 0000000..969e289 --- /dev/null +++ b/src/python/trigger_google_sms.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +""" +Trigger Google SMS by initiating password recovery. +This ALWAYS sends an SMS to the registered phone number. +""" + +import asyncio +from camoufox.async_api import AsyncCamoufox + +async def trigger_google_sms(email, phone): + """Trigger Google to send SMS via password recovery""" + + print("πŸš€ Triggering Google SMS via password recovery...") + print(f" Email: {email}") + print(f" Phone: {phone}") + print("") + + async with AsyncCamoufox( + headless=True, + humanize=True, + geoip=True, + ) as browser: + page = await browser.new_page() + + print("πŸ“Ί Loading Google Account Recovery...") + await page.goto( + 'https://accounts.google.com/signin/v2/recoveryidentifier', + wait_until='domcontentloaded', + timeout=30000 + ) + await asyncio.sleep(3) + + print("πŸ“§ Entering email...") + try: + # Enter email + email_input = await page.wait_for_selector( + 'input[type="email"]', + timeout=10000 + ) + await email_input.type(email, delay=120) + await asyncio.sleep(1) + + # Click Next + next_btn = await page.wait_for_selector( + 'button:has-text("Next")', + timeout=5000 + ) + await next_btn.click() + print(" βœ“ Email submitted") + await asyncio.sleep(5) + + # Look for "Try another way" or phone verification + page_content = await page.content() + + if 'another way' in page_content.lower(): + print("πŸ”„ Clicking 'Try another way'...") + try: + another_way = await page.wait_for_selector( + 'button:has-text("Try another way")', + timeout=5000 + ) + await another_way.click() + await asyncio.sleep(3) + except: + pass + + # Look for SMS option + if 'text message' in page_content.lower() or 'sms' in page_content.lower(): + print("πŸ“± Selecting SMS option...") + try: + sms_option = await page.wait_for_selector( + '[data-challengetype="12"], button:has-text("Text"), button:has-text("SMS")', + timeout=5000 + ) + await sms_option.click() + await asyncio.sleep(3) + print(" βœ“ SMS option selected") + except Exception as e: + print(f" ⚠️ Could not select SMS option: {e}") + + # Enter phone if requested + page_content = await page.content() + if 'phone' in page_content.lower(): + print(f"πŸ“ž Entering phone number: {phone}...") + try: + phone_input = await page.wait_for_selector( + 'input[type="tel"]', + timeout=5000 + ) + await phone_input.type(phone, delay=100) + await asyncio.sleep(1) + + # Click Send + send_btn = await page.wait_for_selector( + 'button:has-text("Send"), button:has-text("Next")', + timeout=5000 + ) + await send_btn.click() + print(" βœ“ Phone submitted") + await asyncio.sleep(3) + + print("") + print("=" * 60) + print("βœ… SMS SHOULD BE SENT NOW!") + print("=" * 60) + print("πŸ“± Check your phone (0695110967)") + print("πŸ”„ SMS Forwarder should auto-forward to server") + print("=" * 60) + + except Exception as e: + print(f" ⚠️ Phone entry error: {e}") + else: + # SMS might already be sent + print("") + print("=" * 60) + print("βœ… SMS MIGHT ALREADY BE SENT!") + print("=" * 60) + print("πŸ“± Check your phone (0695110967)") + print("=" * 60) + + # Take screenshot for debugging + await page.screenshot(path='/tmp/google_recovery.png') + print("") + print("πŸ“Έ Screenshot saved: /tmp/google_recovery.png") + + # Wait a bit to see the result + await asyncio.sleep(10) + + return True + + except Exception as e: + print(f"❌ Error: {e}") + await page.screenshot(path='/tmp/google_recovery_error.png') + print("πŸ“Έ Error screenshot: /tmp/google_recovery_error.png") + return False + +if __name__ == '__main__': + email = "alextingtingqishi@gmail.com" + phone = "0695110967" + + asyncio.run(trigger_google_sms(email, phone)) diff --git a/src/sms_receiver.js b/src/sms_receiver.js new file mode 100644 index 0000000..29979c9 --- /dev/null +++ b/src/sms_receiver.js @@ -0,0 +1,226 @@ +/** + * SMS Receiver Endpoint + * Receives SMS forwarded from Android app + * Stores latest SMS code in memory for auto-login script + */ + +const express = require('express'); +const fs = require('fs'); +const path = require('path'); + +const app = express(); +const PORT = process.env.SMS_RECEIVER_PORT || 4417; + +// Store latest SMS codes in memory +const smsStore = { + latest: null, + history: [] +}; + +// Middleware +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Store SMS in file for persistence +const SMS_FILE = path.join(__dirname, '../.sms_codes.json'); + +function saveSMS(sms) { + smsStore.latest = sms; + smsStore.history.unshift(sms); + + // Keep only last 10 SMS + if (smsStore.history.length > 10) { + smsStore.history = smsStore.history.slice(0, 10); + } + + // Persist to file + fs.writeFileSync(SMS_FILE, JSON.stringify(smsStore, null, 2)); +} + +function loadSMS() { + try { + if (fs.existsSync(SMS_FILE)) { + const data = fs.readFileSync(SMS_FILE, 'utf8'); + Object.assign(smsStore, JSON.parse(data)); + } + } catch (err) { + console.error('Error loading SMS file:', err); + } +} + +// Load on startup +loadSMS(); + +/** + * POST /sms - Receive SMS from forwarder app + * + * Expected formats: + * 1. SMS Forwarder app: + * { from: "+33...", body: "Your code is 123456" } + * + * 2. Generic webhook: + * { sender: "...", message: "...", text: "..." } + */ +app.post('/sms', (req, res) => { + console.log('πŸ“± SMS received:', JSON.stringify(req.body)); + + // Extract SMS data from various formats + const from = req.body.from || req.body.sender || req.body.number || 'unknown'; + const body = req.body.body || req.body.message || req.body.text || ''; + + // Extract 6-digit code from message + const codeMatch = body.match(/\b(\d{6})\b/); + const code = codeMatch ? codeMatch[1] : null; + + const sms = { + from, + body, + code, + timestamp: new Date().toISOString(), + raw: req.body + }; + + saveSMS(sms); + + console.log(`βœ… SMS stored: ${from} β†’ Code: ${code || 'none'}`); + + res.json({ + success: true, + code, + message: 'SMS received and stored' + }); +}); + +/** + * GET /sms/latest - Get latest SMS code + */ +app.get('/sms/latest', (req, res) => { + if (!smsStore.latest) { + return res.status(404).json({ + success: false, + message: 'No SMS received yet' + }); + } + + res.json({ + success: true, + ...smsStore.latest + }); +}); + +/** + * GET /sms/code - Get latest verification code only + */ +app.get('/sms/code', (req, res) => { + if (!smsStore.latest || !smsStore.latest.code) { + return res.status(404).json({ + success: false, + message: 'No verification code found' + }); + } + + res.json({ + success: true, + code: smsStore.latest.code, + timestamp: smsStore.latest.timestamp + }); +}); + +/** + * GET /sms/wait - Wait for new SMS code (long-polling) + * Waits up to 60 seconds for a new code + */ +app.get('/sms/wait', async (req, res) => { + const maxWait = 60000; // 60 seconds + const checkInterval = 1000; // 1 second + const startTime = Date.now(); + const afterTimestamp = req.query.after || new Date(0).toISOString(); + + const checkForNewSMS = () => { + if (smsStore.latest && + smsStore.latest.code && + smsStore.latest.timestamp > afterTimestamp) { + return { + success: true, + code: smsStore.latest.code, + timestamp: smsStore.latest.timestamp, + from: smsStore.latest.from + }; + } + return null; + }; + + // Check immediately + let result = checkForNewSMS(); + if (result) { + return res.json(result); + } + + // Poll every second + const interval = setInterval(() => { + result = checkForNewSMS(); + + if (result) { + clearInterval(interval); + return res.json(result); + } + + if (Date.now() - startTime > maxWait) { + clearInterval(interval); + return res.status(408).json({ + success: false, + message: 'Timeout waiting for SMS' + }); + } + }, checkInterval); + + // Cleanup on connection close + req.on('close', () => clearInterval(interval)); +}); + +/** + * DELETE /sms/clear - Clear SMS history + */ +app.delete('/sms/clear', (req, res) => { + smsStore.latest = null; + smsStore.history = []; + + if (fs.existsSync(SMS_FILE)) { + fs.unlinkSync(SMS_FILE); + } + + res.json({ + success: true, + message: 'SMS history cleared' + }); +}); + +/** + * GET /health - Health check + */ +app.get('/health', (req, res) => { + res.json({ + success: true, + uptime: process.uptime(), + smsCount: smsStore.history.length, + latestSMS: smsStore.latest ? { + timestamp: smsStore.latest.timestamp, + hasCode: !!smsStore.latest.code + } : null + }); +}); + +app.listen(PORT, '0.0.0.0', () => { + console.log(''); + console.log('πŸ“± SMS Receiver started!'); + console.log(` Port: ${PORT}`); + console.log(` Webhook URL: http://YOUR_SERVER_IP:${PORT}/sms`); + console.log(''); + console.log('πŸ“‹ Endpoints:'); + console.log(' POST /sms - Receive SMS'); + console.log(' GET /sms/latest - Get latest SMS'); + console.log(' GET /sms/code - Get latest code'); + console.log(' GET /sms/wait - Wait for new code'); + console.log(' DELETE /sms/clear - Clear history'); + console.log(''); +}); diff --git a/test_auto_login.sh b/test_auto_login.sh new file mode 100755 index 0000000..b9e6407 --- /dev/null +++ b/test_auto_login.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Test script for full auto-login with SMS + +echo "πŸš€ Testing Full Auto-Login with SMS Forwarding" +echo "" + +# Check SMS receiver is running +echo "1. Checking SMS Receiver..." +SMS_STATUS=$(curl -s http://localhost:4417/health 2>/dev/null) +if [ $? -eq 0 ]; then + echo " βœ… SMS Receiver running" +else + echo " ❌ SMS Receiver not running - starting..." + cd /home/debian/videotomp3transcriptor + node src/sms_receiver.js & + sleep 3 +fi + +echo "" +echo "2. Testing SMS endpoint..." +curl -s -X POST http://localhost:4417/sms \ + -H "Content-Type: application/json" \ + -d '{"from":"Test","body":"Test code 999999"}' > /dev/null + +CODE=$(curl -s http://localhost:4417/sms/code | grep -o '"code":"[0-9]*"' | cut -d'"' -f4) +if [ "$CODE" = "999999" ]; then + echo " βœ… SMS endpoint working (code: $CODE)" +else + echo " ❌ SMS endpoint error" + exit 1 +fi + +echo "" +echo "3. Ready to test auto-login!" +echo "" +echo " Run: export DISPLAY=:99" +echo " Run: cd /home/debian/videotomp3transcriptor" +echo " Run: python3 src/python/auto_login_full_auto.py" +echo "" +echo " The script will:" +echo " - Navigate to Google login" +echo " - Enter email & phone" +echo " - WAIT for SMS (you'll receive on phone)" +echo " - SMS Forwarder sends to server" +echo " - Script reads code automatically" +echo " - Completes login!" +echo "" +echo "πŸ“± Make sure SMS Forwarder is configured and running!" diff --git a/test_youtube_download.sh b/test_youtube_download.sh new file mode 100755 index 0000000..4c257d6 --- /dev/null +++ b/test_youtube_download.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Final test: YouTube download with logged-in cookies + PO Token + +echo "πŸ§ͺ Testing YouTube Download (Cookies + PO Token)" +echo "" + +COOKIES_FILE="/home/debian/videotomp3transcriptor/youtube-cookies.txt" +TEST_URL="https://www.youtube.com/watch?v=fukChj4eh-Q" + +# Check cookies exist +if [ ! -f "$COOKIES_FILE" ]; then + echo "❌ Cookies file not found: $COOKIES_FILE" + echo " Waiting for upload from PC..." + exit 1 +fi + +echo "βœ… Cookies file found" +echo " Size: $(wc -c < $COOKIES_FILE) bytes" +echo " Lines: $(wc -l < $COOKIES_FILE) lines" +echo "" + +# Check for logged-in cookies +if grep -q "SID\|SSID\|HSID\|SAPISID" "$COOKIES_FILE"; then + echo "βœ… Logged-in cookies detected!" +else + echo "⚠️ Warning: May be guest cookies" +fi + +echo "" +echo "🎯 Test 1: Info extraction only" +echo "----------------------------------------" +yt-dlp \ + --cookies "$COOKIES_FILE" \ + --extractor-args "youtube:player_client=mweb" \ + --skip-download \ + --print "%(title)s [%(duration)s sec]" \ + "$TEST_URL" + +echo "" +echo "🎯 Test 2: List available formats" +echo "----------------------------------------" +yt-dlp \ + --cookies "$COOKIES_FILE" \ + --extractor-args "youtube:player_client=mweb" \ + --list-formats \ + "$TEST_URL" | head -20 + +echo "" +echo "🎯 Test 3: Download audio (best quality)" +echo "----------------------------------------" +yt-dlp \ + --cookies "$COOKIES_FILE" \ + --extractor-args "youtube:player_client=mweb" \ + --format "bestaudio" \ + --extract-audio \ + --audio-format mp3 \ + --output "/tmp/test_%(id)s.%(ext)s" \ + "$TEST_URL" + +if [ $? -eq 0 ]; then + echo "" + echo "=" * 60 + echo "πŸŽ‰ SUCCESS! YouTube download working!" + echo "=" * 60 + echo "" + echo "βœ… Cookies: Working" + echo "βœ… PO Token: Active" + echo "βœ… Download: Successful" + echo "" + echo "πŸ’‘ Next: Integrate into music service API" +else + echo "" + echo "❌ Download failed" + echo " Check errors above" +fi