steamgifts rating and platforms 2023

Show rating and supported platforms on steamgifts.com according information from Steam

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         steamgifts rating and platforms 2023
// @namespace    Gidden
// @version      3.0.0
// @description  Show rating and supported platforms on steamgifts.com according information from Steam
// @require      http://code.jquery.com/jquery.min.js
// @author       Gidden, update by Титан
// @match        http://www.steamgifts.com/
// @match        http://www.steamgifts.com/giveaways/search?*
// @match        https://www.steamgifts.com/
// @match        https://www.steamgifts.com/giveaways/search?*
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

// I replaced find() funcs with my responseQuerySelector() and responseQuerySelectorAll() funcs because I don't get how to get find() to work with responseText
//TODO: redirect bundles and calculate rating as max of all games in bundle

let log = false;

// Storage existence test
function supports_html5_storage() {
	try {
		return 'localStorage' in window && window.localStorage !== null;
	} catch (e) {
		return false;
	}
}

// Get color according percentage (0% = Red, 100% = Green)
function getColor(value){
	//value from 0 to 1
	var hue=((value)*120).toString(10);
	return ["hsl(",hue,",100%,42%)"].join("");
}

var platforms = ["win", "mac", "linux", "steamplay"];

// Get actual configuration from storage
function getFilterCfg(myItem)
{
	if (myItem == "percentage") {
		myValue = 0;
	} else {
		myValue = 1;
	}
	if (supports_html5_storage()) {
		if (localStorage.getItem(myItem) !== null) {
			myValue = localStorage.getItem(myItem);
		}
	}
	return myValue;
}

// Store information about game
function saveGameData(url, data) {
	if (supports_html5_storage()) {
		localStorage.setItem(url,  JSON.stringify(data));
	}
}

// Get stored information about game
function loadGameData(url) {
	var myValue = null;
	if (supports_html5_storage()) {
		myValue = localStorage.getItem(url);
		if (myValue) {
			myValue = JSON.parse(myValue);
		}
	}
	return myValue;
}

// Filtering functions
var filterFunction = "" +
	"var platforms = [\"win\", \"mac\", \"linux\", \"steamplay\"];" +
	"function supports_html5_storage() {" +
	"  try {" +
	"    return 'localStorage' in window && window.localStorage !== null;" +
	"  } catch (e) {" +
	"    return false;" +
	"  }" +
	"}" +
	"function saveFilterCfg()" +
	"{" +
	"    if (supports_html5_storage()) {" +
	"        for (var x in platforms){" +
	"            platform = platforms[x];" +
	"            if (document.getElementById(\"filter_\" + platform).checked) {" +
	"                localStorage.setItem(platform, 1);" +
	"            } else {" +
	"                localStorage.setItem(platform, 0);" +
	"            }" +
	"        }" +
	"    if (document.getElementById(\"filter_level\").checked) {" +
	"        localStorage.setItem(\"level\", 1);" +
	"    } else {" +
	"        localStorage.setItem(\"level\", 0);" +
	"    }" +
	"        localStorage.setItem(\"percentage\", document.getElementById(\"filter_percentage\").value);" +
	"    }" +
	"}" +
	"function changeFilter(){" +
	"" +
	"    platformCheck = [];" +
	"    for (var x in platforms){" +
	"        platform = platforms[x];" +
	"        if (document.getElementById(\"filter_\" + platform).checked) {" +
	"            platformCheck.push(platform);" +
	"        }" +
	"    }" +
	"    percCheck = document.getElementById(\"filter_percentage\").value;" +
	"" +
	"    var headers = $(\"div.giveaway__summary h2.giveaway__heading\");" +
	"" +
	"    headers.each(function() {" +
	"        var header = $(this);" +
	"        var showEl = true;" +
	"" +
	"        if (document.getElementById(\"filter_level\").checked){" +
	"            if (header.parent().find(\"div.giveaway__column--contributor-level--negative\").length) {" +
	"                header.parent().parent().parent().hide();" +
	"                return;" +
	"            }" +
	"        }" +
	"" +
	"        percE = header.find(\"span.percentage\");" +
	"" +
	"        if (percE.length)" +
	"        {" +
	"            var perc2 = percE[0].getAttribute(\"perc\");" +
	"            if (perc2 < percCheck){" +
	"                showEl = false;" +
	"            } else {" +
	"                showEl = false;" +
	"                for (var x in platformCheck){" +
	"                    actPlatformCheck = platformCheck[x];" +
	"                    if (header.find(\"span.\"+actPlatformCheck).length) {" +
	"                       showEl = true;" +
	"                    }" +
	"                }" +
	"            }" +
	"        }" +
	"" +
	"        if (showEl) {" +
	"            header.parent().parent().parent().show();" +
	"        } else {" +
	"            header.parent().parent().parent().hide();" +
	"        }" +
	"    });" +
	"    saveFilterCfg();" +
	"}";

function renderPluginGame(header, gameData) {

	if ((header === null) || (gameData === null) /*|| ("error" in gameData)*/){
		return;
	}

	// Remove old informations
	header.find("a.giveaway__icon span").remove();
	header.find("h2.giveaway__heading span").remove();

	var a_tag = header.find( "a.giveaway__icon" );
	var i_tag = header.find( "i.giveaway__icon" );
	if (! i_tag.length) {
		i_tag = a_tag;
	}
	a_tag.attr("style", "opacity:1");
	a_tag.find("i.fa-steam").attr("style", "opacity:.35");

	// Render perc + ppl
	if ("perc" in gameData) {
		var rating = gameData.perc + '% (' + gameData.ppl + ') ';
		header.append(" <span style=\"color:" + getColor(gameData.perc /100.0) + "\" class=\"percentage\" perc=\"" + gameData.perc + "\">" + rating + "</span>");
	}

	// Render platforms
	if ("platforms" in gameData)
	{
		//a_tag.append(" <span>" + platform.html() + "</span>");
	}
	var platformsHtml = "<span>";
	for (var platf in gameData.platforms) {
		platformsHtml = platformsHtml + "<span class=\"platform_img " + platf + "\"></span>";
	}
	a_tag.append(platformsHtml + "</span>");

	// Render cards
	if (("cards" in gameData) && (gameData.cards)) {
		i_tag.after("<span class=\"platform_img right_allign\"><img src=\"http://store.akamai.steamstatic.com/public/images/v6/ico/ico_cards.png\"></span>");
	}

	if ((gameData.perc < getFilterCfg("percentage"))) {
		header.parent().parent().parent().hide();
	}

	hide = gameData.platforms?.length > 0;
	for (var x in platforms) {
		if (hide) break;
		platformCheck = platforms[x];
		if ((gameData.platforms[platformCheck] || false) && (getFilterCfg(platformCheck) == 1)) {
			hide = false;
		}
	}
	if (hide) {
		header.parent().parent().parent().hide();
	}
}

/**
 * Looks for a HTMLElement with {@link selector} in the responseText
 * @param responseText array of HTMLElements and shit
 * @param selector css selector
 * @returns {null|HTMLElement} first HTMLElement matching {@link selector} or null if not found
 */
function responseQuerySelector(responseText, selector) {
	for (var i = responseText.length-1; i >= 0 ; i--) {
		var currentResponse = responseText[i];
		if (/*currentResponse.classList?.contains("responsive_page_frame") &&*/ currentResponse.querySelector) { // if it's a HTMLElement /*and is a responsive_page_frame (basically a whole page)*/
			var node = currentResponse.querySelector(selector);
			if (node) {
				return node;
			}
		}
	}
	return null;
}

/**
 * Looks for a HTMLElements with {@link selector} in the responseText
 * @param responseText array of HTMLElements and shit
 * @param selector css selector
 * @returns {null|HTMLElement} array of HTMLElements matching {@link selector} or empty array if not found
 */
function responseQuerySelectorAll(responseText, selector) {
	var nodes = [];
	for (var i = responseText.length-1; i >= 0 ; i--) {
		var currentResponse = responseText[i];
		if (currentResponse.classList?.contains("responsive_page_frame") && currentResponse.querySelectorAll) { // if it's a HTMLElement and is a responsive_page_frame (basically a whole page)
			var node = currentResponse.querySelectorAll(selector);
			if (node) {
				nodes.push(node);
			}
		}
	}
	return nodes;
}

function exportDataFromPage(steamURL, responseText, callFunction) {
	var gameData = {};
	gameData.ppl = false;
	gameData.perc = false;
	gameData.platforms = {};
	gameData.cards = false;
	gameData.fetchTime = new Date().getTime();

	var ratingFull = responseQuerySelector(responseText,".responsive_reviewdesc_short");

	if (!ratingFull === null) {
		gameData.error = true;
		saveGameData(steamURL, gameData);
	} else {
		let stage = "rating"
		let debugVar = ratingFull;
		try {
			// extract percentare review (perc) and peoples reviewed (ppl) variables.

			var ratingFullString = ratingFull.innerText.trim();
			var perc = ratingFullString.match("[0-9]+ ?%");
			if (perc === null) {
				perc = ratingFullString.match("% ?[0-9]+");
			}
			gameData.perc = perc[0].replace("%", "").replace(" ", "");
			ppl = ratingFullString.match(/[0-9,]+/g);
			if (ppl[0] == gameData.perc) {
				ppl = ppl[1];
			} else {
				ppl = ppl[0];
			}
			gameData.ppl = ppl.replace(",", ".");

			stage = "platforms"
			// extract platforms
			var platform = responseQuerySelector(responseText, "div.game_area_purchase_platform");
			debugVar = platform;
			if (platform) {
				for (var x in platforms) {
					var actPlatformCheck = platforms[x];
					if (platform.querySelector("span." + actPlatformCheck)) {
						gameData.platforms[actPlatformCheck] = true;
					}
				}
			}

			stage = "cards"
			// extract support of gaming cards
			var cards;
			cards = responseQuerySelector(responseText, "[class=\"game_area_details_specs_ctn\"][href=\"https://store.steampowered.com/search/?category2=29&snr=1_5_9__423\"]")
			debugVar = cards;
			gameData.cards = cards !== null;

			stage = "save"
			// Save
			saveGameData(steamURL, gameData);

			//renderPluginGame(header, gameData);
			if (callFunction !== null) {
				callFunction(steamURL, gameData);
			}
		} catch (e) {
			gameData.error = true;
			saveGameData(steamURL, gameData);
			if(log) {
				console.log(`[Steamgifts rating] Error on ${stage}: ` + e)
				console.log(debugVar)
				console.log(responseText)
			}
		}
	}
	return gameData;
}

function fetchSteamDate(steamURL, callFunction) {
	var gameData = {};

	GM_xmlhttpRequest({
		method: "GET",
		url: steamURL,
		context: steamURL,
//        synchronous: true,
		onload: function(response) {
			var responseText = $(response.responseText);
			var ageCheck = responseQuerySelector(responseText, "div#agegate_box")
			if (ageCheck) {
				GM_xmlhttpRequest({
					method: "POST",
					data: "snr=1_agecheck_agecheck__age-gate&ageDay=1&ageMonth=January&ageYear=1980",
					headers: {
						"Content-Type": "application/x-www-form-urlencoded"
					},
					url: steamURL.replace("/app/","/agecheck/app/"),
					context: steamURL,
					onload: function(response) {
						var responseText = $(response.responseText);
						exportDataFromPage(steamURL, responseText, callFunction);
					}
				});
			} else {
				exportDataFromPage(steamURL, responseText, callFunction);
			}
		}
	});

	return gameData;
}

//! MAIN function
// Generate html string for checkboxes
var checkboxes = "";
for (var x in platforms){
	platform = platforms[x];
	var checked = "";
	if (getFilterCfg(platform) == 1) {
		checked = "checked";
	}
	checkboxes += "<input class=\"filter_checkboxes\" type=\"checkbox\" name=\"filter_" + platform + "\" id=\"filter_" + platform + "\" onclick=\"changeFilter();\" " + checked + "><label class=\"checkboxes\" for=\"filter_" + platform + "\"><span class=\"platform_img " + platform + "\"></span></label>";
}

// New headers. Mainly css styles
$("head link:last")
	.before("<link rel=stylesheet type=text/css href=https://steamstore-a.akamaihd.net/public/css/v6/store.css>")
	.after("<style>span.platform_img {background-color: black; height:20px; display: inline-block; opacity: 0.35;} input.filter_checkboxes {height:15px; width:20px;} input.filter_percentage {height:18px; width:40px; margin:5px; padding:0px 10px;} label.checkboxes {margin-right:5px;}</style>")
	.after("<style>span.percentage {font-size: medium; font-weight: bold;} h2.giveaway__heading {position:relative;} span img {width: 20px; height: 16px;} span.right_allign {position:absolute; right: 0; top: 0;}</style>")
	.after("<script type=\"text/javascript\">" + filterFunction + "</script>");

var checkedLevel = "";
if (getFilterCfg("level") == 1) {
	checkedLevel = "checked";
}

// Appending left filter panel
filterPanel = "<h3 class=\"sidebar__heading\">Filter</h3><div style=\"margin-left:10px;\">" +
	" <span style=\"font-weight: bold;\">Game</span> &gt;= <input type=\"text\" class=\"filter_percentage\" id=\"filter_percentage\" value=\"" + getFilterCfg("percentage") + "\" onchange=\"changeFilter()\" onkeyup=\"changeFilter()\">% <br>" +
	checkboxes + "</br>" +
	"<input type=\"checkbox\" class=\"filter_checkboxes\" id=\"filter_level\" onclick=\"changeFilter();\" " + checkedLevel + "><label class=\"checkboxes\" for=\"filter_level\"><span style=\"font-weight: bold;\">Hide higher level</span></label>" +
	"</div>";

$("div.sidebar__search-container").after(filterPanel);

// Fetching steam for games information
var needFetch = {};
var headers = $("div.giveaway__summary h2.giveaway__heading");
var actTime = new Date().getTime();
headers.each(function() {
	var header = $(this);
	var iconElement = header.find("a.giveaway__icon");
	if (iconElement.length) {
		var steamURL = iconElement.attr("href").trim();

		if ((getFilterCfg("level") == 1) && (header.parent().find("div.giveaway__column--contributor-level--negative").length)) {
			header.parent().parent().parent().hide();
		}

		var gameData = loadGameData(steamURL);

		if (gameData === null || gameData.error === true) {
			needFetch[steamURL] = true;
		} else {
			renderPluginGame(header, gameData);

			// If game info are older than one day then fetch new info.
			if ((actTime - gameData.fetchTime) > 24*60*60*1000) {
				needFetch[steamURL] = true;
			}
		}
	}
});

function afterFetch(steamURL, gameData) {
	var headers = $("a[href=\"" + steamURL + "\"]");
	headers.each(function() {
		var header = $(this).parent();
		renderPluginGame(header, gameData);
	});
}

for(var steamURL in needFetch) {
	var gameData = fetchSteamDate(steamURL, afterFetch);
}