// ==UserScript==
// @name LoungeStats2
// @namespace LoungeStats2
// @author Kinsi http://reddit.com/u/kinsi55
// @include http://csgolounge.com/myprofile
// @include http://dota2lounge.com/myprofile
// @include https://csgolounge.com/myprofile
// @include https://dota2lounge.com/myprofile
// @version 2.0.0
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.cursor.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.dateAxisRenderer.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.highlighter.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/async/1.5.2/async.min.js
// @require http://loungestats.kinsi.me/dl/datepickr_mod.min.js
// @downloadURL http://loungestats.kinsi.me/dl/LoungeStats.user.js
// @updateURL http://loungestats.kinsi.me/dl/LoungeStats.user.js
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// ==/UserScript==
// You are not allowed to share modified versions of this script, or use parts of it without the authors permission
// You are not allowed to sell the whole, or parts of this script
// Copyright belongs to "Kinsi" (user Kinsi55 on reddit, /id/kinsi on steam)
'use strict';
var version = GM_info.script.version;
var newVersion = (GM_getValue('lastversion') != version);
var APP_CSGO = '730';
var APP_DOTA = '570';
/**
* Helperfunction to zoom the jqPlot
*/
function plot_zomx(plot, minx, maxx) {
if(!minx){
plot.replot({ axes: {
xaxis: {
min: plot.axes.xaxis.min,
max: plot.axes.xaxis.max
},
yaxis: {
min: plot.axes.yaxis.min,
max: plot.axes.yaxis.max
},
}});
}else{
plot.replot({ axes: {
xaxis: {
min: minx,
max: maxx
},
yaxis: {
min: null,
max: null
}
}});
}
}
//http://stackoverflow.com/a/6562764/3526458
function clearSelection() {
if(document.selection) {
document.selection.empty();
} else if(window.getSelection) {
window.getSelection().removeAllRanges();
}
}
function toggleFullscreen(jqplot) {
if($('#loungestats_profitgraph').hasClass('fullsc')) {
$('#loungestats_profitgraph').removeClass('fullsc');
$('#loungestats_fullscreenbutton').removeClass('fullsc');
} else {
$('#loungestats_profitgraph').addClass('fullsc');
$('#loungestats_fullscreenbutton').addClass('fullsc');
}
jqplot.replot();
}
//http://stackoverflow.com/a/5812341/3526458
/**
* Check if the passed date is formatted validly
* @param {String} s date to check
* @return {Boolean} result of the format check
*/
function isValidDate(s) {
var bits = s.split('.');
var d = new Date(bits[2], bits[1] - 1, bits[0]);
return d && (d.getMonth() + 1) == bits[1] && d.getDate() == Number(bits[0]);
}
/**
* Broken out object for handling currency conversion
* @type {Object}
*/
var ConversionRateProvider = {
/**
* Convert some price from one currency to another
* @param {Float} amount amount to convert
* @param {String} to_currency destination currency requested
* @param {String} from_currency Source currency given, USD if not defined
* @return {Float} converted amount
*/
convert: function(amount, to_currency, from_currency){
if(!this.cache) throw "No prices cached...";
if(!from_currency) from_currency = "USD";
if(!this.cache.rates[to_currency]) throw "Unknown destination currency";
if(!this.cache.rates[from_currency]) throw "Unknown source currency";
return amount / this.cache.rates[from_currency] * this.cache.rates[to_currency];
},
/**
* Get available currencies
* @return {Array} Available currencies
*/
getAvailableCurrencies: function(){
if(!this.cache) throw "No prices cached...";
return Object.keys(this.cache.rates);
},
/**
* Load the locally cached, or externally refreshed rate DB in and load it into RAM
* @param {Function} callback callback when done
*/
init: function(callback){
this.cache = GM_getValue('convert_fixer');
if(this.cache) try{
this.cache = JSON.parse(this.cache);
}catch(e){ this.cache = null; }
if(!this.cache || (new Date().getTime() - Number(GM_getValue('fixer_lastload') || 0)) > 259200000){
$.ajax({
url: "https://api.fixer.io/latest?base=USD",
dataType: "json"
}).done(function(parsed) {
if(parsed.base === "USD" && parsed.rates.EUR){
parsed.rates["USD"] = 1.0;
GM_setValue("convert_fixer", JSON.stringify(parsed));
this.cache = parsed;
}
if(!this.cache) return callback("Couldnt load exchange rates from fixer..");
GM_setValue('fixer_lastload', new Date().getTime());
if(callback) callback();
}).error(function(){
if(callback) callback("Couldnt load pricedb from repo..");
});
}else{
this.cache = JSON.parse(GM_getValue("convert_fixer"));
if(callback) callback();
}
}
};
/**
* Broken out object for handling loading of item prices incase i should add / switch providers in the future
* @type {Object}
*/
var PriceProvider = {
/**
* Returns the price out of the cached price database if available
* @param {integer} appId app id which this item belongs to
* @param {String} itemName name of the item wanted
* @param {Function} callback callback function to pipe the price to
* @return {Float} items price
*/
getPriceFor: function(itemName, appId, callback, fallback){
if(!this.cache) throw "No prices cached...";
if(!this.cache[appId][itemName] && !fallback) throw "Requested item not found (" + appId + "/" + itemName + ")"; //Possibly report back and / or fallback to loading from market?
var p = parseFloat(this.cache[appId][itemName] || fallback) / 100;
p = ConversionRateProvider.convert(p, this.destinationCurrency);
if(callback) callback(null, p);
return p;
},
/**
* Load the locally cached, or externally refreshed price DB in and load it into RAM
* @param {Function} callback callback when done
*/
init: function(callback){
this.cache = GM_getValue('pricedb');
this.destinationCurrency = "USD";
if(this.cache) try{
this.cache = JSON.parse(this.cache);
}catch(e){ this.cache = null; }
//if the cache is older than 48 hours re-load it
if(!this.cache || (new Date().getTime() - Number(GM_getValue('pricedb_lastload') || 0)) > 518400000){
//Load latest price database from github and cache it
$.ajax({
url: "https://raw.githubusercontent.com/kinsi55/LoungeStats2/master/misc/pricedb.json",
dataType: "json"
}).done(function(parsed) {
if(parsed[APP_CSGO] && parsed[APP_DOTA]){
GM_setValue("pricedb", JSON.stringify(parsed));
this.cache = parsed;
}
if(!this.cache || this.cache.success !== 1) return callback("Couldnt load pricedb from repo..");
GM_setValue('pricedb_lastload', new Date().getTime());
if(callback) callback();
}).error(function(){
if(callback) callback("Couldnt load pricedb from repo..");
});
}else{
if(callback) callback();
}
},
/**
* Load the locally cached, or externally refreshed price DB in and load it into RAM
* @param {Function} callback callback when done
*/
setWantedCurrency: function(destinationCurrency){
this.destinationCurrency = destinationCurrency;
}
};
/**
* Helperclass holding a Steam Item
* @param {String} itemFullName full IEcon name
* @param {integer} appid app id this item is associated to
*/
var SteamItem = function(itemFullName, appid){
this.name = itemFullName;
this.appid = appid;
};
/**
* Get the worth of this item
* @param {boolean} refresh force updating this price trough steam market
* @param {Function} callback function to return the price to
*/
SteamItem.prototype.getPrice = function(refresh, callback){
if(typeof refresh === "function"){
callback = refresh;
refresh = false;
}
var p = PriceProvider.getPriceFor(this.name, this.appid, callback, 0.06);
if(callback) callback(null, p);
return p;
};
// Lets not use steam market for now mkay
// var Steam = {
// Market: {
// loadPrice: function(appId, itemName, atDate, callback){
// if(typeof atDate === "function"){
// callback = atDate;
// atDate = false;
// }
// if(!atDate){
// $.get("https://steamcommunity.com/market/listings/"+appId+"/"+encodeURI(itemName), function(data){
// betData = data;
// }.bind(this));
// }
// }
// }
// };
/**
* Class containing most of the stuff related to CSGO/DOTA2 Lounge
* @type {Object}
*/
var LoungeClass = function(){};
/**
* App id used in the current site
* @type {String}
*/
LoungeClass.prototype.currentAppid = window.location.hostname == 'dota2lounge.com' ? APP_DOTA : APP_CSGO;
/**
* Account Steamid64 of the current logged in user
* @type {String}
*/
LoungeClass.prototype.currentAccountId = $('#profile .full:last-child input').val().split('=').pop();
/**
* Class to parse all informations from a bet in the bet history
* @param {jQuery element} betRowJq information row from the history table
* @param {jQuery element} betItemsJq row with the bet items
* @param {jQuery element} wonItemsJq row with the won items
*/
LoungeClass.prototype.betHistoryEntry = function(betRowJq, betItemsJq, wonItemsJq, appId){
//Map bet item Row to all contained Item names
var filterForItems = function(toFilter){
return toFilter
.find('div > div.name > b:first-child').get()
.map(function(e){
return new SteamItem(e.textContent.trim(), appId);
});
};
if(betItemsJq !== undefined){
betItemsJq = filterForItems(betItemsJq);
wonItemsJq = filterForItems(wonItemsJq);
this.matchId = parseInt(betRowJq.children()[2].children[0].href.split('=').pop());
this.betDate = new Date(Date.parse(betRowJq.children()[6].textContent.replace(/-/g,' ') + ' +0'));
this.items = {bet: betItemsJq, won: wonItemsJq, lost: []};
this.teams = [betRowJq.children()[2].children[0].textContent, betRowJq.children()[4].children[0].textContent];
this.winner = (betRowJq.children()[4].style.fontWeight == 'bold')+0;
this.betoutcome = betRowJq.children()[1].children[0].classList[0] || 'draw';
if(wonItemsJq.length > 0){
this.betoutcome = 'won'; // http://redd.it/3edctm
}else if(['won', 'draw'].indexOf(this.betoutcome) === -1){
//Match neither won or drawn? Its gotta be a loss..
this.items.lost = betItemsJq;
}
}
};
/**
* Load the bet history from Lounge and process the returned html
* @param {Function} callback callback for the loaded data
*/
LoungeClass.prototype.getBetHistory = function(callback) {
var betData, archivedBetData, parsedBetdata = {};
//Load bet history, and archive asynchronously
$.when(
$.get('/ajax/betHistory.php', function(data){betData = data;}),
$.get('/ajax/betHistoryArchives.php', function(data){archivedBetData = data;})
).then(function(){
if(!betData || !archivedBetData || betData.indexOf('
') == -1 || archivedBetData.indexOf('') == -1){
callback('Failed to load either betHistory or archivedBetHistory (Itemdraft possibly in progress?)', null);
}else{
//"Concat" both html Tables, parse it w/ jQuery, get every tablerow
var preParsedBetdata = $(betData.split('')[0]+archivedBetData.split('')[1]).find('tr');
//Iterate bets from the end so oldest bets are first, step = 2 since theres always a row with bet info, then the won items, then the lost items, so 3 rows per bet
for(var i = preParsedBetdata.length-3; i >= 0; i -= 3){
var bhistoryentry = new this.betHistoryEntry($(preParsedBetdata[i]), $(preParsedBetdata[i + 1]), $(preParsedBetdata[i + 2]), this.currentAppid);
parsedBetdata[bhistoryentry.matchId] = bhistoryentry;
}
callback(null, parsedBetdata);
this.betHistory = parsedBetdata;
}
}.bind(this));
};
/**
* Main class containing most of the features
* @type {Object}
*/
var LoungeStatsClass = function(){
this.Lounge = new LoungeClass();
/**
* Wrapper / helper Class for handling LoungeStats' settings
* @param {String} name name of the setting
* @param {Boolean} json Is the internally handled value an Object (JSON)?
* @param {String} fieldid ID of the Field in the settings to auto populate / read from
*/
var Setting = function(name, json, fieldid){
this.json = json === true;
this.name = name;
this.fieldid = fieldid || json;
if(this.fieldid === this.json) this.fieldid = undefined;
this._val = GM_getValue('setting_'+this.name);
if(this._val && this.json) this._val = JSON.parse(this._val);
};
Setting.prototype = {
/**
* Get the currently set value of this setting
* @return setting value
*/
get value(){
return this._val;
},
/**
* Set the value of the setting and save it
* @param newValue Object / String to save
*/
set value(newValue){
this._val = newValue;
if(!this.json) GM_setValue('setting_'+this.name, this._val);
if(this.json) GM_setValue('setting_'+this.name, JSON.stringify(this._val));
},
populateFormField: function(){
//console.log(this.fieldid, !this.json, this._val, $('.loungestatsSetting#'+this.name));
if(this.fieldid && !this.json && this._val) $('.loungestatsSetting#'+this.name).val(this._val);
},
};
/**
* Predefined settings
* @type {Object}
*/
this.Settings = {
method: new Setting('method', 'method'),
currency: new Setting('currency', 'currency'),
bvalue: new Setting('bvalue', 'bvalue'),
xaxis: new Setting('xaxis', 'xaxis'),
debug: new Setting('debug', 'debug'),
beforedate: new Setting('beforedate', 'beforedate'),
domerge: new Setting('domerge', 'domerge'),
hideclosed: new Setting('hideclosed', 'hideclosed'),
lastversion: new Setting('lastversion'),
accounts: new Setting('accounts', true)
};
/**
* Helperfunction called when pressing the save button in the settings window
* to also save custom stuff like multiaccount settings etc.
*/
this.Settings.save = function(){
$(".loungestatsSetting").each(function(i, setting){
this.Settings[setting.id].value = setting.value;
}.bind(this));
if(isValidDate($('#beforedate').val())){
this.Settings.beforedate.value = $('#beforedate').val();
} else {
alert('The format of the given date is invalid! Use Day.Month.Year!');
return;
}
var x = this.Settings.accounts.value;
x.active[this.Lounge.currentAppid] = $("#loungestats_mergepicks input:checked").map(function(){return $(this).attr("name")}).toArray();
this.Settings.accounts.value = x;
PriceProvider.setWantedCurrency(this.Settings.currency.value);
this.Settings.close();
}.bind(this);
/**
* Helperfunction called to open the settingswindow and pouplate its fields
*/
this.Settings.show = function(){
for(var key in this.Settings){
if(!(this.Settings[key] instanceof Setting)) continue;
this.Settings[key].populateFormField();
}
var multiaccthing = '
');
//Populate currencies field
$('select#currency').html(ConversionRateProvider.getAvailableCurrencies().reduce(function(pv, cv){
return pv + '';
}, ""));
var domergesett = $('.loungestatsSetting#domerge'),
ls_settingswindow = $('#loungestats_settingswindow');
domergesett.change(function() {
ls_settingswindow.toggleClass('accounts', domergesett.val() == 1);
});
new datepickr('beforedate', {dateFormat:'d.m.Y'});
$('.calendar').detach().appendTo('#loungestats_datecontainer');
$('#loungestats_tabbutton').click(this.loadStats);
$('#loungestats_overlay, #loungestats_settings_close').click(this.Settings.close);
$('#loungestats_settings_save').click(this.Settings.save);
ls_settingswindow.find('#loungestats_beforedate, .calendar').click(function(e) {e.stopPropagation();});
ls_settingswindow.click(function(e) {e.stopPropagation(); $('.calendar').css('display', 'none');});
//Predefine settings on first load
if(!this.Settings.accounts.value) this.Settings.accounts.value = {available: {'570': {}, '730': {}},
active: {'570': [], '730': []}};
$(document).on('click', 'a#loungestats_settingsbutton', this.Settings.show);
PriceProvider.setWantedCurrency(this.Settings.currency.value);
callback();
},
/**
* Helperfunction called to cache the bet history for the currently logged in account
* to make it availalbe for multi-account usage
* @param {Object} betHistory Object with the bethistory
*/
cacheBetHistory: function(betHistory){
var x = this.Settings.accounts.value;
x.available[this.Lounge.currentAppid][this.Lounge.currentAccountId] = betHistory;
this.Settings.accounts.value = x;
},
/**
* Get the previously cached bet history for the defined account
* @param {String} requestedAccount Account to get the bet history for, defaults to the currently logged in one
* @return {Object} Object with the bethistory
*/
getCachedBetHistory: function(requestedAccount){
if(!requestedAccount) requestedAccount = this.Lounge.currentAccountId;
var h = this.Settings.accounts.value.available[this.Lounge.currentAppid][requestedAccount];
//console.log(requestedAccount, h);
Object.keys(h).forEach(function(key){
//console.log(h[key] instanceof LoungeClass.prototype.betHistoryEntry);
h[key].__proto__ = LoungeClass.prototype.betHistoryEntry.prototype;
//console.log(h[key] instanceof LoungeClass.prototype.betHistoryEntry);
h[key].betDate = new Date(Date.parse(h[key].betDate));
h[key].items.won = h[key].items.won.map(function(i){return new SteamItem(i.name, i.appid);});
h[key].items.bet = h[key].items.bet.map(function(i){return new SteamItem(i.name, i.appid);});
h[key].items.lost = h[key].items.lost.map(function(i){return new SteamItem(i.name, i.appid);});
});
//console.log(h);
return h;
}
};
var LoungeStats = new LoungeStatsClass();
LoungeStats.loadStats = function(){
if(!LoungeStats.Settings.method.value){
$('#ajaxCont').html('Please set up Loungestats first');
return LoungeStats.Settings.show();
}
$(window).off('resize');
$('#ajaxCont').html('LoungeStats Settings \
Donate ♥ \
Subreddit \
Reset Zoom \
Screenshot \
Export CSV (Excel) \
\