From dc651a7b1e90d825f422811967e6f6f11be1f913 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 29 Jul 2023 11:38:12 +0100 Subject: [PATCH] Adds card for fetching displaying social meta tags --- api/social-tags.js | 68 +++++++++++++++++++++++++++ src/components/Results/SocialTags.tsx | 44 +++++++++++++++++ src/pages/Results.tsx | 10 ++++ 3 files changed, 122 insertions(+) create mode 100644 api/social-tags.js create mode 100644 src/components/Results/SocialTags.tsx diff --git a/api/social-tags.js b/api/social-tags.js new file mode 100644 index 0000000..9b0af39 --- /dev/null +++ b/api/social-tags.js @@ -0,0 +1,68 @@ +const axios = require('axios'); +const cheerio = require('cheerio'); + +exports.handler = async (event, context) => { + let url = event.queryStringParameters.url; + + // Check if url includes protocol + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'http://' + url; + } + + try { + const response = await axios.get(url); + const html = response.data; + const $ = cheerio.load(html); + + const metadata = { + // Basic meta tags + title: $('head title').text(), + description: $('meta[name="description"]').attr('content'), + keywords: $('meta[name="keywords"]').attr('content'), + canonicalUrl: $('link[rel="canonical"]').attr('href'), + + // OpenGraph Protocol + ogTitle: $('meta[property="og:title"]').attr('content'), + ogType: $('meta[property="og:type"]').attr('content'), + ogImage: $('meta[property="og:image"]').attr('content'), + ogUrl: $('meta[property="og:url"]').attr('content'), + ogDescription: $('meta[property="og:description"]').attr('content'), + ogSiteName: $('meta[property="og:site_name"]').attr('content'), + + // Twitter Cards + twitterCard: $('meta[name="twitter:card"]').attr('content'), + twitterSite: $('meta[name="twitter:site"]').attr('content'), + twitterCreator: $('meta[name="twitter:creator"]').attr('content'), + twitterTitle: $('meta[name="twitter:title"]').attr('content'), + twitterDescription: $('meta[name="twitter:description"]').attr('content'), + twitterImage: $('meta[name="twitter:image"]').attr('content'), + + // Misc + themeColor: $('meta[name="theme-color"]').attr('content'), + robots: $('meta[name="robots"]').attr('content'), + googlebot: $('meta[name="googlebot"]').attr('content'), + generator: $('meta[name="generator"]').attr('content'), + viewport: $('meta[name="viewport"]').attr('content'), + author: $('meta[name="author"]').attr('content'), + publisher: $('link[rel="publisher"]').attr('href'), + favicon: $('link[rel="icon"]').attr('href') + }; + + if (Object.keys(metadata).length === 0) { + return { + statusCode: 200, + body: JSON.stringify({ skipped: 'No metadata found' }), + }; + } + + return { + statusCode: 200, + body: JSON.stringify(metadata), + }; + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify({ error: 'Failed fetching data' }), + }; + } +}; diff --git a/src/components/Results/SocialTags.tsx b/src/components/Results/SocialTags.tsx new file mode 100644 index 0000000..d22dc0c --- /dev/null +++ b/src/components/Results/SocialTags.tsx @@ -0,0 +1,44 @@ + +import { Card } from 'components/Form/Card'; +import Row from 'components/Form/Row'; +import colors from 'styles/colors'; + +const cardStyles = ` + .banner-image img { + width: 100%; + border-radius: 4px; + margin: 0.5rem 0; + } + .color-field { + border-radius: 4px; + &:hover { + color: ${colors.primary}; + } + } +`; + +const SocialTagsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => { + const tags = props.data; + return ( + + { tags.title && } + { tags.description && } + { tags.keywords && } + { tags.canonicalUrl && } + { tags.themeColor && + Theme Color + {tags.themeColor} + } + { tags.twitterSite && + Twitter Site + {tags.twitterSite} + } + { tags.author && } + { tags.publisher && } + { tags.generator && } + { tags.ogImage &&
Banner
} +
+ ); +} + +export default SocialTagsCard; diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index 0886a2c..4ba37c2 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -46,6 +46,7 @@ import DnsServerCard from 'components/Results/DnsServer'; import TechStackCard from 'components/Results/TechStack'; import SecurityTxtCard from 'components/Results/SecurityTxt'; import ContentLinksCard from 'components/Results/ContentLinks'; +import SocialTagsCard from 'components/Results/SocialTags'; import keys from 'utils/get-keys'; import { determineAddressType, AddressType } from 'utils/address-type-checker'; @@ -341,6 +342,14 @@ const Results = (): JSX.Element => { fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)), }); + // Get social media previews, from a sites social meta tags + const [socialTagResults, updateSocialTagResults] = useMotherHook({ + jobId: 'social-tags', + updateLoadingJobs, + addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, + fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)), + }); + // Get site features from BuiltWith const [siteFeaturesResults, updateSiteFeaturesResults] = useMotherHook({ jobId: 'features', @@ -435,6 +444,7 @@ const Results = (): JSX.Element => { { id: 'hsts', title: 'HSTS Check', result: hstsResults, Component: HstsCard, refresh: updateHstsResults }, { id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults }, { id: 'dns-server', title: 'DNS Server', result: dnsServerResults, Component: DnsServerCard, refresh: updateDnsServerResults }, + { id: 'social-tags', title: 'Social Tags', result: socialTagResults, Component: SocialTagsCard, refresh: updateSocialTagResults }, { id: 'linked-pages', title: 'Linked Pages', result: linkedPagesResults, Component: ContentLinksCard, refresh: updateLinkedPagesResults }, { id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults }, { id: 'sitemap', title: 'Pages', result: sitemapResults, Component: SitemapCard, refresh: updateSitemapResults },