@shaivpidadi/trends-js 0.0.0-beta.8 → 1.0.1
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 +46 -7
- package/dist/cjs/constants.d.ts +2 -2
- package/dist/cjs/constants.js +7 -7
- package/dist/cjs/errors/GoogleTrendsError.d.ts +1 -1
- package/dist/cjs/helpers/format.d.ts +1 -1
- package/dist/cjs/helpers/format.js +65 -27
- package/dist/cjs/helpers/googleTrendsAPI.d.ts +7 -3
- package/dist/cjs/helpers/googleTrendsAPI.js +136 -65
- package/dist/cjs/index.d.ts +26 -16
- package/dist/cjs/index.js +4 -3
- package/dist/cjs/package.json +1 -0
- package/dist/esm/constants.d.ts +2 -2
- package/dist/esm/constants.js +1 -1
- package/dist/esm/errors/GoogleTrendsError.d.ts +1 -1
- package/dist/esm/helpers/format.d.ts +1 -1
- package/dist/esm/helpers/format.js +59 -21
- package/dist/esm/helpers/googleTrendsAPI.d.ts +7 -3
- package/dist/esm/helpers/googleTrendsAPI.js +108 -37
- package/dist/esm/index.d.ts +26 -16
- package/dist/esm/index.js +2 -1
- package/dist/esm/package.json +1 -0
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
A TypeScript library for interacting with the Google Trends API. This package provides a simple and type-safe way to access Google Trends data programmatically.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Showcase
|
|
4
|
+
|
|
5
|
+
### EliteTimesNews.com — Built with `@shaivpidadi/trends-js`
|
|
6
|
+
**URL:** https://elitetimesnews.com
|
|
7
|
+
**What it uses:** `dailyTrends()` (US, en) to power the home page “Daily Trending” rail, refreshed on a schedule.
|
|
4
8
|
|
|
5
|
-
A TypeScript library for interacting with the Google Trends API. This package provides a simple and type-safe way to access Google Trends data programmatically.
|
|
6
9
|
|
|
7
10
|
## Installation
|
|
8
11
|
|
|
@@ -43,8 +46,26 @@ const result = await GoogleTrendsApi.dailyTrends({
|
|
|
43
46
|
|
|
44
47
|
// Result structure:
|
|
45
48
|
// {
|
|
46
|
-
// allTrendingStories: Array
|
|
47
|
-
//
|
|
49
|
+
// allTrendingStories: Array<{
|
|
50
|
+
// title: string,
|
|
51
|
+
// traffic: string,
|
|
52
|
+
// image?: {
|
|
53
|
+
// newsUrl: string,
|
|
54
|
+
// source: string,
|
|
55
|
+
// imageUrl: string
|
|
56
|
+
// },
|
|
57
|
+
// articles: Array<{
|
|
58
|
+
// title: string,
|
|
59
|
+
// url: string,
|
|
60
|
+
// source: string,
|
|
61
|
+
// time: string,
|
|
62
|
+
// snippet: string
|
|
63
|
+
// }>,
|
|
64
|
+
// shareUrl: string,
|
|
65
|
+
// startTime: number, // Unix timestamp
|
|
66
|
+
// endTime?: number // Unix timestamp (optional)
|
|
67
|
+
// }>,
|
|
68
|
+
// summary: Array<...>
|
|
48
69
|
// }
|
|
49
70
|
```
|
|
50
71
|
|
|
@@ -60,8 +81,26 @@ const result = await GoogleTrendsApi.realTimeTrends({
|
|
|
60
81
|
|
|
61
82
|
// Result structure:
|
|
62
83
|
// {
|
|
63
|
-
// allTrendingStories: Array
|
|
64
|
-
//
|
|
84
|
+
// allTrendingStories: Array<{
|
|
85
|
+
// title: string,
|
|
86
|
+
// traffic: string,
|
|
87
|
+
// image?: {
|
|
88
|
+
// newsUrl: string,
|
|
89
|
+
// source: string,
|
|
90
|
+
// imageUrl: string
|
|
91
|
+
// },
|
|
92
|
+
// articles: Array<{
|
|
93
|
+
// title: string,
|
|
94
|
+
// url: string,
|
|
95
|
+
// source: string,
|
|
96
|
+
// time: string,
|
|
97
|
+
// snippet: string
|
|
98
|
+
// }>,
|
|
99
|
+
// shareUrl: string,
|
|
100
|
+
// startTime: number, // Unix timestamp
|
|
101
|
+
// endTime?: number // Unix timestamp (optional)
|
|
102
|
+
// }>,
|
|
103
|
+
// summary: Array<...>
|
|
65
104
|
// }
|
|
66
105
|
```
|
|
67
106
|
|
package/dist/cjs/constants.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { GoogleTrendsEndpoints } from './types/enums';
|
|
2
|
-
import { GoogleTrendsMapper } from './types';
|
|
1
|
+
import { GoogleTrendsEndpoints } from './types/enums.js';
|
|
2
|
+
import { GoogleTrendsMapper } from './types/index.js';
|
|
3
3
|
export declare const GOOGLE_TRENDS_MAPPER: Record<GoogleTrendsEndpoints, GoogleTrendsMapper>;
|
package/dist/cjs/constants.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GOOGLE_TRENDS_MAPPER = void 0;
|
|
4
|
-
const
|
|
4
|
+
const enums_js_1 = require("./types/enums.js");
|
|
5
5
|
const GOOGLE_TRENDS_BASE_URL = 'trends.google.com';
|
|
6
6
|
exports.GOOGLE_TRENDS_MAPPER = {
|
|
7
|
-
[
|
|
7
|
+
[enums_js_1.GoogleTrendsEndpoints.dailyTrends]: {
|
|
8
8
|
path: '/_/TrendsUi/data/batchexecute',
|
|
9
9
|
method: 'POST',
|
|
10
10
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
@@ -13,7 +13,7 @@ exports.GOOGLE_TRENDS_MAPPER = {
|
|
|
13
13
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
|
-
[
|
|
16
|
+
[enums_js_1.GoogleTrendsEndpoints.autocomplete]: {
|
|
17
17
|
path: '/trends/api/autocomplete',
|
|
18
18
|
method: 'GET',
|
|
19
19
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
@@ -22,28 +22,28 @@ exports.GOOGLE_TRENDS_MAPPER = {
|
|
|
22
22
|
accept: 'application/json, text/plain, */*',
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
|
-
[
|
|
25
|
+
[enums_js_1.GoogleTrendsEndpoints.explore]: {
|
|
26
26
|
path: '/trends/api/explore',
|
|
27
27
|
method: 'POST',
|
|
28
28
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
29
29
|
url: `https://${GOOGLE_TRENDS_BASE_URL}/trends/api/explore`,
|
|
30
30
|
headers: {},
|
|
31
31
|
},
|
|
32
|
-
[
|
|
32
|
+
[enums_js_1.GoogleTrendsEndpoints.interestByRegion]: {
|
|
33
33
|
path: '/trends/api/widgetdata/comparedgeo',
|
|
34
34
|
method: 'GET',
|
|
35
35
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
36
36
|
url: `https://${GOOGLE_TRENDS_BASE_URL}/trends/api/widgetdata/comparedgeo`,
|
|
37
37
|
headers: {},
|
|
38
38
|
},
|
|
39
|
-
[
|
|
39
|
+
[enums_js_1.GoogleTrendsEndpoints.relatedTopics]: {
|
|
40
40
|
path: '/trends/api/widgetdata/relatedtopics',
|
|
41
41
|
method: 'GET',
|
|
42
42
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
43
43
|
url: `https://${GOOGLE_TRENDS_BASE_URL}/trends/api/widgetdata/relatedtopics`,
|
|
44
44
|
headers: {},
|
|
45
45
|
},
|
|
46
|
-
[
|
|
46
|
+
[enums_js_1.GoogleTrendsEndpoints.relatedQueries]: {
|
|
47
47
|
path: '/trends/api/widgetdata/relatedqueries',
|
|
48
48
|
method: 'GET',
|
|
49
49
|
host: GOOGLE_TRENDS_BASE_URL,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DailyTrendingTopics } from '../types';
|
|
1
|
+
import { DailyTrendingTopics } from '../types/index.js';
|
|
2
2
|
export declare const extractJsonFromResponse: (text: string) => DailyTrendingTopics | null;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractJsonFromResponse = void 0;
|
|
4
|
-
const
|
|
5
|
-
// For future
|
|
4
|
+
const GoogleTrendsError_js_1 = require("../errors/GoogleTrendsError.js");
|
|
5
|
+
// For future reference and update: from google trends page rpc call response,
|
|
6
6
|
// 0 "twitter down" The main trending search term.
|
|
7
|
-
// 1 null
|
|
7
|
+
// 1 null OR [newsUrl, source, imageUrl, [articles...]] Image/article data (often null in current API responses).
|
|
8
8
|
// 2 "US" Country code (where the trend is happening).
|
|
9
|
-
// 3 [1741599600] Unix timestamp
|
|
10
|
-
// 4 null
|
|
9
|
+
// 3 [1741599600] Unix timestamp array - first element is when the trend started.
|
|
10
|
+
// 4 null OR [1741602000] Unix timestamp array - trend end time (if available).
|
|
11
11
|
// 5 null Unused (reserved for future data).
|
|
12
12
|
// 6 500000 Search volume index (estimated search interest for the term).
|
|
13
13
|
// 7 null Unused (reserved for future data).
|
|
@@ -21,58 +21,96 @@ const extractJsonFromResponse = (text) => {
|
|
|
21
21
|
try {
|
|
22
22
|
const parsedResponse = JSON.parse(cleanedText);
|
|
23
23
|
if (!Array.isArray(parsedResponse) || parsedResponse.length === 0) {
|
|
24
|
-
throw new
|
|
24
|
+
throw new GoogleTrendsError_js_1.ParseError('Invalid response format: empty array');
|
|
25
25
|
}
|
|
26
26
|
const nestedJsonString = parsedResponse[0][2];
|
|
27
27
|
if (!nestedJsonString) {
|
|
28
|
-
throw new
|
|
28
|
+
throw new GoogleTrendsError_js_1.ParseError('Invalid response format: missing nested JSON');
|
|
29
29
|
}
|
|
30
30
|
const data = JSON.parse(nestedJsonString);
|
|
31
31
|
if (!data || !Array.isArray(data) || data.length < 2) {
|
|
32
|
-
throw new
|
|
32
|
+
throw new GoogleTrendsError_js_1.ParseError('Invalid response format: missing data array');
|
|
33
33
|
}
|
|
34
34
|
return updateResponseObject(data[1]);
|
|
35
35
|
}
|
|
36
36
|
catch (e) {
|
|
37
|
-
if (e instanceof
|
|
37
|
+
if (e instanceof GoogleTrendsError_js_1.ParseError) {
|
|
38
38
|
throw e;
|
|
39
39
|
}
|
|
40
|
-
throw new
|
|
40
|
+
throw new GoogleTrendsError_js_1.ParseError('Failed to parse response');
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
exports.extractJsonFromResponse = extractJsonFromResponse;
|
|
44
|
+
const extractArticles = (item) => {
|
|
45
|
+
const imageData = item[1];
|
|
46
|
+
if (!imageData || !Array.isArray(imageData))
|
|
47
|
+
return [];
|
|
48
|
+
if (imageData.length <= 3)
|
|
49
|
+
return [];
|
|
50
|
+
const articlesArray = imageData[3];
|
|
51
|
+
if (!Array.isArray(articlesArray))
|
|
52
|
+
return [];
|
|
53
|
+
return articlesArray
|
|
54
|
+
.filter((article) => Array.isArray(article) && article.length >= 5)
|
|
55
|
+
.map((article) => ({
|
|
56
|
+
title: String(article[0] || ''),
|
|
57
|
+
url: String(article[1] || ''),
|
|
58
|
+
source: String(article[2] || ''),
|
|
59
|
+
time: String(article[3] || ''),
|
|
60
|
+
snippet: String(article[4] || ''),
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
63
|
+
const extractTimestamp = (item, index) => {
|
|
64
|
+
const timeArray = item[index];
|
|
65
|
+
if (!Array.isArray(timeArray))
|
|
66
|
+
return undefined;
|
|
67
|
+
if (timeArray.length === 0)
|
|
68
|
+
return undefined;
|
|
69
|
+
const timestamp = timeArray[0];
|
|
70
|
+
if (typeof timestamp !== 'number')
|
|
71
|
+
return undefined;
|
|
72
|
+
return timestamp;
|
|
73
|
+
};
|
|
74
|
+
const extractImage = (item) => {
|
|
75
|
+
const imageData = item[1];
|
|
76
|
+
if (!imageData || !Array.isArray(imageData))
|
|
77
|
+
return undefined;
|
|
78
|
+
if (imageData.length < 3)
|
|
79
|
+
return undefined;
|
|
80
|
+
return {
|
|
81
|
+
newsUrl: String(imageData[0] || ''),
|
|
82
|
+
source: String(imageData[1] || ''),
|
|
83
|
+
imageUrl: String(imageData[2] || ''),
|
|
84
|
+
};
|
|
85
|
+
};
|
|
44
86
|
const updateResponseObject = (data) => {
|
|
45
87
|
if (!Array.isArray(data)) {
|
|
46
|
-
throw new
|
|
88
|
+
throw new GoogleTrendsError_js_1.ParseError('Invalid data format: expected array');
|
|
47
89
|
}
|
|
48
90
|
const allTrendingStories = [];
|
|
49
91
|
const summary = [];
|
|
50
92
|
data.forEach((item) => {
|
|
51
93
|
if (Array.isArray(item)) {
|
|
94
|
+
const articles = extractArticles(item);
|
|
95
|
+
const startTime = extractTimestamp(item, 3) ?? 0;
|
|
96
|
+
const endTime = extractTimestamp(item, 4);
|
|
97
|
+
const image = extractImage(item);
|
|
52
98
|
const story = {
|
|
53
99
|
title: String(item[0] || ''),
|
|
54
100
|
traffic: String(item[6] || '0'),
|
|
55
|
-
articles:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
snippet: String(article[4] || '')
|
|
61
|
-
})) : [],
|
|
62
|
-
shareUrl: String(item[12] || '')
|
|
101
|
+
articles: articles,
|
|
102
|
+
shareUrl: String(item[12] || ''),
|
|
103
|
+
startTime,
|
|
104
|
+
...(endTime && { endTime }),
|
|
105
|
+
...(image && { image }),
|
|
63
106
|
};
|
|
64
|
-
if (item[1]) {
|
|
65
|
-
story.image = {
|
|
66
|
-
newsUrl: String(item[1][0] || ''),
|
|
67
|
-
source: String(item[1][1] || ''),
|
|
68
|
-
imageUrl: String(item[1][2] || '')
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
107
|
allTrendingStories.push(story);
|
|
72
108
|
summary.push({
|
|
73
109
|
title: story.title,
|
|
74
110
|
traffic: story.traffic,
|
|
75
|
-
articles: story.articles
|
|
111
|
+
articles: story.articles,
|
|
112
|
+
startTime,
|
|
113
|
+
...(endTime && { endTime }),
|
|
76
114
|
});
|
|
77
115
|
}
|
|
78
116
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DailyTrendingTopics, DailyTrendingTopicsOptions, RealTimeTrendsOptions, ExploreOptions, ExploreResponse, InterestByRegionOptions, InterestByRegionResponse, GoogleTrendsResponse, RelatedTopicsResponse, RelatedQueriesResponse, RelatedData } from '../types/index';
|
|
1
|
+
import { DailyTrendingTopics, DailyTrendingTopicsOptions, RealTimeTrendsOptions, ExploreOptions, ExploreResponse, InterestByRegionOptions, InterestByRegionResponse, GoogleTrendsResponse, GoogleTrendsError, RelatedTopicsResponse, RelatedQueriesResponse, RelatedData } from '../types/index.js';
|
|
2
2
|
export declare class GoogleTrendsApi {
|
|
3
3
|
/**
|
|
4
4
|
* Get autocomplete suggestions for a keyword
|
|
@@ -19,8 +19,12 @@ 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
|
|
23
|
-
|
|
22
|
+
explore({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<ExploreResponse | {
|
|
23
|
+
error: GoogleTrendsError;
|
|
24
|
+
}>;
|
|
25
|
+
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: InterestByRegionOptions): Promise<InterestByRegionResponse | {
|
|
26
|
+
error: GoogleTrendsError;
|
|
27
|
+
}>;
|
|
24
28
|
relatedTopics({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
25
29
|
relatedQueries({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedQueriesResponse>>;
|
|
26
30
|
relatedData({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedData>>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GoogleTrendsApi = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
4
|
+
const enums_js_1 = require("../types/enums.js");
|
|
5
|
+
const request_js_1 = require("./request.js");
|
|
6
|
+
const format_js_1 = require("./format.js");
|
|
7
|
+
const constants_js_1 = require("../constants.js");
|
|
8
|
+
const GoogleTrendsError_js_1 = require("../errors/GoogleTrendsError.js");
|
|
9
9
|
class GoogleTrendsApi {
|
|
10
10
|
/**
|
|
11
11
|
* Get autocomplete suggestions for a keyword
|
|
@@ -18,14 +18,14 @@ class GoogleTrendsApi {
|
|
|
18
18
|
return { data: [] };
|
|
19
19
|
}
|
|
20
20
|
const options = {
|
|
21
|
-
...
|
|
21
|
+
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.autocomplete],
|
|
22
22
|
qs: {
|
|
23
23
|
hl,
|
|
24
24
|
tz: '240',
|
|
25
25
|
},
|
|
26
26
|
};
|
|
27
27
|
try {
|
|
28
|
-
const response = await (0,
|
|
28
|
+
const response = await (0, request_js_1.request)(`${options.url}/${encodeURIComponent(keyword)}`, options);
|
|
29
29
|
const text = await response.text();
|
|
30
30
|
// Remove the first 5 characters (JSONP wrapper) and parse
|
|
31
31
|
const data = JSON.parse(text.slice(5));
|
|
@@ -33,9 +33,9 @@ class GoogleTrendsApi {
|
|
|
33
33
|
}
|
|
34
34
|
catch (error) {
|
|
35
35
|
if (error instanceof Error) {
|
|
36
|
-
return { error: new
|
|
36
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
37
37
|
}
|
|
38
|
-
return { error: new
|
|
38
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -44,7 +44,7 @@ class GoogleTrendsApi {
|
|
|
44
44
|
* @returns Promise with trending topics data
|
|
45
45
|
*/
|
|
46
46
|
async dailyTrends({ geo = 'US', lang = 'en' }) {
|
|
47
|
-
const defaultOptions =
|
|
47
|
+
const defaultOptions = constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.dailyTrends];
|
|
48
48
|
const options = {
|
|
49
49
|
...defaultOptions,
|
|
50
50
|
body: new URLSearchParams({
|
|
@@ -53,19 +53,19 @@ class GoogleTrendsApi {
|
|
|
53
53
|
contentType: 'form'
|
|
54
54
|
};
|
|
55
55
|
try {
|
|
56
|
-
const response = await (0,
|
|
56
|
+
const response = await (0, request_js_1.request)(options.url, options);
|
|
57
57
|
const text = await response.text();
|
|
58
|
-
const trendingTopics = (0,
|
|
58
|
+
const trendingTopics = (0, format_js_1.extractJsonFromResponse)(text);
|
|
59
59
|
if (!trendingTopics) {
|
|
60
|
-
return { error: new
|
|
60
|
+
return { error: new GoogleTrendsError_js_1.ParseError() };
|
|
61
61
|
}
|
|
62
62
|
return { data: trendingTopics };
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
65
|
if (error instanceof Error) {
|
|
66
|
-
return { error: new
|
|
66
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
67
67
|
}
|
|
68
|
-
return { error: new
|
|
68
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
@@ -74,7 +74,7 @@ class GoogleTrendsApi {
|
|
|
74
74
|
* @returns Promise with trending topics data
|
|
75
75
|
*/
|
|
76
76
|
async realTimeTrends({ geo = 'US', trendingHours = 4 }) {
|
|
77
|
-
const defaultOptions =
|
|
77
|
+
const defaultOptions = constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.dailyTrends];
|
|
78
78
|
const options = {
|
|
79
79
|
...defaultOptions,
|
|
80
80
|
body: new URLSearchParams({
|
|
@@ -83,24 +83,24 @@ class GoogleTrendsApi {
|
|
|
83
83
|
contentType: 'form'
|
|
84
84
|
};
|
|
85
85
|
try {
|
|
86
|
-
const response = await (0,
|
|
86
|
+
const response = await (0, request_js_1.request)(options.url, options);
|
|
87
87
|
const text = await response.text();
|
|
88
|
-
const trendingTopics = (0,
|
|
88
|
+
const trendingTopics = (0, format_js_1.extractJsonFromResponse)(text);
|
|
89
89
|
if (!trendingTopics) {
|
|
90
|
-
return { error: new
|
|
90
|
+
return { error: new GoogleTrendsError_js_1.ParseError() };
|
|
91
91
|
}
|
|
92
92
|
return { data: trendingTopics };
|
|
93
93
|
}
|
|
94
94
|
catch (error) {
|
|
95
95
|
if (error instanceof Error) {
|
|
96
|
-
return { error: new
|
|
96
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
97
97
|
}
|
|
98
|
-
return { error: new
|
|
98
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
102
102
|
const options = {
|
|
103
|
-
...
|
|
103
|
+
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.explore],
|
|
104
104
|
qs: {
|
|
105
105
|
hl,
|
|
106
106
|
tz: '240',
|
|
@@ -119,32 +119,43 @@ class GoogleTrendsApi {
|
|
|
119
119
|
contentType: 'form'
|
|
120
120
|
};
|
|
121
121
|
try {
|
|
122
|
-
const response = await (0,
|
|
122
|
+
const response = await (0, request_js_1.request)(options.url, options);
|
|
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')) {
|
|
126
|
-
|
|
127
|
-
return { widgets: [] };
|
|
126
|
+
return { error: new GoogleTrendsError_js_1.ParseError('Explore request returned HTML instead of JSON') };
|
|
128
127
|
}
|
|
129
128
|
// Try to parse as JSON
|
|
130
129
|
try {
|
|
131
130
|
// Remove the first 5 characters (JSONP wrapper) and parse
|
|
132
131
|
const data = JSON.parse(text.slice(5));
|
|
133
|
-
|
|
132
|
+
// Extract widgets from the response
|
|
133
|
+
if (data && Array.isArray(data) && data.length > 0) {
|
|
134
|
+
const widgets = data[0] || [];
|
|
135
|
+
return { widgets };
|
|
136
|
+
}
|
|
137
|
+
return { widgets: [] };
|
|
134
138
|
}
|
|
135
139
|
catch (parseError) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
if (parseError instanceof Error) {
|
|
141
|
+
return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse explore response as JSON: ${parseError.message}`) };
|
|
142
|
+
}
|
|
143
|
+
return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse explore response as JSON') };
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
catch (error) {
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
if (error instanceof Error) {
|
|
148
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(`Explore request failed: ${error.message}`) };
|
|
149
|
+
}
|
|
150
|
+
return { error: new GoogleTrendsError_js_1.UnknownError('Explore request failed') };
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
//
|
|
147
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 }) {
|
|
155
|
+
const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword;
|
|
156
|
+
if (!keywordValue || keywordValue.trim() === '') {
|
|
157
|
+
return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') };
|
|
158
|
+
}
|
|
148
159
|
const formatDate = (date) => {
|
|
149
160
|
return date.toISOString().split('T')[0];
|
|
150
161
|
};
|
|
@@ -172,12 +183,15 @@ class GoogleTrendsApi {
|
|
|
172
183
|
category,
|
|
173
184
|
hl
|
|
174
185
|
});
|
|
186
|
+
if ('error' in exploreResponse) {
|
|
187
|
+
return { error: exploreResponse.error };
|
|
188
|
+
}
|
|
175
189
|
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
176
190
|
if (!widget) {
|
|
177
|
-
return {
|
|
191
|
+
return { error: new GoogleTrendsError_js_1.ParseError('No GEO_MAP widget found in explore response') };
|
|
178
192
|
}
|
|
179
193
|
const options = {
|
|
180
|
-
...
|
|
194
|
+
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.interestByRegion],
|
|
181
195
|
qs: {
|
|
182
196
|
hl,
|
|
183
197
|
tz: timezone.toString(),
|
|
@@ -209,59 +223,116 @@ class GoogleTrendsApi {
|
|
|
209
223
|
}
|
|
210
224
|
};
|
|
211
225
|
try {
|
|
212
|
-
const response = await (0,
|
|
226
|
+
const response = await (0, request_js_1.request)(options.url, options);
|
|
213
227
|
const text = await response.text();
|
|
214
228
|
// Remove the first 5 characters (JSONP wrapper) and parse
|
|
215
229
|
const data = JSON.parse(text.slice(5));
|
|
216
230
|
return data;
|
|
217
231
|
}
|
|
218
232
|
catch (error) {
|
|
219
|
-
|
|
233
|
+
if (error instanceof Error) {
|
|
234
|
+
return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse interest by region response: ${error.message}`) };
|
|
235
|
+
}
|
|
236
|
+
return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse interest by region response') };
|
|
220
237
|
}
|
|
221
238
|
}
|
|
222
239
|
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
223
240
|
try {
|
|
224
241
|
// Validate keyword
|
|
225
242
|
if (!keyword || keyword.trim() === '') {
|
|
226
|
-
return { error: new
|
|
243
|
+
return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') };
|
|
227
244
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
// Step 1: Call explore to get widget data and token
|
|
246
|
+
const exploreResponse = await this.explore({
|
|
247
|
+
keyword,
|
|
248
|
+
geo,
|
|
249
|
+
time,
|
|
250
|
+
category,
|
|
251
|
+
property,
|
|
252
|
+
hl
|
|
253
|
+
});
|
|
254
|
+
if ('error' in exploreResponse) {
|
|
255
|
+
return { error: exploreResponse.error };
|
|
231
256
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
if (!exploreResponse.widgets || exploreResponse.widgets.length === 0) {
|
|
258
|
+
return { error: new GoogleTrendsError_js_1.ParseError('No widgets found in explore response. This might be due to Google blocking the request, invalid parameters, or network issues.') };
|
|
259
|
+
}
|
|
260
|
+
// Step 2: Find the related topics widget or use any available widget
|
|
261
|
+
const relatedTopicsWidget = exploreResponse.widgets.find(widget => widget.id === 'RELATED_TOPICS' ||
|
|
262
|
+
widget.request?.restriction?.complexKeywordsRestriction?.keyword?.[0]?.value === keyword) || exploreResponse.widgets[0]; // Fallback to first widget if no specific one found
|
|
263
|
+
if (!relatedTopicsWidget) {
|
|
264
|
+
return { error: new GoogleTrendsError_js_1.ParseError('No related topics widget found in explore response') };
|
|
265
|
+
}
|
|
266
|
+
// Step 3: Call the related topics API with or without token
|
|
267
|
+
const options = {
|
|
268
|
+
...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.relatedTopics],
|
|
269
|
+
qs: {
|
|
270
|
+
hl,
|
|
271
|
+
tz: '240',
|
|
272
|
+
req: JSON.stringify({
|
|
273
|
+
restriction: {
|
|
274
|
+
geo: { country: geo },
|
|
275
|
+
time: time,
|
|
276
|
+
originalTimeRangeForExploreUrl: time,
|
|
277
|
+
complexKeywordsRestriction: {
|
|
278
|
+
keyword: [{
|
|
279
|
+
type: 'BROAD',
|
|
280
|
+
value: keyword
|
|
281
|
+
}]
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
keywordType: 'ENTITY',
|
|
285
|
+
metric: ['TOP', 'RISING'],
|
|
286
|
+
trendinessSettings: {
|
|
287
|
+
compareTime: time
|
|
288
|
+
},
|
|
289
|
+
requestOptions: {
|
|
290
|
+
property: property,
|
|
291
|
+
backend: 'CM',
|
|
292
|
+
category: category
|
|
293
|
+
},
|
|
294
|
+
language: hl.split('-')[0],
|
|
295
|
+
userCountryCode: geo,
|
|
296
|
+
userConfig: {
|
|
297
|
+
userType: 'USER_TYPE_LEGIT_USER'
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
300
|
+
...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token })
|
|
250
301
|
}
|
|
251
302
|
};
|
|
303
|
+
const response = await (0, request_js_1.request)(options.url, options);
|
|
304
|
+
const text = await response.text();
|
|
305
|
+
// Parse the response
|
|
306
|
+
try {
|
|
307
|
+
const data = JSON.parse(text.slice(5));
|
|
308
|
+
// Return the data in the expected format
|
|
309
|
+
return {
|
|
310
|
+
data: {
|
|
311
|
+
default: {
|
|
312
|
+
rankedList: data.default?.rankedList || []
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (parseError) {
|
|
318
|
+
if (parseError instanceof Error) {
|
|
319
|
+
return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse related topics response: ${parseError.message}`) };
|
|
320
|
+
}
|
|
321
|
+
return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse related topics response') };
|
|
322
|
+
}
|
|
252
323
|
}
|
|
253
324
|
catch (error) {
|
|
254
325
|
if (error instanceof Error) {
|
|
255
|
-
return { error: new
|
|
326
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
256
327
|
}
|
|
257
|
-
return { error: new
|
|
328
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
258
329
|
}
|
|
259
330
|
}
|
|
260
331
|
async relatedQueries({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
261
332
|
try {
|
|
262
333
|
// Validate keyword
|
|
263
334
|
if (!keyword || keyword.trim() === '') {
|
|
264
|
-
return { error: new
|
|
335
|
+
return { error: new GoogleTrendsError_js_1.ParseError() };
|
|
265
336
|
}
|
|
266
337
|
const autocompleteResult = await this.autocomplete(keyword, hl);
|
|
267
338
|
if (autocompleteResult.error) {
|
|
@@ -286,16 +357,16 @@ class GoogleTrendsApi {
|
|
|
286
357
|
}
|
|
287
358
|
catch (error) {
|
|
288
359
|
if (error instanceof Error) {
|
|
289
|
-
return { error: new
|
|
360
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
290
361
|
}
|
|
291
|
-
return { error: new
|
|
362
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
292
363
|
}
|
|
293
364
|
}
|
|
294
365
|
async relatedData({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
295
366
|
try {
|
|
296
367
|
// Validate keyword
|
|
297
368
|
if (!keyword || keyword.trim() === '') {
|
|
298
|
-
return { error: new
|
|
369
|
+
return { error: new GoogleTrendsError_js_1.ParseError() };
|
|
299
370
|
}
|
|
300
371
|
const autocompleteResult = await this.autocomplete(keyword, hl);
|
|
301
372
|
if (autocompleteResult.error) {
|
|
@@ -329,9 +400,9 @@ class GoogleTrendsApi {
|
|
|
329
400
|
}
|
|
330
401
|
catch (error) {
|
|
331
402
|
if (error instanceof Error) {
|
|
332
|
-
return { error: new
|
|
403
|
+
return { error: new GoogleTrendsError_js_1.NetworkError(error.message) };
|
|
333
404
|
}
|
|
334
|
-
return { error: new
|
|
405
|
+
return { error: new GoogleTrendsError_js_1.UnknownError() };
|
|
335
406
|
}
|
|
336
407
|
}
|
|
337
408
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare const
|
|
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 };
|
|
9
15
|
declare const _default: {
|
|
10
|
-
dailyTrends: ({ geo, lang }: import("./types").DailyTrendingTopicsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
|
|
11
|
-
realTimeTrends: ({ geo, trendingHours }: import("./types").RealTimeTrendsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
|
|
12
|
-
autocomplete: (keyword: string, hl?: string) => Promise<import("./types").GoogleTrendsResponse<string[]>>;
|
|
13
|
-
explore: ({ keyword, geo, time, category, property, hl, }: import("./types").ExploreOptions) => Promise<import("./types").ExploreResponse
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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>>;
|
|
18
28
|
};
|
|
19
29
|
export default _default;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.relatedData = exports.relatedQueries = exports.relatedTopics = exports.interestByRegion = exports.explore = exports.autocomplete = exports.realTimeTrends = exports.dailyTrends = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
3
|
+
exports.GoogleTrendsApi = exports.relatedData = exports.relatedQueries = exports.relatedTopics = exports.interestByRegion = exports.explore = exports.autocomplete = exports.realTimeTrends = exports.dailyTrends = void 0;
|
|
4
|
+
const googleTrendsAPI_js_1 = require("./helpers/googleTrendsAPI.js");
|
|
5
|
+
Object.defineProperty(exports, "GoogleTrendsApi", { enumerable: true, get: function () { return googleTrendsAPI_js_1.GoogleTrendsApi; } });
|
|
6
|
+
const api = new googleTrendsAPI_js_1.GoogleTrendsApi();
|
|
6
7
|
exports.dailyTrends = api.dailyTrends.bind(api);
|
|
7
8
|
exports.realTimeTrends = api.realTimeTrends.bind(api);
|
|
8
9
|
exports.autocomplete = api.autocomplete.bind(api);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { GoogleTrendsEndpoints } from './types/enums';
|
|
2
|
-
import { GoogleTrendsMapper } from './types';
|
|
1
|
+
import { GoogleTrendsEndpoints } from './types/enums.js';
|
|
2
|
+
import { GoogleTrendsMapper } from './types/index.js';
|
|
3
3
|
export declare const GOOGLE_TRENDS_MAPPER: Record<GoogleTrendsEndpoints, GoogleTrendsMapper>;
|
package/dist/esm/constants.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DailyTrendingTopics } from '../types';
|
|
1
|
+
import { DailyTrendingTopics } from '../types/index.js';
|
|
2
2
|
export declare const extractJsonFromResponse: (text: string) => DailyTrendingTopics | null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ParseError } from '../errors/GoogleTrendsError';
|
|
2
|
-
// For future
|
|
1
|
+
import { ParseError } from '../errors/GoogleTrendsError.js';
|
|
2
|
+
// For future reference and update: from google trends page rpc call response,
|
|
3
3
|
// 0 "twitter down" The main trending search term.
|
|
4
|
-
// 1 null
|
|
4
|
+
// 1 null OR [newsUrl, source, imageUrl, [articles...]] Image/article data (often null in current API responses).
|
|
5
5
|
// 2 "US" Country code (where the trend is happening).
|
|
6
|
-
// 3 [1741599600] Unix timestamp
|
|
7
|
-
// 4 null
|
|
6
|
+
// 3 [1741599600] Unix timestamp array - first element is when the trend started.
|
|
7
|
+
// 4 null OR [1741602000] Unix timestamp array - trend end time (if available).
|
|
8
8
|
// 5 null Unused (reserved for future data).
|
|
9
9
|
// 6 500000 Search volume index (estimated search interest for the term).
|
|
10
10
|
// 7 null Unused (reserved for future data).
|
|
@@ -37,6 +37,48 @@ export const extractJsonFromResponse = (text) => {
|
|
|
37
37
|
throw new ParseError('Failed to parse response');
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
const extractArticles = (item) => {
|
|
41
|
+
const imageData = item[1];
|
|
42
|
+
if (!imageData || !Array.isArray(imageData))
|
|
43
|
+
return [];
|
|
44
|
+
if (imageData.length <= 3)
|
|
45
|
+
return [];
|
|
46
|
+
const articlesArray = imageData[3];
|
|
47
|
+
if (!Array.isArray(articlesArray))
|
|
48
|
+
return [];
|
|
49
|
+
return articlesArray
|
|
50
|
+
.filter((article) => Array.isArray(article) && article.length >= 5)
|
|
51
|
+
.map((article) => ({
|
|
52
|
+
title: String(article[0] || ''),
|
|
53
|
+
url: String(article[1] || ''),
|
|
54
|
+
source: String(article[2] || ''),
|
|
55
|
+
time: String(article[3] || ''),
|
|
56
|
+
snippet: String(article[4] || ''),
|
|
57
|
+
}));
|
|
58
|
+
};
|
|
59
|
+
const extractTimestamp = (item, index) => {
|
|
60
|
+
const timeArray = item[index];
|
|
61
|
+
if (!Array.isArray(timeArray))
|
|
62
|
+
return undefined;
|
|
63
|
+
if (timeArray.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
const timestamp = timeArray[0];
|
|
66
|
+
if (typeof timestamp !== 'number')
|
|
67
|
+
return undefined;
|
|
68
|
+
return timestamp;
|
|
69
|
+
};
|
|
70
|
+
const extractImage = (item) => {
|
|
71
|
+
const imageData = item[1];
|
|
72
|
+
if (!imageData || !Array.isArray(imageData))
|
|
73
|
+
return undefined;
|
|
74
|
+
if (imageData.length < 3)
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
newsUrl: String(imageData[0] || ''),
|
|
78
|
+
source: String(imageData[1] || ''),
|
|
79
|
+
imageUrl: String(imageData[2] || ''),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
40
82
|
const updateResponseObject = (data) => {
|
|
41
83
|
if (!Array.isArray(data)) {
|
|
42
84
|
throw new ParseError('Invalid data format: expected array');
|
|
@@ -45,30 +87,26 @@ const updateResponseObject = (data) => {
|
|
|
45
87
|
const summary = [];
|
|
46
88
|
data.forEach((item) => {
|
|
47
89
|
if (Array.isArray(item)) {
|
|
90
|
+
const articles = extractArticles(item);
|
|
91
|
+
const startTime = extractTimestamp(item, 3) ?? 0;
|
|
92
|
+
const endTime = extractTimestamp(item, 4);
|
|
93
|
+
const image = extractImage(item);
|
|
48
94
|
const story = {
|
|
49
95
|
title: String(item[0] || ''),
|
|
50
96
|
traffic: String(item[6] || '0'),
|
|
51
|
-
articles:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
snippet: String(article[4] || '')
|
|
57
|
-
})) : [],
|
|
58
|
-
shareUrl: String(item[12] || '')
|
|
97
|
+
articles: articles,
|
|
98
|
+
shareUrl: String(item[12] || ''),
|
|
99
|
+
startTime,
|
|
100
|
+
...(endTime && { endTime }),
|
|
101
|
+
...(image && { image }),
|
|
59
102
|
};
|
|
60
|
-
if (item[1]) {
|
|
61
|
-
story.image = {
|
|
62
|
-
newsUrl: String(item[1][0] || ''),
|
|
63
|
-
source: String(item[1][1] || ''),
|
|
64
|
-
imageUrl: String(item[1][2] || '')
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
103
|
allTrendingStories.push(story);
|
|
68
104
|
summary.push({
|
|
69
105
|
title: story.title,
|
|
70
106
|
traffic: story.traffic,
|
|
71
|
-
articles: story.articles
|
|
107
|
+
articles: story.articles,
|
|
108
|
+
startTime,
|
|
109
|
+
...(endTime && { endTime }),
|
|
72
110
|
});
|
|
73
111
|
}
|
|
74
112
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DailyTrendingTopics, DailyTrendingTopicsOptions, RealTimeTrendsOptions, ExploreOptions, ExploreResponse, InterestByRegionOptions, InterestByRegionResponse, GoogleTrendsResponse, RelatedTopicsResponse, RelatedQueriesResponse, RelatedData } from '../types/index';
|
|
1
|
+
import { DailyTrendingTopics, DailyTrendingTopicsOptions, RealTimeTrendsOptions, ExploreOptions, ExploreResponse, InterestByRegionOptions, InterestByRegionResponse, GoogleTrendsResponse, GoogleTrendsError, RelatedTopicsResponse, RelatedQueriesResponse, RelatedData } from '../types/index.js';
|
|
2
2
|
export declare class GoogleTrendsApi {
|
|
3
3
|
/**
|
|
4
4
|
* Get autocomplete suggestions for a keyword
|
|
@@ -19,8 +19,12 @@ 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
|
|
23
|
-
|
|
22
|
+
explore({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<ExploreResponse | {
|
|
23
|
+
error: GoogleTrendsError;
|
|
24
|
+
}>;
|
|
25
|
+
interestByRegion({ keyword, startTime, endTime, geo, resolution, hl, timezone, category }: InterestByRegionOptions): Promise<InterestByRegionResponse | {
|
|
26
|
+
error: GoogleTrendsError;
|
|
27
|
+
}>;
|
|
24
28
|
relatedTopics({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedTopicsResponse>>;
|
|
25
29
|
relatedQueries({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedQueriesResponse>>;
|
|
26
30
|
relatedData({ keyword, geo, time, category, property, hl, }: ExploreOptions): Promise<GoogleTrendsResponse<RelatedData>>;
|
|
@@ -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
|
|
@@ -120,28 +120,39 @@ export class GoogleTrendsApi {
|
|
|
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')) {
|
|
123
|
-
|
|
124
|
-
return { widgets: [] };
|
|
123
|
+
return { error: new ParseError('Explore request returned HTML instead of JSON') };
|
|
125
124
|
}
|
|
126
125
|
// Try to parse as JSON
|
|
127
126
|
try {
|
|
128
127
|
// Remove the first 5 characters (JSONP wrapper) and parse
|
|
129
128
|
const data = JSON.parse(text.slice(5));
|
|
130
|
-
|
|
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: [] };
|
|
131
135
|
}
|
|
132
136
|
catch (parseError) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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') };
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
143
|
catch (error) {
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
if (error instanceof Error) {
|
|
145
|
+
return { error: new NetworkError(`Explore request failed: ${error.message}`) };
|
|
146
|
+
}
|
|
147
|
+
return { error: new UnknownError('Explore request failed') };
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
//
|
|
144
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 }) {
|
|
152
|
+
const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword;
|
|
153
|
+
if (!keywordValue || keywordValue.trim() === '') {
|
|
154
|
+
return { error: new InvalidRequestError('Keyword is required') };
|
|
155
|
+
}
|
|
145
156
|
const formatDate = (date) => {
|
|
146
157
|
return date.toISOString().split('T')[0];
|
|
147
158
|
};
|
|
@@ -169,9 +180,12 @@ export class GoogleTrendsApi {
|
|
|
169
180
|
category,
|
|
170
181
|
hl
|
|
171
182
|
});
|
|
183
|
+
if ('error' in exploreResponse) {
|
|
184
|
+
return { error: exploreResponse.error };
|
|
185
|
+
}
|
|
172
186
|
const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP');
|
|
173
187
|
if (!widget) {
|
|
174
|
-
return {
|
|
188
|
+
return { error: new ParseError('No GEO_MAP widget found in explore response') };
|
|
175
189
|
}
|
|
176
190
|
const options = {
|
|
177
191
|
...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.interestByRegion],
|
|
@@ -213,39 +227,96 @@ export class GoogleTrendsApi {
|
|
|
213
227
|
return data;
|
|
214
228
|
}
|
|
215
229
|
catch (error) {
|
|
216
|
-
|
|
230
|
+
if (error instanceof Error) {
|
|
231
|
+
return { error: new ParseError(`Failed to parse interest by region response: ${error.message}`) };
|
|
232
|
+
}
|
|
233
|
+
return { error: new ParseError('Failed to parse interest by region response') };
|
|
217
234
|
}
|
|
218
235
|
}
|
|
219
236
|
async relatedTopics({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) {
|
|
220
237
|
try {
|
|
221
238
|
// Validate keyword
|
|
222
239
|
if (!keyword || keyword.trim() === '') {
|
|
223
|
-
return { error: new
|
|
240
|
+
return { error: new InvalidRequestError('Keyword is required') };
|
|
224
241
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
// Step 1: Call explore to get widget data and token
|
|
243
|
+
const exploreResponse = await this.explore({
|
|
244
|
+
keyword,
|
|
245
|
+
geo,
|
|
246
|
+
time,
|
|
247
|
+
category,
|
|
248
|
+
property,
|
|
249
|
+
hl
|
|
250
|
+
});
|
|
251
|
+
if ('error' in exploreResponse) {
|
|
252
|
+
return { error: exploreResponse.error };
|
|
228
253
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
if (!exploreResponse.widgets || exploreResponse.widgets.length === 0) {
|
|
255
|
+
return { error: new ParseError('No widgets found in explore response. This might be due to Google blocking the request, invalid parameters, or network issues.') };
|
|
256
|
+
}
|
|
257
|
+
// Step 2: Find the related topics widget or use any available widget
|
|
258
|
+
const relatedTopicsWidget = exploreResponse.widgets.find(widget => widget.id === 'RELATED_TOPICS' ||
|
|
259
|
+
widget.request?.restriction?.complexKeywordsRestriction?.keyword?.[0]?.value === keyword) || exploreResponse.widgets[0]; // Fallback to first widget if no specific one found
|
|
260
|
+
if (!relatedTopicsWidget) {
|
|
261
|
+
return { error: new ParseError('No related topics widget found in explore response') };
|
|
262
|
+
}
|
|
263
|
+
// Step 3: Call the related topics API with or without token
|
|
264
|
+
const options = {
|
|
265
|
+
...GOOGLE_TRENDS_MAPPER[GoogleTrendsEndpoints.relatedTopics],
|
|
266
|
+
qs: {
|
|
267
|
+
hl,
|
|
268
|
+
tz: '240',
|
|
269
|
+
req: JSON.stringify({
|
|
270
|
+
restriction: {
|
|
271
|
+
geo: { country: geo },
|
|
272
|
+
time: time,
|
|
273
|
+
originalTimeRangeForExploreUrl: time,
|
|
274
|
+
complexKeywordsRestriction: {
|
|
275
|
+
keyword: [{
|
|
276
|
+
type: 'BROAD',
|
|
277
|
+
value: keyword
|
|
278
|
+
}]
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
keywordType: 'ENTITY',
|
|
282
|
+
metric: ['TOP', 'RISING'],
|
|
283
|
+
trendinessSettings: {
|
|
284
|
+
compareTime: time
|
|
285
|
+
},
|
|
286
|
+
requestOptions: {
|
|
287
|
+
property: property,
|
|
288
|
+
backend: 'CM',
|
|
289
|
+
category: category
|
|
290
|
+
},
|
|
291
|
+
language: hl.split('-')[0],
|
|
292
|
+
userCountryCode: geo,
|
|
293
|
+
userConfig: {
|
|
294
|
+
userType: 'USER_TYPE_LEGIT_USER'
|
|
295
|
+
}
|
|
296
|
+
}),
|
|
297
|
+
...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token })
|
|
247
298
|
}
|
|
248
299
|
};
|
|
300
|
+
const response = await request(options.url, options);
|
|
301
|
+
const text = await response.text();
|
|
302
|
+
// Parse the response
|
|
303
|
+
try {
|
|
304
|
+
const data = JSON.parse(text.slice(5));
|
|
305
|
+
// Return the data in the expected format
|
|
306
|
+
return {
|
|
307
|
+
data: {
|
|
308
|
+
default: {
|
|
309
|
+
rankedList: data.default?.rankedList || []
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch (parseError) {
|
|
315
|
+
if (parseError instanceof Error) {
|
|
316
|
+
return { error: new ParseError(`Failed to parse related topics response: ${parseError.message}`) };
|
|
317
|
+
}
|
|
318
|
+
return { error: new ParseError('Failed to parse related topics response') };
|
|
319
|
+
}
|
|
249
320
|
}
|
|
250
321
|
catch (error) {
|
|
251
322
|
if (error instanceof Error) {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare const
|
|
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 };
|
|
9
15
|
declare const _default: {
|
|
10
|
-
dailyTrends: ({ geo, lang }: import("./types").DailyTrendingTopicsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
|
|
11
|
-
realTimeTrends: ({ geo, trendingHours }: import("./types").RealTimeTrendsOptions) => Promise<import("./types").GoogleTrendsResponse<import("./types").DailyTrendingTopics>>;
|
|
12
|
-
autocomplete: (keyword: string, hl?: string) => Promise<import("./types").GoogleTrendsResponse<string[]>>;
|
|
13
|
-
explore: ({ keyword, geo, time, category, property, hl, }: import("./types").ExploreOptions) => Promise<import("./types").ExploreResponse
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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>>;
|
|
18
28
|
};
|
|
19
29
|
export default _default;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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);
|
|
@@ -8,6 +8,7 @@ export const interestByRegion = api.interestByRegion.bind(api);
|
|
|
8
8
|
export const relatedTopics = api.relatedTopics.bind(api);
|
|
9
9
|
export const relatedQueries = api.relatedQueries.bind(api);
|
|
10
10
|
export const relatedData = api.relatedData.bind(api);
|
|
11
|
+
export { GoogleTrendsApi };
|
|
11
12
|
// Default export for CommonJS compatibility
|
|
12
13
|
export default {
|
|
13
14
|
dailyTrends,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
package/package.json
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shaivpidadi/trends-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
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/
|
|
7
|
+
"types": "./dist/cjs/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import":
|
|
11
|
-
|
|
12
|
-
|
|
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
|
},
|
|
@@ -36,7 +42,7 @@
|
|
|
36
42
|
"devDependencies": {
|
|
37
43
|
"@types/jest": "^29.5.0",
|
|
38
44
|
"@types/node": "^20.0.0",
|
|
39
|
-
"jest": "^29.
|
|
45
|
+
"jest": "^29.7.0",
|
|
40
46
|
"ts-jest": "^29.1.0",
|
|
41
47
|
"typescript": "^5.0.0"
|
|
42
48
|
}
|