// ==UserScript==
// @name Lex's SG Chart Maker
// @namespace https://www.steamgifts.com/user/lext
// @version 0.1.6
// @description Create bundle charts for Steam Gifts.
// @author Lex
// @match http://store.steampowered.com/*
// @require http://code.jquery.com/jquery-3.2.1.min.js
// @require http://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.isthereanydeal.com
// ==/UserScript==
(function() {
'use strict';
//GM_deleteValue("gameOrder");
//GM_deleteValue("games");
var ITAD_API_KEY = GM_getValue("ITAD_API_KEY");
const API_KEY_REGEXP = /[0-9A-Za-z]{40}/;
const GameID = window.location.pathname.match(/(app|sub)\/\d+/)[0];
const BUNDLE_BLACKLIST = ["DailyIndieGame", "Chrono.GG", "Chrono.gg", "Ikoid", "Humble Mobile Bundle", "PlayInjector", "Vodo",
"Get Loaded", "GreenMan Gaming", "Indie Ammo Box", "MacGameStore", "PeonBundle", "Select n'Play", "StackSocial",
"StoryBundle", "Bundle Central", "Cult of Mac", "GOG", "Gram.pl", "Indie Fort", "IUP Bundle", "Paddle",
"SavyGamer", "Shinyloot", "Sophie Houlden", "Unversala", "Indie Game Stand", "Fourth Wall Games"];
$("head").append ('<link ' +
'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css" ' +
'rel="stylesheet" type="text/css">'
);
function getGames() { return JSON.parse(GM_getValue("games", '{}')); }
function getGameOrder() { return JSON.parse(GM_getValue("gameOrder", '[]')); }
function itad_api(url, callback) {
GM_xmlhttpRequest({
"method": "GET",
"url": url,
"onload": function(response) {
callback(JSON.parse(response.responseText).data);
}
});
}
function itad_getplains(appids, callback) {
itad_api("https://api.isthereanydeal.com/v01/game/plain/id/?key=" + ITAD_API_KEY + "&shop=steam&ids=" + appids.join(","), callback);
}
function itad_getbundles(plains, callback) {
itad_api("https://api.isthereanydeal.com/v01/game/bundles/us/?key=" + ITAD_API_KEY + "&limit=-1&expired=1&plains=" + plains.join(","), callback);
}
function itad_getusprices(plains, callback) {
itad_api("https://api.isthereanydeal.com/v01/game/prices/us/?key=" + ITAD_API_KEY + "&country=US&plains=" + plains.join(","), callback);
}
function getRating() {
var rating = $("div[itemprop=aggregateRating]").attr('data-store-tooltip').replace(/(\d+)%[^\d]*([\d,]*).*/, "$1% of $2 reviews");
if (rating.startsWith("Need more")) {
let total = $("label[for=review_type_all]").text().match(/[\d,]+/)[0];
let pos = $("label[for=review_type_positive]").text().match(/[\d,]+/)[0];
rating = Math.round(100*pos/total);
rating = rating + `% of ${total} reviews`;
}
return rating;
}
function addAppToChart() {
if (GameID in getGames())
return;
var game = {
gameid: GameID,
appid: window.location.pathname.split('/')[2],
name: $(".apphub_AppName").text(),
rating: getRating(),
cards: Boolean($(".game_meta_data img[src='http://store.edgecast.steamstatic.com/public/images/v6/ico/ico_cards.png']").length),
price: $.trim($(".game_area_purchase_game:first .price,.game_area_purchase_game:first .discount_original_price").text()),
url: window.location.href,
dlc: $(".game_area_dlc_bubble").length,
bundles: undefined,
};
var games = getGames();
games[GameID] = game;
GM_setValue("games", JSON.stringify(games));
let gorder = getGameOrder();
if (!gorder.includes(GameID)) {
gorder.push(GameID);
GM_setValue("gameOrder", JSON.stringify(gorder));
}
}
// elem: the div for the package listing on the main app's page
function addPackageToChart(elem) {
const subid = elem.find("input[name=subid]").attr("value");
const gameid = "sub/" + subid;
if (gameid in getGames())
return;
var game = {
gameid: gameid,
appid: subid,
name: elem.find("h1")[0].childNodes[0].nodeValue.substring(4),
rating: getRating(),
cards: Boolean($(".game_meta_data img[src='http://store.edgecast.steamstatic.com/public/images/v6/ico/ico_cards.png']").length),
price: $.trim(elem.find(".price,.discount_original_price").text()),
url: "http://store.steampowered.com/sub/" + subid,
dlc: $(".game_area_dlc_bubble").length,
bundles: undefined,
};
var games = getGames();
games[gameid] = game;
GM_setValue("games", JSON.stringify(games));
let gorder = getGameOrder();
if (!gorder.includes(gameid)) {
gorder.push(gameid);
GM_setValue("gameOrder", JSON.stringify(gorder));
}
}
function bundleCount(bundleList) {
return bundleList.filter(function(b){
// Bundles not on blacklist and at least 48 hours old
return !BUNDLE_BLACKLIST.includes(b.bundle) && (Date.now()/1000 - b.start) > 48*60*60;
}).length;
}
// Uses and ITAD call, then calls update_func on every response
function itad_games_obj(itad_func, plainArr, update_func, callback) {
var plains = Object.values(plainArr).filter((v) => v !== null);
itad_func(plains, function(list) {
var games = getGames();
for (let plain in list) {
let gid = Object.keys(plainArr).find(key => plainArr[key] === plain); // reverse the dictionary to find the key from value
let game = games[gid];
update_func(game, plain, list[plain]);
}
GM_setValue("games", JSON.stringify(games));
dumpListing();
updateListing();
callback();
});
}
// Load prices from ITAD into the games object
function loadPrices(plainArr) {
itad_games_obj(itad_getusprices, plainArr, function(game, plain, data) {
let price_old = data.list.find(p => p.shop.id == "steam").price_old;
game.price = "$" + price_old;
}, function(){});
}
function loadBundleInfo() {
var gameids = getGameOrder();
itad_getplains(gameids, function(plainArr){
itad_games_obj(itad_getbundles, plainArr, function(game, plain, data) {
game.bundlesUrl = data.urls.bundles;
game.bundles = bundleCount(data.list);
game.plain = plain;
}, function(){ loadPrices(plainArr); });
});
}
function showChartMaker() {
if (!$("#lcm_dialog").length) {
GM_addStyle("#lcm_dialog { display: flex; flex-direction: column; } " +
"#lcm_list { list-style-type: none; margin: 0 auto; padding: 0; width: 60%; }" +
"#lcm_list a { color: blue; text-decoration: underline; }" +
"#lcm_dump { margin: 25px auto 0 auto; display: block; flex-grow: 1; resize: none; width: 95%; }" +
"#lcm_bundle_info { float: right; }" +
"#lcm_itad { margin-bottom: 5px; }");
var d = $(`<div id="lcm_dialog"><div><button id="lcm_bundle_info" class="ui-button ui-widget ui-corner-all">Load Bundle Info</button>
<div id="lcm_itad"><div>IsThereAnyDeal API Key: <input type="text"></input><button>Submit</button></div><a style="display:none" href="javascript:">Delete ITAD API Key?</a></div>
<ul id="lcm_list"></ul></div>
<textarea id="lcm_dump"></textarea></div>`);
$("body").append(d);
if (GM_getValue("ITAD_API_KEY") !== undefined)
$("#lcm_itad div,#lcm_itad a").toggle();
$("#lcm_itad button").click(function(){
try{
ITAD_API_KEY = $("#lcm_itad input").val().match(API_KEY_REGEXP)[0];
GM_setValue("ITAD_API_KEY", ITAD_API_KEY);
$("#lcm_itad div,#lcm_itad a").toggle();
}catch(err){
alert("Error setting API key");
}
});
// Delete key
$("#lcm_itad a").click(function(){
GM_deleteValue("ITAD_API_KEY");
ITAD_API_KEY = undefined;
$("#lcm_itad div,#lcm_itad a").toggle();
});
$("#lcm_dialog").dialog({
modal: false,
title: "Lex's SG Chart Maker",
position: {
my: "center",
at: "center",
of: window,
collusion: "none"
},
width: 800,
height: 400,
minWidth: 400,
minHeight: 200,
zIndex: 3666,
})
.dialog("widget").draggable("option", "containment", "none");
$("#lcm_list").sortable({
deactivate: function(event, ui) {
dumpListing();
}
});
$("#lcm_bundle_info").click(loadBundleInfo);
} else {
$("#lcm_dialog").dialog();
}
updateListing();
dumpListing();
}
function updateListing() {
$("#lcm_list").empty();
for (let id of getGameOrder()) {
var games = getGames();
let g = games[id];
let li = $(`<li class="ui-state-default" data-appid="${id}"><a href="${g.url}">${g.name}</a> - ${id} - ${g.price}
<a href="javascript:" style="float:right; color:red; margin-top:-3px">✖</a></li>`);
$("#lcm_list").append(li);
li.find("a:last").click(function(){
deleteGame($(this).parent().attr("data-appid"));
updateListing();
dumpListing();
});
}
}
function saveGameOrder() {
let gameOrder = $("#lcm_list li.ui-state-default").map(function(){return this.getAttribute("data-appid");}).get();
GM_setValue("gameOrder", JSON.stringify(gameOrder));
}
// Post chart code to the textarea
function dumpListing() {
saveGameOrder();
const games = getGames();
var dump = "Game | Ratings | Cards | Bundled | Retail Price\n :- | :-: | :-: | :-: | :-:\n";
$("#lcm_list li.ui-state-default").each(function(idx, li) {
const g = games[li.getAttribute("data-appid")];
let link = `[**${g.name}**](${g.url})`;
if (g.dlc)
link += " (DLC)";
let cards = "-";
if (g.cards === true) {
cards = `[**?**](http://www.steamcardexchange.net/index.php?gamepage-appid-${g.appid})`;
if (g.dlc === true)
cards = "(Base game has cards)";
}
let bundles = g.bundles !== undefined ? g.bundles : "?";
if (g.bundlesUrl)
bundles = "[" + bundles + "](" + g.bundlesUrl + ")";
let price = g.price;
if (g.plain)
price = "[" + price + "](https://isthereanydeal.com/#/page:game/info?plain=" + g.plain + ")";
dump += [link, g.rating, cards, bundles, price].join(" | ");
dump += "\n";
});
dump += "Chart created with [Lex's SG Chart Maker](https://www.steamgifts.com/discussion/ed1gC/userscript-lexs-sg-chart-maker)";
$("#lcm_dump").val(dump);
}
function deleteGame(aid) {
var games = getGames();
delete games[aid];
GM_setValue("games", JSON.stringify(games));
let gameOrder = getGameOrder();
gameOrder.splice(gameOrder.indexOf(aid), 1);
GM_setValue("gameOrder", JSON.stringify(gameOrder));
$("#lcm_add_btn").removeClass("queue_btn_active");
}
// Add button to app pages
let btn = $(`<a id="lcm_add_btn" class="btnv6_blue_hoverfade btn_medium btn_steamdb"><span>+ <span style="position:relative;top:-1px">⊞</span> Chart</span></a>`);
btn.click(function(){
$(this).addClass("queue_btn_active");
addAppToChart();
showChartMaker();
});
btn.toggleClass("queue_btn_active", GameID in getGames());
$(`.apphub_OtherSiteInfo`).append(btn);
// Add buttons to Packages that include this game
$(".game_area_purchase_game h1:not(:first)").each(function(){
let link = $(`<a href="javascript:"> +⊞ Chart</a>`);
var elem = $(this).closest(".game_area_purchase_game_wrapper");
link.click(function(){
addPackageToChart(elem);
showChartMaker();
});
$(this).append(link);
});
})();