Files
anyclip-video-manager/scripts/intercept-monetization.ts
Varun Shah e32d475aa9 Add AnyClip integration tools and extracted source code
- Add authentication scripts with SubtleCrypto password encryption
- Add sourcemap extraction pipeline (update-urls, download-sourcemaps, extract-sources)
- Add Playwright API interception script for monetization endpoints
- Document two-step auth flow with JWT tokens and dual cookies
- Move extracted source from root to anyclip/ directory
- Add project configuration (.env.example, .gitignore, CLAUDE.md)
2026-01-21 10:36:51 +08:00

245 lines
6.3 KiB
TypeScript

#!/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<string, string>;
requestBody: string | null;
responseStatus: number | null;
responseHeaders: Record<string, string> | 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<void> {
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<void> {
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<void> {
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);
});