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)
This commit is contained in:
244
scripts/intercept-monetization.ts
Normal file
244
scripts/intercept-monetization.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user