您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Get Chaturbate token stats for individual rooms.
// ==UserScript== // @name ChaturbateTokenStats // @namespace http://tampermonkey.net/ // @version 2025.10.21 // @description Get Chaturbate token stats for individual rooms. // @author nyoob/seraphine24 // @match https://chaturbate.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=chaturbate.com // @grant none // @require https://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== // BOC config const groups = { // add more groups like this: // groupname: ["username", "username2"], vtuber: ["emyliveshow", "kajira_kumiho", "kyrawildofficial", "tadakonyanko", "babydollstarlit", "Xstaceyliciousx", "Kittenlush", "Smartfeeling", "animecutie", "projectmelody", "skyeanette", "zonetron"], } // EOC config const userToGroupMap = {}; for (const groupName in groups) { groups[groupName].forEach(user => { userToGroupMap[user] = groupName; }); } const div = ` <div style="background-color: crimson; height: auto; width: 100%; position: static; overflow: hidden; display: block; padding: 5px 0px; text-align: center; box-sizing: border-box; font-size: 14px; font-weight: 400; font-family: UbuntuMedium, Helvetica, Arial, sans-serif; color: rgb(73, 73, 73);" ts="p" class="siteNotice"> <div class="wrapper seratkstats" style="background-color: darksalmon; padding: 15px; border-width: 1px; border-style: solid;"> <h1 style="font-size: 150%">Stop spending money on porn! You can watch for free.</h1> <div>Imagine what you could do with that money — an unforgettable trip, a gift that would make your mother smile, a home that finally feels complete, a car that’s truly yours.<br /> <b>Now imagine throwing all of that away for pixels that pretend to care..</b></div> <div>Chaturbate isn’t harmless fun — <b>it’s engineered addiction</b>. It drains your wallet, your time, your confidence. Like a casino built out of loneliness.<br /> Every tip, every private show, every “hey baby” is a calculated hook, designed to keep you chasing validation that doesn’t exist.</div> <div>The affection you think you’re buying? It’s scripted. The connection you feel? Manufactured.<br /> They’ve learned exactly how to make you feel special — just enough to keep you spending, never enough to make you whole.</div> <div><b>This isn’t intimacy. It’s psychological exploitation disguised as attention.</b><br /> It’s dopamine on demand — <b>stronger than gambling, almost as binding as heroin.</b> Except this one doesn’t just empty your account — it empties you.</div> <div>Check out: <a href="https://www.youtube.com/watch?v=Y0zePr-5ilE">Larry Wheels on camgirl addiction</a> | <a href="https://easypeasymethod.org">EasyPeasyMethod</a> | <a href="https://www.nofap.com/wp-content/uploads/2016/12/Getting-Started-with-NoFap.pdf">Nofap</a></div> <button class="seraBtn" onclick="getTkStats()">Load token stats</button> <button class="seraBtn seraDlBtn" onclick="dlTkTx()" disabled>Download Tx JSON</button> <button class="seraBtn seraDlBtn" onclick="dlTkTx(true)" disabled>Download Tx CSV</button> </div> </div> `; const styles = ` #site_notices .wrapper > div { margin-top: 8px; } .loadStats { color: blue; } .seratable { display: flex; justify-content: center; } .seratable td, .seratable th { border-bottom: 1px solid white; } .seraBtn { padding: 6px 12px; background-color: orange; border-radius: 8px; border: 1px solid pink; margin-top: 8px; } .seraBtn:disabled { background-color: gray; } `; window.getTkStats = () => { var jso = JSON.parse(localStorage.getItem("seraTkStats")) var all = []; if(jso) { all = [...jso]; } function loadMore(last_tx_id) { var params = ""; if(last_tx_id != null) { params += "?max_transaction_id=" + last_tx_id + "&cashpage=0" } fetch("/api/ts/tipping/token-stats/" + params) .then((r) => r.json()) .then((r) => { all = [...all, ...Object.values(r.transactions)]; if(!r.txns_fully_loaded) { loadMore(r.transactions[r.transactions.length - 1].id) } else { const unique = Array.from(new Map(all.map(i => [i.id, i])).values()); // make unique by id, in order not to get accidental duplicates, but still append new ones, even after switching accounts localStorage.setItem("seraTkStats", JSON.stringify(unique)); alert("finished loading tk stats"); window.location.reload(); } }) } loadMore(); } window.dlTkTx = (csv = false) => { const stats = localStorage.getItem("seraTkStats"); var dataStr; if(csv) { dataStr = "data:text/csv;charset=utf-8," + encodeURIComponent(jsonToCsv(JSON.parse(stats))); } else { dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(stats); } var downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", "transactions." + (csv ? "csv" : "json")); document.body.appendChild(downloadAnchorNode); // required for firefox downloadAnchorNode.click(); downloadAnchorNode.remove(); } const calculateTotalStats = () => { const data = JSON.parse(localStorage.getItem("seraTkStats")); var totalSpent = 0; var spentByUser = {}; var spentByGroup = {}; data.forEach((e) => { if(e.description == "Tokens purchased") return; totalSpent += Math.abs(e.tokens); // by user var username = e.username; if(e.description == "Spy on private show" || e.description == "Private show") { username = e.broadcaster_username; } if(!spentByUser[username]) { spentByUser[username] = 0; } spentByUser[username] += Math.abs(e.tokens); // by group const group = userToGroupMap[username] ?? "ungrouped"; if(!spentByGroup[group]) { spentByGroup[group] = 0; } spentByGroup[group] += Math.abs(e.tokens); }); spentByUser = Object.entries(spentByUser).sort(([,a],[,b]) => b-a); spentByGroup = Object.entries(spentByGroup).sort(([,a],[,b]) => b-a); return {totalSpent, spentByUser, spentByGroup}; } const tksToDollar = (tks) => { const minPrice = 0.079 const maxPrice = 0.109 return {min: (tks * minPrice).toFixed(2), max: (tks * maxPrice).toFixed(2)}; } function jsonToCsv(data) { const headers = Object.keys(data[0]); const replacer = (key, value) => value ?? ''; // handle null/undefined const csvRows = data.map(row => headers.map(fieldName => JSON.stringify(row[fieldName], replacer)).join(',') ); return [headers.join(','), ...csvRows].join('\r\n'); } (function() { // div $("#site_notices").append(div); // style $('html > head').append(`<style>${styles}</style>`); // stats const totalStats = calculateTotalStats(); const totalSpentInDollar = tksToDollar(totalStats.totalSpent); if(!totalStats) { $("#site_notices .wrapper").append(` <div class="seraAlert">Please load token stats. You have to be logged in to do that.<br/>Detailed stats will be shown after loading.</div> `) } else { $(".seraDlBtn").prop('disabled', false); $("#site_notices .wrapper").append(` <div>Total spent: ${totalStats.totalSpent}tks (in dollars: ${totalSpentInDollar.min}-${totalSpentInDollar.max})</div> <div> <details> <summary>Tks spent per user</summary> <div class="seratable"> <table><thead> <tr> <td class="">Username</td> <td class="">Tokens spent</td> <td class="">Min Dollars</td> <td class="">Max Dollars</td> </tr></thead> <tbody> ${totalStats.spentByUser.map(([user, spent]) => { const totalSpentInDollar = tksToDollar(spent); return `<tr> <td><a href="https://chaturbate.com/${user}/">${user}</a></td><td>${spent}tks</td> <td>${totalSpentInDollar.min}</td><td>${totalSpentInDollar.max}</td> </tr>`; }).join("")} </tbody> </table> </div> </details> ` + (Object.keys(totalStats.spentByGroup).length > 0 ? `<details> <summary>Tks spent per group</summary> <div class="seratable"> <table><thead> <tr> <td class="">Username</td> <td class="">Tokens spent</td> <td class="">Min Dollars</td> <td class="">Max Dollars</td> </tr></thead> <tbody> ${totalStats.spentByGroup.map(([group, spent]) => { const totalSpentInDollar = tksToDollar(spent); return `<tr> <td>${group}</td><td>${spent}tks</td> <td>${totalSpentInDollar.min}</td><td>${totalSpentInDollar.max}</td> </tr>`; }).join("")} </tbody> </table> </div> </details>` : ``) + `</div>`) } })();