NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Chaturbate Tipping Spree // @namespace https://openuserjs.org/users/fogbanksy // @version 1.1.2 // @author fogbanksy // @copyright 2022, fogbanksy (https://openuserjs.org/users/fogbanksy) // @copyright 2021, pfuixxx (https://openuserjs.org/users/pfuixxx) // @match https://chaturbate.com/* // @exclude https://chaturbate.com/ // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @icon https://chaturbate.com/favicon.ico // @require https://cdn.jsdelivr.net/npm/jquery@3.6.1 // @require https://cdn.jsdelivr.net/npm/jqueryui@1.11.1/jquery-ui.min.js // @require https://cdn.jsdelivr.net/npm/jquery.cookie@1.4.1/jquery.cookie.min.js // @description Adds a button for automatically tipping a bunch of times in a row // @license MIT // ==/UserScript== // ==OpenUserJS== // @author fogbanksy // ==/OpenUserJS== // HELPERS const cr = (tag, obj) => Object.assign(document.createElement(tag), obj); const q = (sel) => document.querySelector(sel); let elements = {}, sibling, saveId; const createButton = () => { const buttonStyle = `background-color: #090; color: #fff; border: 1px solid #288a09; border-radius: 4px; overflow: hidden; line-height: 1.4; height: 24px; font-size: 12px; font-family: UbuntuMedium, Helvetica, Arial, sans-serif; margin: 11px 4px 11px 0px; text-overflow: ellipsis; padding: 3px 10px; box-sizing: border-box; cursor: pointer; display: inline-block; border-width: 1px; border-style: solid;`; elements.popupBtn = cr('span', { className: 'tippingSpreeButton', innerText: 'TIP SPREE', style: buttonStyle }); elements.popupBtn.addEventListener('click', openPopup); sibling.parentElement.appendChild(elements.popupBtn); }; const createPopup = () => { elements.popup = cr('div', { className:'tippingSpreePopup' }); elements.popup.style.position = 'absolute'; elements.popup.appendChild(cr('div', { innerText:'Tipping Spree' })); const vars = GM_getValue('vars', {}); const div = cr('div'); div.appendChild(cr('label', { forHtml:'tippingSpreePerTip', innerText:'Amount per tip:' })); div.appendChild(elements.perTip = cr('input', { id:'tippingSpreePerTip', value:vars.perTip || 1 })); div.appendChild(cr('label', { forHtml:'tippingSpreeNumTimes', innerText:'Number of tips:' })); div.appendChild(elements.numTimes = cr('input', { id:'tippingSpreeNumTimes', value:vars.numTimes || 10 })); div.appendChild(cr('hr')); div.appendChild(cr('label', { forHtml:'tippingSpreeInterval', innerText:'Interval (seconds):' })); div.appendChild(elements.interval = cr('input', { id:'tippingSpreeInterval', value:vars.interval || 5 })); div.appendChild(cr('label', { forHtml:'tippingSpreeRanMin', innerText:'Lower variance (seconds):' })); div.appendChild(elements.ranMin = cr('input', { id:'tippingSpreeRanMin', placeholder:'(optional)', value:vars.ranMin || '' })); div.appendChild(cr('label', { forHtml:'tippingSpreeRanMax', innerText:'Upper variance (seconds):' })); div.appendChild(elements.ranMax = cr('input', { id:'tippingSpreeRanMax', placeholder:'(optional)', value:vars.ranMax || '' })); div.appendChild(cr('hr')); const totTip = cr('span', { innerText:'Total tip: ' }); totTip.appendChild(elements.infoTotal = cr('span', { className:'tippingSpreeHighlight' })); const totDur = cr('span', { innerText:'Total duration: ' }); totDur.appendChild(elements.infoDur = cr('span', { className:'tippingSpreeHighlight' })); div.appendChild(totTip); div.appendChild(totDur); div.appendChild(cr('hr')); div.appendChild(elements.start = cr('button', { innerText:'Start' })); elements.start.style.backgroundColor = '#33ca33'; elements.popup.appendChild(div); const parent = q('#SplitModeTipCallout').parentElement; parent.appendChild(elements.popup); parent.appendChild(elements.close = cr('div', { className:'tippingSpreePopupClose', hidden:true })); closePopup(); // Setup event listeners elements.perTip.addEventListener('input', onInput); elements.numTimes.addEventListener('input', onInput); elements.interval.addEventListener('input', onInput); elements.ranMin.addEventListener('input', onInput); elements.ranMax.addEventListener('input', onInput); elements.close.addEventListener('click', closePopup); elements.start.addEventListener('click', () => { if (0 < tipsLeft) { stopTipSpree(); elements.start.innerText = 'Start'; elements.start.style.backgroundColor = '#33ca33'; } else if (startTipSpree()) { elements.start.innerText = 'Stop'; elements.start.style.backgroundColor = 'red'; } }); onInput(); }; const onInput = () => { clearTimeout(saveId); const vars = getVars(); if (isFinite(vars.numTimes)) { if (isFinite(vars.perTip)) { elements.infoTotal.innerText = vars.perTip * vars.numTimes; } else { elements.infoTotal.innerText = '-'; } if (isFinite(vars.interval)) { let min=0, max=0; if (isFinite(vars.ranMin)) { min = vars.interval - vars.ranMin; } if (isFinite(vars.ranMax)) { max = vars.ranMax + vars.interval; } min *= (vars.numTimes - 1); max *= (vars.numTimes - 1); if (min === max) { elements.infoDur.innerText = `${min}s`; } else { elements.infoDur.innerText = `${min}s-${max}s`; } } else { elements.infoDur.innerText = '-'; } } saveId = setTimeout(() => GM_setValue('vars', vars), 500); }; const getVars = () => { return { perTip: +elements.perTip.value, numTimes: +elements.numTimes.value, interval: +elements.interval.value, ranMin: elements.ranMin.value.length ? +elements.ranMin.value : null, ranMax: +elements.ranMax.value.length ? +elements.ranMax.value : null }; }; const openPopup = () => { if (!elements.popup) { createPopup(); } const btnRect = elements.popupBtn.getBoundingClientRect(); elements.popup.style.display = 'block'; elements.close.style.display = 'block'; elements.popup.style.top = `${scrollY + btnRect.top - 10 - elements.popup.offsetHeight}px`; elements.popup.style.left = `${scrollX + btnRect.left - 50}px`; }; const closePopup = () => { elements.popup.style.display = 'none'; elements.close.style.display = 'none'; }; const sendTip = (username, tipAmount) => { console.log('Sending', tipAmount, 'tip to', username); $.post(`https://chaturbate.com/tipping/send_tip/${username}/`, { csrfmiddlewaretoken: $.cookie('csrftoken'), tip_amount: tipAmount }); }; let spreeId, tipsLeft = 0; const startTipSpree = () => { if (spreeId) { return; } const vars = getVars(); if (!validateVars(vars)) { return false; } tipsLeft = vars.numTimes; const username = /\/(.*?)\//.exec(location.pathname)[1]; const tipAmount = vars.perTip; const nextTip = () => { if (0 < --tipsLeft) { let time = vars.interval + (Math.random() * (vars.ranMax + vars.ranMin) - vars.ranMin); spreeId = setTimeout(() => { sendTip(username, tipAmount); nextTip(); }, time * 1000); elements.popupBtn.innerText = `TIP SPREE (${tipsLeft} left)`; } else { elements.start.innerText = 'Start'; elements.start.style.backgroundColor = 'green'; stopTipSpree(); } }; sendTip(username, tipAmount); nextTip(); return true; }; const stopTipSpree = () => { tipsLeft = 0; clearTimeout(spreeId); spreeId = null; elements.popupBtn.innerText = 'TIP SPREE'; }; const validateVars = (vars) => { if (!isFinite(vars.perTip)) { alert('Amount per tip is not a number'); return false; } else if (Math.round(vars.perTip) !== vars.perTip) { alert('Amount per tip is not an even number'); return false; } else if (vars.perTip < 1) { alert('Amount per tip is not a positive number'); return false; } if (!isFinite(vars.numTimes)) { alert('Number of tips is not a number'); return false; } else if (Math.round(vars.perTip) !== vars.perTip) { alert('Number of tips is not an even number'); return false; } else if (vars.perTip < 1) { alert('Number of tips is not a positive number'); return false; } if (!isFinite(vars.interval)) { alert('Interval is not a number'); return false; } else if (vars.interval <= 0) { alert('Interval is not a positive number'); return false; } if (vars.ranMin != null) { if (!isFinite(vars.ranMin)) { alert('Lower variance is not a number'); return false; } else if (vars.ranMin <= 0) { alert('Lower variance is not a positive number'); return false; } if (vars.interval <= vars.ranMin) { alert('Lower variance is too low'); return false; } } if (vars.ranMax != null) { if (!isFinite(vars.ranMax)) { alert('Upper variance is not a number'); return false; } else if (vars.ranMax <= 0) { alert('Upper variance is not a positive number'); return false; } } return true; }; // Init const init = () => { GM_addStyle(`.tippingSpreePopup { position:absolute; background-color:white; z-index:1001; display:block; border:2px solid #0b5d81; border-radius:4px; font-family:UbuntuRegular,Helvetica,Arial,sans-serif; font-size:12px; color:#494949; } .tippingSpreePopup > div { padding:6px; } .tippingSpreePopup > div:first-child { font-size:15px; font-family:UbuntuRegular,Helvetica,Arial,sans-serif; color:#0b5d81; background-color:#e0e0e0; font-weight:bold; } .tippingSpreePopup label { display:block; font-size:11px; } .tippingSpreePopup input { margin-bottom:5px; } .tippingSpreePopup > div > span { display:block; } .tippingSpreePopup > div > span > span { color:#c35a00; font-weight:bold; } .tippingSpreePopup button { padding:9px; border:1px solid #333; border-radius:4px; } .tippingSpreePopupClose { position:fixed; left:0; top:0; right:0; bottom:0; } .tippingSpreeButton { overflow:hidden; line-height:1.4; height:21px; font-size:12px; font-family:UbuntuMedium,Helvetica,Arial,sans-serif; text-shadow:rgb(88, 141, 61) 1px 1px 0px; margin:11px 5px; text-overflow:ellipsis; padding:3px 10px; border-radius:4px; box-sizing:border-box; cursor:pointer; display:inline-block; }`); createButton(); }; const wait = () => { 'use strict' if (document.querySelector('.sendTipButton')) { if (sibling = document.querySelector('.currentBalance')) { init(); } } else { setTimeout(wait, 500); } }; wait();