Last active
December 29, 2022 15:27
-
-
Save oyale/3b2246c2ddd255e3d1d17c86212d1069 to your computer and use it in GitHub Desktop.
You can get an OPML file with your YouTube subscriptions by running this script on the console while you are logged in at YouTube's main page. The downloaded file can be imported at any invidious instance.
This file contains 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
function downloadOPML(opml) { | |
// Create a new anchor element | |
const anchor = document.createElement('a'); | |
// Set the anchor's download attribute | |
anchor.download = 'subscriptions.opml'; | |
// Set the anchor's href attribute to the OPML data | |
anchor.href = `data:text/xml;charset=utf-8,${encodeURIComponent(opml)}`; | |
// Click the anchor element to trigger the download | |
anchor.click(); | |
console.log("Downloaded OPML file"); | |
} | |
async function getChannelId(url) { | |
let channelId = url.split('/').pop(); | |
if (!url.startsWith('https://www.youtube.com/channel/')) { | |
// Fetch the source code of the URL | |
const response = await fetch(url); | |
const html = await response.text(); | |
// Parse the source code into a DOM tree | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(html, 'text/html'); | |
// Find the <link> element with the correct attributes | |
const linkElement = doc.querySelector("link[rel='canonical'][href^='https://www.youtube.com/channel/']"); | |
// console.log (linkElement) | |
// Extract the channel ID from the href attribute | |
const href = linkElement.getAttribute('href'); | |
channelId = href.split('/').pop(); | |
console.info(`Found channel ID ${channelId} for URL ${url}`); | |
} | |
return channelId; | |
} | |
var start = async function () { | |
// Firefox requires that the slide out is visible to get the list of | |
// subscriptions | |
if (getShowMoreElement() === undefined) { | |
// click on hamburger menu to show slide out | |
document.querySelector("#guide-icon").click(); | |
while (getShowMoreElement() === undefined) { | |
await new Promise(r => setTimeout(r, 500)); | |
} | |
} | |
getShowMoreElement().click(); | |
let subscriptions = Array.from(document.querySelectorAll( | |
"a#endpoint")).filter( | |
ytTextElement => ytTextElement.innerHTML.includes( | |
"style-scope ytd-guide-entry-renderer no-transition")).map(elem => elem.title + "\t" + | |
elem.href); | |
subscriptions.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })); | |
function getShowMoreElement() { | |
let subscriptionsPanel = document.querySelectorAll("ytd-guide-section-renderer")[1]; | |
if (subscriptionsPanel === undefined) return undefined; // it's still loading | |
let subscriptionsPanelInner = subscriptionsPanel.querySelector("#items > ytd-guide-collapsible-entry-renderer"); | |
// it's loaded and there are enough subscriptions to show the "Show | |
// more" button | |
if (subscriptionsPanelInner !== null) { | |
return subscriptionsPanel.querySelector("#items > ytd-guide-collapsible-entry-renderer") | |
.querySelector("#endpoint"); | |
} else { | |
// it's loaded, but there are just a few subscriptions it doesn't | |
// really matter what we click on | |
return document.createElement("foo"); | |
} | |
} | |
} | |
async function main() { | |
start(); | |
// Get all channel elements | |
let subscriptions = Array.from(document.querySelectorAll( | |
"a#endpoint")).filter( | |
ytTextElement => ytTextElement.innerHTML.includes( | |
"style-scope ytd-guide-entry-renderer no-transition")).map( | |
elem => [elem['title'] = elem.title, elem['url'] = elem.href] | |
); | |
console.log("Channels obtained: "+subscriptions.length) | |
const channelData = []; | |
// Iterate over the channels | |
for (const channel of subscriptions) { | |
// Extract the title and URL of the channel | |
const title = channel[0]; | |
let url = channel[1]; | |
// Convert the URL to the correct format if necessary | |
url = await getChannelId(url); | |
url = `https://www.youtube.com/feeds/videos.xml?channel_id=${url}`; | |
channelData.push({ title, url }); | |
} | |
console.table(channelData) | |
// Generate the OPML document | |
const opml = ` | |
<opml version="1.1"> | |
<body> | |
<outline text="YouTube Subscriptions" title="YouTube Subscriptions"> | |
${channelData.map(({ title, url }) => ` | |
<outline text="${title}" title="${title}" type="rss" xmlUrl="${url}" /> | |
`).join('')} | |
</outline> | |
</body> | |
</opml> | |
`; | |
downloadOPML(opml); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inspired by alexyorke/youtube-subscriptions-exporter