UNPKG

@manhgdev/spotifyweb

Version:

Spotify library in typescript without using the Spotify Web API. No authentication required with automatic internal token generation.

347 lines (340 loc) 12.9 kB
/** * Spotify Canvas API Secret Extractor (Puppeteer-based implementation) * Trích xuất secret của Spotify Canvas API bằng cách sử dụng puppeteer để tự động hóa trình duyệt */ import puppeteer from 'puppeteer-extra'; import StealthPlugin from 'puppeteer-extra-plugin-stealth'; import fs from 'fs'; import path from 'path'; // Sử dụng StealthPlugin để tránh phát hiện bot puppeteer.use(StealthPlugin()); // Các biến cấu hình const SPOTIFY_URL = 'https://open.spotify.com'; const TIMEOUT = 60000; // timeout 60 giây const BUNDLE_RE = /(?:vendor~web-player|encore~web-player|web-player)\.[0-9a-f]{4,}\.(?:js|mjs)/; /** * Hàm đợi với promise */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Lưu và định dạng các secret đã trích xuất */ function summarise(caps) { const real = {}; for (const cap of caps) { const sec = cap.secret; if (typeof sec !== "string" || sec === 'string') { continue; } const ver = cap.version || (typeof cap.obj === "object" && cap.obj !== null && cap.obj.version) || 1; if (ver == null) { continue; } real[String(ver)] = sec; } if (Object.keys(real).length === 0) { console.log("Không tìm thấy secret hợp lệ."); return; } const sortedEntries = Object.entries(real).sort((a, b) => parseInt(a[0]) - parseInt(b[0])); const formattedData = sortedEntries.map(([version, secret]) => ({ version: parseInt(version), secret, })); const secretBytes = formattedData.map(({ version, secret }) => ({ version, secret: Array.from(secret).map((c) => c.charCodeAt(0)), })); // Tạo thư mục secrets nếu chưa tồn tại (cùng cấp với file hiện tại) const secretsDir = path.dirname(new URL(import.meta.url).pathname); // Lưu dữ liệu vào file fs.writeFileSync(path.join(secretsDir, 'secrets.json'), JSON.stringify(formattedData, null, 2)); fs.writeFileSync(path.join(secretsDir, 'secretBytes.json'), JSON.stringify(secretBytes, null, 2)); console.log("Các secret đã trích xuất:"); console.log(formattedData); } /** * Trích xuất Canvas API secret từ Spotify Web Player */ async function grabLive() { // Hook để chặn và lấy secret từ các đối tượng JavaScript const hook = `(()=>{ if(globalThis.__secretHookInstalled) return; globalThis.__secretHookInstalled = true; globalThis.__captures = []; Object.defineProperty(Object.prototype, 'secret', { configurable: true, set: function(v) { try { __captures.push({ secret: v, version: this.version, obj: this }); } catch(e) {} Object.defineProperty(this, 'secret', { value: v, writable: true, configurable: true, enumerable: true }); } }); // Thêm hook cho canvasApiSecret Object.defineProperty(Object.prototype, 'canvasApiSecret', { configurable: true, set: function(v) { try { __captures.push({ secret: v, version: this.canvasApiVersion || this.version, obj: this }); } catch(e) {} Object.defineProperty(this, 'canvasApiSecret', { value: v, writable: true, configurable: true, enumerable: true }); } }); // Thêm hook cho các thuộc tính khác có thể chứa secret const possibleSecretProps = ['canvasSecret', 'apiSecret', 'canvas']; possibleSecretProps.forEach(prop => { Object.defineProperty(Object.prototype, prop, { configurable: true, set: function(v) { try { if (typeof v === 'string' && v.length > 8) { __captures.push({ secret: v, version: this.version || 1, obj: this, property: prop }); } } catch(e) {} Object.defineProperty(this, prop, { value: v, writable: true, configurable: true, enumerable: true }); } }); }); })();`; console.log("Khởi chạy trình duyệt..."); const browser = await puppeteer.launch({ headless: "new", // Sử dụng headless mới args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-web-security", "--disable-features=IsolateOrigins,site-per-process", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run", "--no-zygote", "--single-process", "--disable-blink-features=AutomationControlled", "--disable-extensions" ], ignoreDefaultArgs: ["--enable-automation"], ignoreHTTPSErrors: true }); try { const page = await browser.newPage(); // Thiết lập timeout cho trang page.setDefaultNavigationTimeout(TIMEOUT); page.setDefaultTimeout(TIMEOUT); // Thiết lập User-Agent để giả lập trình duyệt thực await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'); // Tối ưu hóa performance await page.setCacheEnabled(false); await page.setRequestInterception(true); // Chặn các request không cần thiết để tăng tốc page.on('request', (req) => { const resourceType = req.resourceType(); if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) { req.abort(); } else { req.continue(); } }); // Chèn hook JavaScript vào trang await page.evaluateOnNewDocument(hook); // Theo dõi các bundle JavaScript được tải page.on("response", (response) => { const url = response.url(); const filename = url.split("/").pop() || ""; if (BUNDLE_RE.test(filename)) { console.log(`Tải bundle: ${filename}: ${response.status()}`); } }); console.log("Mở Spotify Web Player..."); // Thay đổi cách tải trang try { await page.goto(SPOTIFY_URL, { waitUntil: "domcontentloaded", // Chỉ đợi đến khi DOM được tải timeout: TIMEOUT, }); console.log("Trang đã tải xong DOM, đợi thêm để JavaScript thực thi..."); // Đợi cho phần tử cụ thể xuất hiện với nhiều selector khác nhau const selectors = [ 'div[data-testid="root"]', '#main', '.Root', 'body', 'html' ]; let elementFound = false; for (const selector of selectors) { try { await page.waitForSelector(selector, { timeout: 5000 }); console.log(`Đã tìm thấy phần tử: ${selector}`); elementFound = true; break; } catch { console.log(`Không tìm thấy phần tử ${selector}, thử selector tiếp theo...`); } } if (!elementFound) { console.log("Không tìm thấy phần tử nào, nhưng vẫn tiếp tục..."); } } catch (err) { console.log("Lỗi khi tải trang, nhưng vẫn tiếp tục:", err.message); } // Đợi thêm để đảm bảo JavaScript được tải và thực thi console.log("Đợi JavaScript được thực thi..."); await sleep(5000); // Giảm thời gian chờ xuống 5 giây // Thử tương tác với trang để kích hoạt thêm JavaScript try { // Đợi cho body element sẵn sàng await page.waitForSelector('body', { timeout: 5000 }); // Kiểm tra xem body có thể click được không const bodyExists = await page.evaluate(() => { const body = document.querySelector('body'); return body && body.offsetParent !== null; }); if (bodyExists) { await page.click('body'); console.log("Đã click vào body"); } else { // Thử click vào html element thay thế await page.evaluate(() => { document.documentElement.click(); }); console.log("Đã click vào html element"); } await page.keyboard.press('Escape'); console.log("Đã tương tác với trang"); } catch (err) { console.log("Không thể tương tác với trang:", err.message); // Thử phương pháp thay thế - trigger events bằng JavaScript try { await page.evaluate(() => { // Trigger mouse events để kích hoạt JavaScript const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); document.dispatchEvent(event); // Trigger keyboard events const keyEvent = new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true }); document.dispatchEvent(keyEvent); }); console.log("Đã trigger events bằng JavaScript"); } catch (jsErr) { console.log("Không thể trigger events:", jsErr.message); } } // Đợi thêm sau khi tương tác await sleep(3000); // Giảm thời gian chờ xuống 3 giây // Lấy các secret đã bị hook const caps = await page.evaluate(() => { return globalThis.__captures || []; }); if (caps.length > 0) { console.log(`Đã tìm thấy ${caps.length} secret:`); for (const c of caps) { if (typeof c.secret === "string" && c.secret !== 'string') { const version = c.version || 1; const property = c.property || 'secret'; console.log(`${property}(${version}): ${c.secret}`); } } } else { console.log("Không tìm thấy secret nào."); // Thử tìm kiếm trong mã nguồn của trang console.log("Tìm kiếm trong mã nguồn của trang..."); const content = await page.content(); // Tìm các pattern có thể là secret const secretPatterns = [ /secret["'\s:=]+["']([^"']{15,})["']/g, /canvasApiSecret["'\s:=]+["']([^"']{15,})["']/g, /canvas\.secret["'\s:=]+["']([^"']{15,})["']/g ]; const foundSecrets = []; for (const pattern of secretPatterns) { let match; while ((match = pattern.exec(content)) !== null) { if (match[1] && match[1] !== 'string') { foundSecrets.push({ secret: match[1], version: 1 }); } } } if (foundSecrets.length > 0) { console.log(`Tìm thấy ${foundSecrets.length} secret trong mã nguồn:`); for (const s of foundSecrets) { console.log(`Secret: ${s.secret}`); } return foundSecrets; } } return caps; } finally { await browser.close(); } } /** * Hàm chính */ export async function puppeteer_extract() { try { // Lấy secret từ github const github_extra = await import('./github_extra.js'); const data = await github_extra.github_extra(); if (data) return; console.log("Bắt đầu trích xuất Spotify Canvas API secret..."); const caps = await grabLive(); summarise(caps); } catch (error) { console.error("Lỗi:", error); } } // Chạy hàm chính // puppeteer_extract();