Last active
May 6, 2023 04:48
-
-
Save Explosion-Scratch/2e658fb8903cff4bbbcd32a718e5cef1 to your computer and use it in GitHub Desktop.
Get metadata from a URL and parse it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Gets the metadata from a URL and organizes it into an object | |
* @param {String} url The url to get metadata of. | |
* @returns {Promise.<object>} A promise that resolves into an object with all the meta and link tags of the page. | |
*/ | |
async function meta(url) { | |
//Parse HTML as a document element | |
var parser = new DOMParser(); | |
var html = window.html = parser.parseFromString(await fetch(`https://cors.explosionscratc.repl.co/${url.split("//")[1]}`).then(res => res.text()), 'text/html'); | |
var base = document.createElement("base"); | |
// Prevent relative links linking to the current domain | |
// https://stackoverflow.com/a/56025841/14197829 | |
base.href = new URL(url).origin | |
html.head.appendChild(base) | |
//Create objects for meta tags that are in the form "og:url", "twitter:image_src" etc | |
var out = {}; | |
if (html.querySelector("title")) { | |
out.title = html.querySelector("title").innerText; | |
} | |
[...html.querySelectorAll("meta[property], meta[name]")].filter(i => /^[^:]+:[^:]+/.test(i.getAttribute("property") || i.getAttribute("name"))).map(i => { | |
var m = (i.getAttribute("property") || i.getAttribute("name")).match(/^([^:]+):(.+)/); | |
out[m[1]] = out[m[1]] || {}; | |
out[m[1]][m[2]] = i.getAttribute("content"); | |
}); | |
return { | |
//Other meta tags and link tags | |
...Object.fromEntries([ | |
...[...html.querySelectorAll("link")].map((i) => [i.rel, i.href]), | |
...[...html.querySelectorAll("meta[name], meta[value]")].map((i) => [ | |
i.name, | |
i.getAttribute("content") || i.getAttribute("value"), | |
]), | |
]), | |
...out, | |
}; | |
} | |
/** | |
* Parses the metadata returned by the function above and returns it in a form that's easier to use. | |
* @param {Object} m The metadata returned by the meta(url) async function | |
* @returns {Object} An object with the image, title, image alt, theme color and icon of the page. | |
*/ | |
function parseMeta(m) { | |
return { | |
image: m ?.og ?.image || m["twitter:image:src"] || m.image, | |
title: m.title || m ?.twitter ?.title || m ?.og ?.title || m ?.og ?.site_name, | |
description: m.description || m ?.og ?.description || m ?.twitter ?.description, | |
image_alt: m ?.og ?.["image:alt"], | |
color: m["theme-color"], | |
icon: (m.icon || m.favicon || m["alternate icon"] || m["shortcut icon"] || m["alternate-icon"] || m["shortcut icon"] || m["fluid-icon"])?.replace(window.location) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import {onMount} from "svelte"; | |
export let link = ""; | |
let m, title, description, img, img_el; | |
onMount(async () => { | |
m = await meta(link); | |
m = parseMeta(m); | |
title = m.title; | |
description = m.description; | |
img = m.image; | |
}) | |
function parseMeta(m) { | |
return { | |
image: m ?.og ?.image || m["twitter:image:src"] || m.image, | |
title: m.title || m ?.twitter ?.title || m ?.og ?.title || m ?.og ?.site_name, | |
description: m.description || m ?.og ?.description || m ?.twitter ?.description, | |
image_alt: m ?.og ?.["image:alt"], | |
color: m["theme-color"], | |
icon: (m.icon || m.favicon || m["alternate icon"] || m["shortcut icon"] || m["alternate-icon"] || m["shortcut icon"] || m["fluid-icon"])?.replace(window.location) | |
} | |
} | |
async function meta(url) { | |
//Parse HTML as a document element | |
var parser = new DOMParser(); | |
var html = window.html = parser.parseFromString(await fetch(`https://cors.explosionscratc.repl.co/${url.split("//")[1]}`).then(res => res.text()), 'text/html'); | |
var base = document.createElement("base"); | |
// Prevent relative links linking to the current domain | |
// https://stackoverflow.com/a/56025841/14197829 | |
base.href = new URL(url).origin | |
html.head.appendChild(base) | |
//Create objects for meta tags that are in the form "og:url", "twitter:image_src" etc | |
var out = {}; | |
if (html.querySelector("title")) { | |
out.title = html.querySelector("title").innerText; | |
} | |
[...html.querySelectorAll("meta[property], meta[name]")].filter(i => /^[^:]+:[^:]+/.test(i.getAttribute("property") || i.getAttribute("name"))).map(i => { | |
var m = (i.getAttribute("property") || i.getAttribute("name")).match(/^([^:]+):(.+)/); | |
out[m[1]] = out[m[1]] || {}; | |
out[m[1]][m[2]] = i.getAttribute("content"); | |
}); | |
return { | |
//Other meta tags and link tags | |
...Object.fromEntries([ | |
...[...html.querySelectorAll("link")].map((i) => [i.rel, i.href]), | |
...[...html.querySelectorAll("meta[name], meta[value]")].map((i) => [ | |
i.name, | |
i.getAttribute("content") || i.getAttribute("value"), | |
]), | |
]), | |
...out, | |
}; | |
} | |
function slice(text, words){ | |
return text.split(" ").slice(0, words).length === words ? text.split(" ").slice(0, words).join(" ") + "..." : text | |
} | |
function handleError(){ | |
if (img_el.src.startsWith("https://cors.explosionscratc.repl.co")){ | |
console.log("Already cors"); | |
img = null; | |
} else { | |
console.log(img_el) | |
return img = `https://cors.explosionscratc.repl.co/${img_el.src.split("//")[1]}` | |
} | |
} | |
</script> | |
<div class="link_preview"> | |
{#if title && link} | |
{#if img} | |
<div class='img'> | |
<img src={img} on:error={handleError} bind:this={img_el}/> | |
</div> | |
{/if} | |
<div class="right"> | |
<h3> | |
<img src={`https://www.google.com/s2/favicons?domain=${new URL(link).hostname}`}/> {slice(title, 6)} | |
</h3> | |
<div class="description"> | |
{description ? slice(description, 15) : link} | |
</div><br> | |
<a href={link} class="visit"> | |
Visit | |
</a> | |
</div> | |
{:else} | |
<div class="loading"> | |
Loading... | |
</div> | |
{/if} | |
</div> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
.link_preview { | |
margin: 15px auto; | |
display: flex; | |
width: 100%; | |
border-radius: 5px; | |
overflow: hidden; | |
box-shadow: 3px 2px 10px -5px #0004; | |
} | |
.img { | |
display: block; | |
flex: 1; | |
} | |
.img img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.right { | |
flex: 2; | |
padding: 10px; | |
color: #333; | |
padding-bottom: 20px; | |
} | |
.right .description { | |
color: #555; | |
} | |
.right .visit { | |
width: fit-content; | |
display: block; | |
text-decoration: none; | |
padding: 6px 15px; | |
border-radius: 5px; | |
background: transparent; | |
border: 2px solid lightseagreen; | |
color: #066; | |
} | |
.right .visit:hover { | |
box-shadow: 0 0 0 2px #0bb4; | |
} | |
@media (max-width: 400px){ | |
.link_preview { | |
flex-direction: column; | |
box-shadow: 1px 2px 10px -5px #0009; | |
} | |
.link_preview .right .visit { | |
width: 100%; | |
padding: 10px; | |
border-radius: 6px; | |
text-align: center; | |
background: lightseagreen; | |
color: white; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment