@shaivpidadi/trends-js 0.0.0-beta.3 → 0.0.0-beta.5

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/README.md CHANGED
@@ -105,11 +105,14 @@ Get interest data by region:
105
105
 
106
106
  ```typescript
107
107
  const result = await GoogleTrendsApi.interestByRegion({
108
- keyword: 'Stock Market',
109
- geo: 'US', // Default: 'US'
110
- time: 'today 12-m', // Default: 'today 12-m'
111
- resolution: 'REGION', // Default: 'REGION'
112
- hl: 'en-US' // Default: 'en-US'
108
+ keyword: 'Stock Market', // Required - string or string[]
109
+ startTime: new Date('2024-01-01'), // Optional - defaults to 2004-01-01
110
+ endTime: new Date(), // Optional - defaults to current date
111
+ geo: 'US', // Optional - string or string[] - defaults to 'US'
112
+ resolution: 'REGION', // Optional - 'COUNTRY' | 'REGION' | 'CITY' | 'DMA'
113
+ hl: 'en-US', // Optional - defaults to 'en-US'
114
+ timezone: -240, // Optional - defaults to local timezone
115
+ category: 0 // Optional - defaults to 0
113
116
  });
114
117
 
115
118
  // Result structure:
@@ -121,12 +124,28 @@ const result = await GoogleTrendsApi.interestByRegion({
121
124
  // value: number[],
122
125
  // formattedValue: string[],
123
126
  // maxValueIndex: number,
124
- // hasData: boolean[]
127
+ // hasData: boolean[],
128
+ // coordinates?: {
129
+ // lat: number,
130
+ // lng: number
131
+ // }
125
132
  // }>
126
133
  // }
127
134
  // }
128
135
  ```
129
136
 
137
+ Example with multiple keywords and regions:
138
+
139
+ ```typescript
140
+ const result = await GoogleTrendsApi.interestByRegion({
141
+ keyword: ['wine', 'peanuts'],
142
+ geo: ['US-CA', 'US-VA'],
143
+ startTime: new Date('2024-01-01'),
144
+ endTime: new Date(),
145
+ resolution: 'CITY'
146
+ });
147
+ ```
148
+
130
149
  ## API Reference
131
150
 
132
151
  ### DailyTrendsOptions
@@ -164,11 +183,14 @@ interface ExploreOptions {
164
183
 
165
184
  ```typescript
166
185
  interface InterestByRegionOptions {
167
- keyword: string;
168
- geo?: string; // Default: 'US'
169
- time?: string; // Default: 'today 12-m'
170
- resolution?: 'COUNTRY' | 'REGION' | 'CITY' | 'DMA'; // Default: 'REGION'
171
- hl?: string; // Default: 'en-US'
186
+ keyword: string | string[]; // Required - search term(s)
187
+ startTime?: Date; // Optional - start date
188
+ endTime?: Date; // Optional - end date
189
+ geo?: string | string[]; // Optional - geocode(s)
190
+ resolution?: 'COUNTRY' | 'REGION' | 'CITY' | 'DMA'; // Optional
191
+ hl?: string; // Optional - language code
192
+ timezone?: number; // Optional - timezone offset
193
+ category?: number; // Optional - category number
172
194
  }
173
195
  ```
174
196
 
@@ -176,26 +198,4 @@ interface InterestByRegionOptions {
176
198
 
177
199
  ### Building
178
200
 
179
- ```bash
180
- npm run build
181
- ```
182
-
183
- ### Testing
184
-
185
- ```bash
186
- npm test
187
- ```
188
-
189
- ### Linting
190
-
191
- ```bash
192
- npm run lint
193
- ```
194
-
195
- ## License
196
-
197
- MIT
198
-
199
- ## Author
200
-
201
- Shaishav Pidadi
201
+ ```
package/lib/constants.js CHANGED
@@ -23,7 +23,7 @@ exports.GOOGLE_TRENDS_MAPPER = {
23
23
  },
24
24
  ["explore" /* GoogleTrendsEndpoints.explore */]: {
25
25
  path: '/trends/api/explore',
26
- method: 'GET',
26
+ method: 'POST',
27
27
  host: GOOGLE_TRENDS_BASE_URL,
28
28
  url: `https://${GOOGLE_TRENDS_BASE_URL}/trends/api/explore`,
29
29
  headers: {},
@@ -4,7 +4,7 @@ export declare class GoogleTrendsApi {
4
4
  dailyTrends({ geo, lang }: DailyTrendingTopicsOptions): Promise<DailyTrendingTopics>;
5
5
  realTimeTrends({ geo, trendingHours }: RealTimeTrendsOptions): Promise<DailyTrendingTopics>;
6
6
  explore({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<ExploreResponse>;
7
- interestByRegion({ keyword, geo, time, resolution, hl, }: InterestByRegionOptions): Promise<InterestByRegionResponse>;
7
+ interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: InterestByRegionOptions): Promise<InterestByRegionResponse>;
8
8
  }
9
9
  declare const _default: GoogleTrendsApi;
10
10
  export default _default;
@@ -41,7 +41,7 @@ class GoogleTrendsApi {
41
41
  const defaultOptions = constants_1.GOOGLE_TRENDS_MAPPER["dailyTrends" /* GoogleTrendsEndpoints.dailyTrends */];
42
42
  const options = Object.assign(Object.assign({}, defaultOptions), { body: new URLSearchParams({
43
43
  'f.req': `[[["i0OFE","[null,null,\\"${geo}\\",0,\\"${lang}\\",24,1]",null,"generic"]]]`,
44
- }).toString() });
44
+ }).toString(), contentType: 'form' });
45
45
  try {
46
46
  const response = yield (0, request_1.request)(options.url, options);
47
47
  const text = yield response.text();
@@ -68,7 +68,7 @@ class GoogleTrendsApi {
68
68
  const defaultOptions = constants_1.GOOGLE_TRENDS_MAPPER["dailyTrends" /* GoogleTrendsEndpoints.dailyTrends */];
69
69
  const options = Object.assign(Object.assign({}, defaultOptions), { body: new URLSearchParams({
70
70
  'f.req': `[[["i0OFE","[null,null,\\"${geo}\\",0,\\"en\\",${trendingHours},1]",null,"generic"]]]`,
71
- }).toString() });
71
+ }).toString(), contentType: 'form' });
72
72
  try {
73
73
  const response = yield (0, request_1.request)(options.url, options);
74
74
  const text = yield response.text();
@@ -120,44 +120,68 @@ class GoogleTrendsApi {
120
120
  }
121
121
  });
122
122
  }
123
- interestByRegion({ keyword, geo = 'US', time = 'today 12-m', resolution = 'REGION', hl = 'en-US', }) {
123
+ //
124
+ interestByRegion({ keyword, startTime = new Date('2004-01-01'), endTime = new Date(), geo = 'US', resolution = 'REGION', hl = 'en-US', timezone = new Date().getTimezoneOffset(), category = 0 }) {
124
125
  return __awaiter(this, void 0, void 0, function* () {
125
- // First get the widget token from explore
126
- const exploreResponse = yield this.explore({ keyword, geo, time, hl });
127
- const widget = exploreResponse.widgets.find((w) => w.id === 'GEO_MAP_0');
126
+ const formatDate = (date) => {
127
+ return date.toISOString().split('T')[0];
128
+ };
129
+ const formatTrendsDate = (date) => {
130
+ const pad = (n) => n.toString().padStart(2, '0');
131
+ const yyyy = date.getFullYear();
132
+ const mm = pad(date.getMonth() + 1);
133
+ const dd = pad(date.getDate());
134
+ const hh = pad(date.getHours());
135
+ const min = pad(date.getMinutes());
136
+ const ss = pad(date.getSeconds());
137
+ return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
138
+ };
139
+ const getDateRangeParam = (date) => {
140
+ const yesterday = new Date(date);
141
+ yesterday.setDate(date.getDate() - 1);
142
+ const formattedStart = formatTrendsDate(yesterday);
143
+ const formattedEnd = formatTrendsDate(date);
144
+ return `${formattedStart} ${formattedEnd}`;
145
+ };
146
+ const exploreResponse = yield this.explore({
147
+ keyword: Array.isArray(keyword) ? keyword[0] : keyword,
148
+ geo: Array.isArray(geo) ? geo[0] : geo,
149
+ time: `${getDateRangeParam(startTime)} ${getDateRangeParam(endTime)}`,
150
+ category,
151
+ hl
152
+ });
153
+ const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
128
154
  if (!widget) {
129
155
  return { default: { geoMapData: [] } };
130
156
  }
131
157
  const options = Object.assign(Object.assign({}, constants_1.GOOGLE_TRENDS_MAPPER["interestByRegion" /* GoogleTrendsEndpoints.interestByRegion */]), { qs: {
132
158
  hl,
133
- tz: '240',
159
+ tz: timezone.toString(),
134
160
  req: JSON.stringify({
135
- geo: { country: geo },
136
- comparisonItem: [
137
- {
138
- time,
161
+ geo: {
162
+ country: Array.isArray(geo) ? geo[0] : geo
163
+ },
164
+ comparisonItem: [{
165
+ time: `${formatDate(startTime)} ${formatDate(endTime)}`,
139
166
  complexKeywordsRestriction: {
140
- keyword: [
141
- {
167
+ keyword: [{
142
168
  type: 'BROAD',
143
- value: keyword,
144
- },
145
- ],
146
- },
147
- },
148
- ],
169
+ value: Array.isArray(keyword) ? keyword[0] : keyword
170
+ }]
171
+ }
172
+ }],
149
173
  resolution,
150
174
  locale: hl,
151
175
  requestOptions: {
152
176
  property: '',
153
- backend: 'IZG',
154
- category: 0,
177
+ backend: 'CM',
178
+ category
155
179
  },
156
180
  userConfig: {
157
- userType: 'USER_TYPE_SCRAPER',
158
- },
181
+ userType: 'USER_TYPE_LEGIT_USER'
182
+ }
159
183
  }),
160
- token: widget.token,
184
+ token: widget.token
161
185
  } });
162
186
  try {
163
187
  const response = yield (0, request_1.request)(options.url, options);
@@ -167,7 +191,6 @@ class GoogleTrendsApi {
167
191
  return data;
168
192
  }
169
193
  catch (error) {
170
- console.error('Interest by region request failed:', error);
171
194
  return { default: { geoMapData: [] } };
172
195
  }
173
196
  });
@@ -1 +1,9 @@
1
- export declare const request: (url: string, options: RequestInit) => Promise<any>;
1
+ export declare const request: (url: string, options: {
2
+ method?: string;
3
+ qs?: Record<string, any>;
4
+ body?: string | Record<string, any>;
5
+ headers?: Record<string, string>;
6
+ contentType?: 'json' | 'form';
7
+ }) => Promise<{
8
+ text: () => Promise<string>;
9
+ }>;
@@ -8,10 +8,79 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.request = void 0;
16
+ const https_1 = __importDefault(require("https"));
17
+ const querystring_1 = __importDefault(require("querystring"));
18
+ let cookieVal;
19
+ function rereq(options, body) {
20
+ return new Promise((resolve, reject) => {
21
+ const req = https_1.default.request(options, (res) => {
22
+ let chunk = '';
23
+ res.on('data', (data) => { chunk += data; });
24
+ res.on('end', () => resolve(chunk));
25
+ });
26
+ req.on('error', reject);
27
+ if (body)
28
+ req.write(body);
29
+ req.end();
30
+ });
31
+ }
13
32
  const request = (url, options) => __awaiter(void 0, void 0, void 0, function* () {
14
- const response = yield fetch(url, options);
15
- return response;
33
+ const parsedUrl = new URL(url);
34
+ const method = options.method || 'POST';
35
+ // Prepare body
36
+ let bodyString = '';
37
+ const contentType = options.contentType || 'json';
38
+ if (typeof options.body === 'string') {
39
+ bodyString = options.body;
40
+ }
41
+ else if (contentType === 'form') {
42
+ bodyString = querystring_1.default.stringify(options.body || {});
43
+ }
44
+ else if (options.body) {
45
+ bodyString = JSON.stringify(options.body);
46
+ }
47
+ const requestOptions = {
48
+ hostname: parsedUrl.hostname,
49
+ port: parsedUrl.port || 443,
50
+ path: `${parsedUrl.pathname}${options.qs ? '?' + querystring_1.default.stringify(options.qs) : ''}`,
51
+ method,
52
+ headers: Object.assign(Object.assign(Object.assign(Object.assign({}, (options.headers || {})), (contentType === 'form'
53
+ ? { 'Content-Type': 'application/x-www-form-urlencoded' }
54
+ : { 'Content-Type': 'application/json' })), (bodyString ? { 'Content-Length': Buffer.byteLength(bodyString).toString() } : {})), (cookieVal ? { cookie: cookieVal } : {}))
55
+ };
56
+ const response = yield new Promise((resolve, reject) => {
57
+ const req = https_1.default.request(requestOptions, (res) => {
58
+ let chunk = '';
59
+ res.on('data', (data) => { chunk += data; });
60
+ res.on('end', () => __awaiter(void 0, void 0, void 0, function* () {
61
+ if (res.statusCode === 429 && res.headers['set-cookie']) {
62
+ cookieVal = res.headers['set-cookie'][0].split(';')[0];
63
+ requestOptions.headers['cookie'] = cookieVal;
64
+ try {
65
+ const retryResponse = yield rereq(requestOptions, bodyString);
66
+ resolve(retryResponse);
67
+ }
68
+ catch (err) {
69
+ reject(err);
70
+ }
71
+ }
72
+ else {
73
+ resolve(chunk);
74
+ }
75
+ }));
76
+ });
77
+ req.on('error', reject);
78
+ if (bodyString)
79
+ req.write(bodyString);
80
+ req.end();
81
+ });
82
+ return {
83
+ text: () => Promise.resolve(response)
84
+ };
16
85
  });
17
86
  exports.request = request;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaivpidadi/trends-js",
3
- "version": "0.0.0-beta.3",
3
+ "version": "0.0.0-beta.5",
4
4
  "description": "Google Trends API for Node.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",