Return YouTube Dislike

Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/

目前為 2021-12-03 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Return YouTube Dislike
  3. // @namespace https://www.returnyoutubedislike.com/
  4. // @homepage https://www.returnyoutubedislike.com/
  5. // @version 0.6.1
  6. // @encoding utf-8
  7. // @description Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
  8. // @icon https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
  9. // @author Anarios & JRWR
  10. // @match *://*.youtube.com/*
  11. // @compatible chrome
  12. // @compatible firefox
  13. // @compatible opera
  14. // @compatible safari
  15. // @compatible edge
  16. // @grant GM.xmlHttpRequest
  17. // @run-at document-end
  18. // ==/UserScript==
  19. function cLog(text, subtext = '') {
  20. subtext = subtext.trim() === '' ? '' : `(${subtext})`;
  21. console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
  22. }
  23.  
  24. function getButtons() {
  25. if (document.getElementById("menu-container").offsetParent === null) {
  26. return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
  27. } else {
  28. return document
  29. .getElementById("menu-container")
  30. ?.querySelector("#top-level-buttons-computed");
  31. }
  32. }
  33.  
  34. function getLikeButton() {
  35. return getButtons().children[0];
  36. }
  37.  
  38. function getDislikeButton() {
  39. return getButtons().children[1];
  40. }
  41.  
  42. function isVideoLiked() {
  43. return getLikeButton().classList.contains("style-default-active");
  44. }
  45.  
  46. function isVideoDisliked() {
  47. return getDislikeButton().classList.contains("style-default-active");
  48. }
  49.  
  50. function isVideoNotLiked() {
  51. return getLikeButton().classList.contains("style-text");
  52. }
  53.  
  54. function isVideoNotDisliked() {
  55. return getDislikeButton().classList.contains("style-text");
  56. }
  57.  
  58. function getState() {
  59. if (isVideoLiked()) {
  60. return "liked";
  61. }
  62. if (isVideoDisliked()) {
  63. return "disliked";
  64. }
  65. return "neutral";
  66. }
  67.  
  68. function setLikes(likesCount) {
  69. getButtons().children[0].querySelector("#text").innerText = likesCount;
  70. }
  71.  
  72. function setDislikes(dislikesCount) {
  73. getButtons().children[1].querySelector("#text").innerText = dislikesCount;
  74. }
  75.  
  76. function createRateBar(likes, dislikes) {
  77. var rateBar = document.getElementById("return-youtube-dislike-bar-container");
  78.  
  79. const widthPx =
  80. getButtons().children[0].clientWidth +
  81. getButtons().children[1].clientWidth +
  82. 8;
  83.  
  84. const widthPercent =
  85. likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
  86.  
  87. if (!rateBar) {
  88. document.getElementById("menu-container").insertAdjacentHTML(
  89. "beforeend",
  90. `
  91. <div class="ryd-tooltip" style="width: ${widthPx}px">
  92. <div class="ryd-tooltip-bar-container">
  93. <div
  94. id="return-youtube-dislike-bar-container"
  95. style="width: 100%; height: 2px;"
  96. >
  97. <div
  98. id="return-youtube-dislike-bar"
  99. style="width: ${widthPercent}%; height: 100%"
  100. ></div>
  101. </div>
  102. </div>
  103. <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
  104. <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
  105. </tp-yt-paper-tooltip>
  106. </div>
  107. `
  108. );
  109. } else {
  110. document.getElementById(
  111. "return-youtube-dislike-bar-container"
  112. ).style.width = widthPx + "px";
  113. document.getElementById("return-youtube-dislike-bar").style.width =
  114. widthPercent + "%";
  115.  
  116. document.querySelector(
  117. "#ryd-dislike-tooltip > #tooltip"
  118. ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
  119. }
  120. }
  121.  
  122. function setState() {
  123. cLog("Fetching votes...");
  124. let statsSet = false;
  125.  
  126. fetch(`https://www.youtube.com/watch?v=${getVideoId()}`).then((response) => {
  127. response.text().then((text) => {
  128. let result = getDislikesFromYoutubeResponse(text);
  129. if (result) {
  130. cLog("response from youtube:");
  131. cLog(JSON.stringify(result));
  132. if (result.likes && result.dislikes) {
  133. const formattedDislike = numberFormat(result.dislikes);
  134. setDislikes(formattedDislike);
  135. createRateBar(result.likes, result.dislikes);
  136. statsSet = true;
  137. }
  138. }
  139. });
  140. });
  141.  
  142. fetch(
  143. `https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
  144. ).then((response) => {
  145. response.json().then((json) => {
  146. if (json && !statsSet) {
  147. const { dislikes, likes } = json;
  148. cLog(`Received count: ${dislikes}`);
  149. setDislikes(numberFormat(dislikes));
  150. createRateBar(likes, dislikes);
  151. }
  152. });
  153. });
  154. }
  155.  
  156. function likeClicked() {
  157. cLog("Like clicked", getState());
  158. setState();
  159. }
  160.  
  161. function dislikeClicked() {
  162. cLog("Dislike clicked", getState());
  163. setState();
  164. }
  165.  
  166. function setInitalState() {
  167. setState();
  168. }
  169.  
  170. function getVideoId() {
  171. const urlParams = new URLSearchParams(window.location.search);
  172. const videoId = urlParams.get("v");
  173.  
  174. return videoId;
  175. }
  176.  
  177. function isVideoLoaded() {
  178. const videoId = getVideoId();
  179.  
  180. return (
  181. document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null
  182. );
  183. }
  184.  
  185. function roundDown(num) {
  186. if (num < 1000) return num;
  187. const int = Math.floor(Math.log10(num) - 2);
  188. const decimal = int + (int % 3 ? 1 : 0);
  189. const value = Math.floor(num / 10 ** decimal);
  190. return value * 10 ** decimal;
  191. }
  192.  
  193. function numberFormat(numberState) {
  194. const userLocales = navigator.language;
  195.  
  196. const formatter = Intl.NumberFormat(userLocales, {
  197. notation: "compact",
  198. minimumFractionDigits: 1,
  199. maximumFractionDigits: 1,
  200. });
  201.  
  202. return formatter.format(roundDown(numberState)).replace(/\.0|,0/, "");
  203. }
  204.  
  205. function getDislikesFromYoutubeResponse(htmlResponse) {
  206. let start =
  207. htmlResponse.indexOf('"videoDetails":') + '"videoDetails":'.length;
  208. let end =
  209. htmlResponse.indexOf('"isLiveContent":false}', start) +
  210. '"isLiveContent":false}'.length;
  211. if (end < start) {
  212. end =
  213. htmlResponse.indexOf('"isLiveContent":true}', start) +
  214. '"isLiveContent":true}'.length;
  215. }
  216. let jsonStr = htmlResponse.substring(start, end);
  217. let jsonResult = JSON.parse(jsonStr);
  218. let rating = jsonResult.averageRating;
  219.  
  220. start = htmlResponse.indexOf('"topLevelButtons":[', end);
  221. start =
  222. htmlResponse.indexOf('"accessibilityData":', start) +
  223. '"accessibilityData":'.length;
  224. end = htmlResponse.indexOf("}", start);
  225. let likes = +htmlResponse.substring(start, end).replace(/\D/g, "");
  226. let dislikes = (likes * (5 - rating)) / (rating - 1);
  227. let result = {
  228. likes,
  229. dislikes: Math.round(dislikes),
  230. rating,
  231. viewCount: +jsonResult.viewCount,
  232. };
  233. return result;
  234. }
  235.  
  236. function setEventListeners(evt) {
  237. function checkForJS_Finish() {
  238. if (getButtons()?.offsetParent && isVideoLoaded()) {
  239. clearInterval(jsInitChecktimer);
  240. const buttons = getButtons();
  241.  
  242. if (!window.returnDislikeButtonlistenersSet) {
  243. cLog("Registering button listeners...");
  244. buttons.children[0].addEventListener("click", likeClicked);
  245. buttons.children[1].addEventListener("click", dislikeClicked);
  246. window.returnDislikeButtonlistenersSet = true;
  247. }
  248. setInitalState();
  249. }
  250. }
  251.  
  252. if (window.location.href.indexOf("watch?") >= 0) {
  253. cLog("Setting up...");
  254. var jsInitChecktimer = setInterval(checkForJS_Finish, 111);
  255. }
  256. }
  257.  
  258. (function () {
  259. "use strict";
  260. window.addEventListener("yt-navigate-finish", setEventListeners, true);
  261. setEventListeners();
  262. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址