scrapebadger 0.24.2 → 0.25.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.
@@ -52,6 +52,22 @@ declare class BaseClient {
52
52
  * Make an HTTP request to the API.
53
53
  */
54
54
  request<T>(path: string, options?: RequestOptions): Promise<T>;
55
+ /**
56
+ * POST and return the undecoded response body.
57
+ *
58
+ * For endpoints that answer with something other than JSON — currently
59
+ * `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
60
+ * itself. The normal path funnels a non-JSON response into
61
+ * `{ detail: await response.text() }`, which both loses the result and, for
62
+ * a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
63
+ *
64
+ * Returns the raw bytes plus the response headers.
65
+ */
66
+ postBinary(path: string, options?: RequestOptions): Promise<{
67
+ bytes: Uint8Array;
68
+ headers: Headers;
69
+ status: number;
70
+ }>;
55
71
  /**
56
72
  * Make an HTTP request and return both data and rate limit headers.
57
73
  */
@@ -52,6 +52,22 @@ declare class BaseClient {
52
52
  * Make an HTTP request to the API.
53
53
  */
54
54
  request<T>(path: string, options?: RequestOptions): Promise<T>;
55
+ /**
56
+ * POST and return the undecoded response body.
57
+ *
58
+ * For endpoints that answer with something other than JSON — currently
59
+ * `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
60
+ * itself. The normal path funnels a non-JSON response into
61
+ * `{ detail: await response.text() }`, which both loses the result and, for
62
+ * a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
63
+ *
64
+ * Returns the raw bytes plus the response headers.
65
+ */
66
+ postBinary(path: string, options?: RequestOptions): Promise<{
67
+ bytes: Uint8Array;
68
+ headers: Headers;
69
+ status: number;
70
+ }>;
55
71
  /**
56
72
  * Make an HTTP request and return both data and rate limit headers.
57
73
  */
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-jp8WyFsJ.cjs';
2
- export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-jp8WyFsJ.cjs';
1
+ import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-ZD3sVCjc.cjs';
2
+ export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-ZD3sVCjc.cjs';
3
3
  import 'node:events';
4
4
 
5
5
  /**
@@ -65,6 +65,22 @@ interface ScrapeResult {
65
65
  url: string;
66
66
  status_code: number;
67
67
  content: string | null;
68
+ /**
69
+ * Base64 body for a binary target (image/PDF/archive). Set instead of
70
+ * `content`, which is null there — binary bytes have no text form.
71
+ * Null when the body exceeded the 25 MB base64 ceiling.
72
+ */
73
+ content_base64: string | null;
74
+ /** Whether the target returned a binary (non-text) body. */
75
+ is_binary: boolean;
76
+ /** The target's response Content-Type, as a bare media type. */
77
+ content_type: string | null;
78
+ /**
79
+ * Undecoded response body. Only set when `rawContent: true` — that mode
80
+ * returns the body itself rather than a JSON envelope. Write it straight to
81
+ * a file; do not decode it, the payload may be an image or a PDF.
82
+ */
83
+ content_bytes?: Uint8Array;
68
84
  format: string;
69
85
  engine_used: string;
70
86
  credits_used: number;
@@ -144,6 +160,16 @@ declare class WebClient {
144
160
  * @returns The scrape result including content, metadata, and credit usage
145
161
  */
146
162
  scrape(url: string, options?: ScrapeOptions): Promise<ScrapeResult>;
163
+ /**
164
+ * Run a `rawContent` scrape, whose response is not JSON.
165
+ *
166
+ * The normal path funnels a non-JSON response into `{ detail: text }`, so a
167
+ * raw scrape returned a result with no content — and for a binary target,
168
+ * `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
169
+ * body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
170
+ * server sends in this mode.
171
+ */
172
+ private scrapeRaw;
147
173
  /**
148
174
  * Extract structured data from a web page using AI.
149
175
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-jp8WyFsJ.js';
2
- export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-jp8WyFsJ.js';
1
+ import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-ZD3sVCjc.js';
2
+ export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-ZD3sVCjc.js';
3
3
  import 'node:events';
4
4
 
5
5
  /**
@@ -65,6 +65,22 @@ interface ScrapeResult {
65
65
  url: string;
66
66
  status_code: number;
67
67
  content: string | null;
68
+ /**
69
+ * Base64 body for a binary target (image/PDF/archive). Set instead of
70
+ * `content`, which is null there — binary bytes have no text form.
71
+ * Null when the body exceeded the 25 MB base64 ceiling.
72
+ */
73
+ content_base64: string | null;
74
+ /** Whether the target returned a binary (non-text) body. */
75
+ is_binary: boolean;
76
+ /** The target's response Content-Type, as a bare media type. */
77
+ content_type: string | null;
78
+ /**
79
+ * Undecoded response body. Only set when `rawContent: true` — that mode
80
+ * returns the body itself rather than a JSON envelope. Write it straight to
81
+ * a file; do not decode it, the payload may be an image or a PDF.
82
+ */
83
+ content_bytes?: Uint8Array;
68
84
  format: string;
69
85
  engine_used: string;
70
86
  credits_used: number;
@@ -144,6 +160,16 @@ declare class WebClient {
144
160
  * @returns The scrape result including content, metadata, and credit usage
145
161
  */
146
162
  scrape(url: string, options?: ScrapeOptions): Promise<ScrapeResult>;
163
+ /**
164
+ * Run a `rawContent` scrape, whose response is not JSON.
165
+ *
166
+ * The normal path funnels a non-JSON response into `{ detail: text }`, so a
167
+ * raw scrape returned a result with no content — and for a binary target,
168
+ * `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
169
+ * body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
170
+ * server sends in this mode.
171
+ */
172
+ private scrapeRaw;
147
173
  /**
148
174
  * Extract structured data from a web page using AI.
149
175
  *
package/dist/index.js CHANGED
@@ -137,6 +137,39 @@ var BaseClient = class _BaseClient {
137
137
  const { data } = await this.requestRaw(path, options);
138
138
  return data;
139
139
  }
140
+ /**
141
+ * POST and return the undecoded response body.
142
+ *
143
+ * For endpoints that answer with something other than JSON — currently
144
+ * `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
145
+ * itself. The normal path funnels a non-JSON response into
146
+ * `{ detail: await response.text() }`, which both loses the result and, for
147
+ * a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
148
+ *
149
+ * Returns the raw bytes plus the response headers.
150
+ */
151
+ async postBinary(path, options = {}) {
152
+ const url = new URL(path, this.config.baseUrl);
153
+ const { body, headers = {} } = options;
154
+ const response = await this.fetchWithTimeout(url.toString(), {
155
+ method: "POST",
156
+ headers: {
157
+ "Content-Type": "application/json",
158
+ "X-API-Key": this.config.apiKey,
159
+ "User-Agent": `scrapebadger-node/${SDK_VERSION}`,
160
+ ...headers
161
+ },
162
+ body: body ? JSON.stringify(body) : void 0
163
+ });
164
+ if (!response.ok) {
165
+ await this.handleResponse(response);
166
+ }
167
+ return {
168
+ bytes: new Uint8Array(await response.arrayBuffer()),
169
+ headers: response.headers,
170
+ status: response.status
171
+ };
172
+ }
140
173
  /**
141
174
  * Make an HTTP request and return both data and rate limit headers.
142
175
  */
@@ -2481,11 +2514,54 @@ var WebClient = class {
2481
2514
  if (options.aiPrompt !== void 0) body.ai_prompt = options.aiPrompt;
2482
2515
  if (options.rawContent !== void 0) body.raw_content = options.rawContent;
2483
2516
  if (options.skipBotDetection !== void 0) body.skip_bot_detection = options.skipBotDetection;
2517
+ if (options.rawContent) {
2518
+ return this.scrapeRaw(body);
2519
+ }
2484
2520
  return this.client.request("/v1/web/scrape", {
2485
2521
  method: "POST",
2486
2522
  body
2487
2523
  });
2488
2524
  }
2525
+ /**
2526
+ * Run a `rawContent` scrape, whose response is not JSON.
2527
+ *
2528
+ * The normal path funnels a non-JSON response into `{ detail: text }`, so a
2529
+ * raw scrape returned a result with no content — and for a binary target,
2530
+ * `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
2531
+ * body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
2532
+ * server sends in this mode.
2533
+ */
2534
+ async scrapeRaw(body) {
2535
+ const { bytes, headers, status } = await this.client.postBinary("/v1/web/scrape", {
2536
+ body
2537
+ });
2538
+ const int = (name) => {
2539
+ const parsed = Number.parseInt(headers.get(name) ?? "", 10);
2540
+ return Number.isNaN(parsed) ? 0 : parsed;
2541
+ };
2542
+ const mediaType = ((headers.get("content-type") ?? "").split(";")[0] ?? "").trim().toLowerCase();
2543
+ const isText = mediaType.startsWith("text/") || ["application/json", "application/xml", "image/svg+xml"].includes(mediaType);
2544
+ return {
2545
+ success: headers.get("x-scrape-success") !== "0",
2546
+ url: headers.get("x-scrape-url") ?? (typeof body.url === "string" ? body.url : ""),
2547
+ status_code: int("x-scrape-status-code") || status,
2548
+ content: isText ? new TextDecoder().decode(bytes) : null,
2549
+ content_bytes: bytes,
2550
+ content_base64: null,
2551
+ is_binary: !isText,
2552
+ content_type: mediaType || null,
2553
+ format: headers.get("x-scrape-format") ?? "html",
2554
+ engine_used: headers.get("x-scrape-engine") ?? "",
2555
+ credits_used: int("x-credits-used"),
2556
+ duration_ms: int("x-scrape-duration-ms"),
2557
+ retries_used: int("x-scrape-retries"),
2558
+ content_length: int("x-scrape-content-length") || bytes.length,
2559
+ screenshot_url: null,
2560
+ video_url: null,
2561
+ headers: {},
2562
+ blocking_detected: false
2563
+ };
2564
+ }
2489
2565
  /**
2490
2566
  * Extract structured data from a web page using AI.
2491
2567
  *