Repository navigation
Support Piped/Invidious #39
Description
Activity
Invidious would be nice as well, I use Piped myself, but it's always better to have multiple options
Reacted by July, snoutie, Basil, codenyte, Skyler Grey, forever-and-a-day, Maksim Zinin, Jonathan Neidel, Xenira, roadrunner56 and 13 more- pinned this issue
on Jul 6, 2023 solisinvictum commented
on Jul 10, 2023 on Jul 10, 2023 · Hidden as resolvedshow commentMore actionsThanks for your afford to make this possible.
But i dont know, and didnt find a option or so in dearrow, where i could put this user script in.
Oh just use a userscript manager extension/addon, I would recomend Violentmonkey since the project is 100% open-source.
Reacted by Erpe7aggarwalsushant commented
on Jul 17, 2023 on Jul 17, 2023 · Hidden as resolvedshow commentMore actionsI've modified Ajay's script again.
- I've added a simple polyfill for the
fetchAPI that uses a GreaseMonkey API, which should get around CSP issues. - I've added a function for converting Blobs to data URLs, as the
blob:URLs fromURL.createObjectURLcan be blocked by CSP.
// ==UserScript== // @name DeArrow for Invidious // @namespace http://tampermonkey.net/ // @version 0.2.2 // @description Adds support for DeArrow in Invidious // @author You // @match https://yewtu.be/* // @icon https://dearrow.ajay.app/logo.svg // @grant GM.xmlHttpRequest // @author Macic-Dev // @author Minion3665 // @author Basil // @author Ajay // ==/UserScript== (async function() { 'use strict'; /** * A simple fetch polyfill that uses the GreaseMonkey API * * @param {string} url - the URL to fetch */ function fetch(url) { return new Promise(resolve => { GM.xmlHttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { const headers = res.responseHeaders .split('\r\n') .reduce((prev, str) => { const [key, val] = str.split(/: (.*)/s); if (key === '') return prev; prev[key.toLowerCase()] = val; return prev; }, {}); resolve({ headers: { get: key => headers[key.toLowerCase()] }, status: res.status, blob: async () => res.response, json: async () => JSON.parse(await new Response(res.response).text()) }); } }); }); } /** * Converts a Blob to a data URL in order to get around CSP * * @param {Blob} blob - the URL to fetch */ function blobToDataURI(blob) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); }); } /** * Fetch data for a video, and then update the elements passed to reflect that * * @param {string} videoId - the YouTube ID of the video (the bit that comes after v= in watch URLs) * @param {HTMLElement} titleElement - the element containing the video title which will be updated if DeArrow has a title * @param {HTMLElement|undefined} thumbnailElement - the element containing the video thumbnail which will be updated if DeArrow has a thumbnail */ async function fetchAndUpdate(videoId, titleElement, thumbnailElement = undefined) { const oldTitle = titleElement.textContent; const oldThumbnail = thumbnailElement?.src; const cachedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}`); const cachedNewTitle = cachedThumbnailAPIResponse.headers.get('X-Title'); if (cachedNewTitle) replaceTitle(titleElement, cachedNewTitle); if (thumbnailElement !== undefined && cachedThumbnailAPIResponse.status === 200) { const cachedNewThumbnail = await cachedThumbnailAPIResponse.blob(); thumbnailElement.src = await blobToDataURI(cachedNewThumbnail); } const brandingAPIResponse = await (await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`)).json(); { let topTitle = brandingAPIResponse.titles[0]; let usedTitle = (topTitle && (topTitle.votes >= 0 || topTitle.locked)) ? topTitle : null; replaceTitle(titleElement, usedTitle?.title ?? oldTitle); } { let topThumbnail = brandingAPIResponse.thumbnails[0]; let usedThumbnail = (topThumbnail && (topThumbnail.votes >= 0 || topThumbnail.locked)) ? topThumbnail : null; let randomTime = brandingAPIResponse.videoDuration ? brandingAPIResponse.videoDuration * brandingAPIResponse.randomTime : 0; if (usedThumbnail && usedThumbnail.original) { thumbnailElement.src = oldThumbnail; } else { const votedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${usedThumbnail?.timestamp ?? randomTime}`); thumbnailElement.src = await blobToDataURI(await votedThumbnailAPIResponse.blob()); } } } // Replaces the title of elements function replaceTitle(element, text) { if (element.nodeName === 'H1') { for (const child of element.childNodes) { if (child.nodeName !== '#text') continue; child.textContent = text; break; } } else { element.textContent = text; } } // Videos on page const thumbnailDivs = document.querySelectorAll('div.thumbnail'); thumbnailDivs.forEach((div) => { const videoUrl = new URL(div.parentNode.href); const videoId = videoUrl.searchParams.get('v'); const titleP = [...div.parentNode.querySelectorAll('p')][1]; const thumbnailImg = div.querySelector('img.thumbnail'); void fetchAndUpdate(videoId, titleP, thumbnailImg); }); // Current video const currentUrl = new URL(document.URL); if (currentUrl.pathname == '/watch') { const currentId = currentUrl.searchParams.get('v'); const titleH = document.querySelector('h1'); const thumbnailDiv = document.querySelector('.vjs-poster'); await fetchAndUpdate(currentId, titleH, thumbnailDiv); if (thumbnailDiv.src) thumbnailDiv.style.backgroundImage = `url(${thumbnailDiv.src})`; document.title = `${titleH.textContent} - Invidious`; } })();
@aggarwalsushant This may fix your issue. Also, you have to change the URL in @match to the URL of whichever Invidious instance you're using.
Reacted by solisinvictum, Sushant Aggarwal, Skyler Grey, Chev and sma33inReacted by solisinvictum, Sushant Aggarwal and Skyler Grey- I've added a simple polyfill for the
thanks! that works great.
Seems doesn't work with piped for now.
28 remaining items
Piped now has native support for DeArrow everywhere with TeamPiped/Piped@9539d51.
Well, for the frontend. However, as far as I know, one can't do any submissions yet. That would be quite helpful.
Reacted by codenyte, Basil and Andrea Jemmett// ==UserScript== // @name DeArrow for Invidious // @namespace http://tampermonkey.net/ // @version 0.2.3 // @description Adds support for DeArrow in Invidious // @match https://yewtu.be/* // @match https://iv.ggtyler.dev/* // @icon https://dearrow.ajay.app/logo.svg // @grant GM.xmlHttpRequest // @author Macic-Dev // @author Minion3665 // @author Basil // @author Ajay // ==/UserScript== (async function() { 'use strict'; /** * A simple fetch polyfill that uses the GreaseMonkey API * * @param {string} url - the URL to fetch */ function fetch(url) { return new Promise(resolve => { GM.xmlHttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { const headers = res.responseHeaders .split('\r\n') .reduce((prev, str) => { const [key, val] = str.split(/: (.*)/s); if (key === '') return prev; prev[key.toLowerCase()] = val; return prev; }, {}); resolve({ headers: { get: key => headers[key.toLowerCase()] }, status: res.status, blob: async () => res.response, json: async () => JSON.parse(await new Response(res.response).text()) }); } }); }); } /** * Converts a Blob to a data URL in order to get around CSP * * @param {Blob} blob - the URL to fetch */ function blobToDataURI(blob) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); }); } /** * Fetch data for a video, and then update the elements passed to reflect that * * @param {string} videoId - the YouTube ID of the video (the bit that comes after v= in watch URLs) * @param {HTMLElement} titleElement - the element containing the video title which will be updated if DeArrow has a title * @param {HTMLElement|undefined} thumbnailElement - the element containing the video thumbnail which will be updated if DeArrow has a thumbnail */ async function fetchAndUpdate(videoId, titleElement, thumbnailElement = undefined) { const oldTitle = titleElement.textContent; const oldThumbnail = thumbnailElement?.src; const cachedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}`); const cachedNewTitle = cachedThumbnailAPIResponse.headers.get('X-Title'); if (cachedNewTitle) replaceTitle(titleElement, cachedNewTitle); if (thumbnailElement !== undefined && cachedThumbnailAPIResponse.status === 200) { const cachedNewThumbnail = await cachedThumbnailAPIResponse.blob(); thumbnailElement.src = await blobToDataURI(cachedNewThumbnail); } const brandingAPIResponse = await (await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`)).json(); { let topTitle = brandingAPIResponse.titles[0]; let usedTitle = (topTitle && (topTitle.votes >= 0 || topTitle.locked)) ? topTitle : null; replaceTitle(titleElement, usedTitle?.title ?? oldTitle); } { let topThumbnail = brandingAPIResponse.thumbnails[0]; let usedThumbnail = (topThumbnail && (topThumbnail.votes >= 0 || topThumbnail.locked)) ? topThumbnail : null; let randomTime = brandingAPIResponse.videoDuration ? brandingAPIResponse.videoDuration * brandingAPIResponse.randomTime : 0; if (usedThumbnail && usedThumbnail.original) { thumbnailElement.src = oldThumbnail; } else { const votedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${usedThumbnail?.timestamp ?? randomTime}`); thumbnailElement.src = await blobToDataURI(await votedThumbnailAPIResponse.blob()); } } } // Replaces the title of elements function replaceTitle(element, text) { if (element.nodeName === 'H1') { for (const child of element.childNodes) { if (child.nodeName !== '#text') continue; child.textContent = text; break; } } else { element.textContent = text; } } // Videos on page const thumbnailDivs = document.querySelectorAll('div.thumbnail'); thumbnailDivs.forEach((div) => { if (div.parentNode.nodeName === 'DIV') { // First link. Selector could be better. const link = div.querySelector('a'); // Make sure we aren't fetching channel URLs if (link.href.startsWith('/channel/')) return; const videoUrl = new URL(link.href); const videoId = videoUrl.searchParams.get('v'); const titleP = div.parentNode.children[1].querySelector('p'); const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } else { // Make sure we aren't fetching channel URLs if (div.parentNode.href.startsWith('/channel/')) return; const videoUrl = new URL(div.querySelector('a').href); const videoId = videoUrl.searchParams.get('v'); const titleP = [...div.parentNode.querySelectorAll('p')][1]; const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } }); // Current video const currentUrl = new URL(document.URL); if (currentUrl.pathname == '/watch') { const currentId = currentUrl.searchParams.get('v'); const titleH = document.querySelector('h1'); const thumbnailDiv = document.querySelector('.vjs-poster'); await fetchAndUpdate(currentId, titleH, thumbnailDiv); if (thumbnailDiv.src) thumbnailDiv.style.backgroundImage = `url(${thumbnailDiv.src})`; document.title = `${titleH.textContent} - Invidious`; } })();
Maybe author of this script should make repo for it, so we can easily find latest version and pull request our changes?
// ==UserScript== // @name DeArrow for Invidious // @namespace http://tampermonkey.net/ // @version 0.2.3 // @description Adds support for DeArrow in Invidious // @match https://yewtu.be/* // @match https://iv.ggtyler.dev/* // @icon https://dearrow.ajay.app/logo.svg // @grant GM.xmlHttpRequest // @author Macic-Dev // @author Minion3665 // @author Basil // @author Ajay // ==/UserScript== (async function() { 'use strict'; /** * A simple fetch polyfill that uses the GreaseMonkey API * * @param {string} url - the URL to fetch */ function fetch(url) { return new Promise(resolve => { GM.xmlHttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { const headers = res.responseHeaders .split('\r\n') .reduce((prev, str) => { const [key, val] = str.split(/: (.*)/s); if (key === '') return prev; prev[key.toLowerCase()] = val; return prev; }, {}); resolve({ headers: { get: key => headers[key.toLowerCase()] }, status: res.status, blob: async () => res.response, json: async () => JSON.parse(await new Response(res.response).text()) }); } }); }); } /** * Converts a Blob to a data URL in order to get around CSP * * @param {Blob} blob - the URL to fetch */ function blobToDataURI(blob) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); }); } /** * Fetch data for a video, and then update the elements passed to reflect that * * @param {string} videoId - the YouTube ID of the video (the bit that comes after v= in watch URLs) * @param {HTMLElement} titleElement - the element containing the video title which will be updated if DeArrow has a title * @param {HTMLElement|undefined} thumbnailElement - the element containing the video thumbnail which will be updated if DeArrow has a thumbnail */ async function fetchAndUpdate(videoId, titleElement, thumbnailElement = undefined) { const oldTitle = titleElement.textContent; const oldThumbnail = thumbnailElement?.src; const cachedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}`); const cachedNewTitle = cachedThumbnailAPIResponse.headers.get('X-Title'); if (cachedNewTitle) replaceTitle(titleElement, cachedNewTitle); if (thumbnailElement !== undefined && cachedThumbnailAPIResponse.status === 200) { const cachedNewThumbnail = await cachedThumbnailAPIResponse.blob(); thumbnailElement.src = await blobToDataURI(cachedNewThumbnail); } const brandingAPIResponse = await (await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`)).json(); { let topTitle = brandingAPIResponse.titles[0]; let usedTitle = (topTitle && (topTitle.votes >= 0 || topTitle.locked)) ? topTitle : null; replaceTitle(titleElement, usedTitle?.title ?? oldTitle); } { let topThumbnail = brandingAPIResponse.thumbnails[0]; let usedThumbnail = (topThumbnail && (topThumbnail.votes >= 0 || topThumbnail.locked)) ? topThumbnail : null; let randomTime = brandingAPIResponse.videoDuration ? brandingAPIResponse.videoDuration * brandingAPIResponse.randomTime : 0; if (usedThumbnail && usedThumbnail.original) { thumbnailElement.src = oldThumbnail; } else { const votedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${usedThumbnail?.timestamp ?? randomTime}`); thumbnailElement.src = await blobToDataURI(await votedThumbnailAPIResponse.blob()); } } } // Replaces the title of elements function replaceTitle(element, text) { if (element.nodeName === 'H1') { for (const child of element.childNodes) { if (child.nodeName !== '#text') continue; child.textContent = text; break; } } else { element.textContent = text; } } // Videos on page const thumbnailDivs = document.querySelectorAll('div.thumbnail'); thumbnailDivs.forEach((div) => { if (div.parentNode.nodeName === 'DIV') { // First link. Selector could be better. const link = div.querySelector('a'); // Make sure we aren't fetching channel URLs if (link.href.startsWith('/channel/')) return; const videoUrl = new URL(link.href); const videoId = videoUrl.searchParams.get('v'); const titleP = div.parentNode.children[1].querySelector('p'); const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } else { // Make sure we aren't fetching channel URLs if (div.parentNode.href.startsWith('/channel/')) return; const videoUrl = new URL(div.querySelector('a').href); const videoId = videoUrl.searchParams.get('v'); const titleP = [...div.parentNode.querySelectorAll('p')][1]; const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } }); // Current video const currentUrl = new URL(document.URL); if (currentUrl.pathname == '/watch') { const currentId = currentUrl.searchParams.get('v'); const titleH = document.querySelector('h1'); const thumbnailDiv = document.querySelector('.vjs-poster'); await fetchAndUpdate(currentId, titleH, thumbnailDiv); if (thumbnailDiv.src) thumbnailDiv.style.backgroundImage = `url(${thumbnailDiv.src})`; document.title = `${titleH.textContent} - Invidious`; } })();
Maybe author of this script should make repo for it, so we can easily find latest version and pull request our changes?
I'm not sure who the original author is, but maybe Ajay could? But it would be ideal if this was just integrated directly into DeArrow (I'm surprised it hasn't been added yet).
Reacted by Cirnos and T3RM1N4LPiped now has native support for DeArrow everywhere with TeamPiped/Piped@9539d51.
Well, for the frontend. However, as far as I know, one can't do any submissions yet. That would be quite helpful.
Are there any plans to implement that?
This update should fix any thumbnail-related issues:
// ==UserScript== // @name DeArrow for Invidious // @namespace http://tampermonkey.net/ // @version 0.2.4 // @description Adds support for DeArrow in Invidious // @match https://yewtu.be/* // @match https://iv.ggtyler.dev/* // @icon https://dearrow.ajay.app/logo.svg // @grant GM.xmlHttpRequest // @author Macic-Dev // @author Minion3665 // @author Basil // @author Ajay // @author Euphoriyy // ==/UserScript== (async function() { 'use strict'; /** * A simple fetch polyfill that uses the GreaseMonkey API * * @param {string} url - the URL to fetch */ function fetch(url) { return new Promise(resolve => { GM.xmlHttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { const headers = res.responseHeaders .split('\r\n') .reduce((prev, str) => { const [key, val] = str.split(/: (.*)/s); if (key === '') return prev; prev[key.toLowerCase()] = val; return prev; }, {}); resolve({ headers: { get: key => headers[key.toLowerCase()] }, status: res.status, blob: async () => res.response, json: async () => JSON.parse(await new Response(res.response).text()) }); } }); }); } /** * Converts a Blob to a data URL in order to get around CSP * * @param {Blob} blob - the URL to fetch */ function blobToDataURI(blob) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); }); } /** * Fetch data for a video, and then update the elements passed to reflect that * * @param {string} videoId - the YouTube ID of the video (the bit that comes after v= in watch URLs) * @param {HTMLElement} titleElement - the element containing the video title which will be updated if DeArrow has a title * @param {HTMLElement|undefined} thumbnailElement - the element containing the video thumbnail which will be updated if DeArrow has a thumbnail */ async function fetchAndUpdate(videoId, titleElement, thumbnailElement = undefined) { const oldTitle = titleElement.textContent; const oldThumbnail = thumbnailElement?.src; const cachedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}`); const cachedNewTitle = cachedThumbnailAPIResponse.headers.get('X-Title'); if (cachedNewTitle) replaceTitle(titleElement, cachedNewTitle); if (thumbnailElement !== undefined && cachedThumbnailAPIResponse.status === 200 && cachedThumbnailAPIResponse.headers.get("X-Timestamp") !== "0.0") { const cachedNewThumbnail = await cachedThumbnailAPIResponse.blob(); thumbnailElement.src = await blobToDataURI(cachedNewThumbnail); } const brandingAPIResponse = await (await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`)).json(); { let topTitle = brandingAPIResponse.titles[0]; let usedTitle = (topTitle && (topTitle.votes >= 0 || topTitle.locked)) ? topTitle : null; replaceTitle(titleElement, usedTitle?.title ?? oldTitle); } if (brandingAPIResponse.thumbnails.length > 0) { let topThumbnail = brandingAPIResponse.thumbnails[0]; let usedThumbnail = (topThumbnail && (topThumbnail.votes >= 0 || topThumbnail.locked)) ? topThumbnail : null; let randomTime = brandingAPIResponse.videoDuration ? brandingAPIResponse.videoDuration * brandingAPIResponse.randomTime : 0; if (usedThumbnail && usedThumbnail.original) { thumbnailElement.src = oldThumbnail; } else { const votedThumbnailAPIResponse = await fetch(`https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${usedThumbnail?.timestamp ?? randomTime}`); thumbnailElement.src = await blobToDataURI(await votedThumbnailAPIResponse.blob()); } } } // Replaces the title of elements function replaceTitle(element, text) { if (element.nodeName === 'H1') { for (const child of element.childNodes) { if (child.nodeName !== '#text') continue; child.textContent = text; break; } } else { element.textContent = text; } } // Videos on page const thumbnailDivs = document.querySelectorAll('div.thumbnail'); thumbnailDivs.forEach((div) => { if (div.parentNode.nodeName === 'DIV') { // First link. Selector could be better. const link = div.querySelector('a'); // Make sure we aren't fetching channel URLs if (link.href.startsWith('/channel/')) return; const videoUrl = new URL(link.href); const videoId = videoUrl.searchParams.get('v'); const titleP = div.parentNode.children[1].querySelector('p'); const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } else { // Make sure we aren't fetching channel URLs if (div.parentNode.href.startsWith('/channel/')) return; const videoUrl = new URL(div.querySelector('a').href); const videoId = videoUrl.searchParams.get('v'); const titleP = [...div.parentNode.querySelectorAll('p')][1]; const thumbnailImg = div.querySelector('img.thumbnail'); fetchAndUpdate(videoId, titleP, thumbnailImg); } }); // Current video const currentUrl = new URL(document.URL); if (currentUrl.pathname == '/watch') { const currentId = currentUrl.searchParams.get('v'); const titleH = document.querySelector('h1'); const thumbnailDiv = document.querySelector('.vjs-poster'); await fetchAndUpdate(currentId, titleH, thumbnailDiv); if (thumbnailDiv.src) thumbnailDiv.style.backgroundImage = `url(${thumbnailDiv.src})`; document.title = `${titleH.textContent} - Invidious`; } })();
I have tried fixing this myself, and I have got it to work, with the catch of it only being when manually activated via context menu. This is because that playlist sidebar is loaded with javascript after the initial page load, meaning that the script is already finished executing before that sidebar loads in, preventing the thumbnails and titles from changing.
Here is the script in case anyone finds a solution to this.I implemented basic DeArrow support in my extension for Invidious: extraneous.
Slapped it together today; it has some additional features such as LocalStorage-based watched video markers and hiding videos based on duration/title. Contributions are welcome. Only tested on Firefox so far.Reacted by Skyler Grey, Ajay Ramachandran, Cirnos and Moheshwar Amarnath BiswasI have tried fixing this myself, and I have got it to work, with the catch of it only being when manually activated via context menu. This is because that playlist sidebar is loaded with javascript after the initial page load, meaning that the script is already finished executing before that sidebar loads in, preventing the thumbnails and titles from changing. Here is the script in case anyone finds a solution to this.
Would something like this work? Maybe add each video on the page to some list and check against that so as to avoid spamming the deArrow API but just running the main section of the code multiple times after a delay?
let retries = 3; const intervalID = setInterval(_ => { console.log(retries) main(); //The main loop of the function, ie. the "videos on page" section retries--; if(retries == 0) clearInterval(intervalID); }, 2500); //some delay, here 2.5 seconds
I have tried fixing this myself, and I have got it to work, with the catch of it only being when manually activated via context menu. This is because that playlist sidebar is loaded with javascript after the initial page load, meaning that the script is already finished executing before that sidebar loads in, preventing the thumbnails and titles from changing. Here is the script in case anyone finds a solution to this.
Would something like this work? Maybe add each video on the page to some list and check against that so as to avoid spamming the deArrow API but just running the main section of the code multiple times after a delay?
let retries = 3; const intervalID = setInterval(_ => { console.log(retries) main(); //The main loop of the function, ie. the "videos on page" section retries--; if(retries == 0) clearInterval(intervalID); }, 2500); //some delay, here 2.5 seconds
In fact, here's a working version (with an option for formatting titles as lowercase with no emotes added, for myself)
Reacted by sysrqmagicianI have tried fixing this myself, and I have got it to work, with the catch of it only being when manually activated via context menu. This is because that playlist sidebar is loaded with javascript after the initial page load, meaning that the script is already finished executing before that sidebar loads in, preventing the thumbnails and titles from changing. Here is the script in case anyone finds a solution to this.
Would something like this work? Maybe add each video on the page to some list and check against that so as to avoid spamming the deArrow API but just running the main section of the code multiple times after a delay?
let retries = 3; const intervalID = setInterval(_ => { console.log(retries) main(); //The main loop of the function, ie. the "videos on page" section retries--; if(retries == 0) clearInterval(intervalID); }, 2500); //some delay, here 2.5 seconds
The above might work, but you'd typically use a MutationObserver to intercept the playlist videos as they are being added to the DOM and do the required processing.
I'm currently working on implementing this in extraneous.Edit: Correction - Not just a might. It does work, nice!
Reacted by beesarefriends and Sebastian HädrichI have tried fixing this myself, and I have got it to work, with the catch of it only being when manually activated via context menu. This is because that playlist sidebar is loaded with javascript after the initial page load, meaning that the script is already finished executing before that sidebar loads in, preventing the thumbnails and titles from changing. Here is the script in case anyone finds a solution to this.
Would something like this work? Maybe add each video on the page to some list and check against that so as to avoid spamming the deArrow API but just running the main section of the code multiple times after a delay?
let retries = 3; const intervalID = setInterval(_ => { console.log(retries) main(); //The main loop of the function, ie. the "videos on page" section retries--; if(retries == 0) clearInterval(intervalID); }, 2500); //some delay, here 2.5 seconds
The above might work, but you'd typically use a MutationObserver to intercept the playlist videos as they are being added to the DOM and do the required processing. I'm currently working on implementing this in extraneous.
Edit: Correction - Not just a might. It does work, nice!
Just found out about MutationObservers actually! Updated the script with one that works and is a little less spammy, although ideally we wouldn't re-check every video on the page afterwards. That requires a bit of refactoring, though.
Nice that you found a solution. I have updated the script again now to use MutationObservers, but instead only update the playlist items instead of every video: https://gist.github.com/Kladki/101f0da6511906460252e26ea78d8ec3
Extraneous (repo, AMO) has been screened and is now available via the official Firefox Addon Store! It behaves as specified in the documentation and is reasonably robust. Contributions, feature requests or general feedback are very welcome.
For Chromium, manual installation instructions are provided in the repository, although the DeArrow module is currently not working (issue 6). Chromium won't be officially supported, but should anyone be interested in using the plugin themselves, I'm not opposed to merging fixes for Chromium.
Reacted by KrodzangoReacted by Ajay Ramachandran, Rikj000 and divyanshReacted by jvschiavo, Cirnos, Rikj000 and Jake Klingler



Please add support for 3rd party front-ends, similar to SponsorBlock. Specifically, for Piped.