return-youtube-dislike.script.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. var LIKED_STATE = "LIKED_STATE";
  2. var DISLIKED_STATE = "DISLIKED_STATE";
  3. var NEUTRAL_STATE = "NEUTRAL_STATE";
  4. if (!storedData) {
  5. var storedData = {
  6. likes: 0,
  7. dislikes: 0,
  8. previousState: NEUTRAL_STATE,
  9. };
  10. }
  11. function cLog(message, writer) {
  12. message = `[return youtube dislike]: ${message}`;
  13. if (writer) {
  14. writer(message);
  15. } else {
  16. console.log(message);
  17. }
  18. }
  19. function getButtons() {
  20. let menu_container = document.getElementById("menu-container");
  21. //--- m.youtube.com: ---//
  22. if (menu_container === null) {
  23. return document.querySelector(".slim-video-action-bar-actions");
  24. //--- If Menu Element Is Displayed: ---//
  25. } else if (menu_container.offsetParent === null) {
  26. return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
  27. //--- If Menu Element Isnt Displayed: ---//
  28. } else {
  29. return menu_container.querySelector("#top-level-buttons-computed");
  30. }
  31. }
  32. function getLikeButton() {
  33. return getButtons().children[0];
  34. }
  35. function getDislikeButton() {
  36. return getButtons().children[1];
  37. }
  38. function isVideoLiked() {
  39. return getLikeButton().classList.contains("style-default-active")
  40. || getLikeButton().querySelector('[aria-pressed="true"]') !== null;
  41. }
  42. function isVideoDisliked() {
  43. return getDislikeButton().classList.contains("style-default-active")
  44. || getDislikeButton().querySelector('[aria-pressed="true"]') !== null;
  45. }
  46. function isVideoNotLiked() {
  47. return getLikeButton().classList.contains("style-text")
  48. || getLikeButton().querySelector('[aria-pressed="false"]') !== null;
  49. }
  50. function isVideoNotDisliked() {
  51. return getDislikeButton().classList.contains("style-text")
  52. || getDislikeButton().querySelector('[aria-pressed="false"]') !== null;
  53. }
  54. function checkForUserAvatarButton() {
  55. if (document.querySelector('#avatar-btn')) {
  56. return true
  57. } else {
  58. return false
  59. }
  60. }
  61. function getState() {
  62. if (isVideoLiked()) {
  63. return { current: LIKED_STATE, previous: storedData.previousState };
  64. }
  65. if (isVideoDisliked()) {
  66. return { current: DISLIKED_STATE, previous: storedData.previousState };
  67. }
  68. return { current: NEUTRAL_STATE, previous: storedData.previousState };
  69. }
  70. //--- Sets The Likes And Dislikes Values ---//
  71. function setLikes(likesCount) {
  72. getLikeButton().querySelector("#text, .button-renderer-text").innerText = likesCount;
  73. }
  74. function setDislikes(dislikesCount) {
  75. getDislikeButton().querySelector("#text, .button-renderer-text").innerText = dislikesCount;
  76. }
  77. function setState() {
  78. let statsSet = false;
  79. browser.runtime.sendMessage(
  80. {
  81. message: "fetch_from_youtube",
  82. videoId: getVideoId(window.location.href),
  83. },
  84. function (response) {
  85. if (response != undefined) {
  86. cLog("response from youtube:");
  87. cLog(JSON.stringify(response));
  88. try {
  89. if ("likes" in response && "dislikes" in response) {
  90. const formattedDislike = numberFormat(response.dislikes);
  91. setDislikes(formattedDislike);
  92. storedData.dislikes = parseInt(response.dislikes);
  93. storedData.likes = parseInt(response.likes)
  94. createRateBar(response.likes, response.dislikes);
  95. statsSet = true;
  96. }
  97. } catch (e) {
  98. statsSet = false;
  99. }
  100. }
  101. }
  102. );
  103. browser.runtime.sendMessage(
  104. {
  105. message: "set_state",
  106. videoId: getVideoId(window.location.href),
  107. state: getState().current,
  108. },
  109. function (response) {
  110. cLog("response from api:");
  111. cLog(JSON.stringify(response));
  112. if (response != undefined && !("traceId" in response) && !statsSet) {
  113. const formattedDislike = numberFormat(response.dislikes);
  114. storedData.dislikes = response.dislikes;
  115. // setLikes(response.likes);
  116. console.log(response);
  117. setDislikes(formattedDislike);
  118. createRateBar(response.likes, response.dislikes);
  119. } else {
  120. }
  121. }
  122. );
  123. }
  124. function likeClicked() {
  125. if (checkForUserAvatarButton() == true) {
  126. if (storedData.previousState == DISLIKED_STATE) {
  127. storedData.dislikes--;
  128. storedData.likes++;
  129. createRateBar(storedData.likes, storedData.dislikes);
  130. setDislikes(numberFormat(storedData.dislikes));
  131. storedData.previousState = LIKED_STATE;
  132. } else if (storedData.previousState == NEUTRAL_STATE) {
  133. storedData.likes++;
  134. createRateBar(storedData.likes, storedData.dislikes);
  135. storedData.previousState = LIKED_STATE;
  136. } else if (storedData.previousState = LIKED_STATE) {
  137. storedData.likes--;
  138. createRateBar(storedData.likes, storedData.dislikes)
  139. storedData.previousState = NEUTRAL_STATE;
  140. }
  141. }
  142. }
  143. function dislikeClicked() {
  144. if (checkForUserAvatarButton() == true) {
  145. if (storedData.previousState == NEUTRAL_STATE) {
  146. storedData.dislikes++;
  147. setDislikes(numberFormat(storedData.dislikes));
  148. createRateBar(storedData.likes, storedData.dislikes);
  149. storedData.previousState = DISLIKED_STATE;
  150. } else if (storedData.previousState == DISLIKED_STATE) {
  151. storedData.dislikes--;
  152. setDislikes(numberFormat(storedData.dislikes));
  153. createRateBar(storedData.likes, storedData.dislikes);
  154. storedData.previousState = NEUTRAL_STATE;
  155. } else if (storedData.previousState == LIKED_STATE) {
  156. storedData.likes--;
  157. storedData.dislikes++;
  158. setDislikes(numberFormat(storedData.dislikes));
  159. createRateBar(storedData.likes, storedData.dislikes);
  160. storedData.previousState = DISLIKED_STATE;
  161. }
  162. }
  163. }
  164. function setInitialState() {
  165. setState();
  166. // setTimeout(() => sendVideoIds(), 1500);
  167. }
  168. function getVideoId(url) {
  169. const urlObject = new URL(url);
  170. const pathname = urlObject.pathname;
  171. if (pathname.startsWith("/clip")) {
  172. return document.querySelector("meta[itemprop='videoId']").content;
  173. } else {
  174. return urlObject.searchParams.get("v");
  175. }
  176. }
  177. function isVideoLoaded() {
  178. const videoId = getVideoId(window.location.href);
  179. return (
  180. document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||
  181. // mobile: no video-id attribute
  182. document.querySelector('#player[loading="false"]:not([hidden])') !== null
  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. function numberFormat(numberState) {
  193. const userLocales = new URL(
  194. Array.from(document.querySelectorAll("head > link[rel='search']"))
  195. ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
  196. ?.getAttribute("href")
  197. )?.searchParams?.get("locale");
  198. const formatter = Intl.NumberFormat(document.documentElement.lang || userLocales || navigator.language, {
  199. notation: "compact",
  200. });
  201. return formatter.format(roundDown(numberState));
  202. }
  203. function setEventListeners(evt) {
  204. let jsInitChecktimer;
  205. function checkForJS_Finish() {
  206. if (getButtons()?.offsetParent && isVideoLoaded()) {
  207. clearInterval(jsInitChecktimer);
  208. const buttons = getButtons();
  209. if (!window.returnDislikeButtonlistenersSet) {
  210. getLikeButton().addEventListener("click", likeClicked);
  211. getDislikeButton().addEventListener("click", dislikeClicked);
  212. let lastKnownScrollPosition = 0;
  213. let ticking = false;
  214. // document.addEventListener('scroll', function(e) {
  215. // lastKnownScrollPosition = window.scrollY;
  216. //
  217. // if (!ticking) {
  218. // window.requestAnimationFrame(function() {
  219. // // sendVideoIds();
  220. // ticking = false;
  221. // });
  222. //
  223. // ticking = true;
  224. // }
  225. // });
  226. window.returnDislikeButtonlistenersSet = true;
  227. }
  228. setInitialState();
  229. }
  230. }
  231. if (window.location.href.indexOf("watch?") >= 0) {
  232. jsInitChecktimer = setInterval(checkForJS_Finish, 111);
  233. }
  234. }
  235. function createRateBar(likes, dislikes) {
  236. let rateBar = document.getElementById("return-youtube-dislike-bar-container");
  237. const widthPx =
  238. getLikeButton().clientWidth +
  239. getDislikeButton().clientWidth +
  240. 8;
  241. const widthPercent =
  242. likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
  243. if (!rateBar) {
  244. (
  245. document.getElementById("actions-inner") ||
  246. document.getElementById("menu-container") ||
  247. document.querySelector("ytm-slim-video-action-bar-renderer")
  248. ).insertAdjacentHTML(
  249. "beforeend",
  250. `
  251. <div class="ryd-tooltip" style="width: ${widthPx}px">
  252. <div class="ryd-tooltip-bar-container">
  253. <div
  254. id="return-youtube-dislike-bar-container"
  255. style="width: 100%; height: 2px;"
  256. >
  257. <div
  258. id="return-youtube-dislike-bar"
  259. style="width: ${widthPercent}%; height: 100%"
  260. ></div>
  261. </div>
  262. </div>
  263. <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
  264. <div>
  265. <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
  266. </tp-yt-paper-tooltip>
  267. </div>
  268. `
  269. );
  270. } else {
  271. document.getElementById(
  272. "return-youtube-dislike-bar-container"
  273. ).style.width = widthPx + "px";
  274. document.getElementById("return-youtube-dislike-bar").style.width =
  275. widthPercent + "%";
  276. document.querySelector(
  277. "#ryd-dislike-tooltip > #tooltip"
  278. ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
  279. }
  280. }
  281. function sendVideoIds() {
  282. let links = Array.from(
  283. document.getElementsByClassName(
  284. "yt-simple-endpoint ytd-compact-video-renderer"
  285. )
  286. ).concat(
  287. Array.from(
  288. document.getElementsByClassName("yt-simple-endpoint ytd-thumbnail")
  289. )
  290. );
  291. // Also try mobile
  292. if (links.length < 1) links = Array.from(
  293. document.querySelectorAll(".large-media-item-metadata > a, a.large-media-item-thumbnail-container")
  294. );
  295. const ids = links.filter((x) => x.href && x.href.indexOf("/watch?v=") > 0)
  296. .map((x) => getVideoId(x.href));
  297. browser.runtime.sendMessage({
  298. message: "send_links",
  299. videoIds: ids,
  300. });
  301. }
  302. setEventListeners();
  303. setTimeout(() => sendVideoIds(), 1500);