UNPKG

googlethis-augmented

Version:

A fork of googlethis to get specific data for different needs.

166 lines (135 loc) 4.77 kB
'use strict'; const Constants = require('../../utils/constants'); class OrganicResult { /** @type {string} */ title; /** @type {string} */ description; /** @type {string} */ url; /** @type {boolean} */ is_sponsored; /** @type {{ high_res: string; low_res: string; }} */ favicons; constructor(data) { this.title = data.title; this.description = data.description; this.url = data.url; this.is_sponsored = data.is_sponsored; this.favicons = data.favicons; } } class OrganicResults { /** * @returns {{ * title: string; * description: string; * url: string; * is_sponsored: boolean; * favicons: { * high_res: string; * low_res: string; * } * }[]} */ static parse($, parse_ads = false, is_mobile = true) { // Stores advert indexes so we can identify them later const ad_indexes = []; const titles = $(Constants.SELECTORS.TITLE) .map((_i, el) => { const is_ad = el.parent.attribs.style == '-webkit-line-clamp:2' || (!is_mobile && el.parent.attribs.class.startsWith('vdQmEd')); // Ignore ad titles if parse_ads is false if (!parse_ads && is_ad) return null; return is_mobile ? $(el).text().trim() : $(el).find('h3').text().trim() || $(el).find('a > div > span').first().text().trim(); }).get(); const subtitles = $(Constants.SELECTORS.SUBTITLE) .map((_i, el) => { const is_ad = el.parent.attribs.class == 'w1C3Le' || (!is_mobile && !Object.keys(el.parent.attribs).length); // Ignore ad descriptions if parse_ads is false if (!parse_ads && is_ad) { return null; } else if (is_ad) { ad_indexes.push(_i); } const children = $(el).children; if (!children.length || children.length === 0) { return ''; } return children[children.length - 1].text().trim(); }).get(); const descriptions = $(Constants.SELECTORS.DESCRIPTION) .map((_i, el) => { const is_ad = el.parent.attribs.class == 'w1C3Le' || (!is_mobile && !Object.keys(el.parent.attribs).length); // Ignore ad descriptions if parse_ads is false if (!parse_ads && is_ad) { return null; } else if (is_ad) { ad_indexes.push(_i); } return $(el).text().trim(); }).get(); const urls = $(is_mobile ? Constants.SELECTORS.URL : `${Constants.SELECTORS.TITLE} > a`) .map((_i, el) => { const is_ad = el.parent?.parent?.attribs?.class?.startsWith('vdQmEd'); /** * Since the selector for URLs is the same as the one for titles on desktop, * we need to check if the element is an ad. If we're parsing the mobile page, * then ads are simply stripped out of the results. */ if (!is_mobile && !parse_ads && is_ad) { return null; } return $(el).attr('href'); }).get(); // Refine results if (titles.length < urls.length && titles.length < descriptions.length) { urls.shift(); } if (urls.length > titles.length) { urls.shift(); } const is_innacurate_data = descriptions.length < urls.slice(1).length; urls.forEach((item, index) => { // Why YouTube? Because video results usually don't have a description. if (item.includes('m.youtube.com') && is_innacurate_data) { urls.splice(index, 1); titles.splice(index, 1); index--; } }); const results = []; for (let i = 0; i < titles.length; i++) { const title = titles[i]; const subtitle = subtitles[i]; const description = descriptions[i]; // let url = urls[i]; // Some results have a different URL format (AMP and ad results). // if (url?.startsWith('/aclk') || url?.startsWith('/amp/s')) { // url = `${Constants.URLS.W_GOOGLE}${url.substring(1)}`; // } // const high_res_favicon = `${Constants.URLS.FAVICONKIT}/${new URL(url || Constants.URLS.W_GOOGLE).hostname}/192`; // const low_res_favicon = `${Constants.URLS.W_GOOGLE}s2/favicons?sz=64&domain_url=${new URL(url || Constants.URLS.W_GOOGLE).hostname}`; if (titles[i] && descriptions[i] && urls[i]) { results.push(new OrganicResult({ title, subtitle, description, // url, // is_sponsored: ad_indexes.includes(i), // favicons: { // high_res: high_res_favicon, // low_res: low_res_favicon // } })); } } return results; } } module.exports = OrganicResults;