@shaivpidadi/trends-js 1.0.1 → 1.0.2
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 +5 -2
- package/dist/cjs/helpers/format.d.ts +2 -0
- package/dist/cjs/helpers/format.js +16 -1
- package/dist/cjs/helpers/googleTrendsAPI.d.ts +3 -5
- package/dist/cjs/helpers/googleTrendsAPI.js +48 -76
- package/dist/cjs/helpers/request.d.ts +1 -1
- package/dist/cjs/helpers/request.js +34 -29
- package/dist/cjs/index.d.ts +6 -10
- package/dist/cjs/utils/formatters.d.ts +4 -0
- package/dist/cjs/utils/formatters.js +26 -0
- package/dist/esm/helpers/format.d.ts +2 -0
- package/dist/esm/helpers/format.js +13 -0
- package/dist/esm/helpers/googleTrendsAPI.d.ts +3 -5
- package/dist/esm/helpers/googleTrendsAPI.js +49 -77
- package/dist/esm/helpers/request.d.ts +1 -1
- package/dist/esm/helpers/request.js +34 -29
- package/dist/esm/index.d.ts +6 -10
- package/dist/esm/utils/formatters.d.ts +4 -0
- package/dist/esm/utils/formatters.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,14 +147,15 @@ Get interest data by region:
|
|
|
147
147
|
|
|
148
148
|
```typescript
|
|
149
149
|
const result = await GoogleTrendsApi.interestByRegion({
|
|
150
|
-
keyword: 'Stock Market', // Required - string
|
|
150
|
+
keyword: 'Stock Market', // Required - string
|
|
151
151
|
startTime: new Date('2024-01-01'), // Optional - defaults to 2004-01-01
|
|
152
152
|
endTime: new Date(), // Optional - defaults to current date
|
|
153
|
-
geo: 'US', // Optional - string
|
|
153
|
+
geo: 'US', // Optional - string - defaults to 'US'
|
|
154
154
|
resolution: 'REGION', // Optional - 'COUNTRY' | 'REGION' | 'CITY' | 'DMA'
|
|
155
155
|
hl: 'en-US', // Optional - defaults to 'en-US'
|
|
156
156
|
timezone: -240, // Optional - defaults to local timezone
|
|
157
157
|
category: 0, // Optional - defaults to 0
|
|
158
|
+
enableBackoff: true // Optional - defaults to false
|
|
158
159
|
});
|
|
159
160
|
|
|
160
161
|
// Result structure:
|
|
@@ -185,6 +186,7 @@ const result = await GoogleTrendsApi.interestByRegion({
|
|
|
185
186
|
startTime: new Date('2024-01-01'),
|
|
186
187
|
endTime: new Date(),
|
|
187
188
|
resolution: 'CITY',
|
|
189
|
+
enableBackoff: true
|
|
188
190
|
});
|
|
189
191
|
```
|
|
190
192
|
|
|
@@ -324,6 +326,7 @@ interface InterestByRegionOptions {
|
|
|
324
326
|
hl?: string; // Optional - language code
|
|
325
327
|
timezone?: number; // Optional - timezone offset
|
|
326
328
|
category?: number; // Optional - category number
|
|
329
|
+
enableBackoff?: boolean // Optional
|
|
327
330
|
}
|
|
328
331
|
```
|
|
329
332
|
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { DailyTrendingTopics } from '../types/index.js';
|
|
2
|
+
export declare const formatTrendsDate: (date: Date) => string;
|
|
3
|
+
export declare const formatDate: (date: Date) => string;
|
|
2
4
|
export declare const extractJsonFromResponse: (text: string) => DailyTrendingTopics | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractJsonFromResponse = void 0;
|
|
3
|
+
exports.extractJsonFromResponse = exports.formatDate = exports.formatTrendsDate = void 0;
|
|
4
4
|
const GoogleTrendsError_js_1 = require("../errors/GoogleTrendsError.js");
|
|
5
5
|
// For future reference and update: from google trends page rpc call response,
|
|
6
6
|
// 0 "twitter down" The main trending search term.
|
|
@@ -16,6 +16,21 @@ const GoogleTrendsError_js_1 = require("../errors/GoogleTrendsError.js");
|
|
|
16
16
|
// 10 [11] Unclear, possibly a category identifier.
|
|
17
17
|
// 11 [[3606769742, "en", "US"], [3596035008, "en", "US"]] User demographics or trending sources, with numerical IDs, language ("en" for English), and country ("US" for United States).
|
|
18
18
|
// 12 "twitter down" The original trending keyword (sometimes a duplicate of index 0).
|
|
19
|
+
const formatTrendsDate = (date) => {
|
|
20
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
21
|
+
const yyyy = date.getFullYear();
|
|
22
|
+
const mm = pad(date.getMonth() + 1);
|
|
23
|
+
const dd = pad(date.getDate());
|
|
24
|
+
const hh = pad(date.getHours());
|
|
25
|
+
const min = pad(date.getMinutes());
|
|
26
|
+
const ss = pad(date.getSeconds());
|
|
27
|
+
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
28
|
+
};
|
|
29
|
+
exports.formatTrendsDate = formatTrendsDate;
|
|
30
|
+
const formatDate = (date) => {
|
|
31
|
+
return date.toISOString().split('T')[0];
|
|
32
|
+
};
|
|
33
|
+
exports.formatDate = formatDate;
|
|
19
34
|
const extractJsonFromResponse = (text) => {
|
|
20
35
|
const cleanedText = text.replace(/^\)\]\}'/, '').trim();
|
|
21
36
|
try {
|
|
@@ -19,13 +19,11 @@ export declare class GoogleTrendsApi {
|
|
|
19
19
|
* @returns Promise with trending topics data
|
|
20
20
|
*/
|
|
21
21
|
realTimeTrends({ geo, trendingHours }: RealTimeTrendsOptions): Promise<GoogleTrendsResponse<DailyTrendingTopics>>;
|
|
22
|
-
explore({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<ExploreResponse | {
|
|
22
|
+
explore({ keyword, geo, time, category, property, hl, enableBackoff, }: ExploreOptions): Promise<ExploreResponse | {
|
|
23
23
|
error: GoogleTrendsError;
|
|
24
24
|
}>;
|
|
25
|
-
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: InterestByRegionOptions): Promise<InterestByRegionResponse
|
|
26
|
-
|
|
27
|
-
}>;
|
|
28
|
-
relatedTopics({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
25
|
+
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: InterestByRegionOptions): Promise<GoogleTrendsResponse<InterestByRegionResponse>>;
|
|
26
|
+
relatedTopics({ keyword, geo, time, category, property, hl, enableBackoff, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
29
27
|
relatedQueries({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedQueriesResponse>>;
|
|
30
28
|
relatedData({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedData>>;
|
|
31
29
|
}
|
|
@@ -98,7 +98,7 @@ class GoogleTrendsApi {
|
|
|
98
98
|
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
101
|
+
async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', enableBackoff = false, }) {
|
|
102
102
|
const options = {
|
|
103
103
|
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.explore],
|
|
104
104
|
qs: {
|
|
@@ -116,10 +116,10 @@ class GoogleTrendsApi {
|
|
|
116
116
|
property,
|
|
117
117
|
}),
|
|
118
118
|
},
|
|
119
|
-
contentType: 'form'
|
|
119
|
+
// contentType: 'form' as const
|
|
120
120
|
};
|
|
121
121
|
try {
|
|
122
|
-
const response = await (0, request_js_1.request)(options.url, options);
|
|
122
|
+
const response = await (0, request_js_1.request)(options.url, options, enableBackoff);
|
|
123
123
|
const text = await response.text();
|
|
124
124
|
// Check if response is HTML (error page)
|
|
125
125
|
if (text.includes('<html') || text.includes('<!DOCTYPE')) {
|
|
@@ -134,6 +134,9 @@ class GoogleTrendsApi {
|
|
|
134
134
|
const widgets = data[0] || [];
|
|
135
135
|
return { widgets };
|
|
136
136
|
}
|
|
137
|
+
if (data && typeof data === 'object' && Array.isArray(data.widgets)) {
|
|
138
|
+
return { widgets: data.widgets };
|
|
139
|
+
}
|
|
137
140
|
return { widgets: [] };
|
|
138
141
|
}
|
|
139
142
|
catch (parseError) {
|
|
@@ -151,92 +154,60 @@ class GoogleTrendsApi {
|
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
//
|
|
154
|
-
async interestByRegion({ keyword, startTime = new Date('2004-01-01'), endTime = new Date(), geo = 'US', resolution = 'REGION', hl = 'en-US', timezone = new Date().getTimezoneOffset(), category = 0 }) {
|
|
157
|
+
async interestByRegion({ keyword, startTime = new Date('2004-01-01'), endTime = new Date(), geo = 'US', resolution = 'REGION', hl = 'en-US', timezone = new Date().getTimezoneOffset(), category = 0, enableBackoff = false }) {
|
|
155
158
|
const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword;
|
|
159
|
+
const geoValue = Array.isArray(geo) ? geo[0] : geo;
|
|
156
160
|
if (!keywordValue || keywordValue.trim() === '') {
|
|
157
161
|
return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') };
|
|
158
162
|
}
|
|
159
|
-
|
|
160
|
-
return
|
|
161
|
-
};
|
|
162
|
-
const formatTrendsDate = (date) => {
|
|
163
|
-
const pad = (n) => n.toString().padStart(2, '0');
|
|
164
|
-
const yyyy = date.getFullYear();
|
|
165
|
-
const mm = pad(date.getMonth() + 1);
|
|
166
|
-
const dd = pad(date.getDate());
|
|
167
|
-
const hh = pad(date.getHours());
|
|
168
|
-
const min = pad(date.getMinutes());
|
|
169
|
-
const ss = pad(date.getSeconds());
|
|
170
|
-
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
171
|
-
};
|
|
172
|
-
const getDateRangeParam = (date) => {
|
|
173
|
-
const yesterday = new Date(date);
|
|
174
|
-
yesterday.setDate(date.getDate() - 1);
|
|
175
|
-
const formattedStart = formatTrendsDate(yesterday);
|
|
176
|
-
const formattedEnd = formatTrendsDate(date);
|
|
177
|
-
return `${formattedStart} ${formattedEnd}`;
|
|
178
|
-
};
|
|
179
|
-
const exploreResponse = await this.explore({
|
|
180
|
-
keyword: Array.isArray(keyword) ? keyword[0] : keyword,
|
|
181
|
-
geo: Array.isArray(geo) ? geo[0] : geo,
|
|
182
|
-
time: `${getDateRangeParam(startTime)} ${getDateRangeParam(endTime)}`,
|
|
183
|
-
category,
|
|
184
|
-
hl
|
|
185
|
-
});
|
|
186
|
-
if ('error' in exploreResponse) {
|
|
187
|
-
return { error: exploreResponse.error };
|
|
188
|
-
}
|
|
189
|
-
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
190
|
-
if (!widget) {
|
|
191
|
-
return { error: new GoogleTrendsError_js_1.ParseError('No GEO_MAP widget found in explore response') };
|
|
163
|
+
if (!geoValue || geoValue.trim() === '') {
|
|
164
|
+
return { error: new GoogleTrendsError_js_1.InvalidRequestError('Geo is required') };
|
|
192
165
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
166
|
+
try {
|
|
167
|
+
const exploreResponse = await this.explore({
|
|
168
|
+
keyword: keywordValue,
|
|
169
|
+
geo: geoValue,
|
|
170
|
+
time: `${(0, format_js_1.formatDate)(startTime)} ${(0, format_js_1.formatDate)(endTime)}`,
|
|
171
|
+
category,
|
|
196
172
|
hl,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
comparisonItem: [{
|
|
203
|
-
time: `${formatDate(startTime)} ${formatDate(endTime)}`,
|
|
204
|
-
complexKeywordsRestriction: {
|
|
205
|
-
keyword: [{
|
|
206
|
-
type: 'BROAD', //'ENTITY',
|
|
207
|
-
value: Array.isArray(keyword) ? keyword[0] : keyword
|
|
208
|
-
}]
|
|
209
|
-
}
|
|
210
|
-
}],
|
|
211
|
-
resolution,
|
|
212
|
-
locale: hl,
|
|
213
|
-
requestOptions: {
|
|
214
|
-
property: '',
|
|
215
|
-
backend: 'CM', //'IZG',
|
|
216
|
-
category
|
|
217
|
-
},
|
|
218
|
-
userConfig: {
|
|
219
|
-
userType: 'USER_TYPE_LEGIT_USER'
|
|
220
|
-
}
|
|
221
|
-
}),
|
|
222
|
-
token: widget.token
|
|
173
|
+
enableBackoff
|
|
174
|
+
});
|
|
175
|
+
if ('error' in exploreResponse) {
|
|
176
|
+
return { error: exploreResponse.error };
|
|
223
177
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
178
|
+
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
179
|
+
if (!widget) {
|
|
180
|
+
return { error: new GoogleTrendsError_js_1.ParseError('No GEO_MAP widget found in explore response') };
|
|
181
|
+
}
|
|
182
|
+
const requestFromWidget = {
|
|
183
|
+
...widget.request, resolution
|
|
184
|
+
};
|
|
185
|
+
const options = {
|
|
186
|
+
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.interestByRegion],
|
|
187
|
+
qs: {
|
|
188
|
+
hl,
|
|
189
|
+
tz: timezone.toString(),
|
|
190
|
+
req: JSON.stringify(requestFromWidget),
|
|
191
|
+
token: widget.token
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const response = await (0, request_js_1.request)(options.url, options, enableBackoff);
|
|
227
195
|
const text = await response.text();
|
|
228
|
-
|
|
196
|
+
if (text.includes('<!DOCTYPE') || text.includes('<html')) {
|
|
197
|
+
return { error: new GoogleTrendsError_js_1.ParseError('Interest by region request failed') };
|
|
198
|
+
}
|
|
199
|
+
// Handle JSONP wrapper (usually starts with )]}' or similar)
|
|
229
200
|
const data = JSON.parse(text.slice(5));
|
|
230
|
-
return data;
|
|
201
|
+
return { data: data.default.geoMapData };
|
|
231
202
|
}
|
|
232
203
|
catch (error) {
|
|
233
204
|
if (error instanceof Error) {
|
|
234
|
-
return { error: new GoogleTrendsError_js_1.
|
|
205
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(`Interest by region request failed: ${error.message}`) };
|
|
235
206
|
}
|
|
236
|
-
return { error: new GoogleTrendsError_js_1.
|
|
207
|
+
return { error: new GoogleTrendsError_js_1.UnknownError('Interest by region request failed') };
|
|
237
208
|
}
|
|
238
209
|
}
|
|
239
|
-
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
210
|
+
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', enableBackoff = false, }) {
|
|
240
211
|
try {
|
|
241
212
|
// Validate keyword
|
|
242
213
|
if (!keyword || keyword.trim() === '') {
|
|
@@ -249,7 +220,8 @@ class GoogleTrendsApi {
|
|
|
249
220
|
time,
|
|
250
221
|
category,
|
|
251
222
|
property,
|
|
252
|
-
hl
|
|
223
|
+
hl,
|
|
224
|
+
enableBackoff
|
|
253
225
|
});
|
|
254
226
|
if ('error' in exploreResponse) {
|
|
255
227
|
return { error: exploreResponse.error };
|
|
@@ -300,7 +272,7 @@ class GoogleTrendsApi {
|
|
|
300
272
|
...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token })
|
|
301
273
|
}
|
|
302
274
|
};
|
|
303
|
-
const response = await (0, request_js_1.request)(options.url, options);
|
|
275
|
+
const response = await (0, request_js_1.request)(options.url, options, enableBackoff);
|
|
304
276
|
const text = await response.text();
|
|
305
277
|
// Parse the response
|
|
306
278
|
try {
|
|
@@ -7,12 +7,40 @@ exports.request = void 0;
|
|
|
7
7
|
const https_1 = __importDefault(require("https"));
|
|
8
8
|
const querystring_1 = __importDefault(require("querystring"));
|
|
9
9
|
let cookieVal;
|
|
10
|
-
|
|
10
|
+
const MAX_RETRIES = 3;
|
|
11
|
+
const BASE_DELAY_MS = 750;
|
|
12
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
async function runRequest(options, body, attempt) {
|
|
11
14
|
return new Promise((resolve, reject) => {
|
|
12
15
|
const req = https_1.default.request(options, (res) => {
|
|
13
16
|
let chunk = '';
|
|
14
17
|
res.on('data', (data) => { chunk += data; });
|
|
15
|
-
res.on('end', () =>
|
|
18
|
+
res.on('end', async () => {
|
|
19
|
+
const hasSetCookie = !!res.headers['set-cookie']?.length;
|
|
20
|
+
const isRateLimited = res.statusCode === 429 ||
|
|
21
|
+
chunk.includes('Error 429') ||
|
|
22
|
+
chunk.includes('Too Many Requests');
|
|
23
|
+
if (isRateLimited) {
|
|
24
|
+
if (hasSetCookie) {
|
|
25
|
+
cookieVal = res.headers['set-cookie'][0].split(';')[0];
|
|
26
|
+
options.headers['cookie'] = cookieVal;
|
|
27
|
+
}
|
|
28
|
+
if (attempt < MAX_RETRIES) {
|
|
29
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.floor(Math.random() * 250);
|
|
30
|
+
await sleep(delay);
|
|
31
|
+
try {
|
|
32
|
+
const retryResponse = await runRequest(options, body, attempt + 1);
|
|
33
|
+
resolve(retryResponse);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
reject(err);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
resolve(chunk);
|
|
43
|
+
});
|
|
16
44
|
});
|
|
17
45
|
req.on('error', reject);
|
|
18
46
|
if (body)
|
|
@@ -20,7 +48,7 @@ function rereq(options, body) {
|
|
|
20
48
|
req.end();
|
|
21
49
|
});
|
|
22
50
|
}
|
|
23
|
-
const request = async (url, options) => {
|
|
51
|
+
const request = async (url, options, enableBackoff = false) => {
|
|
24
52
|
const parsedUrl = new URL(url);
|
|
25
53
|
const method = options.method || 'POST';
|
|
26
54
|
// Prepare body
|
|
@@ -49,32 +77,9 @@ const request = async (url, options) => {
|
|
|
49
77
|
...(cookieVal ? { cookie: cookieVal } : {})
|
|
50
78
|
}
|
|
51
79
|
};
|
|
52
|
-
const response =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
res.on('data', (data) => { chunk += data; });
|
|
56
|
-
res.on('end', async () => {
|
|
57
|
-
if (res.statusCode === 429 && res.headers['set-cookie']) {
|
|
58
|
-
cookieVal = res.headers['set-cookie'][0].split(';')[0];
|
|
59
|
-
requestOptions.headers['cookie'] = cookieVal;
|
|
60
|
-
try {
|
|
61
|
-
const retryResponse = await rereq(requestOptions, bodyString);
|
|
62
|
-
resolve(retryResponse);
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
reject(err);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
resolve(chunk);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
req.on('error', reject);
|
|
74
|
-
if (bodyString)
|
|
75
|
-
req.write(bodyString);
|
|
76
|
-
req.end();
|
|
77
|
-
});
|
|
80
|
+
const response = enableBackoff
|
|
81
|
+
? await runRequest(requestOptions, bodyString, 0)
|
|
82
|
+
: await runRequest(requestOptions, bodyString, MAX_RETRIES);
|
|
78
83
|
return {
|
|
79
84
|
text: () => Promise.resolve(response)
|
|
80
85
|
};
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -2,13 +2,11 @@ import { GoogleTrendsApi } from './helpers/googleTrendsAPI.js';
|
|
|
2
2
|
export declare const dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
3
3
|
export declare const realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
4
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 | {
|
|
5
|
+
export declare const explore: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
|
|
6
6
|
error: import("./types/index.js").GoogleTrendsError;
|
|
7
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
|
-
|
|
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>>;
|
|
8
|
+
export declare const interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").InterestByRegionResponse>>;
|
|
9
|
+
export declare const relatedTopics: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
|
|
12
10
|
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
11
|
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
12
|
export { GoogleTrendsApi };
|
|
@@ -16,13 +14,11 @@ declare const _default: {
|
|
|
16
14
|
dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
17
15
|
realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
18
16
|
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 | {
|
|
17
|
+
explore: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
|
|
23
18
|
error: import("./types/index.js").GoogleTrendsError;
|
|
24
19
|
}>;
|
|
25
|
-
|
|
20
|
+
interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").InterestByRegionResponse>>;
|
|
21
|
+
relatedTopics: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
|
|
26
22
|
relatedQueries: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedQueriesResponse>>;
|
|
27
23
|
relatedData: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedData>>;
|
|
28
24
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDateRangeParam = exports.formatTrendsDate = exports.formatDate = void 0;
|
|
4
|
+
const formatDate = (date) => {
|
|
5
|
+
return date.toISOString().split('T')[0];
|
|
6
|
+
};
|
|
7
|
+
exports.formatDate = formatDate;
|
|
8
|
+
const formatTrendsDate = (date) => {
|
|
9
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
10
|
+
const yyyy = date.getFullYear();
|
|
11
|
+
const mm = pad(date.getMonth() + 1);
|
|
12
|
+
const dd = pad(date.getDate());
|
|
13
|
+
const hh = pad(date.getHours());
|
|
14
|
+
const min = pad(date.getMinutes());
|
|
15
|
+
const ss = pad(date.getSeconds());
|
|
16
|
+
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
17
|
+
};
|
|
18
|
+
exports.formatTrendsDate = formatTrendsDate;
|
|
19
|
+
const getDateRangeParam = (date) => {
|
|
20
|
+
const yesterday = new Date(date);
|
|
21
|
+
yesterday.setDate(date.getDate() - 1);
|
|
22
|
+
const formattedStart = formatTrendsDate(yesterday);
|
|
23
|
+
const formattedEnd = formatTrendsDate(date);
|
|
24
|
+
return `${formattedStart} ${formattedEnd}`;
|
|
25
|
+
};
|
|
26
|
+
exports.getDateRangeParam = getDateRangeParam;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { DailyTrendingTopics } from '../types/index.js';
|
|
2
|
+
export declare const formatTrendsDate: (date: Date) => string;
|
|
3
|
+
export declare const formatDate: (date: Date) => string;
|
|
2
4
|
export declare const extractJsonFromResponse: (text: string) => DailyTrendingTopics | null;
|
|
@@ -13,6 +13,19 @@ import { ParseError } from '../errors/GoogleTrendsError.js';
|
|
|
13
13
|
// 10 [11] Unclear, possibly a category identifier.
|
|
14
14
|
// 11 [[3606769742, "en", "US"], [3596035008, "en", "US"]] User demographics or trending sources, with numerical IDs, language ("en" for English), and country ("US" for United States).
|
|
15
15
|
// 12 "twitter down" The original trending keyword (sometimes a duplicate of index 0).
|
|
16
|
+
export const formatTrendsDate = (date) => {
|
|
17
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
18
|
+
const yyyy = date.getFullYear();
|
|
19
|
+
const mm = pad(date.getMonth() + 1);
|
|
20
|
+
const dd = pad(date.getDate());
|
|
21
|
+
const hh = pad(date.getHours());
|
|
22
|
+
const min = pad(date.getMinutes());
|
|
23
|
+
const ss = pad(date.getSeconds());
|
|
24
|
+
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
25
|
+
};
|
|
26
|
+
export const formatDate = (date) => {
|
|
27
|
+
return date.toISOString().split('T')[0];
|
|
28
|
+
};
|
|
16
29
|
export const extractJsonFromResponse = (text) => {
|
|
17
30
|
const cleanedText = text.replace(/^\)\]\}'/, '').trim();
|
|
18
31
|
try {
|
|
@@ -19,13 +19,11 @@ export declare class GoogleTrendsApi {
|
|
|
19
19
|
* @returns Promise with trending topics data
|
|
20
20
|
*/
|
|
21
21
|
realTimeTrends({ geo, trendingHours }: RealTimeTrendsOptions): Promise<GoogleTrendsResponse<DailyTrendingTopics>>;
|
|
22
|
-
explore({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<ExploreResponse | {
|
|
22
|
+
explore({ keyword, geo, time, category, property, hl, enableBackoff, }: ExploreOptions): Promise<ExploreResponse | {
|
|
23
23
|
error: GoogleTrendsError;
|
|
24
24
|
}>;
|
|
25
|
-
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: InterestByRegionOptions): Promise<InterestByRegionResponse
|
|
26
|
-
|
|
27
|
-
}>;
|
|
28
|
-
relatedTopics({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
25
|
+
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: InterestByRegionOptions): Promise<GoogleTrendsResponse<InterestByRegionResponse>>;
|
|
26
|
+
relatedTopics({ keyword, geo, time, category, property, hl, enableBackoff, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
29
27
|
relatedQueries({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedQueriesResponse>>;
|
|
30
28
|
relatedData({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedData>>;
|
|
31
29
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GoogleTrendsEndpoints } from '../types/enums.js';
|
|
2
2
|
import { request } from './request.js';
|
|
3
|
-
import { extractJsonFromResponse } from './format.js';
|
|
3
|
+
import { extractJsonFromResponse, formatDate } from './format.js';
|
|
4
4
|
import { GOOGLE_TRENDS_MAPPER } from '../constants.js';
|
|
5
5
|
import { InvalidRequestError, NetworkError, ParseError, UnknownError, } from '../errors/GoogleTrendsError.js';
|
|
6
6
|
export class GoogleTrendsApi {
|
|
@@ -95,7 +95,7 @@ export class GoogleTrendsApi {
|
|
|
95
95
|
return { error: new UnknownError() };
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
98
|
+
async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', enableBackoff = false, }) {
|
|
99
99
|
const options = {
|
|
100
100
|
...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.explore],
|
|
101
101
|
qs: {
|
|
@@ -113,10 +113,10 @@ export class GoogleTrendsApi {
|
|
|
113
113
|
property,
|
|
114
114
|
}),
|
|
115
115
|
},
|
|
116
|
-
contentType: 'form'
|
|
116
|
+
// contentType: 'form' as const
|
|
117
117
|
};
|
|
118
118
|
try {
|
|
119
|
-
const response = await request(options.url, options);
|
|
119
|
+
const response = await request(options.url, options, enableBackoff);
|
|
120
120
|
const text = await response.text();
|
|
121
121
|
// Check if response is HTML (error page)
|
|
122
122
|
if (text.includes('<html') || text.includes('<!DOCTYPE')) {
|
|
@@ -131,6 +131,9 @@ export class GoogleTrendsApi {
|
|
|
131
131
|
const widgets = data[0] || [];
|
|
132
132
|
return { widgets };
|
|
133
133
|
}
|
|
134
|
+
if (data && typeof data === 'object' && Array.isArray(data.widgets)) {
|
|
135
|
+
return { widgets: data.widgets };
|
|
136
|
+
}
|
|
134
137
|
return { widgets: [] };
|
|
135
138
|
}
|
|
136
139
|
catch (parseError) {
|
|
@@ -148,92 +151,60 @@ export class GoogleTrendsApi {
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
//
|
|
151
|
-
async interestByRegion({ keyword, startTime = new Date('2004-01-01'), endTime = new Date(), geo = 'US', resolution = 'REGION', hl = 'en-US', timezone = new Date().getTimezoneOffset(), category = 0 }) {
|
|
154
|
+
async interestByRegion({ keyword, startTime = new Date('2004-01-01'), endTime = new Date(), geo = 'US', resolution = 'REGION', hl = 'en-US', timezone = new Date().getTimezoneOffset(), category = 0, enableBackoff = false }) {
|
|
152
155
|
const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword;
|
|
156
|
+
const geoValue = Array.isArray(geo) ? geo[0] : geo;
|
|
153
157
|
if (!keywordValue || keywordValue.trim() === '') {
|
|
154
158
|
return { error: new InvalidRequestError('Keyword is required') };
|
|
155
159
|
}
|
|
156
|
-
|
|
157
|
-
return
|
|
158
|
-
};
|
|
159
|
-
const formatTrendsDate = (date) => {
|
|
160
|
-
const pad = (n) => n.toString().padStart(2, '0');
|
|
161
|
-
const yyyy = date.getFullYear();
|
|
162
|
-
const mm = pad(date.getMonth() + 1);
|
|
163
|
-
const dd = pad(date.getDate());
|
|
164
|
-
const hh = pad(date.getHours());
|
|
165
|
-
const min = pad(date.getMinutes());
|
|
166
|
-
const ss = pad(date.getSeconds());
|
|
167
|
-
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
168
|
-
};
|
|
169
|
-
const getDateRangeParam = (date) => {
|
|
170
|
-
const yesterday = new Date(date);
|
|
171
|
-
yesterday.setDate(date.getDate() - 1);
|
|
172
|
-
const formattedStart = formatTrendsDate(yesterday);
|
|
173
|
-
const formattedEnd = formatTrendsDate(date);
|
|
174
|
-
return `${formattedStart} ${formattedEnd}`;
|
|
175
|
-
};
|
|
176
|
-
const exploreResponse = await this.explore({
|
|
177
|
-
keyword: Array.isArray(keyword) ? keyword[0] : keyword,
|
|
178
|
-
geo: Array.isArray(geo) ? geo[0] : geo,
|
|
179
|
-
time: `${getDateRangeParam(startTime)} ${getDateRangeParam(endTime)}`,
|
|
180
|
-
category,
|
|
181
|
-
hl
|
|
182
|
-
});
|
|
183
|
-
if ('error' in exploreResponse) {
|
|
184
|
-
return { error: exploreResponse.error };
|
|
185
|
-
}
|
|
186
|
-
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
187
|
-
if (!widget) {
|
|
188
|
-
return { error: new ParseError('No GEO_MAP widget found in explore response') };
|
|
160
|
+
if (!geoValue || geoValue.trim() === '') {
|
|
161
|
+
return { error: new InvalidRequestError('Geo is required') };
|
|
189
162
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
163
|
+
try {
|
|
164
|
+
const exploreResponse = await this.explore({
|
|
165
|
+
keyword: keywordValue,
|
|
166
|
+
geo: geoValue,
|
|
167
|
+
time: `${formatDate(startTime)} ${formatDate(endTime)}`,
|
|
168
|
+
category,
|
|
193
169
|
hl,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
},
|
|
199
|
-
comparisonItem: [{
|
|
200
|
-
time: `${formatDate(startTime)} ${formatDate(endTime)}`,
|
|
201
|
-
complexKeywordsRestriction: {
|
|
202
|
-
keyword: [{
|
|
203
|
-
type: 'BROAD', //'ENTITY',
|
|
204
|
-
value: Array.isArray(keyword) ? keyword[0] : keyword
|
|
205
|
-
}]
|
|
206
|
-
}
|
|
207
|
-
}],
|
|
208
|
-
resolution,
|
|
209
|
-
locale: hl,
|
|
210
|
-
requestOptions: {
|
|
211
|
-
property: '',
|
|
212
|
-
backend: 'CM', //'IZG',
|
|
213
|
-
category
|
|
214
|
-
},
|
|
215
|
-
userConfig: {
|
|
216
|
-
userType: 'USER_TYPE_LEGIT_USER'
|
|
217
|
-
}
|
|
218
|
-
}),
|
|
219
|
-
token: widget.token
|
|
170
|
+
enableBackoff
|
|
171
|
+
});
|
|
172
|
+
if ('error' in exploreResponse) {
|
|
173
|
+
return { error: exploreResponse.error };
|
|
220
174
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
175
|
+
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
176
|
+
if (!widget) {
|
|
177
|
+
return { error: new ParseError('No GEO_MAP widget found in explore response') };
|
|
178
|
+
}
|
|
179
|
+
const requestFromWidget = {
|
|
180
|
+
...widget.request, resolution
|
|
181
|
+
};
|
|
182
|
+
const options = {
|
|
183
|
+
...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.interestByRegion],
|
|
184
|
+
qs: {
|
|
185
|
+
hl,
|
|
186
|
+
tz: timezone.toString(),
|
|
187
|
+
req: JSON.stringify(requestFromWidget),
|
|
188
|
+
token: widget.token
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const response = await request(options.url, options, enableBackoff);
|
|
224
192
|
const text = await response.text();
|
|
225
|
-
|
|
193
|
+
if (text.includes('<!DOCTYPE') || text.includes('<html')) {
|
|
194
|
+
return { error: new ParseError('Interest by region request failed') };
|
|
195
|
+
}
|
|
196
|
+
// Handle JSONP wrapper (usually starts with )]}' or similar)
|
|
226
197
|
const data = JSON.parse(text.slice(5));
|
|
227
|
-
return data;
|
|
198
|
+
return { data: data.default.geoMapData };
|
|
228
199
|
}
|
|
229
200
|
catch (error) {
|
|
230
201
|
if (error instanceof Error) {
|
|
231
|
-
return { error: new
|
|
202
|
+
return { error: new NetworkError(`Interest by region request failed: ${error.message}`) };
|
|
232
203
|
}
|
|
233
|
-
return { error: new
|
|
204
|
+
return { error: new UnknownError('Interest by region request failed') };
|
|
234
205
|
}
|
|
235
206
|
}
|
|
236
|
-
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
207
|
+
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', enableBackoff = false, }) {
|
|
237
208
|
try {
|
|
238
209
|
// Validate keyword
|
|
239
210
|
if (!keyword || keyword.trim() === '') {
|
|
@@ -246,7 +217,8 @@ export class GoogleTrendsApi {
|
|
|
246
217
|
time,
|
|
247
218
|
category,
|
|
248
219
|
property,
|
|
249
|
-
hl
|
|
220
|
+
hl,
|
|
221
|
+
enableBackoff
|
|
250
222
|
});
|
|
251
223
|
if ('error' in exploreResponse) {
|
|
252
224
|
return { error: exploreResponse.error };
|
|
@@ -297,7 +269,7 @@ export class GoogleTrendsApi {
|
|
|
297
269
|
...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token })
|
|
298
270
|
}
|
|
299
271
|
};
|
|
300
|
-
const response = await request(options.url, options);
|
|
272
|
+
const response = await request(options.url, options, enableBackoff);
|
|
301
273
|
const text = await response.text();
|
|
302
274
|
// Parse the response
|
|
303
275
|
try {
|
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import querystring from 'querystring';
|
|
3
3
|
let cookieVal;
|
|
4
|
-
|
|
4
|
+
const MAX_RETRIES = 3;
|
|
5
|
+
const BASE_DELAY_MS = 750;
|
|
6
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
async function runRequest(options, body, attempt) {
|
|
5
8
|
return new Promise((resolve, reject) => {
|
|
6
9
|
const req = https.request(options, (res) => {
|
|
7
10
|
let chunk = '';
|
|
8
11
|
res.on('data', (data) => { chunk += data; });
|
|
9
|
-
res.on('end', () =>
|
|
12
|
+
res.on('end', async () => {
|
|
13
|
+
const hasSetCookie = !!res.headers['set-cookie']?.length;
|
|
14
|
+
const isRateLimited = res.statusCode === 429 ||
|
|
15
|
+
chunk.includes('Error 429') ||
|
|
16
|
+
chunk.includes('Too Many Requests');
|
|
17
|
+
if (isRateLimited) {
|
|
18
|
+
if (hasSetCookie) {
|
|
19
|
+
cookieVal = res.headers['set-cookie'][0].split(';')[0];
|
|
20
|
+
options.headers['cookie'] = cookieVal;
|
|
21
|
+
}
|
|
22
|
+
if (attempt < MAX_RETRIES) {
|
|
23
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.floor(Math.random() * 250);
|
|
24
|
+
await sleep(delay);
|
|
25
|
+
try {
|
|
26
|
+
const retryResponse = await runRequest(options, body, attempt + 1);
|
|
27
|
+
resolve(retryResponse);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
reject(err);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
resolve(chunk);
|
|
37
|
+
});
|
|
10
38
|
});
|
|
11
39
|
req.on('error', reject);
|
|
12
40
|
if (body)
|
|
@@ -14,7 +42,7 @@ function rereq(options, body) {
|
|
|
14
42
|
req.end();
|
|
15
43
|
});
|
|
16
44
|
}
|
|
17
|
-
export const request = async (url, options) => {
|
|
45
|
+
export const request = async (url, options, enableBackoff = false) => {
|
|
18
46
|
const parsedUrl = new URL(url);
|
|
19
47
|
const method = options.method || 'POST';
|
|
20
48
|
// Prepare body
|
|
@@ -43,32 +71,9 @@ export const request = async (url, options) => {
|
|
|
43
71
|
...(cookieVal ? { cookie: cookieVal } : {})
|
|
44
72
|
}
|
|
45
73
|
};
|
|
46
|
-
const response =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
res.on('data', (data) => { chunk += data; });
|
|
50
|
-
res.on('end', async () => {
|
|
51
|
-
if (res.statusCode === 429 && res.headers['set-cookie']) {
|
|
52
|
-
cookieVal = res.headers['set-cookie'][0].split(';')[0];
|
|
53
|
-
requestOptions.headers['cookie'] = cookieVal;
|
|
54
|
-
try {
|
|
55
|
-
const retryResponse = await rereq(requestOptions, bodyString);
|
|
56
|
-
resolve(retryResponse);
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
reject(err);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
resolve(chunk);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
req.on('error', reject);
|
|
68
|
-
if (bodyString)
|
|
69
|
-
req.write(bodyString);
|
|
70
|
-
req.end();
|
|
71
|
-
});
|
|
74
|
+
const response = enableBackoff
|
|
75
|
+
? await runRequest(requestOptions, bodyString, 0)
|
|
76
|
+
: await runRequest(requestOptions, bodyString, MAX_RETRIES);
|
|
72
77
|
return {
|
|
73
78
|
text: () => Promise.resolve(response)
|
|
74
79
|
};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,13 +2,11 @@ import { GoogleTrendsApi } from './helpers/googleTrendsAPI.js';
|
|
|
2
2
|
export declare const dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
3
3
|
export declare const realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
4
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 | {
|
|
5
|
+
export declare const explore: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
|
|
6
6
|
error: import("./types/index.js").GoogleTrendsError;
|
|
7
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
|
-
|
|
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>>;
|
|
8
|
+
export declare const interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").InterestByRegionResponse>>;
|
|
9
|
+
export declare const relatedTopics: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
|
|
12
10
|
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
11
|
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
12
|
export { GoogleTrendsApi };
|
|
@@ -16,13 +14,11 @@ declare const _default: {
|
|
|
16
14
|
dailyTrends: ({ geo, lang }: import("./types/index.js").DailyTrendingTopicsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
17
15
|
realTimeTrends: ({ geo, trendingHours }: import("./types/index.js").RealTimeTrendsOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").DailyTrendingTopics>>;
|
|
18
16
|
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 | {
|
|
17
|
+
explore: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").ExploreResponse | {
|
|
23
18
|
error: import("./types/index.js").GoogleTrendsError;
|
|
24
19
|
}>;
|
|
25
|
-
|
|
20
|
+
interestByRegion: ({ keyword, startTime, endTime, geo, resolution, hl, timezone, category, enableBackoff }: import("./types/index.js").InterestByRegionOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").InterestByRegionResponse>>;
|
|
21
|
+
relatedTopics: ({ keyword, geo, time, category, property, hl, enableBackoff, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedTopicsResponse>>;
|
|
26
22
|
relatedQueries: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedQueriesResponse>>;
|
|
27
23
|
relatedData: ({ keyword, geo, time, category, property, hl, }: import("./types/index.js").ExploreOptions) => Promise<import("./types/index.js").GoogleTrendsResponse<import("./types/index.js").RelatedData>>;
|
|
28
24
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const formatDate = (date) => {
|
|
2
|
+
return date.toISOString().split('T')[0];
|
|
3
|
+
};
|
|
4
|
+
const formatTrendsDate = (date) => {
|
|
5
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
6
|
+
const yyyy = date.getFullYear();
|
|
7
|
+
const mm = pad(date.getMonth() + 1);
|
|
8
|
+
const dd = pad(date.getDate());
|
|
9
|
+
const hh = pad(date.getHours());
|
|
10
|
+
const min = pad(date.getMinutes());
|
|
11
|
+
const ss = pad(date.getSeconds());
|
|
12
|
+
return `${yyyy}-${mm}-${dd}T${hh}\\:${min}\\:${ss}`;
|
|
13
|
+
};
|
|
14
|
+
const getDateRangeParam = (date) => {
|
|
15
|
+
const yesterday = new Date(date);
|
|
16
|
+
yesterday.setDate(date.getDate() - 1);
|
|
17
|
+
const formattedStart = formatTrendsDate(yesterday);
|
|
18
|
+
const formattedEnd = formatTrendsDate(date);
|
|
19
|
+
return `${formattedStart} ${formattedEnd}`;
|
|
20
|
+
};
|
|
21
|
+
export { formatDate, formatTrendsDate, getDateRangeParam };
|