Last active
November 25, 2021 07:05
-
-
Save kael/3f9acb87f57e2db8f00082ccd33a5748 to your computer and use it in GitHub Desktop.
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
// ==UserScript== | |
// @name YouTube Metadata Discrepancy Preview | |
// @namespace http://github.com/kael#GM | |
// @version 0.1 | |
// @description Compare client-side and remote YouTube pages metadata | |
// @author http://github.com/kael | |
// | |
// @include https://*.youtube.com/* | |
// ==/UserScript== | |
const ANCHOR_SELECTOR = "#microformat"; | |
const TABLE_SELECTOR = "#gm-youtube-table"; | |
const STYLE_NAVBAR = | |
"font-size:medium;background-color: aliceblue; color: black; display: flex; position: sticky; top: 0; z-index: 2020;"; | |
const STYLE_TABLE = | |
"font-family: arial, sans-serif; border-collapse: collapse; width: 100%;"; | |
const STYLE_TRB = | |
" border: 1px solid #dddddd;text-align: center; padding: 8px;"; | |
const TABLE_HEADERS = ["Location", "YouTubeID", "Local", "Remote"]; | |
const navBarTable = nav => { | |
const table = document.createElement("table"); | |
table.setAttribute("id", "gm-youtube-table"); | |
table.setAttribute("style", STYLE_TABLE); | |
const tr = document.createElement("tr"); | |
tr.setAttribute("style", STYLE_TRB); | |
for (let header of TABLE_HEADERS) { | |
const th = document.createElement("th"); | |
th.setAttribute( | |
"style", | |
"border: 1px solid #dddddd;text-align: center; padding: 8px;" | |
); | |
th.textContent = header; | |
tr.appendChild(th); | |
} | |
table.appendChild(tr); | |
nav.appendChild(table); | |
}; | |
const createNavBar = () => { | |
const nav = document.createElement("nav"); | |
nav.setAttribute("style", STYLE_NAVBAR); | |
const mainBar = document.querySelector(ANCHOR_SELECTOR); | |
mainBar.after(nav); | |
navBarTable(nav); | |
}; | |
const metadataTable = ({ title, canonical }) => { | |
const table = document.createElement("table"); | |
table.setAttribute( | |
"style", | |
"font-family: arial, sans-serif; border-collapse: collapse; width: 100%;display:flex;flex-direction:column;" | |
); | |
const trHeader = document.createElement("tr"); | |
trHeader.setAttribute( | |
"style", | |
"display: flex;justify-content: space-between;" | |
); | |
const titleHeader = document.createElement("th"); | |
titleHeader.setAttribute( | |
"style", | |
"border: 1px solid #dddddd;background-color:lavender;flex-basis: 100%;" | |
); | |
titleHeader.textContent = "Title"; | |
const canonicalURLHeader = document.createElement("th"); | |
canonicalURLHeader.textContent = "Canonical URL"; | |
canonicalURLHeader.setAttribute( | |
"style", | |
"border: 1px solid #dddddd;background-color:lavender;flex-basis: 100%;" | |
); | |
trHeader.appendChild(titleHeader); | |
trHeader.appendChild(canonicalURLHeader); | |
table.appendChild(trHeader); | |
const trLocalHeader = document.createElement("tr"); | |
trLocalHeader.setAttribute( | |
"style", | |
"display: flex;justify-content: space-between;align-items: center;" | |
); | |
const tdLocalTitle = document.createElement("td"); | |
tdLocalTitle.setAttribute("style", "flex-basis: 100%;"); | |
tdLocalTitle.textContent = title; | |
const tdLocalCanonicalURL = document.createElement("td"); | |
tdLocalCanonicalURL.setAttribute("style", "flex-basis: 100%;"); | |
tdLocalCanonicalURL.textContent = canonical; | |
trLocalHeader.appendChild(tdLocalTitle); | |
trLocalHeader.appendChild(tdLocalCanonicalURL); | |
table.appendChild(trLocalHeader); | |
return table; | |
}; | |
const createTableRow = ({ uri, local, remote }) => { | |
const tr = document.createElement("tr"); | |
tr.setAttribute("style", STYLE_TRB); | |
const td = document.createElement("td"); | |
td.setAttribute("style", "border: 1px solid #dddddd;padding:2px;"); | |
td.textContent = uri; | |
tr.appendChild(td); | |
const tdId = document.createElement("td"); | |
tdId.setAttribute("style", "border: 1px solid #dddddd;padding:2px;"); | |
tdId.textContent = extractYouTubeId(uri); | |
tr.appendChild(tdId); | |
const tdLocal = document.createElement("td"); | |
tdLocal.setAttribute("style", "border: 1px solid #dddddd;flex-basis: 100%;"); | |
tdLocal.appendChild(metadataTable(local)); | |
tr.appendChild(tdLocal); | |
const tdRemote = document.createElement("td"); | |
tdRemote.setAttribute("style", "border: 1px solid #dddddd;flex-basis: 100%;"); | |
tdRemote.appendChild(metadataTable(remote)); | |
tr.appendChild(tdRemote); | |
return tr; | |
}; | |
// https://stackoverflow.com/a/27728417/17228578 | |
const rx = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/; | |
const extractYouTubeId = url => url.match(rx)[1]; | |
const parseHeaderMetadata = page => ({ | |
title: page.querySelector("title").textContent, | |
canonical: page.querySelector("link[rel='canonical']").href | |
}); | |
const fetchAndParse = async url => { | |
const response = await fetch(url); | |
const text = await response.text(); | |
const parser = new DOMParser(); | |
const html = parser.parseFromString(text, "text/html"); | |
return { | |
url, | |
...parseHeaderMetadata(html) | |
}; | |
}; | |
window.onload = async function() { | |
console.log("YouTube Canonical URL demo loaded"); | |
createNavBar(); | |
let currentLocation = window.location.href; | |
const local = parseHeaderMetadata(document); | |
const remote = await fetchAndParse(currentLocation); | |
const tr = createTableRow({ | |
uri: currentLocation, | |
local, | |
remote | |
}); | |
document.querySelector(TABLE_SELECTOR).appendChild(tr); | |
const head = document.querySelector("head"); | |
// https://stackoverflow.com/a/46428962/17228578 | |
const observer = new MutationObserver(function(mutations) { | |
mutations.forEach(async mutation => { | |
// Detect if document location has changed | |
if (currentLocation != document.location.href) { | |
console.log("Observed url changed", document.location.href); | |
if (mutation.type === "childList") { | |
console.log("A child node has been added or removed.", mutation); | |
currentLocation = document.location.href; | |
const remote = await fetchAndParse(currentLocation); | |
const local = parseHeaderMetadata(document); | |
const tr = createTableRow({ | |
uri: currentLocation, | |
local, | |
remote | |
}); | |
document.querySelector(TABLE_SELECTOR).appendChild(tr); | |
} else if (mutation.type === "attributes") { | |
console.log( | |
"The " + mutation.attributeName + " attribute was modified." | |
); | |
} | |
} | |
}); | |
}); | |
const config = { | |
attributes: true, | |
childList: true, | |
subtree: true | |
}; | |
observer.observe(head, config); | |
}; | |
window.addEventListener("popstate", function() { | |
console.log("onpopstate"); | |
}); | |
window.addEventListener("hashchange", function() { | |
console.log("onhashchange", location.hash); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment