@blockrun/franklin 3.15.7 → 3.15.8
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/tools/webfetch.js +98 -1
- package/package.json +1 -1
package/dist/tools/webfetch.js
CHANGED
|
@@ -58,6 +58,22 @@ async function execute(input, ctx) {
|
|
|
58
58
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
59
59
|
return { output: `Error: only http/https URLs are supported`, isError: true };
|
|
60
60
|
}
|
|
61
|
+
// ── Pre-flight: known anti-bot domains ──
|
|
62
|
+
// Sites that systematically block scripted access return 403 / 429 /
|
|
63
|
+
// captcha challenges to plain GET requests no matter what UA we send.
|
|
64
|
+
// Without this guard the model burns multiple turns retrying variations
|
|
65
|
+
// (Zillow → /research/austin-tx, /homedetails/X, /sold/Y...) that all
|
|
66
|
+
// 403 the same way, padding the step counter and the user's bill.
|
|
67
|
+
// Short-circuiting here returns a single actionable error instead.
|
|
68
|
+
const blocked = isBlockedDomain(parsed.hostname);
|
|
69
|
+
if (blocked) {
|
|
70
|
+
return {
|
|
71
|
+
output: `${parsed.hostname} systematically blocks automated fetch (${blocked.reason}). ` +
|
|
72
|
+
`Switch tools: ${blocked.alternative}. Don't retry this URL with WebFetch — ` +
|
|
73
|
+
`every variant of the same hostname returns the same block.`,
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
61
77
|
const maxLen = Math.min(max_length ?? DEFAULT_MAX_LENGTH, MAX_BODY_BYTES);
|
|
62
78
|
// ── YouTube special case ──
|
|
63
79
|
// Plain HTML fetch on a youtube.com URL returns the SPA bundle (a wall of
|
|
@@ -108,8 +124,19 @@ async function execute(input, ctx) {
|
|
|
108
124
|
redirect: 'follow',
|
|
109
125
|
});
|
|
110
126
|
if (!response.ok) {
|
|
127
|
+
// 403 / 429 from a domain not in the static block list often still
|
|
128
|
+
// means anti-bot — many sites tier their detection (first hit OK,
|
|
129
|
+
// subsequent ones blocked) or rely on UA fingerprinting. Surface
|
|
130
|
+
// this as an actionable hint so the model switches strategy
|
|
131
|
+
// instead of retrying the same URL with a different path.
|
|
132
|
+
const isAntiBot = response.status === 403 || response.status === 429 ||
|
|
133
|
+
response.status === 503;
|
|
134
|
+
const hint = isAntiBot
|
|
135
|
+
? ` — ${parsed.hostname} likely blocks automated fetch. Try WebSearch for the same query, ` +
|
|
136
|
+
`or fetch a different domain that publishes the same data.`
|
|
137
|
+
: '';
|
|
111
138
|
return {
|
|
112
|
-
output: `HTTP ${response.status} ${response.statusText} for ${url}`,
|
|
139
|
+
output: `HTTP ${response.status} ${response.statusText} for ${url}${hint}`,
|
|
113
140
|
isError: true,
|
|
114
141
|
};
|
|
115
142
|
}
|
|
@@ -176,6 +203,76 @@ async function execute(input, ctx) {
|
|
|
176
203
|
ctx.abortSignal.removeEventListener('abort', onAbort);
|
|
177
204
|
}
|
|
178
205
|
}
|
|
206
|
+
const BLOCKED_DOMAINS = [
|
|
207
|
+
{
|
|
208
|
+
pattern: /(^|\.)zillow\.com$/i,
|
|
209
|
+
reason: '403 to all non-browser GETs',
|
|
210
|
+
alternative: 'use WebSearch for "Austin TX home price trends" or similar',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
pattern: /(^|\.)redfin\.com$/i,
|
|
214
|
+
reason: '403 / captcha challenge to scripted requests',
|
|
215
|
+
alternative: 'use WebSearch with the property address or zip code',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
pattern: /(^|\.)realtor\.com$/i,
|
|
219
|
+
reason: '403 / interstitial to non-browser UAs',
|
|
220
|
+
alternative: 'use WebSearch',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
pattern: /(^|\.)linkedin\.com$/i,
|
|
224
|
+
reason: 'auth wall on every page',
|
|
225
|
+
alternative: 'use SearchX (X is the better discovery surface for the same people) or WebSearch',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
pattern: /(^|\.)instagram\.com$/i,
|
|
229
|
+
reason: 'auth wall + 401 to public profile fetches',
|
|
230
|
+
alternative: 'use WebSearch for the username',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
pattern: /(^|\.)facebook\.com$/i,
|
|
234
|
+
reason: 'auth wall on most public content',
|
|
235
|
+
alternative: 'use WebSearch',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
pattern: /(^|\.)x\.com$/i,
|
|
239
|
+
reason: 'X.com requires authenticated API',
|
|
240
|
+
alternative: 'use SearchX (the dedicated X tool) instead of WebFetch',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
pattern: /(^|\.)twitter\.com$/i,
|
|
244
|
+
reason: 'X.com requires authenticated API',
|
|
245
|
+
alternative: 'use SearchX (the dedicated X tool) instead of WebFetch',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
pattern: /(^|\.)tiktok\.com$/i,
|
|
249
|
+
reason: 'returns SPA shell + JS challenge',
|
|
250
|
+
alternative: 'use WebSearch with the @username',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
pattern: /(^|\.)reuters\.com$/i,
|
|
254
|
+
reason: 'paywall + bot detection',
|
|
255
|
+
alternative: 'use WebSearch which surfaces cached headlines',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
pattern: /(^|\.)bloomberg\.com$/i,
|
|
259
|
+
reason: 'paywall + bot detection',
|
|
260
|
+
alternative: 'use WebSearch for the same story',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
pattern: /(^|\.)wsj\.com$/i,
|
|
264
|
+
reason: 'paywall',
|
|
265
|
+
alternative: 'use WebSearch for the same story',
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
function isBlockedDomain(hostname) {
|
|
269
|
+
for (const entry of BLOCKED_DOMAINS) {
|
|
270
|
+
if (entry.pattern.test(hostname)) {
|
|
271
|
+
return { reason: entry.reason, alternative: entry.alternative };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
179
276
|
// ─── YouTube transcript fetcher ─────────────────────────────────────────────
|
|
180
277
|
// Fetches auto-generated or uploaded captions for a YouTube video by parsing
|
|
181
278
|
// the watch-page's `ytInitialPlayerResponse` JSON. Pure HTTP, no deps. Saves
|
package/package.json
CHANGED