NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Chaturbate Easy Tipping // @namespace madTipper // @version 0.1 // @author omgmikey // @match https://chaturbate.com/* // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js // @grant GM_getValue // @grant GM_setValue // @description Adds a new tipping popup and modifies the existing one // ==/UserScript== /* jshint -W097 */ 'use strict'; CSS_GREY = {'color': 'rgb(88,141,61)'}; CSS_WHITE = {'color': '#FFFFFF'}; CSS_BLACK = {'color': '#000000'}; ID_PREFIX = '#madTipper' CLASS_PREFIX = '.madTipper' CLASS_INPUT = CLASS_PREFIX + '_input'; var HTML_IDS = { 'BUTTON': 'button', 'POPUP': 'popup', 'AMOUNT': 'amount', 'COUNT': 'count', 'INTERVAL': 'interval', 'VARIANCE_LOWER': 'variance_lower', 'VARIANCE_UPPER': 'variance_upper', 'START': 'start', 'STOP': 'stop', 'TOTAL': 'total', 'ETA': 'eta' } for (var key in HTML_IDS) { HTML_IDS[key] = ID_PREFIX + '_' + HTML_IDS[key]; } var shell = $('.tip_shell'); var tipsLeft = 0; var tipFunctionTimeout = null; var juration = loadJuration(); (function initialize() { createTipperButton(); createTipperPopup(); injectCSS(); improveDefaultTipPopup(); loadPreviousSettings(); initializeButtonCallbacks(); updateTipperButton(); })(); function createTipperButton() { shell.append('<div id="madTipper_button_bg"><a href="#" id="madTipper_button"></a></div>'); } function updateTipperButton() { if (tipsLeft == 0) { $(HTML_IDS['BUTTON']).html('MAD TIPPER').css({'width': '80px'}); } else { $(HTML_IDS['BUTTON']).html('MAD TIPPER (' + tipsLeft + ')').css({'width': '120px'}); } } function createTipperPopup() { shell.append( '<div class="overlay_popup" id="madTipper_popup">' + '<table width="100%" border="0" cellspacing="0" cellpadding="0">' + '<tbody>' + '<tr>' + '<td class="formborder">' + '<div class="title">Mad Tipper</div>' + '<div class="body">' + '<form>' + '<label>Amount per tip:</label><br >' + '<input type="text" id="madTipper_amount" class="madTipper_input">' + '<br />' + '<label>Number of tips:</label><br >' + '<input type="text" id="madTipper_count" class="madTipper_input">' + '<br /><hr />' + '<label>Interval:</label><br >' + '<input type="text" id="madTipper_interval" class="madTipper_input">' + '<br />' + '<label>Interval variance lower (optional):</label><br >' + '<input type="text" id="madTipper_variance_lower" class="madTipper_input">' + '<br />' + '<label>Interval variance upper (optional):</label><br >' + '<input type="text" id="madTipper_variance_upper" class="madTipper_input">' + '<br /><hr />' + 'Total tip: ' + '<a id="madTipper_total"></a>' + '<br />' + 'Estimated duration: ' + '<a id="madTipper_eta"></a>' + '</form>' + '<hr />' + '<button id="madTipper_start">Start</button>' + '<button id="madTipper_stop" disabled="disabled">Stop</button>' + '</div>' + '</td>' + '</tr>' + '</tbody>' + '</table>' + '</div>' ); } function injectCSS() { var buttonBackgroundUrl = 'url("https://ssl-ccstatic.highwebmedia.com/images/btn-sprites2.gif?ac5eba7d5cf3") no-repeat right'; var buttonFontFamily = 'UbuntuMedium,Arial,Helvetica,sans-serif'; var genericButtonCSS = { 'height':'21px', 'width':'100px', 'padding-left':'10px', 'margin-right':'10px', 'font-size':'12px', 'text-shadow':'1px 1px 0 #588d3d', 'color': '#FFFFFF' }; genericButtonCSS['font-family'] = buttonFontFamily; genericButtonCSS['background'] = buttonBackgroundUrl + ' -84px'; var mainButtonCSS = { 'position': 'absolute', 'z-index': 1000, 'left': '500px', 'top': '30px', 'height': '18px', 'padding': '3px 10px 0 0', 'text-decoration': 'none', 'text-align': 'center', 'width': '80px' } for (var key in genericButtonCSS) { if (mainButtonCSS[key] === undefined) { mainButtonCSS[key] = genericButtonCSS[key]; } } $(HTML_IDS['BUTTON']).css(mainButtonCSS); $(CLASS_INPUT).css({ 'width': 'auto', 'margin-bottom': '10px' }); $(HTML_IDS['POPUP']).css({ 'position': 'absolute', 'z-index': 1000, 'width': '280px', 'top': '-456px', 'left': '452px', 'display': 'none' }).draggable(); $(HTML_IDS['POPUP'] + ' .formborder').css({ 'border-bottom': '2px solid #0b5d81', 'height': '420px' }); $(HTML_IDS['START']).css(genericButtonCSS); genericButtonCSS['background'] = buttonBackgroundUrl + ' -42px'; delete genericButtonCSS['color']; $(HTML_IDS['STOP']).css(genericButtonCSS); } function improveDefaultTipPopup() { $('.overlay_popup.tip_popup').css({ 'top': '-240px' }).draggable(); $('#tip_message').css({ 'margin-bottom': '20px' }) .append('<input type="checkbox" class="float_right" id="tip_keepopen"></input><br />') .append('<br /><label class="float_right" for="tip_keepopen">Keep this window open after tipping</label>'); $('.float_right').css({ 'float': 'right' }); setPopupHeight('250px'); var tipPopup = $('.tip_popup'); var keepOpenCheckbox = $('#tip_keepopen'); var popupIsForcedOpen = false; /* use CB jquery to ensure correct callback execution order */ var tipPopupForm = defchat_settings.domroot.find('.tip_popup form'); tipPopupForm.submit(onFormSubmit); keepOpenCheckbox.css({ 'margin-top': '10px' }); $('body').click(function(ev) { if ($('.tip_button').is(ev.target)) { popupIsForcedOpen = false; return; } if (!popupIsForcedOpen || tipPopup.has(ev.target).length) { return; } if (tipPopup.is(':visible')) { tipPopup.hide(); } popupIsForcedOpen = false; }); function onFormSubmit() { setPopupHeight('270px'); if (!keepOpenCheckbox.is(':checked')) { return; } if (!tipPopup.is(':visible')) { tipPopup.show(); popupIsForcedOpen = true; } } function setPopupHeight(value) { $('.overlay_popup.tip_popup .formborder').css({ 'height': value, }); } } function startTipping() { var err = verifyTipperFields(); if (err) { alert(err); stopTipping(); return; } saveCurrentSettings(); $(HTML_IDS['START']).prop('disabled', true).css(CSS_GREY); $(HTML_IDS['STOP']).prop('disabled', false).css(CSS_WHITE); $(CLASS_INPUT).prop('disabled', true).css(CSS_GREY); tipsLeft = getTipCount(); /* we really want to send the first one immediately */ sendTip(); if (tipsLeft > 0) { chainQueueTips(); } } function verifyTipperFields() { function isInt(value) { var regex = /^[0-9]+$/; return regex.test(String(value)); } function isDuration(value) { try { juration.parse(value); return true; } catch(ex) { return false; } } function isDurationOrEmpty(value) { return value === '' || isDuration(value); } if (!isInt(getTipAmountRaw()) || getTipAmount() <= 0) { return 'Tip amount field should be a positive integer.'; } if (!isInt(getTipCountRaw()) || getTipCount() <= 0) { return 'Tip count field should be a positive integer.'; } if (!isDuration(getTipInterval())) { return 'Tip interval should contain a duration. E.g.: "2.5s", "1", "2min"'; } if (!isDurationOrEmpty(getVarianceLowerRaw()) || !isDurationOrEmpty(getVarianceUpperRaw())) { return 'Variance fields should contain durations, or be left blank. E.g.: "", "2.5s"'; } } function getSleepInterval() { var interval = juration.parse(getTipInterval()); var lower_bound = interval - getVarianceLower(); var upper_bound = interval + getVarianceUpper(); return getRandomNumber(lower_bound, upper_bound) * 1000; } function getRandomNumber(min, max) { return Math.random() * (max - min) + min; } function chainQueueTips() { var sleepTime = getSleepInterval(); tipFunctionTimeout = setTimeout(function() { sendTip(chainQueueTips); }, sleepTime); } function sendTip(queueNextTipFn) { var queryUrl = $('.tip_popup form').attr('action'); var queryParams = $.param({ 'csrfmiddlewaretoken': $.cookie('csrftoken'), 'tip_amount': getTipAmount(), 'message': '', 'tip_room_type': $('#id_tip_room_type').val(), 'tip_v': defchat_settings.v_tip_vol, }); $.ajax({ url: queryUrl, data: queryParams, dataType: 'json', type: 'post', success: function(response) { if (response.error) { alert(response.error); stopTipping(); } } }); updateTipsLeft(); if (tipsLeft === 0) { stopTipping(); } else if (queueNextTipFn) { queueNextTipFn(); } } function updateTipsLeft() { tipsLeft--; updateTipperButton(); } function stopTipping() { clearTimeout(tipFunctionTimeout); tipFunctionTimeout = null; tipsLeft = 0; updateTipperButton(); $(HTML_IDS['STOP']).prop('disabled', true).css(CSS_GREY); $(HTML_IDS['START']).prop('disabled', false).css(CSS_WHITE); $(CLASS_INPUT).prop('disabled', false).css(CSS_BLACK); } function initializeButtonCallbacks() { var popup = $(HTML_IDS['POPUP']); var button = $(HTML_IDS['BUTTON']); button.click(function(ev) { if (popup.is(':visible')) { popup.hide(); } else { popup.show(); } }); popup.click(function(ev) { ev.stopPropagation(); }); $(HTML_IDS['START']).click(function() { startTipping(); $(HTML_IDS['POPUP']).hide(); }); $(HTML_IDS['STOP']).click(function() { stopTipping(); }); $('body').click(function(ev) { if (ev.target.id != button.prop('id')) { $(HTML_IDS['POPUP']).hide(); } }); $(CLASS_INPUT).change(function() { calculateAndSetTotalTip(); calculateAndSetETA(); }); } function calculateAndSetTotalTip() { var value = $(HTML_IDS['AMOUNT']).val() * $(HTML_IDS['COUNT']).val(); $(HTML_IDS['TOTAL']).html(value + ' tokens'); } function calculateAndSetETA() { var interval = juration.parse($(HTML_IDS['INTERVAL']).val()); /* we're not counting the first tip */ var count = getTipCount() - 1; var variance_lower = getVarianceLower(); var variance_upper = getVarianceUpper(); var eta = (interval + variance_upper - variance_lower) * count; $(HTML_IDS['ETA']).html(juration.stringify(eta, {'format': 'long', 'units': 2})); } function getTipAmount() { return parseInt(getTipAmountRaw()); } function getTipAmountRaw() { return $(HTML_IDS['AMOUNT']).val(); } function getTipInterval() { return $(HTML_IDS['INTERVAL']).val(); } function getTipCount() { return parseInt(getTipCountRaw()); } function getTipCountRaw() { return $(HTML_IDS['COUNT']).val(); } function getVarianceLower() { return parseVariance(getVarianceLowerRaw()); } function getVarianceLowerRaw() { return $(HTML_IDS['VARIANCE_LOWER']).val(); } function getVarianceUpper() { return parseVariance(getVarianceUpperRaw()); } function getVarianceUpperRaw() { return $(HTML_IDS['VARIANCE_UPPER']).val(); } function parseVariance(variance) { if (variance == '0') { variance = 0; } variance = variance || 0; if (variance != 0) { variance = juration.parse(variance); } return variance; } function saveCurrentSettings() { GM_setValue('amount', getTipAmount()); GM_setValue('interval', getTipInterval()); GM_setValue('count', getTipCount()); GM_setValue('variance_lower', getVarianceLower()); GM_setValue('variance_upper', getVarianceUpper()); } function loadPreviousSettings() { var amount = GM_getValue('amount', 1); $(HTML_IDS['AMOUNT']).val(amount); var count = GM_getValue('count', 10); $(HTML_IDS['COUNT']).val(count); var interval = GM_getValue('interval', '1s'); $(HTML_IDS['INTERVAL']).val(interval); var variance_lower = GM_getValue('variance_lower', ''); $(HTML_IDS['VARIANCE_LOWER']).val(variance_lower); var variance_upper = GM_getValue('variance_upper', ''); $(HTML_IDS['VARIANCE_UPPER']).val(variance_upper); calculateAndSetTotalTip(); calculateAndSetETA(); } // Script ends here // Libs included because they're not on a popular cdn /* * juration - a natural language duration parser * https://github.com/domchristie/juration * * Copyright 2011, Dom Christie * Licenced under the MIT licence * */ function loadJuration() { var UNITS = { seconds: { patterns: ['second', 'sec', 's'], value: 1, formats: { 'chrono': '', 'micro': 's', 'short': 'sec', 'long': 'second' } }, minutes: { patterns: ['minute', 'min', 'm(?!s)'], value: 60, formats: { 'chrono': ':', 'micro': 'm', 'short': 'min', 'long': 'minute' } }, hours: { patterns: ['hour', 'hr', 'h'], value: 3600, formats: { 'chrono': ':', 'micro': 'h', 'short': 'hr', 'long': 'hour' } }, days: { patterns: ['day', 'dy', 'd'], value: 86400, formats: { 'chrono': ':', 'micro': 'd', 'short': 'day', 'long': 'day' } }, weeks: { patterns: ['week', 'wk', 'w'], value: 604800, formats: { 'chrono': ':', 'micro': 'w', 'short': 'wk', 'long': 'week' } }, months: { patterns: ['month', 'mon', 'mo', 'mth'], value: 2628000, formats: { 'chrono': ':', 'micro': 'm', 'short': 'mth', 'long': 'month' } }, years: { patterns: ['year', 'yr', 'y'], value: 31536000, formats: { 'chrono': ':', 'micro': 'y', 'short': 'yr', 'long': 'year' } } }; var stringify = function(seconds, options) { if(!_isNumeric(seconds)) { throw "juration.stringify(): Unable to stringify a non-numeric value"; } if((typeof options === 'object' && options.format !== undefined) && (options.format !== 'micro' && options.format !== 'short' && options.format !== 'long' && options.format !== 'chrono')) { throw "juration.stringify(): format cannot be '" + options.format + "', and must be either 'micro', 'short', or 'long'"; } var defaults = { format: 'short', units: undefined }; var opts = _extend(defaults, options); var units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'], values = []; var remaining = seconds; var activeUnits = 0; for(var i = 0, len = units.length; i < len && (opts.units == undefined || activeUnits < opts.units); i++) { var unit = UNITS[units[i]]; values[i] = Math.floor(remaining / unit.value); if (values[i] > 0 || activeUnits > 0) activeUnits++; if(opts.format === 'micro' || opts.format === 'chrono') { values[i] += unit.formats[opts.format]; } else { values[i] += ' ' + _pluralize(values[i], unit.formats[opts.format]); } remaining = remaining % unit.value; } var output = ''; for(i = 0, len = values.length; i < len; i++) { if(values[i].charAt(0) !== "0" && opts.format != 'chrono') { output += values[i] + ' '; } else if (opts.format == 'chrono') { output += _padLeft(values[i]+'', '0', i==values.length-1 ? 2 : 3); } } return output.replace(/\s+$/, '').replace(/^(00:)+/g, '').replace(/^0/, ''); }; var parse = function(string) { // returns calculated values separated by spaces for(var unit in UNITS) { for(var i = 0, mLen = UNITS[unit].patterns.length; i < mLen; i++) { var regex = new RegExp("((?:\\d+\\.\\d+)|\\d+)\\s?(" + UNITS[unit].patterns[i] + "s?(?=\\s|\\d|\\b))", 'gi'); string = string.replace(regex, function(str, p1, p2) { return " " + (p1 * UNITS[unit].value).toString() + " "; }); } } var sum = 0, numbers = string .replace(/(?!\.)\W+/g, ' ') // replaces non-word chars (excluding '.') with whitespace .replace(/^\s+|\s+$|(?:and|plus|with)\s?/g, '') // trim L/R whitespace, replace known join words with '' .split(' '); for(var j = 0, nLen = numbers.length; j < nLen; j++) { if(numbers[j] && isFinite(numbers[j])) { sum += parseFloat(numbers[j]); } else if(!numbers[j]) { throw "juration.parse(): Unable to parse: a falsey value"; } else { // throw an exception if it's not a valid word/unit throw "juration.parse(): Unable to parse: " + numbers[j].replace(/^\d+/g, ''); } } return sum; }; // _padLeft('5', '0', 2); // 05 var _padLeft = function(s, c, n) { if (! s || ! c || s.length >= n) { return s; } var max = (n - s.length)/c.length; for (var i = 0; i < max; i++) { s = c + s; } return s; }; var _pluralize = function(count, singular) { return count == 1 ? singular : singular + "s"; }; var _isNumeric = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); }; var _extend = function(obj, extObj) { for (var i in extObj) { if(extObj[i] !== undefined) { obj[i] = extObj[i]; } } return obj; }; var juration = { parse: parse, stringify: stringify, humanize: stringify }; return juration; };