iv-org / invidious

Invidious is an alternative front-end to YouTube
https://invidious.io
GNU Affero General Public License v3.0
16.18k stars 1.79k forks source link

[Feature request] A way to categorize subscriptions #4745

Closed gptlang closed 2 months ago

gptlang commented 3 months ago

Is your feature request related to a problem? Please describe.

I have 254 subscriptions, some less important than others. Shorts #2585 and other content regularly clutters the feed such that I miss important ones.

Describe the solution you'd like

A dropdown can be added to the subscribe button which allows the subscription can be added to pre-defined lists. Similar to the Playlists section, we can have "Sublists" where these lists are stored.

Describe alternatives you've considered

Write my own UI for that via the invidious APIs. It might be inefficient to fetch /api/v1/channels/:CHANNEL_ID repeatedly for every channel & caching it so I suppose another feature request would be to expose whatever invidious uses to keep track of subscriptions such that I can send an array of channel IDs and receive a small paginated response.

gptlang commented 3 months ago

Note: /api/v1/channels/:ucid/videos is missing from the documentation. Remember to URL encode continuation from the response. Looked a bit at the code and it seems that invidious actually fetches the videos for each channel and stores that for subscriptions so there isn't actually a more efficient way. Looks like maintainers are busy putting out fires set by youtube so I'll try to work this out myself.

gptlang commented 3 months ago

I've started working on this a bit: https://github.com/iv-org/invidious/compare/master...gptlang:invidious:master.

This is the first time I've touched crystal and can't figure out how to get an LSP and syntax highlighting working on Neovim. Could someone from @iv-org point me in the right direction?

unixfox commented 3 months ago

I've started working on this a bit: master...gptlang:invidious:master.

This is the first time I've touched crystal and can't figure out how to get an LSP and syntax highlighting working on Neovim. Could someone from @iv-org point me in the right direction?

Join matrix (https://matrix.to/#/#invidious:matrix.org) or IRC (https://web.libera.chat/?channel=#invidious) for discussing about that.

cortsf commented 2 months ago

Related: #4690, #2150, #482, #2998

I'm also working on a new experimental multi user frontend and also (in the mean time..) managed to solve the problem by switching invidious users programatically, by clicking on some injected buttons on the login page and/or using some browser hotkeys.

But since I'm doing so much weird customizations (code below is just the greasemonkey stuff), it makes no sense not to write a new frontend instead (I'm not willing to learn a new language just to fork invidious)

If you want to try this approach, check the code at the end of the following block:

// ==UserScript==
// @run-at document-end
// @match http://localhost:3000/*
// ==/UserScript==
// Note: querySelectorAll's and forEach loops are used just to prevent setting attributes of null values.

// Always go to login page after clicking "Log out" 
const userName = document.querySelector("#user_name")
if ( userName !== null ) {
    document.querySelector("input[type='submit']").parentElement.parentElement.action='/signout?referer=login'
}

// Bg color
document.querySelector("body").style.background="#141414"

// Searchbox style
document.querySelector("#searchbox").style.background="#222"
document.querySelector("#searchbox").style.border="none"
document.querySelector("#searchbox").style.borderRadius="10px"

// Remove dark/light theme icon
const themeIcons = document.querySelectorAll("#toggle_theme")
themeIcons.forEach(icon => icon.parentElement.remove());

// Remove user settings icon
const cogIcons = document.querySelectorAll(".ion-ios-cog")
cogIcons.forEach(icon => icon.parentElement.parentElement.remove());

// Highlight live streams
const views = document.querySelectorAll('.video-data')

views.forEach(view => {
    if (view.innerText == "0 views"){
    view.innerText = "0"
    view.style.color = "red"
    } else if (new RegExp('^Premieres in').test(view.innerText)){
    view.style.color = "red"
    }
}
)

// Set subscriptions icon with text
const subsIcons = document.querySelectorAll("#notification_ticker")
subsIcons.forEach(sub => {
    const textnode = document.createTextNode("subscriptions");
    sub.appendChild(textnode);
});

// User name links to settings
const userNames = document.querySelectorAll("#user_name")
userNames.forEach(user => user.innerHTML = '<a href="/preferences">' + user.innerHTML + '</a>');

// Show "Playlist" button instead of rss icon
const rssLogo = document.querySelector(".ion-logo-rss")
if ( rssLogo !== null && !(new RegExp('^http://localhost:3000/channel').test(document.location.href))) {
    const node = document.createElement("div");
    node.classList.add("pure-u-1-3")
    node.innerHTML = "<h3 style='text-align:right'><a href='/feed/playlists'>Playlists</a></h3>"
    rssLogo.parentElement.parentElement.parentElement.parentElement.appendChild(node);
    rssLogo.parentElement.parentElement.parentElement.remove()
}

// Thumbs style
const thumbs = document.querySelectorAll("img.thumbnail")
thumbs.forEach(thumb => thumb.style.borderRadius="8px");
const thumbsWatched = document.querySelectorAll(".watched-overlay")
thumbsWatched.forEach(thumb => thumb.style.borderRadius="8px");

// Horizontal lines
const hrs = document.querySelectorAll("hr")
hrs.forEach(hr => {
    hr.style.background="#444"
    hr.style.border="none"
    hr.style.height="4px"
}
);

// Title style
if (document.location.href == "http://localhost:3000/feed/popular") {
    document.getElementsByClassName("index-link pure-menu-heading")[0].style.color="#B500AD"
    document.getElementsByClassName("index-link pure-menu-heading")[0].text="INVIDIOUS"
}

// Login page buttons
function mkButton (user) {
    const node = document.createElement("button");
    const textnode = document.createTextNode(user);
    node.style.background="#222"
    node.style.float="right"
    node.style.width="60%"
    node.style.marginRight="20px"
    node.style.marginBottom="6px"
    node.style.paddingTop="4px"
    node.style.paddingBottom="4px"
    node.style.borderRadius="6px"
    node.style.border="none"
    node.appendChild(textnode);
    node.addEventListener("click", function(){
    document.getElementsByName('email')[0].value=user
    document.getElementsByName('password')[0].value='123'
    document.getElementsByName('action')[0].click()
    }, true);
    document.querySelector(".pure-u-1 .pure-u-lg-1-5").appendChild(node)
}

if (new RegExp('^http://localhost:3000/login').test(document.location.href)) {
    document.querySelector(".pure-form-stacked").setAttribute("action", "/login?referer=%2Ffeed%2Fsubscriptions&amp;type=invidious" )
    const invidious_users = ["french" , "study" , "fun" , "arg" , "ai" , "tech" , "histo" , "health" , "philo" , "geo" , "music" , "crypto" , "english" , "uk" , "food" , "weird" , "youtube" ];
    invidious_users.forEach((user) => mkButton(user));
}
gptlang commented 2 months ago

Closed as duplicate of #2150

cortsf commented 1 month ago

@gptlang, in case this is useful for you, this little tweak also allows to switch users by setting a (custom) param in the login page url. No need to inject buttons if you can bookmark a few of these url's. But you'll have to logout before being able to access the login page (a pain in the ass to do it manually but not a problem if you can automate both steps)

if (new RegExp('^http://localhost:3000/login').test(document.location.href)) {
    const invidious_user = new URLSearchParams(window.location.search).get('invidious_user');
    if(invidious_user !== null){
    document.getElementsByName('email')[0].value=invidious_user
    document.getElementsByName('password')[0].value='123'
    document.getElementsByName('action')[0].click()
    }else{
    document.querySelector(".pure-form-stacked").setAttribute("action", "/login?referer=%2Ffeed%2Fsubscriptions&amp;type=invidious" )
    const invidious_users = ["french" , "study" , "fun" , "arg" , "ai" , "tech" , "histo" , "health" , "philo" , "geo" , "music" , "crypto" , "english" , "uk" , "food" , "weird" , "youtube" ];
    invidious_users.forEach((user) => mkButton(user));
    }
}