您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Browser userscript that shows follower count next to channel name in a twitch channel page.
当前为
// ==UserScript== // @name Twitch Follower Count // @namespace https://github.com/aranciro/ // @version 0.1.14 // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt // @description Browser userscript that shows follower count next to channel name in a twitch channel page. // @author aranciro // @homepage https://github.com/aranciro/Twitch-Follower-Count // @supportURL https://github.com/aranciro/Twitch-Follower-Count/issues // @icon https://github.com/aranciro/Twitch-Follower-Count/raw/master/res/twitch-follower-count-icon32.png // @icon64 https://github.com/aranciro/Twitch-Follower-Count/raw/master/res/twitch-follower-count-icon64.png // @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @include *://*.twitch.tv/* // @run-at document-idle // ==/UserScript== const configLiterals = { smallFontSize: "Small", mediumFontSize: "Medium", bigFontSize: "Big", positionChannelName: "Next to channel name", positionFollowButton: "Left of the follow button", }; GM_config.init({ id: "Twitch_Follower_Count_config", title: "Twitch Follower Count - Configuration", fields: { fontSize: { label: "Font size", type: "select", options: [ configLiterals.smallFontSize, configLiterals.mediumFontSize, configLiterals.bigFontSize, ], default: configLiterals.mediumFontSize, title: "Select the follower count font size", }, position: { label: "Position", type: "select", options: [ configLiterals.positionChannelName, configLiterals.positionFollowButton, ], default: configLiterals.positionChannelName, title: "Select where the follower count should appear", }, localeString: { type: "checkbox", default: true, label: "Format the follower count (puts thousands separator etc.)", title: "Uncheck if you don't want the follower count to have separator for thousands", }, enclosed: { type: "checkbox", default: false, label: "Parenthesize the follower count", title: "Parenthesize the follower count", }, }, }); GM_registerMenuCommand("Configure Twitch Follower Count", () => { GM_config.open(); }); const fontSizeMap = { [configLiterals.smallFontSize]: "5", [configLiterals.mediumFontSize]: "4", [configLiterals.bigFontSize]: "3", }; const config = { fontSize: fontSizeMap[GM_config.get("fontSize")], insertNextToFollowButton: configLiterals.positionFollowButton === GM_config.get("position"), localeString: GM_config.get("localeString"), enclosed: GM_config.get("enclosed"), }; let currentChannel = ""; const channelNameNodeSelector = "div.tw-align-items-center.tw-flex > a > h1"; const channelPartnerBadgeNodeSelector = "div.tw-align-items-center.tw-flex > div.tw-align-items-center.tw-c-text-link.tw-flex.tw-full-height.tw-mg-l-05 > figure > svg"; const divWithButtonsSelector = "div.metadata-layout__support.tw-align-items-baseline.tw-flex.tw-flex-wrap-reverse.tw-justify-content-between > div.tw-flex.tw-flex-grow-1.tw-justify-content-end"; const updatingCounterAnimationCSS = ".updating-counter {\r\n animation: blinker 1s linear infinite;\r\n}\r\n\r\n@keyframes blinker { \r\n 50% { opacity: 0; }\r\n}"; const followerCountNodeName = "ChannelFollowerCount"; const run = () => { const updatingCounterStyleNode = document.createElement("style"); updatingCounterStyleNode.innerHTML = updatingCounterAnimationCSS; document.head.appendChild(updatingCounterStyleNode); const channelNameNode = document.querySelector(channelNameNodeSelector); if (channelNameNode) { const channelName = channelNameNode.innerText; const followerCountNodes = document.getElementsByName( followerCountNodeName ); const followerCountNodesExist = followerCountNodes.length > 0; if (currentChannel !== channelName || !followerCountNodesExist) { if (followerCountNodesExist) { followerCountNodes[0].classList.add("updating-counter"); } currentChannel = channelName; getFollowerCount(channelName) .then((response) => handleFollowerCountAPIResponse(response)) .catch((error) => { console.log("Error while fetching follower count"); console.log(error); }); } } }; (() => { console.log("Twitch Follower Count userscript - START"); try { run(); setInterval(function () { run(); }, 5000); } catch (e) { console.log("Twitch Follower Count userscript - STOP (EXCEPTION) "); console.log(e); } })(); const responseIsValid = (response) => { response && Array.isArray(response) && response.length > 0 && "data" in response[0] && "user" in response[0].data && "followers" in response[0].data.user && "totalCount" in response[0].data.user.followers && response[0].data.user.followers.totalCount; }; const handleFollowerCountAPIResponse = (response) => { const followers = response[0].data.user.followers.totalCount; const followerCountNodes = document.getElementsByName(followerCountNodeName); const followerCountNodesExist = followerCountNodes.length > 0; if (followerCountNodesExist) { followerCountNodes.forEach((fcNode) => fcNode.remove()); } insertFollowerCountNode(followers); }; const getFollowerCount = async (channelName) => { const url = "https://gql.twitch.tv/gql"; const requestBody = JSON.stringify([ { operationName: "ChannelPage_ChannelFollowerCount", variables: { login: channelName, }, extensions: { persistedQuery: { version: 1, sha256Hash: "87f496584ac60bcfb00db2ce59054b73155f297f1796e5e2418d685213233ad9", }, }, }, ]); const followerCountResponse = await fetch(url, { method: "POST", headers: { "Content-type": "application/json", "Client-Id": "kimne78kx3ncx6brgo4mv6wki5h1ko", }, body: requestBody, }); if (followerCountResponse.status == 200) { let followerCountResponseBody = await followerCountResponse.json(); if (responseIsValid) { return followerCountResponseBody; } } else { console.log( `Endpoint responded with status: ${followerCountResponse.status}` ); } throw new Error(followerCountResponse); }; const insertFollowerCountNode = (followers) => { const channelNameNode = document.querySelector(channelNameNodeSelector); const channelPartnerBadgeNode = document.querySelector( channelPartnerBadgeNodeSelector ); let followersText = followers; if (config.localeString) { followersText = Number(followersText).toLocaleString(); } if (config.enclosed) { followersText = `(${followersText})`; } const followerCountTextNode = document.createTextNode(followersText); const followerCountNode = document.createElement("h2"); followerCountNode.setAttribute("name", followerCountNodeName); followerCountNode.setAttribute( "class", `tw-c-text-alt-2 tw-font-size-${config.fontSize} tw-semibold` ); followerCountNode.setAttribute( "style", "margin-left:10px;margin-right:10px;display:inline-block;" ); followerCountNode.appendChild(followerCountTextNode); channelNameNode.style.display = "inline-block"; if (config.insertNextToFollowButton) { const divWithButtons = document.querySelector(divWithButtonsSelector); const followerCountContainerNode = document.createElement("div"); followerCountContainerNode.setAttribute( "style", "display:flex;align-items:center;" ); followerCountContainerNode.appendChild(followerCountNode); divWithButtons.insertBefore( followerCountContainerNode, divWithButtons.firstChild ); } else if (channelPartnerBadgeNode) { channelPartnerBadgeNode.parentNode.insertBefore( followerCountNode, channelPartnerBadgeNode.nextSibling ); } else { channelNameNode.parentNode.insertBefore( followerCountNode, channelNameNode.nextSibling ); } };
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址