Repository: Anarios/return-youtube-dislike

Issue #344: Privacy concern
Created: 2021-12-18T15:32:26Z
Browser
Brave
Browser Version
Latest
Extension or Userscript?
Extension
Extension/Userscript Version
Latest
Video link where you see the problem
Every
What happened?
How can we be sure our viewing habbits are not being tracked if every video I watch is being sent to your domain:
fetch(
    `https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
  ).then((response) => {
    response.json().then((json) => {

Can we solve this question to market/make this plugin even more popular and trusted by everyone?

Duplicate issue #90: (Feature Request): Support for Invidious and/or Piped
Created: 2021-11-30T00:20:15Z
Extension or Userscript?
Userscript
Request or suggest a new feature!
It would be nice if the extension changed the dislike counter for Invidious and/or piped. They are a private way of accessing youtube and it would be nice to have this support.
Ways to implement this!
We can implement it by scanning for the watch?v= ID and then just grabbing the dislikes counter for that video, then changing the dislike element for the watch page on each of the respective sites.
Can you work on this?

 Yes
 No

Closing comment (2021-12-29T21:39:46Z): I think this issue should be opened in Invidious and/or Piped repos

I think it's useful to look at how SponsorBlock handles Invidious. They have a checkbox to enable 3rd party YouTube website support in options, and users can add Invidious instances there. This prompts the browser to request website permissions and allows the extensions to access those websites.
This makes it possible for users to switch SponsorBlock server or have an associated SponsorBlock identity without having to go through the Invidious instance.
I noticed in the return-youtube-dislike README that there is a limit of 100 requests per minute and 10,000 requests per day for the API. I am not sure about exact numbers, but it's possible that some Invidious instances have more than 100 videos loaded per minute or 10,000 per day. Maybe client-side JavaScript could make the request instead, but given Invidious's privacy-focused stance I doubt that they would be adding code that leaks video IDs and their associated viewer IP addresses to a third-party service ( related to #344 )
For these reasons I think it would be good for the extension to support Invidious, rather than for Invidious to have to hard-code an API link and hope instances don't go over 10,000 requests per day.
For reference, I've modified the original userscript to support Invidious:
Userscript
// ==UserScript==
// @name         Return YouTube Dislike for Invidious
// @namespace    https://www.returnyoutubedislike.com/
// @homepage     https://www.returnyoutubedislike.com/
// @version      0.9.0
// @encoding     utf-8
// @description  Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
// @icon         https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
// @author       Anarios & JRWR
// @match        *://yewtu.be/*
// @match        *://invidious.snopyta.org/*
// @match        *://vid.puffyan.us/*
// @match        *://invidious.kavin.rocks/*
// @match        *://invidious.osi.kr/*
// @match        *://invidious-us.kavin.rocks/*
// @match        *://invidious.mutahar.rocks/*
// @match        *://invidio.xamh.de/*
// @match        *://inv.cthd.icu/*
// @match        *://invidious.namazso.eu/*
// @match        *://inv.riverside.rocks/*
// @match        *://youtube.076.ne.jp/*
// @match        *://vid.mint.lgbt/*
// @match        *://yt.artemislena.eu/*
// @compatible   chrome
// @compatible   firefox
// @compatible   opera
// @compatible   safari
// @compatible   edge
// ==/UserScript==
let likesvalue = 0;
let dislikesvalue = 0;

function cLog(text, subtext = "") {
  subtext = subtext.trim() === "" ? "" : `(${subtext})`;
  console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
}

function setLikesOrDislikes(element, count) {
  // The "likes" or "dislikes" elements have an icon embedded within them, like so
  // <p id="likes"><i class="icon ion-ios-thumbs-up"></i> 123,456</p>
  // I chose to split by </i>[space] to keep the icon and spacing when updating text
  let list = element.innerHTML.split("</i> ");
  list[list.length - 1] = count;

  element.innerHTML = list.join("</i> ");
}


function setLikes(likesCount) {
  setLikesOrDislikes(document.getElementById("likes"), likesCount);
}

function setDislikes(dislikesCount) {
  setLikesOrDislikes(document.getElementById("dislikes"), dislikesCount);
}


function setState() {
  cLog("Fetching votes...");
  let statsSet = false;

  fetch(
    `https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
  ).then((response) => {
    response.json().then((json) => {
      if (json && !("traceId" in response) && !statsSet) {
        const { dislikes, likes } = json;
        cLog(`Received count: ${dislikes}`);
        likesvalue = likes;
        dislikesvalue = dislikes;
        setDislikes(numberFormat(dislikes));
      }
    });
  });
}

function setInitialState() {
  setState();
}

function getVideoId() {
  const urlObject = new URL(window.location.href);
  const pathname = urlObject.pathname;
  if (pathname.startsWith("/clip")) {
    return document.querySelector("meta[itemprop='videoId']").content;
  } else {
    return urlObject.searchParams.get("v");
  }
}

function numberFormat(numberState) {
  let userLocales;
  try {
    userLocales = new URL(
      Array.from(document.querySelectorAll("head > link[rel='search']"))
        ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
        ?.getAttribute("href")
    )?.searchParams?.get("locale");
  } catch {}
  const formatter = Intl.NumberFormat(
    document.documentElement.lang || userLocales || navigator.language,
    {
      notation: "standard",
    }
  );

  return formatter.format(numberState);
}

function setEventListeners(evt) {
  let jsInitChecktimer;

  function checkForJS_Finish(check) {
    console.log();
    clearInterval(jsInitChecktimer);
    setInitialState();
  }

  if (window.location.href.indexOf("watch?") >= 0) {
    cLog("Setting up...");
    jsInitChecktimer = setInterval(checkForJS_Finish, 111);
  }
}

(function () {
  "use strict";
  setEventListeners();
})();