Rework YouTube list again
I'm moving to self-hosting this site and building it from my self-hosted code repo and CI, which won't have access to my FreeTube subscriptions when it builds. To get around this, I've moved to a script that will snapshot my local FreeTube subscriptions to a JSON file. I'll commit this file, and it can be used in the build on CI.
This commit is contained in:
parent
70a11752bf
commit
fffa8cc1da
4 changed files with 2075 additions and 11 deletions
|
|
@ -1,125 +0,0 @@
|
|||
// Finds your FreeTube profiles.db and adds the channel objects divided by profile to global data
|
||||
// Access this anywhere via `freetubeCategories`, which will return an array of profile objects
|
||||
// Note: I refer to 'profiles' as 'categories' since that naming makes more sense given how I'm using them in this automation and on my site. In this documentation, a "category" is the same thing as a FreeTube "Profile."
|
||||
//
|
||||
// Category object structure:
|
||||
// - `name`: string - The name of the category/profile as assigned in FreeTube
|
||||
// - 'subscriptions': array of subscription objects - The channels added to this category. See below for the subscription object structure. Note that channels added to multiple categories are distinct objects, so making a change to a channel object in one category does not affect that same channel's object if it appears in another category.
|
||||
// - `bgColor`: string - The background color chosen for the category in FreeTube as a hexidecimal string with leading `#`
|
||||
// - `textColor`: string - The foreground color chosen for the category in FreeTube as a hexidecimal string with leading `#`
|
||||
// - `_id`: string - A unique ID generated by FreeTube to track the category. This is generally a seemingly random string of alphanumeric characters, but in the case of the "All Channels" category, the `_id` value is `"allChannels"`.
|
||||
// - `$$deleted`: boolean - This property only exists on categories that have been deleted. Deleted categories seem to only persist in profiles.db while the app is still running and are removed when the app is closed.
|
||||
// Note: You may see multiple instances of each category while the app is still running. Make sure to close it before you generate anything from this data.
|
||||
//
|
||||
// Subscription object structure:
|
||||
// - `id`: string - The YouTube channel ID. You can build a URL to this channel by using this format: https://www.youtube.com/channel/{id}
|
||||
// - `name`: string - The channel's name
|
||||
// - `thumbnail`: string - A URI pointing to the channel's thumbnail
|
||||
// - `tags`: string - For any channels assigned to tag categories, this property will contain a string with all of that channel's emoji tags
|
||||
// - `selected`: boolean - Not entirely sure what this is to be perfectly honest.
|
||||
//
|
||||
// This would be more generally useful if I parameterized some things like the tag categories, the config location, and some others, but I won't bother with that for now.
|
||||
// Maybe in a future revision… or maybe someone would like to submit that as a pull request!
|
||||
|
||||
const fs = require('fs');
|
||||
const fsPromises = fs.promises;
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const homeDir = os.homedir();
|
||||
const flatpakConfigPath = path.join(homeDir, '.var', 'app', 'io.freetubeapp.FreeTube', 'config', 'FreeTube');
|
||||
const nonFlatpakConfigPath = path.join(homeDir, '.config', 'FreeTube');
|
||||
const freetubeConfigLocationByOS = {
|
||||
linux: () => fs.existsSync(flatpakConfigPath) ? flatpakConfigPath : nonFlatpakConfigPath,
|
||||
darwin: () => path.join(homeDir, 'Library', 'Application Support', 'FreeTube'),
|
||||
win32: () => path.join(process.env.APPDATA, 'FreeTube'),
|
||||
};
|
||||
|
||||
module.exports = async function getSubscriptions() {
|
||||
const osName = os.platform();
|
||||
|
||||
if (!(osName in freetubeConfigLocationByOS)) {
|
||||
console.warn(`Unsupported OS: ${osName}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const configLocation = freetubeConfigLocationByOS[osName]();
|
||||
const subsLocation = path.join(configLocation, 'profiles.db');
|
||||
|
||||
// Instead of creating a new channel category, tag categories will add an emoji to a channel's `tag` property.
|
||||
// Add your own categories to this object.
|
||||
// The key should be the full name of the category in FreeTube.
|
||||
// The value should be the emoji (or any unicode character, for that matter) that the channels should be prepended with.
|
||||
// Channels in one or more tag category will have a `tags` property containing a string with all of that channel's tags.
|
||||
// If the channel is in multiple categories, each instance of it will receive the `tags` property with the same value.
|
||||
const tagCategories = {
|
||||
'Instant Watch': '👀',
|
||||
Virtuosic: '🤌',
|
||||
Relaxing: '😌',
|
||||
};
|
||||
|
||||
const categories = [];
|
||||
const privateSubscriptionIds = new Set();
|
||||
const subscriptionTags = {};
|
||||
let allChannels;
|
||||
|
||||
try {
|
||||
await fsPromises.access(subsLocation);
|
||||
} catch (error) {
|
||||
console.warn(`Subscriptions file not found at ${subsLocation}`, error);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fsPromises.readFile(subsLocation, { encoding: 'utf8' });
|
||||
const lines = data.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
let category;
|
||||
try {
|
||||
category = JSON.parse(line);
|
||||
} catch (error) {
|
||||
console.error(`Error parsing line: ${line}`, error);
|
||||
}
|
||||
if (category.name === 'Private') {
|
||||
category.subscriptions?.forEach(sub => privateSubscriptionIds.add(sub.id));
|
||||
} else if (category._id === 'allChannels') {
|
||||
allChannels = category.subscriptions || [];
|
||||
} else if (tagCategories.hasOwnProperty(category.name)) {
|
||||
const tagEmoji = tagCategories[category.name];
|
||||
category.subscriptions?.forEach(channel => subscriptionTags[channel.id] = (subscriptionTags[channel.id] || '') + tagEmoji);
|
||||
} else if (!category.$$deleted) {
|
||||
categories.push(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file: ${subsLocation}`, error);
|
||||
return [];
|
||||
}
|
||||
|
||||
const channelsInCategory = new Set(categories.flatMap(category => category.subscriptions.map(channel => channel.id)));
|
||||
const othersCategory = {
|
||||
name: "Others",
|
||||
subscriptions: allChannels.filter(channel =>
|
||||
!privateSubscriptionIds.has(channel.id) &&
|
||||
!channelsInCategory.has(channel.id)
|
||||
)
|
||||
};
|
||||
|
||||
// Categories are sorted alphabetically.
|
||||
const sortedCategories = categories.sort((a, b) => a.name.localeCompare(b.name));
|
||||
sortedCategories.push(othersCategory);
|
||||
|
||||
// Subscriptions are sorted alphabetically.
|
||||
sortedCategories.forEach(category => {
|
||||
category.subscriptions.sort((a, b) => a.name.localeCompare(b.name));
|
||||
});
|
||||
|
||||
sortedCategories.forEach(category => {
|
||||
category.subscriptions?.forEach(channel => channel.tags = subscriptionTags[channel.id])
|
||||
});
|
||||
|
||||
return sortedCategories;
|
||||
};
|
||||
2052
src/_data/youtubeSubscriptions.json
Normal file
2052
src/_data/youtubeSubscriptions.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,16 +6,18 @@ tags: list
|
|||
templateEngineOverride: njk,md
|
||||
---
|
||||
|
||||
Most of the YouTube channels I subscribe to, categorized. This list is now automated! You can [learn how it's implemented](/blog/automating-my-youtube-channel-list/) and use the code yourself if you use FreeTube and have an Eleventy site.
|
||||
Most of the YouTube channels I subscribe to, categorized. This list is now automated! You can [learn how it was implemented](/blog/automating-my-youtube-channel-list/) and use the code yourself if you use FreeTube and have an Eleventy site.
|
||||
|
||||
I can't think of any particular trends or bubbling interests that contribute to the latest changes here. There aren't many new subscriptions, so most of the changes are a result of my pruning subscriptions I was no longer watching regularly.
|
||||
I've made another change to the way this page builds in moving to building on CI since that ☝️ blog post was written. It's still basically the same script, but instead of running during the build, I run it on-demand on my computer to snapshot the current state of my subscriptions and commit that data file to the repo. That file is then used by the build in CI to create this page.
|
||||
|
||||
Continuing to prune channels I don't watch regularly anymore. I've continued to unsubscribe from some tabletop channels since I'm not super engaged with that hobby right now. That's not the only category that's seen pruning, but it's the most prominent one.
|
||||
|
||||
## Key
|
||||
- 👀- Instant watch
|
||||
- 🤌- Online video virtuoso
|
||||
- 😌- Good for sleepytime (This doesn't mean the content is bad, just that it's relaxing.)
|
||||
|
||||
{% for category in freetubeCategories %}
|
||||
{% for category in youtubeSubscriptions %}
|
||||
## {{ category.name }}
|
||||
|
||||
<ul>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue