您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add (dis)like percentage to Youtube video descriptions. Dislike count is provided by https://www.returnyoutubedislike.com/
// ==UserScript== // @name Show Youtube like/dislike ratios in video descriptions // @license MIT // @namespace https://github.com/2chen // @version 1.14 // @description Add (dis)like percentage to Youtube video descriptions. Dislike count is provided by https://www.returnyoutubedislike.com/ // @author Yichen // @icon https://www.google.com/s2/favicons?sz=64&domain=gf.qytechs.cn // @match https://*.youtube.com/* // @grant unsafeWindow // @run-at document-end // @connect returnyoutubedislikeapi.com // ==/UserScript== (function() { 'use strict'; console.info("YICHEN's super cool rating script"); let shouldBackOff = false; const API_RATE_LIMIT_PER_MINUTE = 100; const counts = { msgs: 0, errors: 0 }; const getRating = async (id) => { // we can't be above this limit, so assume caching if (counts.msgs < API_RATE_LIMIT_PER_MINUTE) { counts.msgs++; } setTimeout(() => { counts.msgs = Math.max(0, counts.msgs - 1); }, 60_000); let response; try { response = await fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${id}`, {cache: "force-cache"}); const { likes, dislikes } = await response.json(); counts.errors = Math.max(0, counts.errors - 1); const percent = 100 * likes / (likes + dislikes); return { likes, dislikes, percent }; } catch (e) { console.debug("YICHEN error in getRating", ++counts.errors, response, e); shouldBackOff = true; counts.msgs = 100; } } const TYPES = {homepage: "homepage", shorts: "shorts", channel: "channel", theater: "theater", search: "search"}; const LOADING_ID = "loading!"; const LOADING_TEXT = "??.?%"; const handleNode = async (type, videoNode, descriptionEl, videoId) => { try { if (videoId == null) { console.error("YICHEN videoId null for type {} node {}. this is an error with the application)", type, videoNode); return; } if (descriptionEl == null) { // remove stale rating el videoNode.parentElement.parentElement.querySelector(".yichen-rating")?.remove(); return; } let ratingEl = descriptionEl.parentElement.getElementsByClassName("yichen-rating")[0]; // set loading state if (ratingEl == null) { ratingEl = document.createElement("span"); ratingEl.className = "yichen-rating"; ratingEl.dataset.videoId = LOADING_ID; ratingEl.innerText = LOADING_TEXT; descriptionEl.parentElement.insertBefore(ratingEl, descriptionEl); // hackily remove "streamed" const ageNode = descriptionEl.parentElement.querySelector("span:last-of-type"); let ageText = undefined; if (ageNode != null) { // avoid overflows if (ageNode.parentElement.style.fontSize === "1.2em") { ageNode.parentElement.style.fontSize = "1.1em"; } ageText = ageNode.textContent; const numIndex = ageText.match("[0-9]")?.index; ageText = !numIndex ? undefined : ageText.substring(numIndex); } if (ageText != null) { const streamIconDiv = '<div style="width: 20px; height: 100%; fill: gray"><svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" width="16" focusable="false" style="pointer-events: none; display: block; width: 100%; height: 100%;"><path d="M9 8c0 .55-.45 1-1 1s-1-.45-1-1 .45-1 1-1 1 .45 1 1Zm1.11 2.13.71.71C11.55 10.11 12 9.11 12 8c0-1.11-.45-2.11-1.18-2.84l-.71.71c.55.55.89 1.3.89 2.13 0 .83-.34 1.58-.89 2.13Zm-4.93.71.71-.71C5.34 9.58 5 8.83 5 8c0-.83.34-1.58.89-2.13l-.71-.71C4.45 5.89 4 6.89 4 8c0 1.11.45 2.11 1.18 2.84Zm7.05 1.41.71.71C14.21 11.69 15 9.94 15 8s-.79-3.69-2.06-4.96l-.71.71C13.32 4.84 14 6.34 14 8c0 1.66-.68 3.16-1.77 4.25Zm-9.17.71.71-.71C2.68 11.16 2 9.66 2 8c0-1.66.68-3.16 1.77-4.25l-.71-.71C1.79 4.31 1 6.06 1 8s.79 3.69 2.06 4.96Z"></path></svg></div>'; ageNode.innerHTML = `<div style="display: inline-flex"><span>​</span>${streamIconDiv}<span> ${ageText}</span></div>`; // we don't want ellpsis if (type === TYPES.theater) { ageNode.parentElement.style.whiteSpace = "nowrap"; } } } else if (ratingEl.dataset.videoId === videoId) { return; } else if (ratingEl.dataset.videoId !== LOADING_ID) { ratingEl.dataset.videoId = LOADING_ID; ratingEl.innerText = LOADING_TEXT; } if (shouldBackOff) return; const ratings = await getRating(videoId); if (ratings == null) return; const { likes, dislikes, percent } = ratings; ratingEl.removeChild(ratingEl.firstChild); ratingEl.title = `${likes} / ${dislikes}`; ratingEl.innerText = `${percent.toFixed(1)}%`; ratingEl.dataset.videoId = videoId; } catch (e) { console.error("YICHEN unexpected error in handleNode", e); } }; let logCounter = 0; let backOffTimeout; unsafeWindow.addEventListener('load', () => { if (unsafeWindow.location.href.startsWith("https://accounts.youtube")) { return; } console.info(`YICHEN starting main loop for href {}`, unsafeWindow.location.href); setInterval(() => { if (++logCounter % 5 === 0) { console.debug("YICHEN messages in last min", counts.msgs); } if (shouldBackOff && backOffTimeout == null) { const backoffSeconds = 2**Math.min(5,counts.errors); console.warn("YICHEN backing off for {} seconds due to {} errors", backoffSeconds, counts.errors); counts.errors = 0; backOffTimeout = setTimeout(() => { shouldBackOff = false; backOffTimeout = undefined; }, backoffSeconds * 1_000); } let videoNodes = document.querySelectorAll("#video-title-link"); videoNodes.forEach(videoNode => handleNode( TYPES.homepage, videoNode, videoNode.parentElement.parentElement.querySelector(".inline-metadata-item"), videoNode.href.substring(32, 32+11)) ); videoNodes = document.querySelectorAll("ytd-rich-grid-slim-media"); videoNodes.forEach(videoNode => handleNode( TYPES.shorts, videoNode, videoNode.querySelector("#metadata span.ytd-video-meta-block"), videoNode.querySelector("a").href.substring(31, 31+11)) ); videoNodes = document.querySelectorAll("ytd-grid-video-renderer"); videoNodes.forEach(videoNode => handleNode( TYPES.channel, videoNode, videoNode.querySelector("#text-metadata span.ytd-grid-video-renderer"), videoNode.querySelector("a").href.substring(32, 32+11)) ); videoNodes = document.querySelectorAll("ytd-compact-video-renderer"); videoNodes.forEach(videoNode => handleNode( TYPES.theater, videoNode, videoNode.querySelector("#metadata-line span.inline-metadata-item"), videoNode.querySelector("a").href.substring(32, 32+11)) ); videoNodes = document.querySelectorAll("a#video-title.ytd-video-renderer"); videoNodes.forEach(videoNode => handleNode( TYPES.search, videoNode, videoNode.parentElement.parentElement.parentElement.querySelector(".inline-metadata-item"), videoNode.href.substring(32, 32+11)) ); }, 1_000); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址