Return YouTube Dislike

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

当前为 2021-12-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Return YouTube Dislike
  3. // @namespace https://www.returnyoutubedislike.com/
  4. // @homepage https://www.returnyoutubedislike.com/
  5. // @version 0.9.0
  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. // @exclude *://music.youtube.com/*
  12. // @exclude *://*.music.youtube.com/*
  13. // @compatible chrome
  14. // @compatible firefox
  15. // @compatible opera
  16. // @compatible safari
  17. // @compatible edge
  18. // @grant GM.xmlHttpRequest
  19. // @connect youtube.com
  20. // @grant GM_addStyle
  21. // @run-at document-end
  22. // ==/UserScript==
  23. const LIKED_STATE = "LIKED_STATE";
  24. const DISLIKED_STATE = "DISLIKED_STATE";
  25. const NEUTRAL_STATE = "NEUTRAL_STATE";
  26. let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL
  27. let likesvalue = 0;
  28. let dislikesvalue = 0;
  29.  
  30. let isMobile = location.hostname == "m.youtube.com";
  31. let mobileDislikes = 0;
  32. function cLog(text, subtext = "") {
  33. subtext = subtext.trim() === "" ? "" : `(${subtext})`;
  34. console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
  35. }
  36.  
  37. function getButtons() {
  38. if (isMobile) {
  39. return document.querySelector(".slim-video-action-bar-actions");
  40. }
  41. if (document.getElementById("menu-container")?.offsetParent === null) {
  42. return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
  43. } else {
  44. return document
  45. .getElementById("menu-container")
  46. ?.querySelector("#top-level-buttons-computed");
  47. }
  48. }
  49.  
  50. function getLikeButton() {
  51. return getButtons().children[0];
  52. }
  53.  
  54. function getDislikeButton() {
  55. return getButtons().children[1];
  56. }
  57.  
  58. function isVideoLiked() {
  59. if (isMobile) {
  60. return (
  61. getLikeButton().querySelector("button").getAttribute("aria-label") ==
  62. "true"
  63. );
  64. }
  65. return getLikeButton().classList.contains("style-default-active");
  66. }
  67.  
  68. function isVideoDisliked() {
  69. if (isMobile) {
  70. return (
  71. getDislikeButton().querySelector("button").getAttribute("aria-label") ==
  72. "true"
  73. );
  74. }
  75. return getDislikeButton().classList.contains("style-default-active");
  76. }
  77.  
  78. function isVideoNotLiked() {
  79. if (isMobile) {
  80. return !isVideoLiked();
  81. }
  82. return getLikeButton().classList.contains("style-text");
  83. }
  84.  
  85. function isVideoNotDisliked() {
  86. if (isMobile) {
  87. return !isVideoDisliked();
  88. }
  89. return getDislikeButton().classList.contains("style-text");
  90. }
  91.  
  92. function checkForUserAvatarButton() {
  93. if (isMobile) {
  94. return;
  95. }
  96. if (document.querySelector('#avatar-btn')) {
  97. return true
  98. } else {
  99. return false
  100. }
  101. }
  102.  
  103. function getState() {
  104. if (isVideoLiked()) {
  105. return LIKED_STATE;
  106. }
  107. if (isVideoDisliked()) {
  108. return DISLIKED_STATE;
  109. }
  110. return NEUTRAL_STATE;
  111. }
  112.  
  113. function setLikes(likesCount) {
  114. if (isMobile) {
  115. getButtons().children[0].querySelector(".button-renderer-text").innerText =
  116. likesCount;
  117. return;
  118. }
  119. getButtons().children[0].querySelector("#text").innerText = likesCount;
  120. }
  121.  
  122. function setDislikes(dislikesCount) {
  123. if (isMobile) {
  124. mobileDislikes = dislikesCount;
  125. return;
  126. }
  127. getButtons().children[1].querySelector("#text").innerText = dislikesCount;
  128. }
  129.  
  130. (typeof GM_addStyle != "undefined"
  131. ? GM_addStyle
  132. : (styles) => {
  133. let styleNode = document.createElement("style");
  134. styleNode.type = "text/css";
  135. styleNode.innerText = styles;
  136. document.head.appendChild(styleNode);
  137. })(`
  138. #return-youtube-dislike-bar-container {
  139. background: var(--yt-spec-icon-disabled);
  140. border-radius: 2px;
  141. }
  142.  
  143. #return-youtube-dislike-bar {
  144. background: var(--yt-spec-text-primary);
  145. border-radius: 2px;
  146. transition: all 0.15s ease-in-out;
  147. }
  148.  
  149. .ryd-tooltip {
  150. position: relative;
  151. display: block;
  152. height: 2px;
  153. top: 9px;
  154. }
  155.  
  156. .ryd-tooltip-bar-container {
  157. width: 100%;
  158. height: 2px;
  159. position: absolute;
  160. padding-top: 6px;
  161. padding-bottom: 28px;
  162. top: -6px;
  163. }
  164. `);
  165.  
  166. function createRateBar(likes, dislikes) {
  167. if (isMobile) {
  168. return;
  169. }
  170. let rateBar = document.getElementById("return-youtube-dislike-bar-container");
  171.  
  172. const widthPx =
  173. getButtons().children[0].clientWidth +
  174. getButtons().children[1].clientWidth +
  175. 8;
  176.  
  177. const widthPercent =
  178. likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
  179.  
  180. if (!rateBar) {
  181. document.getElementById("menu-container").insertAdjacentHTML(
  182. "beforeend",
  183. `
  184. <div class="ryd-tooltip" style="width: ${widthPx}px">
  185. <div class="ryd-tooltip-bar-container">
  186. <div
  187. id="return-youtube-dislike-bar-container"
  188. style="width: 100%; height: 2px;"
  189. >
  190. <div
  191. id="return-youtube-dislike-bar"
  192. style="width: ${widthPercent}%; height: 100%"
  193. ></div>
  194. </div>
  195. </div>
  196. <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
  197. <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
  198. </tp-yt-paper-tooltip>
  199. </div>
  200. `
  201. );
  202. } else {
  203. document.getElementById(
  204. "return-youtube-dislike-bar-container"
  205. ).style.width = widthPx + "px";
  206. document.getElementById("return-youtube-dislike-bar").style.width =
  207. widthPercent + "%";
  208.  
  209. document.querySelector(
  210. "#ryd-dislike-tooltip > #tooltip"
  211. ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
  212. }
  213. }
  214.  
  215. function setState() {
  216. cLog("Fetching votes...");
  217. let statsSet = false;
  218.  
  219. fetch(
  220. `https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
  221. ).then((response) => {
  222. response.json().then((json) => {
  223. if (json && !("traceId" in response) && !statsSet) {
  224. const { dislikes, likes } = json;
  225. cLog(`Received count: ${dislikes}`);
  226. likesvalue = likes;
  227. dislikesvalue = dislikes;
  228. setDislikes(numberFormat(dislikes));
  229. createRateBar(likes, dislikes);
  230. }
  231. });
  232. });
  233. }
  234.  
  235. function likeClicked() {
  236. if (checkForUserAvatarButton() == true) {
  237. if (previousState == 1) {
  238. likesvalue--;
  239. createRateBar(likesvalue, dislikesvalue);
  240. setDislikes(numberFormat(dislikesvalue));
  241. previousState = 3
  242. } else if (previousState == 2) {
  243. likesvalue++;
  244. dislikesvalue--;
  245. setDislikes(numberFormat(dislikesvalue))
  246. createRateBar(likesvalue, dislikesvalue);
  247. previousState = 1
  248. } else if (previousState == 3) {
  249. likesvalue++;
  250. createRateBar(likesvalue, dislikesvalue)
  251. previousState = 1
  252. }
  253. }
  254. }
  255.  
  256. function dislikeClicked() {
  257. if (checkForUserAvatarButton() == true) {
  258. if (previousState == 3) {
  259. dislikesvalue++;
  260. setDislikes(numberFormat(dislikesvalue));
  261. createRateBar(likesvalue, dislikesvalue);
  262. previousState = 2
  263. } else if (previousState == 2) {
  264. dislikesvalue--;
  265. setDislikes(numberFormat(dislikesvalue));
  266. createRateBar(likesvalue, dislikesvalue);
  267. previousState = 3
  268. } else if (previousState == 1) {
  269. likesvalue--;
  270. dislikesvalue++;
  271. setDislikes(numberFormat(dislikesvalue));
  272. createRateBar(likesvalue, dislikesvalue);
  273. previousState = 2
  274. }
  275. }
  276. }
  277.  
  278. function setInitialState() {
  279. setState();
  280. }
  281.  
  282. function getVideoId() {
  283. const urlObject = new URL(window.location.href);
  284. const pathname = urlObject.pathname;
  285. if (pathname.startsWith("/clip")) {
  286. return document.querySelector("meta[itemprop='videoId']").content;
  287. } else {
  288. return urlObject.searchParams.get("v");
  289. }
  290. }
  291.  
  292. function isVideoLoaded() {
  293. if (isMobile) {
  294. return document.getElementById("player").getAttribute("loading") == "false";
  295. }
  296. const videoId = getVideoId();
  297.  
  298. return (
  299. document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null
  300. );
  301. }
  302.  
  303. function roundDown(num) {
  304. if (num < 1000) return num;
  305. const int = Math.floor(Math.log10(num) - 2);
  306. const decimal = int + (int % 3 ? 1 : 0);
  307. const value = Math.floor(num / 10 ** decimal);
  308. return value * 10 ** decimal;
  309. }
  310.  
  311. function numberFormat(numberState) {
  312. let localeURL = Array.from(document.querySelectorAll("head > link[rel='search']"))
  313. ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
  314. ?.getAttribute("href");
  315.  
  316. const userLocales = localeURL ? new URL(localeURL)?.searchParams?.get("locale") : document.body.lang;
  317.  
  318. const formatter = Intl.NumberFormat(
  319. document.documentElement.lang || userLocales || navigator.language,
  320. {
  321. notation: "compact",
  322. minimumFractionDigits: 1,
  323. maximumFractionDigits: 1,
  324. }
  325. );
  326.  
  327. return formatter.format(roundDown(numberState)).replace(/\.0|,0/, "");
  328. }
  329.  
  330. function getDislikesFromYoutubeResponse(htmlResponse) {
  331. let start =
  332. htmlResponse.indexOf('"videoDetails":') + '"videoDetails":'.length;
  333. let end =
  334. htmlResponse.indexOf('"isLiveContent":false}', start) +
  335. '"isLiveContent":false}'.length;
  336. if (end < start) {
  337. end =
  338. htmlResponse.indexOf('"isLiveContent":true}', start) +
  339. '"isLiveContent":true}'.length;
  340. }
  341. let jsonStr = htmlResponse.substring(start, end);
  342. let jsonResult = JSON.parse(jsonStr);
  343. let rating = jsonResult.averageRating;
  344.  
  345. start = htmlResponse.indexOf('"topLevelButtons":[', end);
  346. start =
  347. htmlResponse.indexOf('"accessibilityData":', start) +
  348. '"accessibilityData":'.length;
  349. end = htmlResponse.indexOf("}", start);
  350. let likes = +htmlResponse.substring(start, end).replace(/\D/g, "");
  351. let dislikes = (likes * (5 - rating)) / (rating - 1);
  352. let result = {
  353. likes,
  354. dislikes: Math.round(dislikes),
  355. rating,
  356. viewCount: +jsonResult.viewCount,
  357. };
  358. return result;
  359. }
  360.  
  361. function setEventListeners(evt) {
  362. let jsInitChecktimer;
  363.  
  364. function checkForJS_Finish(check) {
  365. console.log();
  366. if (getButtons()?.offsetParent && isVideoLoaded()) {
  367. clearInterval(jsInitChecktimer);
  368. const buttons = getButtons();
  369.  
  370. if (!window.returnDislikeButtonlistenersSet) {
  371. cLog("Registering button listeners...");
  372. buttons.children[0].addEventListener("click", likeClicked);
  373. buttons.children[1].addEventListener("click", dislikeClicked);
  374. window.returnDislikeButtonlistenersSet = true;
  375. }
  376. setInitialState();
  377. }
  378. }
  379.  
  380. if (
  381. window.location.href.indexOf("watch?") >= 0 ||
  382. (isMobile && evt?.indexOf("watch?") >= 0)
  383. ) {
  384. cLog("Setting up...");
  385. jsInitChecktimer = setInterval(checkForJS_Finish, 111);
  386. }
  387. }
  388.  
  389. (function () {
  390. "use strict";
  391. window.addEventListener("yt-navigate-finish", setEventListeners, true);
  392. setEventListeners();
  393. })();
  394. if (isMobile) {
  395. let originalPush = history.pushState;
  396. history.pushState = function (...args) {
  397. window.returnDislikeButtonlistenersSet = false;
  398. setEventListeners(args[2]);
  399. return originalPush.apply(history, args);
  400. };
  401. setInterval(() => {
  402. getDislikeButton().querySelector(".button-renderer-text").innerText =
  403. mobileDislikes;
  404. }, 1000);
  405. }

QingJ © 2025

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