// ==UserScript==
// @name Chaturbate Easy Tipping Fixed Version
// @namespace madTipper
// @version 0.13.1
// @author omgmikey - fixed on 1/21/2018 by tblopgreg
// @match https://es.chaturbate.com/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/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
// @license MIT
// @run-at document-idle
// @description Adds a new tipping popup and modifies the existing one
// ==/UserScript==
var CSS_GREY = {'color': 'rgb(88,141,61)'};
var CSS_WHITE = {'color': '#FFFFFF'};
var CSS_BLACK = {'color': '#000000'};
var ID_PREFIX = '#madTipper'
var CLASS_PREFIX = '.madTipper'
var 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;
};