Coder Social home page Coder Social logo

dearrow-invidious's Issues

As of 2024-05, this user-script doesn't work. I found one which does. https://github.com/ajayyy/DeArrow/issues/39#issuecomment-1935353557

Please refer to ajayyy/DeArrow#39 (comment).

// ==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`;
    }
})();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.