mcp-scraper 0.4.13 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +50 -145
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +1 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-OL65BO4S.js +7 -0
- package/dist/chunk-OL65BO4S.js.map +1 -0
- package/dist/{chunk-EA2AANFI.js → chunk-XZ2GPRCM.js} +2 -2
- package/dist/{server-HNZG36IP.js → server-TGORO4AJ.js} +49 -144
- package/dist/server-TGORO4AJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-O2D5HGKF.js +0 -7
- package/dist/chunk-O2D5HGKF.js.map +0 -1
- package/dist/server-HNZG36IP.js.map +0 -1
- /package/dist/{chunk-EA2AANFI.js.map → chunk-XZ2GPRCM.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
registerMemoryMcpTools,
|
|
8
8
|
registerPaaExtractorMcpTools,
|
|
9
9
|
registerSerpIntelligenceCaptureTools
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-XZ2GPRCM.js";
|
|
11
11
|
import "../chunk-GWRIO6JT.js";
|
|
12
12
|
import "../chunk-HPV4VOQX.js";
|
|
13
13
|
import {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import "../chunk-XGIPATLV.js";
|
|
17
17
|
import {
|
|
18
18
|
PACKAGE_VERSION
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-OL65BO4S.js";
|
|
20
20
|
import "../chunk-M2S27J6Z.js";
|
|
21
21
|
|
|
22
22
|
// bin/mcp-stdio-server.ts
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = '0.4.14'\n"],"mappings":";AAAO,IAAM,kBAAkB;","names":[]}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./chunk-XGIPATLV.js";
|
|
18
18
|
import {
|
|
19
19
|
PACKAGE_VERSION
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OL65BO4S.js";
|
|
21
21
|
import {
|
|
22
22
|
sanitizeVendorName
|
|
23
23
|
} from "./chunk-M2S27J6Z.js";
|
|
@@ -8453,4 +8453,4 @@ export {
|
|
|
8453
8453
|
registerMemoryMcpTools,
|
|
8454
8454
|
MemoryMcpToolExecutor
|
|
8455
8455
|
};
|
|
8456
|
-
//# sourceMappingURL=chunk-
|
|
8456
|
+
//# sourceMappingURL=chunk-XZ2GPRCM.js.map
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
registerSerpIntelligenceCaptureTools,
|
|
34
34
|
sanitizeAttempts,
|
|
35
35
|
sanitizeHarvestResult
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-XZ2GPRCM.js";
|
|
37
37
|
import {
|
|
38
38
|
auditImages,
|
|
39
39
|
getBlobStore
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
RawMapsOverviewSchema,
|
|
68
68
|
RawMapsReviewStatsSchema
|
|
69
69
|
} from "./chunk-XGIPATLV.js";
|
|
70
|
-
import "./chunk-
|
|
70
|
+
import "./chunk-OL65BO4S.js";
|
|
71
71
|
import {
|
|
72
72
|
completeExtractJob,
|
|
73
73
|
countSuccessfulPages,
|
|
@@ -8788,7 +8788,6 @@ async function transcribeMediaUrl(mediaUrl, markdownTitle = "# Media Transcript"
|
|
|
8788
8788
|
}
|
|
8789
8789
|
|
|
8790
8790
|
// src/youtube/CaptionFetcher.ts
|
|
8791
|
-
import { fal as fal2 } from "@fal-ai/client";
|
|
8792
8791
|
async function fetchViaYoutubeTranscript(videoId) {
|
|
8793
8792
|
try {
|
|
8794
8793
|
const { YoutubeTranscript } = await import("youtube-transcript");
|
|
@@ -8861,13 +8860,12 @@ var INNERTUBE_CLIENTS = [
|
|
|
8861
8860
|
{ name: "IOS", version: "20.10.4", userAgent: "com.google.ios.youtube/20.10.4 (iPhone16,2; U; CPU iOS 18_3_2 like Mac OS X;)" },
|
|
8862
8861
|
{ name: "ANDROID_VR", version: "1.62.27", userAgent: "com.google.android.apps.youtube.vr.oculus/1.62.27 (Linux; U; Android 12L) gzip" }
|
|
8863
8862
|
];
|
|
8864
|
-
function isBotBlockStatus(status) {
|
|
8865
|
-
return status === "LOGIN_REQUIRED" || status === "AGE_VERIFICATION_REQUIRED";
|
|
8866
|
-
}
|
|
8867
8863
|
async function probePlayer(page, videoId) {
|
|
8868
8864
|
return page.evaluate(
|
|
8869
8865
|
async ({ vid, clients }) => {
|
|
8870
8866
|
let lastStatus = null;
|
|
8867
|
+
let sawBotBlock = false;
|
|
8868
|
+
let sawAccessible = false;
|
|
8871
8869
|
for (const client2 of clients) {
|
|
8872
8870
|
const resp = await fetch("/youtubei/v1/player?prettyPrint=false", {
|
|
8873
8871
|
method: "POST",
|
|
@@ -8883,98 +8881,24 @@ async function probePlayer(page, videoId) {
|
|
|
8883
8881
|
}
|
|
8884
8882
|
const data = await resp.json();
|
|
8885
8883
|
lastStatus = data?.playabilityStatus?.status ?? null;
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
let captions = null;
|
|
8884
|
+
if (lastStatus === "LOGIN_REQUIRED" || lastStatus === "AGE_VERIFICATION_REQUIRED") sawBotBlock = true;
|
|
8885
|
+
else if (lastStatus === "OK") sawAccessible = true;
|
|
8889
8886
|
const tracks = data?.captions?.playerCaptionsTracklistRenderer?.captionTracks ?? [];
|
|
8890
8887
|
if (tracks.length > 0) {
|
|
8891
8888
|
const track = tracks.find((t) => t.languageCode === "en") ?? tracks[0];
|
|
8892
8889
|
const sep = track.baseUrl.includes("?") ? "&" : "?";
|
|
8893
8890
|
const xmlResp = await fetch(`${track.baseUrl}${sep}fmt=json3`).catch(() => null);
|
|
8894
|
-
if (xmlResp?.ok)
|
|
8891
|
+
if (xmlResp?.ok) {
|
|
8892
|
+
const captions = await xmlResp.text();
|
|
8893
|
+
if (captions) return { captions, status: lastStatus, escalate: false };
|
|
8894
|
+
}
|
|
8895
8895
|
}
|
|
8896
|
-
if (!captions && !pick) continue;
|
|
8897
|
-
return {
|
|
8898
|
-
captions,
|
|
8899
|
-
status: lastStatus,
|
|
8900
|
-
audio: pick ? {
|
|
8901
|
-
url: pick.url,
|
|
8902
|
-
mimeType: pick.mimeType,
|
|
8903
|
-
bitrate: pick.bitrate ?? null,
|
|
8904
|
-
contentLength: pick.contentLength ? Number(pick.contentLength) || null : null,
|
|
8905
|
-
approxDurationMs: pick.approxDurationMs ? Number(pick.approxDurationMs) || null : null
|
|
8906
|
-
} : null
|
|
8907
|
-
};
|
|
8908
8896
|
}
|
|
8909
|
-
return { captions: null,
|
|
8897
|
+
return { captions: null, status: lastStatus, escalate: sawBotBlock && !sawAccessible };
|
|
8910
8898
|
},
|
|
8911
8899
|
{ vid: videoId, clients: INNERTUBE_CLIENTS }
|
|
8912
8900
|
).catch(() => null);
|
|
8913
8901
|
}
|
|
8914
|
-
var MAX_AUDIO_BYTES = 96 * 1024 * 1024;
|
|
8915
|
-
async function downloadWholeAudioStreamInOneRequestBecauseYouTubeAllowsOnlyOnePerIp(page, audio) {
|
|
8916
|
-
if (audio.contentLength && audio.contentLength > MAX_AUDIO_BYTES) {
|
|
8917
|
-
console.warn(`[CaptionFetcher] audio stream ${audio.contentLength} bytes exceeds ${MAX_AUDIO_BYTES} byte cap`);
|
|
8918
|
-
return null;
|
|
8919
|
-
}
|
|
8920
|
-
const sep = audio.url.includes("?") ? "&" : "?";
|
|
8921
|
-
const url = audio.contentLength ? `${audio.url}${sep}range=0-${audio.contentLength - 1}` : audio.url;
|
|
8922
|
-
const result = await page.evaluate(async (u) => {
|
|
8923
|
-
try {
|
|
8924
|
-
const resp = await fetch(u);
|
|
8925
|
-
if (!resp.ok) return { ok: false, error: `http_${resp.status}` };
|
|
8926
|
-
const blob = await resp.blob();
|
|
8927
|
-
const dataUrl = await new Promise((resolve, reject) => {
|
|
8928
|
-
const reader = new FileReader();
|
|
8929
|
-
reader.onload = () => resolve(reader.result);
|
|
8930
|
-
reader.onerror = () => reject(reader.error);
|
|
8931
|
-
reader.readAsDataURL(blob);
|
|
8932
|
-
});
|
|
8933
|
-
return { ok: true, base64: dataUrl.slice(dataUrl.indexOf(",") + 1) };
|
|
8934
|
-
} catch (e) {
|
|
8935
|
-
return { ok: false, error: String(e) };
|
|
8936
|
-
}
|
|
8937
|
-
}, url).catch((e) => ({ ok: false, error: String(e) }));
|
|
8938
|
-
if (!result.ok || !result.base64) {
|
|
8939
|
-
console.warn(`[CaptionFetcher] audio fetch failed (${result.error ?? "unknown"}) for a ${audio.contentLength ?? 0} byte stream`);
|
|
8940
|
-
return null;
|
|
8941
|
-
}
|
|
8942
|
-
const bytes = Buffer.from(result.base64, "base64");
|
|
8943
|
-
if (audio.contentLength && bytes.length < audio.contentLength) {
|
|
8944
|
-
console.warn(`[CaptionFetcher] partial audio: ${bytes.length} of ${audio.contentLength} bytes`);
|
|
8945
|
-
return null;
|
|
8946
|
-
}
|
|
8947
|
-
return { bytes, mimeType: audio.mimeType.split(";")[0].trim() };
|
|
8948
|
-
}
|
|
8949
|
-
async function transcribeWithWizper(videoId, audio, startMs) {
|
|
8950
|
-
const falKey = process.env.FAL_KEY;
|
|
8951
|
-
if (!falKey) return null;
|
|
8952
|
-
try {
|
|
8953
|
-
fal2.config({ credentials: falKey });
|
|
8954
|
-
const ext = audio.mimeType.includes("webm") ? "webm" : audio.mimeType.includes("mp4") ? "m4a" : "audio";
|
|
8955
|
-
const audioFile = new File([new Uint8Array(audio.bytes)], `audio.${ext}`, { type: audio.mimeType });
|
|
8956
|
-
const uploadedUrl = await fal2.storage.upload(audioFile);
|
|
8957
|
-
const subscribeStartMs = Date.now();
|
|
8958
|
-
const result = await fal2.subscribe("fal-ai/wizper", {
|
|
8959
|
-
input: { audio_url: uploadedUrl, task: "transcribe", language: "en", chunk_level: "segment" },
|
|
8960
|
-
logs: false,
|
|
8961
|
-
pollInterval: 3e3
|
|
8962
|
-
});
|
|
8963
|
-
const subscribeDurationMs = Date.now() - subscribeStartMs;
|
|
8964
|
-
const data = result.data;
|
|
8965
|
-
const chunks = (data.chunks ?? []).map((c) => ({
|
|
8966
|
-
timestamp: c.timestamp,
|
|
8967
|
-
text: c.text.trim()
|
|
8968
|
-
}));
|
|
8969
|
-
void recordVendorUsage({ vendor: "fal_wizper", model: "fal-ai/wizper", units: subscribeDurationMs / 1e3, unitType: "compute_sec" });
|
|
8970
|
-
const text = data.text ?? chunks.map((c) => c.text).join(" ");
|
|
8971
|
-
if (!text) return null;
|
|
8972
|
-
return { videoId, text, chunks, durationMs: Date.now() - startMs, method: "browser-whisper" };
|
|
8973
|
-
} catch (err) {
|
|
8974
|
-
console.warn(`[CaptionFetcher] wizper transcription failed for ${videoId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8975
|
-
return null;
|
|
8976
|
-
}
|
|
8977
|
-
}
|
|
8978
8902
|
var YT_RESIDENTIAL_LOCATION = process.env.YOUTUBE_PROXY_LOCATION ?? "Austin, Texas";
|
|
8979
8903
|
async function resolveResidentialProxy(kernelApiKey) {
|
|
8980
8904
|
try {
|
|
@@ -8991,14 +8915,13 @@ async function resolveResidentialProxy(kernelApiKey) {
|
|
|
8991
8915
|
return {};
|
|
8992
8916
|
}
|
|
8993
8917
|
}
|
|
8994
|
-
async function
|
|
8918
|
+
async function probeCaptions(videoId, kernelProxyIdOverride) {
|
|
8995
8919
|
const kernelApiKey = browserServiceApiKey();
|
|
8996
8920
|
if (!kernelApiKey) return null;
|
|
8997
|
-
const kernelProxyId =
|
|
8921
|
+
const kernelProxyId = kernelProxyIdOverride ?? browserServiceProxyId();
|
|
8998
8922
|
const driver = new BrowserDriver();
|
|
8999
8923
|
return runWithCostContext({ ...currentCostContext(), op: "yt_transcription", subOp: "browser-innertube" }, async () => {
|
|
9000
8924
|
let entries = [];
|
|
9001
|
-
let audio = null;
|
|
9002
8925
|
let retryWithFreshIp = false;
|
|
9003
8926
|
try {
|
|
9004
8927
|
await driver.launch({ kernelApiKey, kernelProxyId, headless: true, headlessMode: "headless", viewport: { width: 1280, height: 800 }, locale: "en-US" });
|
|
@@ -9010,69 +8933,51 @@ async function probeAndExtract(videoId, opts) {
|
|
|
9010
8933
|
);
|
|
9011
8934
|
const probe = await probePlayer(page, videoId);
|
|
9012
8935
|
if (!probe) return null;
|
|
9013
|
-
if (probe.captions
|
|
8936
|
+
if (probe.captions) {
|
|
9014
8937
|
entries = parseTimedtextJson3(probe.captions);
|
|
9015
8938
|
if (entries.length === 0) entries = parseTimedtextXml(probe.captions);
|
|
9016
8939
|
}
|
|
9017
8940
|
if (entries.length === 0) {
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
console.warn(`[CaptionFetcher] no captions and no audio stream for ${videoId} (playability=${probe.status ?? "unknown"}, proxied=${Boolean(kernelProxyId)})`);
|
|
9021
|
-
return { entries, audio: null, retryWithFreshIp };
|
|
9022
|
-
}
|
|
9023
|
-
audio = await downloadWholeAudioStreamInOneRequestBecauseYouTubeAllowsOnlyOnePerIp(page, probe.audio);
|
|
9024
|
-
if (!audio) return { entries, audio: null, retryWithFreshIp: true };
|
|
8941
|
+
retryWithFreshIp = probe.escalate;
|
|
8942
|
+
console.warn(`[CaptionFetcher] no caption track for ${videoId} (playability=${probe.status ?? "unknown"}, proxied=${Boolean(kernelProxyId)}, escalate=${probe.escalate})`);
|
|
9025
8943
|
}
|
|
9026
8944
|
} catch (err) {
|
|
9027
8945
|
console.warn(`[CaptionFetcher] kernel session failed for ${videoId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9028
8946
|
return null;
|
|
9029
8947
|
} finally {
|
|
9030
|
-
|
|
9031
|
-
await runWithCostContext({ ...currentCostContext(), op: "yt_transcription", subOp: servedSubOp }, () => driver.close());
|
|
8948
|
+
await driver.close();
|
|
9032
8949
|
}
|
|
9033
|
-
return { entries,
|
|
8950
|
+
return { entries, retryWithFreshIp };
|
|
9034
8951
|
});
|
|
9035
8952
|
}
|
|
9036
|
-
async function extractWithProxyEscalation(videoId, opts) {
|
|
9037
|
-
const direct = await probeAndExtract(videoId, opts);
|
|
9038
|
-
if (direct && (direct.entries.length > 0 || direct.audio)) return direct;
|
|
9039
|
-
const shouldEscalate = direct === null || direct.retryWithFreshIp;
|
|
9040
|
-
if (!shouldEscalate) return direct;
|
|
9041
|
-
const kernelApiKey = browserServiceApiKey();
|
|
9042
|
-
if (!kernelApiKey) return direct;
|
|
9043
|
-
const { kernelProxyId, disposableProxyId } = await resolveResidentialProxy(kernelApiKey);
|
|
9044
|
-
if (!kernelProxyId) return direct;
|
|
9045
|
-
try {
|
|
9046
|
-
console.warn(`[CaptionFetcher] ${videoId}: retrying through residential proxy after bot block`);
|
|
9047
|
-
const viaProxy = await probeAndExtract(videoId, { ...opts, kernelProxyId });
|
|
9048
|
-
return viaProxy ?? direct;
|
|
9049
|
-
} finally {
|
|
9050
|
-
if (disposableProxyId) {
|
|
9051
|
-
await deleteKernelProxyId(kernelApiKey, disposableProxyId).catch(
|
|
9052
|
-
(err) => console.warn(`[CaptionFetcher] failed to delete disposable proxy: ${err instanceof Error ? err.message : String(err)}`)
|
|
9053
|
-
);
|
|
9054
|
-
}
|
|
9055
|
-
}
|
|
9056
|
-
}
|
|
9057
8953
|
async function fetchViaKernelSession(videoId) {
|
|
9058
8954
|
const start = Date.now();
|
|
9059
|
-
|
|
9060
|
-
if (!outcome)
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
8955
|
+
let outcome = await probeCaptions(videoId);
|
|
8956
|
+
if (!outcome || outcome.entries.length === 0 && outcome.retryWithFreshIp) {
|
|
8957
|
+
const kernelApiKey = browserServiceApiKey();
|
|
8958
|
+
if (kernelApiKey) {
|
|
8959
|
+
const { kernelProxyId, disposableProxyId } = await resolveResidentialProxy(kernelApiKey);
|
|
8960
|
+
if (kernelProxyId) {
|
|
8961
|
+
try {
|
|
8962
|
+
console.warn(`[CaptionFetcher] ${videoId}: retrying captions through residential proxy after bot block`);
|
|
8963
|
+
outcome = await probeCaptions(videoId, kernelProxyId) ?? outcome;
|
|
8964
|
+
} finally {
|
|
8965
|
+
if (disposableProxyId) {
|
|
8966
|
+
await deleteKernelProxyId(kernelApiKey, disposableProxyId).catch(
|
|
8967
|
+
(err) => console.warn(`[CaptionFetcher] failed to delete disposable proxy: ${err instanceof Error ? err.message : String(err)}`)
|
|
8968
|
+
);
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
9074
8973
|
}
|
|
9075
|
-
return null;
|
|
8974
|
+
if (!outcome || outcome.entries.length === 0) return null;
|
|
8975
|
+
const chunks = outcome.entries.map((e) => ({
|
|
8976
|
+
timestamp: [e.start / 1e3, (e.start + e.dur) / 1e3],
|
|
8977
|
+
text: e.text
|
|
8978
|
+
}));
|
|
8979
|
+
const text = chunks.map((c) => c.text).join(" ");
|
|
8980
|
+
return { videoId, text, chunks, durationMs: Date.now() - start, method: "browser-innertube" };
|
|
9076
8981
|
}
|
|
9077
8982
|
async function fetchViaResolvedAudio(videoId) {
|
|
9078
8983
|
if (!mediaResolverConfigured()) return null;
|
|
@@ -10485,7 +10390,7 @@ async function extractFacebookOrganicVideoFromPage(page, sourceUrl, quality = "b
|
|
|
10485
10390
|
}
|
|
10486
10391
|
|
|
10487
10392
|
// src/api/facebook-ad-routes.ts
|
|
10488
|
-
import { fal as
|
|
10393
|
+
import { fal as fal2 } from "@fal-ai/client";
|
|
10489
10394
|
var FacebookAdBodySchema = z13.object({
|
|
10490
10395
|
url: z13.string().trim().optional(),
|
|
10491
10396
|
libraryId: z13.string().trim().optional(),
|
|
@@ -10665,7 +10570,7 @@ facebookAdApp.post("/transcribe", createApiKeyAuth(), async (c) => {
|
|
|
10665
10570
|
metadata: { videoUrl }
|
|
10666
10571
|
});
|
|
10667
10572
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
10668
|
-
|
|
10573
|
+
fal2.config({ credentials: process.env.FAL_KEY });
|
|
10669
10574
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
10670
10575
|
let debited = false;
|
|
10671
10576
|
try {
|
|
@@ -10709,7 +10614,7 @@ facebookAdApp.post("/video-transcribe", createApiKeyAuth(), async (c) => {
|
|
|
10709
10614
|
metadata: { url: sourceUrl.href }
|
|
10710
10615
|
});
|
|
10711
10616
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
10712
|
-
|
|
10617
|
+
fal2.config({ credentials: process.env.FAL_KEY });
|
|
10713
10618
|
const driver = new BrowserDriver();
|
|
10714
10619
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
10715
10620
|
let debited = false;
|
|
@@ -11062,7 +10967,7 @@ var GoogleAdsExtractor = class {
|
|
|
11062
10967
|
};
|
|
11063
10968
|
|
|
11064
10969
|
// src/api/google-ads-routes.ts
|
|
11065
|
-
import { fal as
|
|
10970
|
+
import { fal as fal3 } from "@fal-ai/client";
|
|
11066
10971
|
var GoogleAdsSearchBodySchema = z14.object({
|
|
11067
10972
|
query: z14.string().trim().min(1, "query is required"),
|
|
11068
10973
|
region: z14.string().trim().toUpperCase().length(2).optional(),
|
|
@@ -11192,7 +11097,7 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
|
|
|
11192
11097
|
const user = c.get("user");
|
|
11193
11098
|
const gate = await acquireConcurrencyGate(user, "google_ads_transcribe", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { videoUrl } });
|
|
11194
11099
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11195
|
-
|
|
11100
|
+
fal3.config({ credentials: process.env.FAL_KEY });
|
|
11196
11101
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
11197
11102
|
let debited = false;
|
|
11198
11103
|
try {
|
|
@@ -22114,4 +22019,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
22114
22019
|
export {
|
|
22115
22020
|
app
|
|
22116
22021
|
};
|
|
22117
|
-
//# sourceMappingURL=server-
|
|
22022
|
+
//# sourceMappingURL=server-TGORO4AJ.js.map
|