#!/usr/bin/env bun /** * AnyClip Monetization API Interception Script * * This script launches a visible browser, allows manual login, * and captures all API traffic on the monetization page. * * Usage: bun scripts/intercept-monetization.ts * * Press Ctrl+C to stop and save captured data. */ import { chromium, type Page, type Request, type Response } from "playwright"; import { writeFileSync } from "fs"; interface CapturedRequest { timestamp: string; url: string; method: string; headers: Record; requestBody: string | null; responseStatus: number | null; responseHeaders: Record | null; responseBody: string | null; duration: number | null; } const capturedRequests: CapturedRequest[] = []; const pendingRequests = new Map< Request, { startTime: number; captured: CapturedRequest } >(); const OUTPUT_FILE = "captured-apis.json"; const BASE_URL = "https://videomanager.anyclip.com"; const MONETIZATION_PATH = "/analytics-new/monetization"; // Filter for API requests we care about function isApiRequest(url: string): boolean { // Capture GraphQL, REST API calls, and data endpoints const apiPatterns = [ "/graphql", "/api/", "/analytics", "/monetization", "/v1/", "/v2/", "anyclip.com", ]; // Exclude static assets const excludePatterns = [ ".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", "/static/", "fonts.googleapis.com", "google-analytics.com", "googletagmanager.com", ]; const urlLower = url.toLowerCase(); if (excludePatterns.some((pattern) => urlLower.includes(pattern))) { return false; } return apiPatterns.some((pattern) => urlLower.includes(pattern)); } function saveCaptures(): void { const output = { capturedAt: new Date().toISOString(), totalRequests: capturedRequests.length, requests: capturedRequests, }; writeFileSync(OUTPUT_FILE, JSON.stringify(output, null, 2)); console.log(`\nāœ… Saved ${capturedRequests.length} requests to ${OUTPUT_FILE}`); } async function handleRequest(request: Request): Promise { const url = request.url(); if (!isApiRequest(url)) { return; } const captured: CapturedRequest = { timestamp: new Date().toISOString(), url, method: request.method(), headers: request.headers(), requestBody: request.postData() || null, responseStatus: null, responseHeaders: null, responseBody: null, duration: null, }; pendingRequests.set(request, { startTime: Date.now(), captured, }); console.log(`šŸ“¤ ${request.method()} ${url}`); } async function handleResponse(response: Response): Promise { const request = response.request(); const pending = pendingRequests.get(request); if (!pending) { return; } const { startTime, captured } = pending; captured.responseStatus = response.status(); captured.responseHeaders = response.headers(); captured.duration = Date.now() - startTime; try { const contentType = response.headers()["content-type"] || ""; if ( contentType.includes("application/json") || contentType.includes("text/") ) { captured.responseBody = await response.text(); } else { captured.responseBody = `[Binary content: ${contentType}]`; } } catch (error) { captured.responseBody = `[Error reading response: ${error}]`; } capturedRequests.push(captured); pendingRequests.delete(request); console.log( `šŸ“„ ${response.status()} ${captured.url} (${captured.duration}ms)` ); } async function main(): Promise { console.log("šŸš€ AnyClip Monetization API Interceptor"); console.log("=========================================\n"); console.log("Launching browser..."); const browser = await chromium.launch({ headless: false, args: ["--start-maximized"], }); const context = await browser.newContext({ viewport: null, // Use full window size }); const page = await context.newPage(); // Set up request/response interception page.on("request", handleRequest); page.on("response", handleResponse); // Navigate to AnyClip console.log(`\nNavigating to ${BASE_URL}...`); await page.goto(BASE_URL); console.log("\n" + "=".repeat(50)); console.log("šŸ‘¤ Please log in manually in the browser window"); console.log("=".repeat(50) + "\n"); // Wait for login - user must navigate to a protected page console.log("Waiting for authentication..."); console.log("(Navigate to any page after logging in, or the script will auto-detect)\n"); // Wait for URL to change to a protected route (not login/logout) await page.waitForFunction( () => { const path = window.location.pathname; // Must be on a real app page, not login/logout/root const protectedRoutes = [ "/dashboard", "/analytics", "/video", "/library", "/settings", "/playlists", "/players", ]; return protectedRoutes.some((route) => path.startsWith(route)); }, { timeout: 300000 } // 5 minute timeout for login ); console.log("āœ… Authentication detected!\n"); // Navigate to monetization page const monetizationUrl = `${BASE_URL}${MONETIZATION_PATH}`; console.log(`Navigating to ${monetizationUrl}...`); try { await page.goto(monetizationUrl, { waitUntil: "domcontentloaded" }); } catch (error) { // Navigation might fail if page redirects - that's okay console.log("Navigation completed (may have redirected)"); } // Wait for page to stabilize await page.waitForTimeout(2000); await page.waitForLoadState("networkidle").catch(() => {}); console.log("\n" + "=".repeat(50)); console.log("šŸ“Š Now capturing API traffic on the Monetization page"); console.log(" Interact with filters, date ranges, etc."); console.log(" Press Ctrl+C when done to save captured data"); console.log("=".repeat(50) + "\n"); // Handle Ctrl+C gracefully process.on("SIGINT", async () => { console.log("\n\nšŸ›‘ Stopping capture..."); saveCaptures(); await browser.close(); process.exit(0); }); // Keep the script running await new Promise(() => {}); // Never resolves - keeps script alive } main().catch((error) => { console.error("Error:", error); saveCaptures(); process.exit(1); });