@shaivpidadi/trends-js 0.0.0-beta.7 → 1.0.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.
@@ -1,8 +1,8 @@
1
- import { GoogleTrendsEndpoints } from '../types/enums';
2
- import { request } from './request';
3
- import { extractJsonFromResponse } from './format';
4
- import { GOOGLE_TRENDS_MAPPER } from '../constants';
5
- import { NetworkError, ParseError, UnknownError, } from '../errors/GoogleTrendsError';
1
+ import { GoogleTrendsEndpoints } from '../types/enums.js';
2
+ import { request } from './request.js';
3
+ import { extractJsonFromResponse } from './format.js';
4
+ import { GOOGLE_TRENDS_MAPPER } from '../constants.js';
5
+ import { InvalidRequestError, NetworkError, ParseError, UnknownError, } from '../errors/GoogleTrendsError.js';
6
6
  export class GoogleTrendsApi {
7
7
  /**
8
8
  * Get autocomplete suggestions for a keyword
@@ -118,13 +118,33 @@ export class GoogleTrendsApi {
118
118
  try {
119
119
  const response = await request(options.url, options);
120
120
  const text = await response.text();
121
- // Remove the first 5 characters (JSONP wrapper) and parse
122
- const data = JSON.parse(text.slice(5));
123
- return data;
121
+ // Check if response is HTML (error page)
122
+ if (text.includes('<html') || text.includes('<!DOCTYPE')) {
123
+ return { error: new ParseError('Explore request returned HTML instead of JSON') };
124
+ }
125
+ // Try to parse as JSON
126
+ try {
127
+ // Remove the first 5 characters (JSONP wrapper) and parse
128
+ const data = JSON.parse(text.slice(5));
129
+ // Extract widgets from the response
130
+ if (data && Array.isArray(data) && data.length > 0) {
131
+ const widgets = data[0] || [];
132
+ return { widgets };
133
+ }
134
+ return { widgets: [] };
135
+ }
136
+ catch (parseError) {
137
+ if (parseError instanceof Error) {
138
+ return { error: new ParseError(`Failed to parse explore response as JSON: ${parseError.message}`) };
139
+ }
140
+ return { error: new ParseError('Failed to parse explore response as JSON') };
141
+ }
124
142
  }
125
143
  catch (error) {
126
- console.error('Explore request failed:', error);
127
- return { widgets: [] };
144
+ if (error instanceof Error) {
145
+ return { error: new NetworkError(`Explore request failed: ${error.message}`) };
146
+ }
147
+ return { error: new UnknownError('Explore request failed') };
128
148
  }
129
149
  }
130
150
  //
@@ -156,9 +176,12 @@ export class GoogleTrendsApi {
156
176
  category,
157
177
  hl
158
178
  });
179
+ if ('error' in exploreResponse) {
180
+ return { error: exploreResponse.error };
181
+ }
159
182
  const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
160
183
  if (!widget) {
161
- return { default: { geoMapData: [] } };
184
+ return { error: new ParseError('No GEO_MAP widget found in explore response') };
162
185
  }
163
186
  const options = {
164
187
  ...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.interestByRegion],
@@ -173,7 +196,7 @@ export class GoogleTrendsApi {
173
196
  time: `${formatDate(startTime)} ${formatDate(endTime)}`,
174
197
  complexKeywordsRestriction: {
175
198
  keyword: [{
176
- type: 'BROAD',
199
+ type: 'BROAD', //'ENTITY',
177
200
  value: Array.isArray(keyword) ? keyword[0] : keyword
178
201
  }]
179
202
  }
@@ -182,7 +205,7 @@ export class GoogleTrendsApi {
182
205
  locale: hl,
183
206
  requestOptions: {
184
207
  property: '',
185
- backend: 'CM',
208
+ backend: 'CM', //'IZG',
186
209
  category
187
210
  },
188
211
  userConfig: {
@@ -200,7 +223,179 @@ export class GoogleTrendsApi {
200
223
  return data;
201
224
  }
202
225
  catch (error) {
203
- return { default: { geoMapData: [] } };
226
+ if (error instanceof Error) {
227
+ return { error: new ParseError(`Failed to parse interest by region response: ${error.message}`) };
228
+ }
229
+ return { error: new ParseError('Failed to parse interest by region response') };
230
+ }
231
+ }
232
+ async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
233
+ try {
234
+ // Validate keyword
235
+ if (!keyword || keyword.trim() === '') {
236
+ return { error: new InvalidRequestError('Keyword is required') };
237
+ }
238
+ // Step 1: Call explore to get widget data and token
239
+ const exploreResponse = await this.explore({
240
+ keyword,
241
+ geo,
242
+ time,
243
+ category,
244
+ property,
245
+ hl
246
+ });
247
+ if ('error' in exploreResponse) {
248
+ return { error: exploreResponse.error };
249
+ }
250
+ if (!exploreResponse.widgets || exploreResponse.widgets.length === 0) {
251
+ return { error: new ParseError('No widgets found in explore response. This might be due to Google blocking the request, invalid parameters, or network issues.') };
252
+ }
253
+ // Step 2: Find the related topics widget or use any available widget
254
+ const relatedTopicsWidget = exploreResponse.widgets.find(widget => widget.id === 'RELATED_TOPICS' ||
255
+ widget.request?.restriction?.complexKeywordsRestriction?.keyword?.[0]?.value === keyword) || exploreResponse.widgets[0]; // Fallback to first widget if no specific one found
256
+ if (!relatedTopicsWidget) {
257
+ return { error: new ParseError('No related topics widget found in explore response') };
258
+ }
259
+ // Step 3: Call the related topics API with or without token
260
+ const options = {
261
+ ...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.relatedTopics],
262
+ qs: {
263
+ hl,
264
+ tz: '240',
265
+ req: JSON.stringify({
266
+ restriction: {
267
+ geo: { country: geo },
268
+ time: time,
269
+ originalTimeRangeForExploreUrl: time,
270
+ complexKeywordsRestriction: {
271
+ keyword: [{
272
+ type: 'BROAD',
273
+ value: keyword
274
+ }]
275
+ }
276
+ },
277
+ keywordType: 'ENTITY',
278
+ metric: ['TOP', 'RISING'],
279
+ trendinessSettings: {
280
+ compareTime: time
281
+ },
282
+ requestOptions: {
283
+ property: property,
284
+ backend: 'CM',
285
+ category: category
286
+ },
287
+ language: hl.split('-')[0],
288
+ userCountryCode: geo,
289
+ userConfig: {
290
+ userType: 'USER_TYPE_LEGIT_USER'
291
+ }
292
+ }),
293
+ ...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token })
294
+ }
295
+ };
296
+ const response = await request(options.url, options);
297
+ const text = await response.text();
298
+ // Parse the response
299
+ try {
300
+ const data = JSON.parse(text.slice(5));
301
+ // Return the data in the expected format
302
+ return {
303
+ data: {
304
+ default: {
305
+ rankedList: data.default?.rankedList || []
306
+ }
307
+ }
308
+ };
309
+ }
310
+ catch (parseError) {
311
+ if (parseError instanceof Error) {
312
+ return { error: new ParseError(`Failed to parse related topics response: ${parseError.message}`) };
313
+ }
314
+ return { error: new ParseError('Failed to parse related topics response') };
315
+ }
316
+ }
317
+ catch (error) {
318
+ if (error instanceof Error) {
319
+ return { error: new NetworkError(error.message) };
320
+ }
321
+ return { error: new UnknownError() };
322
+ }
323
+ }
324
+ async relatedQueries({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
325
+ try {
326
+ // Validate keyword
327
+ if (!keyword || keyword.trim() === '') {
328
+ return { error: new ParseError() };
329
+ }
330
+ const autocompleteResult = await this.autocomplete(keyword, hl);
331
+ if (autocompleteResult.error) {
332
+ return { error: autocompleteResult.error };
333
+ }
334
+ const relatedQueries = autocompleteResult.data?.slice(0, 10).map((suggestion, index) => ({
335
+ query: suggestion,
336
+ value: 100 - index * 10,
337
+ formattedValue: (100 - index * 10).toString(),
338
+ hasData: true,
339
+ link: `/trends/explore?q=${encodeURIComponent(suggestion)}&date=${time}&geo=${geo}`
340
+ })) || [];
341
+ return {
342
+ data: {
343
+ default: {
344
+ rankedList: [{
345
+ rankedKeyword: relatedQueries
346
+ }]
347
+ }
348
+ }
349
+ };
350
+ }
351
+ catch (error) {
352
+ if (error instanceof Error) {
353
+ return { error: new NetworkError(error.message) };
354
+ }
355
+ return { error: new UnknownError() };
356
+ }
357
+ }
358
+ async relatedData({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
359
+ try {
360
+ // Validate keyword
361
+ if (!keyword || keyword.trim() === '') {
362
+ return { error: new ParseError() };
363
+ }
364
+ const autocompleteResult = await this.autocomplete(keyword, hl);
365
+ if (autocompleteResult.error) {
366
+ return { error: autocompleteResult.error };
367
+ }
368
+ const suggestions = autocompleteResult.data?.slice(0, 10) || [];
369
+ const topics = suggestions.map((suggestion, index) => ({
370
+ topic: {
371
+ mid: `/m/${index}`,
372
+ title: suggestion,
373
+ type: 'Topic'
374
+ },
375
+ value: 100 - index * 10,
376
+ formattedValue: (100 - index * 10).toString(),
377
+ hasData: true,
378
+ link: `/trends/explore?q=${encodeURIComponent(suggestion)}&date=${time}&geo=${geo}`
379
+ }));
380
+ const queries = suggestions.map((suggestion, index) => ({
381
+ query: suggestion,
382
+ value: 100 - index * 10,
383
+ formattedValue: (100 - index * 10).toString(),
384
+ hasData: true,
385
+ link: `/trends/explore?q=${encodeURIComponent(suggestion)}&date=${time}&geo=${geo}`
386
+ }));
387
+ return {
388
+ data: {
389
+ topics,
390
+ queries
391
+ }
392
+ };
393
+ }
394
+ catch (error) {
395
+ if (error instanceof Error) {
396
+ return { error: new NetworkError(error.message) };
397
+ }
398
+ return { error: new UnknownError() };
204
399
  }
205
400
  }
206
401
  }
@@ -3,7 +3,7 @@ export declare const request: (url: string, options: {
3
3
  qs?: Record<string, any>;
4
4
  body?: string | Record<string, any>;
5
5
  headers?: Record<string, string>;
6
- contentType?: 'json' | 'form';
6
+ contentType?: "json" | "form";
7
7
  }) => Promise<{
8
8
  text: () => Promise<string>;
9
9
  }>;
@@ -1,9 +1,29 @@
1
- export declare const dailyTrends: ({ geo, lang }: import("./types").DailyTrendingTopicsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
2
- export declare const realTimeTrends: ({ geo, trendingHours }: import("./types").RealTimeTrendsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
3
- export declare const autocomplete: (keyword: string, hl?: string) => Promise<import("./types").GoogleTrendsResponse<string[]>>;
1
+ import { GoogleTrendsApi } from './helpers/googleTrendsAPI.js';
2
+ export declare const dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
3
+ export declare const realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
4
+ export declare const autocomplete: (keyword: string, hl?: string) => Promise<import("./types/index.js").GoogleTrendsResponse<string[]>>;
5
+ export declare const explore: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
6
+ error: import("./types/index.js").GoogleTrendsError;
7
+ }>;
8
+ export declare const interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").InterestByRegionResponse | {
9
+ error: import("./types/index.js").GoogleTrendsError;
10
+ }>;
11
+ export declare const relatedTopics: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
12
+ export declare const relatedQueries: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedQueriesResponse>>;
13
+ export declare const relatedData: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedData>>;
14
+ export { GoogleTrendsApi };
4
15
  declare const _default: {
5
- dailyTrends: ({ geo, lang }: import("./types").DailyTrendingTopicsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
6
- realTimeTrends: ({ geo, trendingHours }: import("./types").RealTimeTrendsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
7
- autocomplete: (keyword: string, hl?: string) => Promise<import("./types").GoogleTrendsResponse<string[]>>;
16
+ dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
17
+ realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
18
+ autocomplete: (keyword: string, hl?: string) => Promise<import("./types/index.js").GoogleTrendsResponse<string[]>>;
19
+ explore: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
20
+ error: import("./types/index.js").GoogleTrendsError;
21
+ }>;
22
+ interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").InterestByRegionResponse | {
23
+ error: import("./types/index.js").GoogleTrendsError;
24
+ }>;
25
+ relatedTopics: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
26
+ relatedQueries: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedQueriesResponse>>;
27
+ relatedData: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedData>>;
8
28
  };
9
29
  export default _default;
package/dist/esm/index.js CHANGED
@@ -1,11 +1,22 @@
1
- import { GoogleTrendsApi } from './helpers/googleTrendsAPI';
1
+ import { GoogleTrendsApi } from './helpers/googleTrendsAPI.js';
2
2
  const api = new GoogleTrendsApi();
3
3
  export const dailyTrends = api.dailyTrends.bind(api);
4
4
  export const realTimeTrends = api.realTimeTrends.bind(api);
5
5
  export const autocomplete = api.autocomplete.bind(api);
6
+ export const explore = api.explore.bind(api);
7
+ export const interestByRegion = api.interestByRegion.bind(api);
8
+ export const relatedTopics = api.relatedTopics.bind(api);
9
+ export const relatedQueries = api.relatedQueries.bind(api);
10
+ export const relatedData = api.relatedData.bind(api);
11
+ export { GoogleTrendsApi };
6
12
  // Default export for CommonJS compatibility
7
13
  export default {
8
14
  dailyTrends,
9
15
  realTimeTrends,
10
- autocomplete
16
+ autocomplete,
17
+ explore,
18
+ interestByRegion,
19
+ relatedTopics,
20
+ relatedQueries,
21
+ relatedData
11
22
  };
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -2,7 +2,9 @@ export declare enum GoogleTrendsEndpoints {
2
2
  dailyTrends = "dailyTrends",
3
3
  autocomplete = "autocomplete",
4
4
  explore = "explore",
5
- interestByRegion = "interestByRegion"
5
+ interestByRegion = "interestByRegion",
6
+ relatedTopics = "relatedTopics",
7
+ relatedQueries = "relatedQueries"
6
8
  }
7
9
  export declare enum GoogleTrendsTrendingHours {
8
10
  fourHrs = 4,
@@ -4,6 +4,8 @@ export var GoogleTrendsEndpoints;
4
4
  GoogleTrendsEndpoints["autocomplete"] = "autocomplete";
5
5
  GoogleTrendsEndpoints["explore"] = "explore";
6
6
  GoogleTrendsEndpoints["interestByRegion"] = "interestByRegion";
7
+ GoogleTrendsEndpoints["relatedTopics"] = "relatedTopics";
8
+ GoogleTrendsEndpoints["relatedQueries"] = "relatedQueries";
7
9
  })(GoogleTrendsEndpoints || (GoogleTrendsEndpoints = {}));
8
10
  export var GoogleTrendsTrendingHours;
9
11
  (function (GoogleTrendsTrendingHours) {
package/package.json CHANGED
@@ -1,24 +1,30 @@
1
1
  {
2
2
  "name": "@shaivpidadi/trends-js",
3
- "version": "0.0.0-beta.7",
3
+ "version": "1.0.0",
4
4
  "description": "Google Trends API for Node.js",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
7
- "types": "./dist/types/index.d.ts",
7
+ "types": "./dist/cjs/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/esm/index.js",
11
- "require": "./dist/cjs/index.js",
12
- "types": "./dist/types/index.d.ts"
10
+ "import": {
11
+ "types": "./dist/esm/index.d.ts",
12
+ "default": "./dist/esm/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/cjs/index.d.ts",
16
+ "default": "./dist/cjs/index.js"
17
+ }
13
18
  }
14
19
  },
15
20
  "files": [
16
21
  "dist"
17
22
  ],
18
23
  "scripts": {
19
- "build": "npm run build:esm && npm run build:cjs",
24
+ "build": "npm run build:esm && npm run build:cjs && npm run build:pkg",
20
25
  "build:esm": "tsc -p tsconfig.json --outDir dist/esm",
21
- "build:cjs": "tsc -p tsconfig.json --outDir dist/cjs --module commonjs",
26
+ "build:cjs": "tsc -p tsconfig.json --outDir dist/cjs --module commonjs --moduleResolution node",
27
+ "build:pkg": "echo '{\"type\":\"module\"}' > dist/esm/package.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
22
28
  "test": "jest",
23
29
  "prepare": "npm run build"
24
30
  },
@@ -40,4 +46,4 @@
40
46
  "ts-jest": "^29.1.0",
41
47
  "typescript": "^5.0.0"
42
48
  }
43
- }
49
+ }