scrapebadger 0.24.2 → 0.26.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/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
  *
@@ -6750,6 +6826,150 @@ var LinkedInClient = class {
6750
6826
  }
6751
6827
  };
6752
6828
 
6829
+ // src/chatgpt/ask.ts
6830
+ var AskClient = class {
6831
+ client;
6832
+ constructor(client) {
6833
+ this.client = client;
6834
+ }
6835
+ /**
6836
+ * Ask ChatGPT a question and get the answer with its sources.
6837
+ *
6838
+ * Costs 20 credits. Typical latency is 20-25s ungrounded, 30-70s with web search.
6839
+ *
6840
+ * @param params - Ask parameters.
6841
+ * @param params.prompt - The prompt to send (max 4096 characters).
6842
+ * @param params.country - ISO-3166 alpha-2 egress country (default: "US").
6843
+ * @param params.web_search - "auto", "force", or "off" (default: "auto").
6844
+ * @returns The answer, its citations, and the full retrieved search set.
6845
+ *
6846
+ * @example
6847
+ * ```typescript
6848
+ * const result = await client.chatgpt.ask.ask({
6849
+ * prompt: "what is the best CRM for a 10-person startup?",
6850
+ * country: "GB",
6851
+ * web_search: "force",
6852
+ * });
6853
+ * console.log(result.web_search_triggered, result.model);
6854
+ * for (const source of result.search_results) {
6855
+ * console.log(`${source.cited ? "*" : " "} ${source.url}`);
6856
+ * }
6857
+ * ```
6858
+ */
6859
+ async ask(params) {
6860
+ return this.client.request("/v1/chatgpt/ask", {
6861
+ params: {
6862
+ prompt: params.prompt,
6863
+ country: params.country,
6864
+ web_search: params.web_search
6865
+ }
6866
+ });
6867
+ }
6868
+ };
6869
+
6870
+ // src/chatgpt/brand.ts
6871
+ var BrandClient = class {
6872
+ client;
6873
+ constructor(client) {
6874
+ this.client = client;
6875
+ }
6876
+ /**
6877
+ * Analyse how a brand shows up in ChatGPT's answer to a prompt.
6878
+ *
6879
+ * Costs 25 credits.
6880
+ *
6881
+ * @param params - Brand-visibility parameters.
6882
+ * @param params.prompt - The prompt to send (max 4096 characters).
6883
+ * @param params.brand - The brand name to look for in the answer.
6884
+ * @param params.domain - The brand's domain, used to detect brand citations.
6885
+ * @param params.aliases - Other spellings that should count as mentions.
6886
+ * @param params.competitors - Competitors to measure share of voice against.
6887
+ * @param params.country - ISO-3166 alpha-2 egress country (default: "US").
6888
+ * @param params.web_search - "auto", "force", or "off" (default: "force").
6889
+ * @returns The brand analysis plus the answer and its citations.
6890
+ *
6891
+ * @example
6892
+ * ```typescript
6893
+ * const result = await client.chatgpt.brand.visibility({
6894
+ * prompt: "which proxy provider should I use?",
6895
+ * brand: "ScrapeBadger",
6896
+ * domain: "scrapebadger.com",
6897
+ * aliases: ["Scrape Badger"],
6898
+ * competitors: ["Bright Data", "Oxylabs"],
6899
+ * country: "DE",
6900
+ * });
6901
+ * console.log(`position score: ${result.position_score}`);
6902
+ * for (const competitor of result.competitors) {
6903
+ * console.log(`${competitor.name}: ${competitor.mention_count}`);
6904
+ * }
6905
+ * ```
6906
+ */
6907
+ async visibility(params) {
6908
+ return this.client.request("/v1/chatgpt/brand-visibility", {
6909
+ params: {
6910
+ prompt: params.prompt,
6911
+ brand: params.brand,
6912
+ domain: params.domain,
6913
+ aliases: params.aliases?.length ? params.aliases.join(",") : void 0,
6914
+ competitors: params.competitors?.length ? params.competitors.join(",") : void 0,
6915
+ country: params.country,
6916
+ web_search: params.web_search
6917
+ }
6918
+ });
6919
+ }
6920
+ };
6921
+
6922
+ // src/chatgpt/reference.ts
6923
+ var ReferenceClient11 = class {
6924
+ client;
6925
+ constructor(client) {
6926
+ this.client = client;
6927
+ }
6928
+ /**
6929
+ * Get the models chatgpt.com currently offers.
6930
+ *
6931
+ * Costs 1 credit.
6932
+ *
6933
+ * @param params - Optional parameters.
6934
+ * @param params.country - ISO-3166 alpha-2 egress country (default: "US").
6935
+ * @returns The available models.
6936
+ *
6937
+ * @example
6938
+ * ```typescript
6939
+ * const result = await client.chatgpt.reference.models({ country: "GB" });
6940
+ * console.log(`${result.count} models`);
6941
+ * for (const model of result.models) {
6942
+ * console.log(`${model.slug}: ${model.max_tokens} tokens`);
6943
+ * }
6944
+ * ```
6945
+ */
6946
+ async models(params = {}) {
6947
+ return this.client.request("/v1/chatgpt/models", {
6948
+ params: { country: params.country }
6949
+ });
6950
+ }
6951
+ };
6952
+
6953
+ // src/chatgpt/client.ts
6954
+ var ChatGPTClient = class {
6955
+ /** Client for asking ChatGPT a question */
6956
+ ask;
6957
+ /** Client for AEO/GEO brand-visibility analysis */
6958
+ brand;
6959
+ /** Client for reference data (available models) */
6960
+ reference;
6961
+ /**
6962
+ * Create a new ChatGPT client.
6963
+ *
6964
+ * @param client - The base HTTP client for making requests.
6965
+ */
6966
+ constructor(client) {
6967
+ this.ask = new AskClient(client);
6968
+ this.brand = new BrandClient(client);
6969
+ this.reference = new ReferenceClient11(client);
6970
+ }
6971
+ };
6972
+
6753
6973
  // src/client.ts
6754
6974
  var ScrapeBadger = class {
6755
6975
  baseClient;
@@ -6789,6 +7009,8 @@ var ScrapeBadger = class {
6789
7009
  depop;
6790
7010
  /** LinkedIn scraper API client — 11 no-auth endpoints (jobs, company, school, profile, post, article, learning, geo) */
6791
7011
  linkedin;
7012
+ /** ChatGPT scraper API client — ask, brand visibility, models (the real chatgpt.com, anonymous) */
7013
+ chatgpt;
6792
7014
  /**
6793
7015
  * Create a new ScrapeBadger client.
6794
7016
  *
@@ -6841,6 +7063,7 @@ var ScrapeBadger = class {
6841
7063
  this.loopnet = new LoopNetClient(this.baseClient);
6842
7064
  this.depop = new DepopClient(this.baseClient);
6843
7065
  this.linkedin = new LinkedInClient(this.baseClient);
7066
+ this.chatgpt = new ChatGPTClient(this.baseClient);
6844
7067
  }
6845
7068
  };
6846
7069
 
@@ -6852,6 +7075,10 @@ exports.AmazonReferenceClient = ReferenceClient2;
6852
7075
  exports.AmazonSearchClient = SearchClient4;
6853
7076
  exports.AmazonSellersClient = SellersClient;
6854
7077
  exports.AuthenticationError = AuthenticationError;
7078
+ exports.ChatGPTAskClient = AskClient;
7079
+ exports.ChatGPTBrandClient = BrandClient;
7080
+ exports.ChatGPTClient = ChatGPTClient;
7081
+ exports.ChatGPTReferenceClient = ReferenceClient11;
6855
7082
  exports.CommunitiesClient = CommunitiesClient;
6856
7083
  exports.ConflictError = ConflictError;
6857
7084
  exports.DepopClient = DepopClient;