mcp-scraper 0.33.7 → 0.34.0
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 +10063 -10012
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +2 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- 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 +15 -8
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/bin/paa-harvest.cjs +22 -0
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-XVCETVYY.js → chunk-4KQVIYIH.js} +11 -3
- package/dist/chunk-4KQVIYIH.js.map +1 -0
- package/dist/{chunk-V73MPRU6.js → chunk-CB5C3BPB.js} +17 -1
- package/dist/chunk-CB5C3BPB.js.map +1 -0
- package/dist/{chunk-H6QECE2U.js → chunk-J32XSMJI.js} +16 -10
- package/dist/chunk-J32XSMJI.js.map +1 -0
- package/dist/chunk-NFMIHEYE.js +7 -0
- package/dist/chunk-NFMIHEYE.js.map +1 -0
- package/dist/{chunk-AT2PFWHW.js → chunk-ZID3WQID.js} +2 -2
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -2
- package/dist/{server-B35CQJB3.js → server-LKVPEQFE.js} +130 -108
- package/dist/server-LKVPEQFE.js.map +1 -0
- package/dist/{worker-3LYNVGLF.js → worker-ZZHSYI3Y.js} +3 -3
- package/docs/mcp-tool-manifest.generated.json +36 -8
- package/package.json +1 -1
- package/dist/chunk-H6QECE2U.js.map +0 -1
- package/dist/chunk-HAMN42SP.js +0 -7
- package/dist/chunk-HAMN42SP.js.map +0 -1
- package/dist/chunk-V73MPRU6.js.map +0 -1
- package/dist/chunk-XVCETVYY.js.map +0 -1
- package/dist/server-B35CQJB3.js.map +0 -1
- /package/dist/{chunk-AT2PFWHW.js.map → chunk-ZID3WQID.js.map} +0 -0
- /package/dist/{worker-3LYNVGLF.js.map → worker-ZZHSYI3Y.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -24,6 +24,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
24
24
|
format: z.ZodDefault<z.ZodEnum<["json", "csv", "both"]>>;
|
|
25
25
|
serpOnly: z.ZodDefault<z.ZodBoolean>;
|
|
26
26
|
pages: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
recency: z.ZodOptional<z.ZodEnum<["day", "week", "month", "year"]>>;
|
|
27
28
|
softDeadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
28
29
|
}, "strip", z.ZodTypeAny, {
|
|
29
30
|
query: string;
|
|
@@ -49,6 +50,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
49
50
|
kernelApiKey?: string | undefined;
|
|
50
51
|
kernelProxyId?: string | undefined;
|
|
51
52
|
kernelProxyResolution?: unknown;
|
|
53
|
+
recency?: "day" | "week" | "month" | "year" | undefined;
|
|
52
54
|
softDeadlineMs?: number | undefined;
|
|
53
55
|
}, {
|
|
54
56
|
query: string;
|
|
@@ -74,6 +76,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
74
76
|
format?: "json" | "csv" | "both" | undefined;
|
|
75
77
|
serpOnly?: boolean | undefined;
|
|
76
78
|
pages?: number | undefined;
|
|
79
|
+
recency?: "day" | "week" | "month" | "year" | undefined;
|
|
77
80
|
softDeadlineMs?: number | undefined;
|
|
78
81
|
}>;
|
|
79
82
|
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
24
24
|
format: z.ZodDefault<z.ZodEnum<["json", "csv", "both"]>>;
|
|
25
25
|
serpOnly: z.ZodDefault<z.ZodBoolean>;
|
|
26
26
|
pages: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
recency: z.ZodOptional<z.ZodEnum<["day", "week", "month", "year"]>>;
|
|
27
28
|
softDeadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
28
29
|
}, "strip", z.ZodTypeAny, {
|
|
29
30
|
query: string;
|
|
@@ -49,6 +50,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
49
50
|
kernelApiKey?: string | undefined;
|
|
50
51
|
kernelProxyId?: string | undefined;
|
|
51
52
|
kernelProxyResolution?: unknown;
|
|
53
|
+
recency?: "day" | "week" | "month" | "year" | undefined;
|
|
52
54
|
softDeadlineMs?: number | undefined;
|
|
53
55
|
}, {
|
|
54
56
|
query: string;
|
|
@@ -74,6 +76,7 @@ declare const HarvestOptionsSchema: z.ZodObject<{
|
|
|
74
76
|
format?: "json" | "csv" | "both" | undefined;
|
|
75
77
|
serpOnly?: boolean | undefined;
|
|
76
78
|
pages?: number | undefined;
|
|
79
|
+
recency?: "day" | "week" | "month" | "year" | undefined;
|
|
77
80
|
softDeadlineMs?: number | undefined;
|
|
78
81
|
}>;
|
|
79
82
|
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
harvest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4KQVIYIH.js";
|
|
4
4
|
import {
|
|
5
5
|
recordVendorUsage
|
|
6
6
|
} from "./chunk-V36LS5YV.js";
|
|
7
7
|
import "./chunk-K443GQY5.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-CB5C3BPB.js";
|
|
9
9
|
import "./chunk-M2S27J6Z.js";
|
|
10
10
|
import "./chunk-62DQAWPF.js";
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveBackupKernelProxyId,
|
|
21
21
|
resolveKernelProxyId,
|
|
22
22
|
serpArrivalUrl
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-4KQVIYIH.js";
|
|
24
24
|
import {
|
|
25
25
|
HttpMcpToolExecutor,
|
|
26
26
|
assessTranscriptSignal,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
sanitizeAttempts,
|
|
41
41
|
sanitizeHarvestResult,
|
|
42
42
|
transcribeMediaUrl
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-J32XSMJI.js";
|
|
44
44
|
import {
|
|
45
45
|
auditImages,
|
|
46
46
|
buildLinkReport,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
workflowOutputBaseDir,
|
|
70
70
|
workflowStepCount,
|
|
71
71
|
workflowSupportsSteps
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-ZID3WQID.js";
|
|
73
73
|
import "./chunk-K443GQY5.js";
|
|
74
74
|
import {
|
|
75
75
|
DEFAULT_MAPS_PROXY_MODE,
|
|
@@ -80,8 +80,8 @@ import {
|
|
|
80
80
|
RawMapsHoursRowSchema,
|
|
81
81
|
RawMapsOverviewSchema,
|
|
82
82
|
RawMapsReviewStatsSchema
|
|
83
|
-
} from "./chunk-
|
|
84
|
-
import "./chunk-
|
|
83
|
+
} from "./chunk-CB5C3BPB.js";
|
|
84
|
+
import "./chunk-NFMIHEYE.js";
|
|
85
85
|
import {
|
|
86
86
|
completeExtractJob,
|
|
87
87
|
countSuccessfulPages,
|
|
@@ -10367,7 +10367,8 @@ var HarvestBodySchema = z14.object({
|
|
|
10367
10367
|
proxyZip: z14.string().regex(/^\d{5}$/).optional(),
|
|
10368
10368
|
debug: z14.boolean().optional(),
|
|
10369
10369
|
serpOnly: z14.boolean().optional(),
|
|
10370
|
-
pages: z14.number().int().min(1).max(2).optional()
|
|
10370
|
+
pages: z14.number().int().min(1).max(2).optional(),
|
|
10371
|
+
recency: z14.enum(["day", "week", "month", "year"]).optional()
|
|
10371
10372
|
});
|
|
10372
10373
|
var ExtractUrlBodySchema = z14.object({
|
|
10373
10374
|
url: z14.string().min(1, "url is required"),
|
|
@@ -13163,33 +13164,44 @@ import { Hono as Hono10 } from "hono";
|
|
|
13163
13164
|
import { z as z19 } from "zod";
|
|
13164
13165
|
|
|
13165
13166
|
// src/api/reddit-trending.ts
|
|
13166
|
-
|
|
13167
|
-
const
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
const
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13167
|
+
function buildSerpQuery(topic, subreddit) {
|
|
13168
|
+
const scope = subreddit ? `site:reddit.com/r/${subreddit}` : "site:reddit.com";
|
|
13169
|
+
return `${scope} ${topic}`.trim();
|
|
13170
|
+
}
|
|
13171
|
+
function subredditFromUrl(url) {
|
|
13172
|
+
const m = url.match(/\/r\/([^/]+)\//i);
|
|
13173
|
+
return m ? m[1] : "";
|
|
13174
|
+
}
|
|
13175
|
+
function extractRedditThreadUrls(organicResults, limit) {
|
|
13176
|
+
if (!Array.isArray(organicResults)) return [];
|
|
13177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13178
|
+
const out = [];
|
|
13179
|
+
for (const raw of organicResults) {
|
|
13180
|
+
if (!raw || typeof raw !== "object") continue;
|
|
13181
|
+
const r = raw;
|
|
13182
|
+
const url = canonicalThreadUrl(r.url);
|
|
13183
|
+
if (!url || seen.has(url)) continue;
|
|
13184
|
+
seen.add(url);
|
|
13185
|
+
out.push({
|
|
13186
|
+
title: typeof r.title === "string" ? r.title.trim() : "",
|
|
13187
|
+
url,
|
|
13188
|
+
subreddit: subredditFromUrl(url)
|
|
13181
13189
|
});
|
|
13190
|
+
if (out.length >= limit) break;
|
|
13191
|
+
}
|
|
13192
|
+
return out;
|
|
13193
|
+
}
|
|
13194
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
13195
|
+
const results = new Array(items.length);
|
|
13196
|
+
let next = 0;
|
|
13197
|
+
const workers = Array.from({ length: Math.max(1, Math.min(concurrency, items.length)) }, async () => {
|
|
13198
|
+
while (next < items.length) {
|
|
13199
|
+
const i = next++;
|
|
13200
|
+
results[i] = await fn(items[i], i);
|
|
13201
|
+
}
|
|
13182
13202
|
});
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
function buildRedditSearchUrl(topic, subreddit) {
|
|
13186
|
-
const base = subreddit ? `https://old.reddit.com/r/${encodeURIComponent(subreddit)}/search` : "https://old.reddit.com/search";
|
|
13187
|
-
const params = new URLSearchParams();
|
|
13188
|
-
params.set("q", topic);
|
|
13189
|
-
params.set("sort", "top");
|
|
13190
|
-
params.set("t", "month");
|
|
13191
|
-
if (subreddit) params.set("restrict_sr", "on");
|
|
13192
|
-
return `${base}?${params.toString()}`;
|
|
13203
|
+
await Promise.all(workers);
|
|
13204
|
+
return results;
|
|
13193
13205
|
}
|
|
13194
13206
|
function parseCountText(text2) {
|
|
13195
13207
|
if (typeof text2 !== "string") return 0;
|
|
@@ -13214,35 +13226,6 @@ function canonicalThreadUrl(href) {
|
|
|
13214
13226
|
function engagementScore(score, commentCount) {
|
|
13215
13227
|
return score + 2 * commentCount;
|
|
13216
13228
|
}
|
|
13217
|
-
function normalizeSearchRows(rows) {
|
|
13218
|
-
if (!Array.isArray(rows)) return [];
|
|
13219
|
-
const seen = /* @__PURE__ */ new Set();
|
|
13220
|
-
const out = [];
|
|
13221
|
-
for (const raw of rows) {
|
|
13222
|
-
if (!raw || typeof raw !== "object") continue;
|
|
13223
|
-
const r = raw;
|
|
13224
|
-
const url = canonicalThreadUrl(r.url);
|
|
13225
|
-
const title = typeof r.title === "string" ? r.title.trim() : "";
|
|
13226
|
-
if (!url || !title || seen.has(url)) continue;
|
|
13227
|
-
seen.add(url);
|
|
13228
|
-
const score = parseCountText(r.scoreText);
|
|
13229
|
-
const commentCount = parseCountText(r.commentsText);
|
|
13230
|
-
out.push({
|
|
13231
|
-
title,
|
|
13232
|
-
url,
|
|
13233
|
-
subreddit: typeof r.subreddit === "string" ? r.subreddit.trim() : "",
|
|
13234
|
-
score,
|
|
13235
|
-
commentCount,
|
|
13236
|
-
engagementScore: engagementScore(score, commentCount),
|
|
13237
|
-
ageText: typeof r.ageText === "string" ? r.ageText.trim() : "",
|
|
13238
|
-
topQuestions: []
|
|
13239
|
-
});
|
|
13240
|
-
}
|
|
13241
|
-
return out;
|
|
13242
|
-
}
|
|
13243
|
-
function rankThreads(rows, maxThreads) {
|
|
13244
|
-
return [...rows].sort((a, b) => b.engagementScore - a.engagementScore).slice(0, maxThreads);
|
|
13245
|
-
}
|
|
13246
13229
|
var INTERROGATIVE_STARTERS = /* @__PURE__ */ new Set([
|
|
13247
13230
|
"what",
|
|
13248
13231
|
"whats",
|
|
@@ -13335,7 +13318,8 @@ var RedditThreadBodySchema = z19.object({
|
|
|
13335
13318
|
var RedditTrendingBodySchema = z19.object({
|
|
13336
13319
|
topic: z19.string().trim().min(1, "topic is required"),
|
|
13337
13320
|
subreddit: z19.string().trim().regex(/^(r\/)?[A-Za-z0-9_]{2,24}$/i, "subreddit must be a bare name like SEO").optional(),
|
|
13338
|
-
|
|
13321
|
+
window: z19.enum(["week", "month"]).default("month"),
|
|
13322
|
+
maxThreads: z19.number().int().min(1).max(40).default(20),
|
|
13339
13323
|
includeComments: z19.boolean().default(true),
|
|
13340
13324
|
maxCommentsPerThread: z19.number().int().min(1).max(200).default(50)
|
|
13341
13325
|
});
|
|
@@ -13486,50 +13470,76 @@ redditApp.post("/thread", createApiKeyAuth(), async (c) => {
|
|
|
13486
13470
|
await releaseConcurrencyGate(gate.lockId);
|
|
13487
13471
|
}
|
|
13488
13472
|
});
|
|
13473
|
+
var TRENDING_SOFT_DEADLINE_MS = 24e4;
|
|
13474
|
+
var TRENDING_SCRAPE_CONCURRENCY = 4;
|
|
13489
13475
|
redditApp.post("/trending", createApiKeyAuth(), async (c) => {
|
|
13490
13476
|
const raw = await c.req.json().catch(() => ({}));
|
|
13491
13477
|
const parsed = RedditTrendingBodySchema.safeParse(raw);
|
|
13492
13478
|
if (!parsed.success) return c.json(invalidRequest4(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
13493
13479
|
const body = parsed.data;
|
|
13494
13480
|
const subreddit = body.subreddit ? body.subreddit.replace(/^r\//i, "") : void 0;
|
|
13495
|
-
const
|
|
13481
|
+
const serpQuery = buildSerpQuery(body.topic, subreddit);
|
|
13496
13482
|
const user = c.get("user");
|
|
13497
13483
|
const gate = await acquireConcurrencyGate(user, "reddit_trending", {
|
|
13498
13484
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
13499
13485
|
metadata: { topic: body.topic }
|
|
13500
13486
|
});
|
|
13501
13487
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
13488
|
+
const softDeadlineMs = Date.now() + TRENDING_SOFT_DEADLINE_MS;
|
|
13502
13489
|
let discoveryDebited = false;
|
|
13503
13490
|
let discoveryRefunded = false;
|
|
13504
|
-
|
|
13505
|
-
let commentDebitMcOutstanding = 0;
|
|
13491
|
+
const outstanding = { thread: 0, comment: 0 };
|
|
13506
13492
|
try {
|
|
13507
|
-
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD,
|
|
13493
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD, serpQuery);
|
|
13508
13494
|
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.reddit_thread), 402);
|
|
13509
13495
|
discoveryDebited = true;
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13496
|
+
let candidates = [];
|
|
13497
|
+
try {
|
|
13498
|
+
const serp = await harvest({
|
|
13499
|
+
query: serpQuery,
|
|
13500
|
+
serpOnly: true,
|
|
13501
|
+
proxyMode: "none",
|
|
13502
|
+
pages: 2,
|
|
13503
|
+
recency: body.window,
|
|
13504
|
+
kernelApiKey: browserServiceApiKey(),
|
|
13505
|
+
format: "json",
|
|
13506
|
+
outputDir: "/tmp/reddit-trending-serp",
|
|
13507
|
+
softDeadlineMs
|
|
13508
|
+
});
|
|
13509
|
+
candidates = extractRedditThreadUrls(serp.organicResults, body.maxThreads);
|
|
13510
|
+
} catch {
|
|
13511
|
+
candidates = [];
|
|
13520
13512
|
}
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13513
|
+
if (candidates.length === 0) {
|
|
13514
|
+
await creditMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD_REFUND, "no reddit threads discovered");
|
|
13515
|
+
discoveryRefunded = true;
|
|
13516
|
+
await logRequestEvent({ userId: user.id, source: "reddit_trending", status: "failed", query: body.topic, error: "no reddit threads discovered" });
|
|
13517
|
+
return c.json({ error: "No Reddit threads found for that topic and window (refunded)" }, 404);
|
|
13518
|
+
}
|
|
13519
|
+
let ranked;
|
|
13520
|
+
if (!body.includeComments) {
|
|
13521
|
+
ranked = candidates.map((cand) => ({
|
|
13522
|
+
title: cand.title,
|
|
13523
|
+
url: cand.url,
|
|
13524
|
+
subreddit: cand.subreddit,
|
|
13525
|
+
score: 0,
|
|
13526
|
+
commentCount: 0,
|
|
13527
|
+
engagementScore: 0,
|
|
13528
|
+
ageText: "",
|
|
13529
|
+
topQuestions: []
|
|
13530
|
+
}));
|
|
13531
|
+
} else {
|
|
13532
|
+
let balanceExhausted = false;
|
|
13533
|
+
const scrapedThreads = await mapWithConcurrency(candidates, TRENDING_SCRAPE_CONCURRENCY, async (cand) => {
|
|
13534
|
+
if (balanceExhausted || Date.now() >= softDeadlineMs) return null;
|
|
13535
|
+
const oldUrl = toOldReddit(cand.url);
|
|
13536
|
+
if (!oldUrl) return null;
|
|
13537
|
+
const threadDebit = await debitMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD, cand.url);
|
|
13538
|
+
if (!threadDebit.ok) {
|
|
13539
|
+
balanceExhausted = true;
|
|
13540
|
+
return null;
|
|
13541
|
+
}
|
|
13542
|
+
outstanding.thread += MC_COSTS.reddit_thread;
|
|
13533
13543
|
const scraped = await scrapeOldRedditWithRetries(
|
|
13534
13544
|
oldUrl,
|
|
13535
13545
|
PARSE_REDDIT,
|
|
@@ -13537,46 +13547,57 @@ redditApp.post("/trending", createApiKeyAuth(), async (c) => {
|
|
|
13537
13547
|
);
|
|
13538
13548
|
if (!scraped) {
|
|
13539
13549
|
await creditMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD_REFUND, "reddit blocked/empty after retries");
|
|
13540
|
-
|
|
13541
|
-
|
|
13550
|
+
outstanding.thread -= MC_COSTS.reddit_thread;
|
|
13551
|
+
return null;
|
|
13542
13552
|
}
|
|
13553
|
+
const score = parseCountText(scraped.data.score);
|
|
13543
13554
|
let comments = Array.isArray(scraped.data.comments) ? scraped.data.comments : [];
|
|
13555
|
+
const commentCount = comments.length;
|
|
13544
13556
|
comments = comments.slice(0, body.maxCommentsPerThread);
|
|
13545
13557
|
let commentMc = comments.length * MC_COSTS.reddit_comment;
|
|
13546
13558
|
if (commentMc > 0) {
|
|
13547
|
-
const commentDebit = await debitMc(user.id, commentMc, LedgerOperation.REDDIT_COMMENT,
|
|
13559
|
+
const commentDebit = await debitMc(user.id, commentMc, LedgerOperation.REDDIT_COMMENT, cand.url);
|
|
13548
13560
|
if (!commentDebit.ok) {
|
|
13561
|
+
balanceExhausted = true;
|
|
13549
13562
|
const affordable = Math.floor((commentDebit.balance_mc ?? 0) / MC_COSTS.reddit_comment);
|
|
13550
13563
|
comments = comments.slice(0, affordable);
|
|
13551
13564
|
commentMc = affordable * MC_COSTS.reddit_comment;
|
|
13552
|
-
if (commentMc > 0) await debitMc(user.id, commentMc, LedgerOperation.REDDIT_COMMENT,
|
|
13553
|
-
insufficient = true;
|
|
13565
|
+
if (commentMc > 0) await debitMc(user.id, commentMc, LedgerOperation.REDDIT_COMMENT, cand.url);
|
|
13554
13566
|
}
|
|
13555
|
-
|
|
13567
|
+
outstanding.comment += commentMc;
|
|
13556
13568
|
}
|
|
13557
13569
|
const topLevelBodies = comments.filter((cm) => cm && cm.depth === 0).map((cm) => cm.body);
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13570
|
+
return {
|
|
13571
|
+
title: cand.title || scraped.data.title || "",
|
|
13572
|
+
url: cand.url,
|
|
13573
|
+
subreddit: cand.subreddit,
|
|
13574
|
+
score,
|
|
13575
|
+
commentCount,
|
|
13576
|
+
engagementScore: engagementScore(score, commentCount),
|
|
13577
|
+
ageText: "",
|
|
13578
|
+
topQuestions: extractQuestions([scraped.data.postBody, ...topLevelBodies])
|
|
13579
|
+
};
|
|
13580
|
+
});
|
|
13581
|
+
ranked = scrapedThreads.filter((t) => t !== null).sort((a, b) => b.engagementScore - a.engagementScore);
|
|
13561
13582
|
}
|
|
13562
13583
|
const result = {
|
|
13563
13584
|
topic: body.topic,
|
|
13564
13585
|
subreddit: subreddit ?? null,
|
|
13565
|
-
window: "30d",
|
|
13586
|
+
window: body.window === "week" ? "7d" : "30d",
|
|
13566
13587
|
totals: trendingTotals(ranked),
|
|
13567
13588
|
rankedThreads: ranked,
|
|
13568
13589
|
questions: flattenQuestions(ranked),
|
|
13569
|
-
threadsScraped,
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13590
|
+
threadsScraped: body.includeComments ? ranked.length : 0,
|
|
13591
|
+
candidatesFound: candidates.length,
|
|
13592
|
+
partial: body.includeComments && ranked.length < candidates.length,
|
|
13593
|
+
searchQuery: serpQuery
|
|
13573
13594
|
};
|
|
13574
13595
|
await logRequestEvent({ userId: user.id, source: "reddit_trending", status: "done", query: body.topic, resultCount: ranked.length, result });
|
|
13575
13596
|
return c.json(result);
|
|
13576
13597
|
} catch (err) {
|
|
13577
13598
|
if (discoveryDebited && !discoveryRefunded) await creditMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD_REFUND, "failed call");
|
|
13578
|
-
if (
|
|
13579
|
-
if (
|
|
13599
|
+
if (outstanding.thread > 0) await creditMc(user.id, outstanding.thread, LedgerOperation.REDDIT_THREAD_REFUND, "failed call");
|
|
13600
|
+
if (outstanding.comment > 0) await creditMc(user.id, outstanding.comment, LedgerOperation.REDDIT_COMMENT_REFUND, "failed call");
|
|
13580
13601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13581
13602
|
await logRequestEvent({ userId: user.id, source: "reddit_trending", status: "failed", query: body.topic, error: msg });
|
|
13582
13603
|
return c.json({ error: msg }, 500);
|
|
@@ -21923,7 +21944,7 @@ async function readMemory(tool, args, token) {
|
|
|
21923
21944
|
}
|
|
21924
21945
|
return result;
|
|
21925
21946
|
}
|
|
21926
|
-
async function
|
|
21947
|
+
async function mapWithConcurrency2(items, limit, map) {
|
|
21927
21948
|
const values = new Array(items.length);
|
|
21928
21949
|
let cursor = 0;
|
|
21929
21950
|
const worker = async () => {
|
|
@@ -21940,7 +21961,7 @@ async function readVault(vault, token) {
|
|
|
21940
21961
|
const list = await readMemory("listTool", { vault }, token);
|
|
21941
21962
|
if (!list.ok) throw new Error(list.error ?? `unable to read ${vault}`);
|
|
21942
21963
|
const noteRefs = (list.notes ?? []).slice(0, MAX_VAULT_RECORDS);
|
|
21943
|
-
const notes = await
|
|
21964
|
+
const notes = await mapWithConcurrency2(
|
|
21944
21965
|
noteRefs,
|
|
21945
21966
|
MAX_MEMORY_READ_CONCURRENCY,
|
|
21946
21967
|
({ path: path5 }) => readMemory("getTool", { vault, path: path5 }, token)
|
|
@@ -28160,7 +28181,8 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
28160
28181
|
proxyZip: body.proxyZip,
|
|
28161
28182
|
debug: body.debug ?? false,
|
|
28162
28183
|
serpOnly: body.serpOnly ?? false,
|
|
28163
|
-
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
28184
|
+
pages: Math.min(2, Math.max(1, body.pages ?? 1)),
|
|
28185
|
+
recency: body.recency
|
|
28164
28186
|
};
|
|
28165
28187
|
const syncCost = options.serpOnly ? MC_COSTS.serp_headful : paaCostForQuestionCount(options.maxQuestions);
|
|
28166
28188
|
const { ok: syncOk, balance_mc: syncBal } = await debitMc(user.id, syncCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
@@ -28915,7 +28937,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
28915
28937
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
28916
28938
|
return c.json({ error: "Unauthorized" }, 401);
|
|
28917
28939
|
}
|
|
28918
|
-
const { drainQueue } = await import("./worker-
|
|
28940
|
+
const { drainQueue } = await import("./worker-ZZHSYI3Y.js");
|
|
28919
28941
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
28920
28942
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
28921
28943
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup] = await Promise.all([
|
|
@@ -29120,4 +29142,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
29120
29142
|
export {
|
|
29121
29143
|
app
|
|
29122
29144
|
};
|
|
29123
|
-
//# sourceMappingURL=server-
|
|
29145
|
+
//# sourceMappingURL=server-LKVPEQFE.js.map
|