Skip to content

Instantly share code, notes, and snippets.

@kael
Last active November 25, 2021 07:05
Show Gist options
  • Save kael/3f9acb87f57e2db8f00082ccd33a5748 to your computer and use it in GitHub Desktop.
Save kael/3f9acb87f57e2db8f00082ccd33a5748 to your computer and use it in GitHub Desktop.
// ==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