UNPKG

@shaivpidadi/trends-js

Version:
387 lines (386 loc) 18.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GoogleTrendsApi = void 0; const enums_js_1 = require("../types/enums.js"); const request_js_1 = require("./request.js"); const format_js_1 = require("./format.js"); const constants_js_1 = require("../constants.js"); const GoogleTrendsError_js_1 = require("../errors/GoogleTrendsError.js"); class GoogleTrendsApi { /** * Get autocomplete suggestions for a keyword * @param keyword - The keyword to get suggestions for * @param hl - Language code (default: 'en-US') * @returns Promise with array of suggestion strings */ async autocomplete(keyword, hl = 'en-US') { if (!keyword) { return { data: [] }; } const options = { ...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.autocomplete], qs: { hl, tz: '240', }, }; try { const response = await (0, request_js_1.request)(`${options.url}/${encodeURIComponent(keyword)}`, options); const text = await response.text(); // Remove the first 5 characters (JSONP wrapper) and parse const data = JSON.parse(text.slice(5)); return { data: data.default.topics.map((topic) => topic.title) }; } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } /** * Get daily trending topics * @param options - Options for daily trends request * @returns Promise with trending topics data */ async dailyTrends({ geo = 'US', lang = 'en' }) { const defaultOptions = constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.dailyTrends]; const options = { ...defaultOptions, body: new URLSearchParams({ 'f.req': `[[["i0OFE","[null,null,\\"${geo}\\",0,\\"${lang}\\",24,1]",null,"generic"]]]`, }).toString(), contentType: 'form' }; try { const response = await (0, request_js_1.request)(options.url, options); const text = await response.text(); const trendingTopics = (0, format_js_1.extractJsonFromResponse)(text); if (!trendingTopics) { return { error: new GoogleTrendsError_js_1.ParseError() }; } return { data: trendingTopics }; } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } /** * Get real-time trending topics * @param options - Options for real-time trends request * @returns Promise with trending topics data */ async realTimeTrends({ geo = 'US', trendingHours = 4 }) { const defaultOptions = constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.dailyTrends]; const options = { ...defaultOptions, body: new URLSearchParams({ 'f.req': `[[["i0OFE","[null,null,\\"${geo}\\",0,\\"en\\",${trendingHours},1]",null,"generic"]]]`, }).toString(), contentType: 'form' }; try { const response = await (0, request_js_1.request)(options.url, options); const text = await response.text(); const trendingTopics = (0, format_js_1.extractJsonFromResponse)(text); if (!trendingTopics) { return { error: new GoogleTrendsError_js_1.ParseError() }; } return { data: trendingTopics }; } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } async explore({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', enableBackoff = false, }) { const options = { ...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.explore], qs: { hl, tz: '240', req: JSON.stringify({ comparisonItem: [ { keyword, geo, time, }, ], category, property, }), }, // contentType: 'form' as const }; try { const response = await (0, request_js_1.request)(options.url, options, enableBackoff); const text = await response.text(); // Check if response is HTML (error page) if (text.includes('<html') || text.includes('<!DOCTYPE')) { return { error: new GoogleTrendsError_js_1.ParseError('Explore request returned HTML instead of JSON') }; } // Try to parse as JSON try { // Remove the first 5 characters (JSONP wrapper) and parse const data = JSON.parse(text.slice(5)); // Extract widgets from the response if (data && Array.isArray(data) && data.length > 0) { const widgets = data[0] || []; return { widgets }; } if (data && typeof data === 'object' && Array.isArray(data.widgets)) { return { widgets: data.widgets }; } return { widgets: [] }; } catch (parseError) { if (parseError instanceof Error) { return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse explore response as JSON: ${parseError.message}`) }; } return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse explore response as JSON') }; } } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(`Explore request failed: ${error.message}`) }; } return { error: new GoogleTrendsError_js_1.UnknownError('Explore request failed') }; } } // 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 }) { const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword; const geoValue = Array.isArray(geo) ? geo[0] : geo; if (!keywordValue || keywordValue.trim() === '') { return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') }; } if (!geoValue || geoValue.trim() === '') { return { error: new GoogleTrendsError_js_1.InvalidRequestError('Geo is required') }; } try { const exploreResponse = await this.explore({ keyword: keywordValue, geo: geoValue, time: `${(0, format_js_1.formatDate)(startTime)} ${(0, format_js_1.formatDate)(endTime)}`, category, hl, enableBackoff }); if ('error' in exploreResponse) { return { error: exploreResponse.error }; } const widget = exploreResponse.widgets.find(w => w.id === 'GEO_MAP'); if (!widget) { return { error: new GoogleTrendsError_js_1.ParseError('No GEO_MAP widget found in explore response') }; } const requestFromWidget = { ...widget.request, resolution }; const options = { ...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.interestByRegion], qs: { hl, tz: timezone.toString(), req: JSON.stringify(requestFromWidget), token: widget.token } }; const response = await (0, request_js_1.request)(options.url, options, enableBackoff); const text = await response.text(); if (text.includes('<!DOCTYPE') || text.includes('<html')) { return { error: new GoogleTrendsError_js_1.ParseError('Interest by region request failed') }; } // Handle JSONP wrapper (usually starts with )]}' or similar) const data = JSON.parse(text.slice(5)); return { data: data.default.geoMapData }; } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(`Interest by region request failed: ${error.message}`) }; } return { error: new GoogleTrendsError_js_1.UnknownError('Interest by region request failed') }; } } async relatedTopics({ keyword, geo = 'US', startTime = new Date('2004-01-01'), endTime = new Date(), category = 0, property = '', hl = 'en-US', enableBackoff = false, }) { try { // Validate keyword if (!keyword || keyword.trim() === '') { return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') }; } const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword; const geoValue = Array.isArray(geo) ? geo[0] : geo; // Step 1: Call explore to get widget data and token const timeValue = `${(0, format_js_1.formatDate)(startTime)} ${(0, format_js_1.formatDate)(endTime)}`; const exploreResponse = await this.explore({ keyword: keywordValue, geo: geoValue, time: timeValue, category, hl, enableBackoff }); if ('error' in exploreResponse) { return { error: exploreResponse.error }; } if (!exploreResponse.widgets || exploreResponse.widgets.length === 0) { 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.') }; } // Step 2: Find the related topics widget or use any available widget const relatedTopicsWidget = exploreResponse.widgets.find(widget => widget.id === 'RELATED_TOPICS' || widget.request?.restriction?.complexKeywordsRestriction?.keyword?.[0]?.value === keyword) || exploreResponse.widgets[0]; // Fallback to first widget if no specific one found if (!relatedTopicsWidget) { return { error: new GoogleTrendsError_js_1.ParseError('No related topics widget found in explore response') }; } const requestFromWidget = { ...relatedTopicsWidget.request }; // Step 3: Call the related topics API with or without token const options = { ...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.relatedTopics], qs: { hl, tz: '240', req: JSON.stringify(requestFromWidget), ...(relatedTopicsWidget.token && { token: relatedTopicsWidget.token }) } }; const response = await (0, request_js_1.request)(options.url, options, enableBackoff); const text = await response.text(); // Parse the response try { const data = JSON.parse(text.slice(5)); // Return the data in the expected format return { data: data.default?.rankedList || [] }; } catch (parseError) { if (parseError instanceof Error) { return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse related topics response: ${parseError.message}`) }; } return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse related topics response') }; } } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } async relatedQueries({ keyword, geo = 'US', startTime = new Date('2004-01-01'), endTime = new Date(), category = 0, property = '', hl = 'en-US', enableBackoff = false, }) { try { // Validate keyword if (!keyword || keyword.trim() === '') { return { error: new GoogleTrendsError_js_1.InvalidRequestError('Keyword is required') }; } const keywordValue = Array.isArray(keyword) ? keyword[0] : keyword; const geoValue = Array.isArray(geo) ? geo[0] : geo; // Step 1: Call explore to get widget data and token const timeValue = `${(0, format_js_1.formatDate)(startTime)} ${(0, format_js_1.formatDate)(endTime)}`; const exploreResponse = await this.explore({ keyword: keywordValue, geo: geoValue, time: timeValue, category, hl, enableBackoff }); if ('error' in exploreResponse) { return { error: exploreResponse.error }; } if (!exploreResponse.widgets || exploreResponse.widgets.length === 0) { 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.') }; } const relatedQueriesWidget = exploreResponse.widgets.find(widget => widget.id === 'RELATED_QUERIES') || null; // Fallback to first widget if no specific one found if (!relatedQueriesWidget) { return { error: new GoogleTrendsError_js_1.ParseError('No related queries widget found in explore response') }; } const requestFromWidget = { ...relatedQueriesWidget.request }; const options = { ...constants_js_1.GOOGLE_TRENDS_MAPPER[enums_js_1.GoogleTrendsEndpoints.relatedQueries], qs: { hl, tz: '240', req: JSON.stringify(requestFromWidget), token: relatedQueriesWidget.token } }; const response = await (0, request_js_1.request)(options.url, options, enableBackoff); const text = await response.text(); // Parse the response try { const data = JSON.parse(text.slice(5)); // Return the data in the expected format return { data: data.default?.rankedList || [] }; } catch (parseError) { if (parseError instanceof Error) { return { error: new GoogleTrendsError_js_1.ParseError(`Failed to parse related queries response: ${parseError.message}`) }; } return { error: new GoogleTrendsError_js_1.ParseError('Failed to parse related queries response') }; } } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } async relatedData({ keyword, geo = 'US', time = 'now 1-d', category = 0, property = '', hl = 'en-US', }) { try { // Validate keyword if (!keyword || keyword.trim() === '') { return { error: new GoogleTrendsError_js_1.ParseError() }; } const autocompleteResult = await this.autocomplete(keyword, hl); if (autocompleteResult.error) { return { error: autocompleteResult.error }; } const suggestions = autocompleteResult.data?.slice(0, 10) || []; const topics = suggestions.map((suggestion, index) => ({ topic: { mid: `/m/${index}`, title: suggestion, type: 'Topic' }, value: 100 - index * 10, formattedValue: (100 - index * 10).toString(), hasData: true, link: `/trends/explore?q=${encodeURIComponent(suggestion)}&date=${time}&geo=${geo}` })); const queries = suggestions.map((suggestion, index) => ({ query: suggestion, value: 100 - index * 10, formattedValue: (100 - index * 10).toString(), hasData: true, link: `/trends/explore?q=${encodeURIComponent(suggestion)}&date=${time}&geo=${geo}` })); return { data: { topics, queries } }; } catch (error) { if (error instanceof Error) { return { error: new GoogleTrendsError_js_1.NetworkError(error.message) }; } return { error: new GoogleTrendsError_js_1.UnknownError() }; } } } exports.GoogleTrendsApi = GoogleTrendsApi; exports.default = new GoogleTrendsApi();