Javascript to get the OPML of all YouTube subscriptions
Table of Contents
Getting OPML for all YouTube subscriptions
I just followed the directions here: https://github.com/jeb5/YouTube-Subscriptions-RSS#bookmarklet
It scrapes the page and so expects a certain format, so it is brittle and might break; but it only needed to work for me once, and it did great! After pasting the thing I got a “download?” window that even gave me a count and a progress report.
The Code
(async () => {
const dialog = document.createElement("dialog");
const label = document.createElement("label");
const progress = document.createElement("progress");
dialog.style.cssText = "display: flex; flex-direction: column; gap: 15px; padding: 20px;";
dialog.appendChild(label);
dialog.appendChild(progress);
document.querySelector("ytd-app").appendChild(dialog);
dialog.showModal();
label.innerText = "Loading subscriptions...";
const content = document.getElementById("content");
let contentH;
do {
contentH = content.offsetHeight;
window.scrollBy(0, 100000);
await new Promise((r) => setTimeout(r, 500));
} while (content.querySelector("#spinnerContainer.active") != null || content.offsetHeight > contentH);
try {
const channelElements = [...content.querySelectorAll("ytd-browse:not([hidden]) #main-link.channel-link")];
progress.max = channelElements.length;
progress.value = 0;
const channels = [];
for (e of channelElements) {
label.innerText = `Fetching URLS... (${progress.value}/${progress.max})`;
try {
const channelName = e.querySelector("yt-formatted-string.ytd-channel-name").innerText;
const channelReq = await fetch(e.href);
if (!channelReq.ok) {
console.error(`Couldn't fetch channel page for ${channelName}`);
continue;
}
const channelPageDoc = new DOMParser().parseFromString(await channelReq.text(), "text/html");
const links = channelPageDoc.querySelectorAll("body > link[rel=alternate], body > link[rel=canonical]");
const channelIdMatch = [...links].map((e) => e.href.match("/channel/([a-zA-Z0-9_-]+?)$")).find((e) => e != null);
if (channelIdMatch == null) {
console.error(`Couldn't find channel id for ${channelName}`);
continue;
}
channels.push([`https://www.youtube.com/feeds/videos.xml?channel_id=${channelIdMatch[1]}`, channelName, e.href]);
} finally {
progress.value++;
progress.replaceWith(progress);
}
}
if (channelElements.length == 0) alert("Couldn't find any subscriptions");
const missedChannels = channelElements.length - channels.length;
if (missedChannels > 0)
alert(`${missedChannels} channel${missedChannels > 1 ? "s" : ""} couldn't be fetched. Check the console for more info.`);
const escapeXML = (str) =>
str.replace(/[<>&'"]/g, (c) => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }[c]));
if (channels.length > 0) {
console.log(channels.map(([feed]) => feed).join("\n"));
let opmlText = `<?xml version="1.0" encoding="UTF-8"?>\n<opml version="1.0">\n\t<head>\n\t\t<title>YouTube Subscriptions as RSS</title>\n\t</head>\n\t<body>\n\t\t<outline text="YouTube Subscriptions">${channels
.map(
([feed, channelName, channelUrl]) =>
`\n\t\t\t<outline type="rss" text="${escapeXML(channelName)}" title="${escapeXML(
channelName
)}" xmlUrl="${feed}" htmlUrl="${channelUrl}"/>`
)
.join("")}\n\t\t</outline>\n\t</body>\n</opml>`;
const url = window.URL.createObjectURL(new Blob([opmlText], { type: "text/plain" }));
const anchorTag = document.createElement("a");
anchorTag.setAttribute("download", "youtube_subs.opml");
anchorTag.setAttribute("href", url);
anchorTag.dataset.downloadurl = `text/plain:youtube_subs.opml:${url}`;
anchorTag.click();
}
} catch (e) {
console.error(e);
alert("Something went wrong. Check the console for more info.");
} finally {
dialog.close();
dialog.remove();
}
})();