- // ==UserScript==
- // @name SteamHistoryLowestPrice
- // @namespace SteamHistoryLowestPrice@Byzod.user.js
- // @description 显示游戏在各种商店中当前和历史最低价格及进包次数
- // @include /^https?:\/\/store\.steampowered\.com\/(app|sub|bundle)\/\d+/
- // @license GPL version 3 or any later version
- // @version 2017-7-8
- // @grant GM_xmlhttpRequest
- // jshint esversion:6
- // ==/UserScript==
-
- // 显示样式
- // 0 = 显示在购买按钮上面
- // 1 = 显示在购买信息框上面
- const INFO_STYLE = 0;
-
- // 货币区域覆盖,两个字母的国家代号,大小写均可
- // 空字符("")代表不覆盖,使用steam的cookie中steamCountry的值
- // 见 https://zh.wikipedia.org/wiki/ISO_3166-1 或 https://en.wikipedia.org/wiki/ISO_3166-1
- // 常用 美国USD:"us", 中国CNY: "cn", 英国GBP: "uk", 日本JPY: "jp", 俄国RUS: "ru"
- const CC_OVERRIDE = "";
-
- // 货币符号
- const CURRENCY_SYMBOLS = {
- 'AED': 'DH',
- 'AUD': 'A$',
- 'BRL': 'R$',
- 'CAD': 'CDN$',
- 'CHF': 'CHF',
- 'CLP': 'CLP$',
- 'CNY': '¥', // Chines Yuan
- 'COP': 'COL$',
- 'CRC': '₡', // Costa Rican Colón
- 'EUR': '€', // Euro
- 'GBP': '£', // British Pound Sterling
- 'HKD': 'HK$',
- 'IDR': 'Rp',
- 'ILS': '₪', // Israeli New Sheqel
- 'INR': '₹', // Indian Rupee
- 'JPY': '¥', // Japanese Yen
- 'KRW': '₩', // South Korean Won
- 'MXN': 'Mex$',
- 'MYR': 'RM',
- 'NGN': '₦', // Nigerian Naira
- 'NOK': 'kr',
- 'NZD': 'NZ$',
- 'PEN': 'S/.',
- 'PHP': '₱', // Philippine Peso
- 'PLN': 'zł', // Polish Zloty
- 'PYG': '₲', // Paraguayan Guarani
- 'RUB': 'pуб',
- 'SAR': 'SR',
- 'SGD': 'S$',
- 'THB': '฿', // Thai Baht
- 'TRY': 'TL',
- 'TWD': 'NT$',
- 'UAH': '₴', // Ukrainian Hryvnia
- 'USD': '$', // US Dollar
- 'VND': '₫', // Vietnamese Dong
- 'ZAR': 'R ',
- };
-
- // 查询历史低价包括的商店
- const STORES = [
- "steam",
- "amazonus",
- "impulse",
- "gamersgate",
- "direct2drive",
- "origin",
- "uplay",
- "indiegalastore",
- "gamesplanet",
- "indiegamestand",
- "gog",
- "nuuvem",
- "dlgamer",
- "humblestore",
- "squenix",
- "bundlestars",
- "fireflower",
- "humblewidgets",
- "newegg",
- "coinplay",
- "wingamestore",
- "macgamestore",
- "gamebillet",
- "silagames",
- "itchio",
- "gamejolt",
- "paradox"
- ];
-
-
- // 在app页和愿望单页显示史低价格
- let urlMatch = location.href.match(/(app|sub|bundle)\/(\d+)/);
- let appId = "";
- let type = "";
- let subIds = [];
- if(urlMatch && urlMatch.length == 3){
- type = urlMatch[1]
- appId = urlMatch[2];
- }
-
- // console.log('[史低]: ' + type + ' : ' + appId + ', requesting data info...'); // DEBUG
-
- // 获取subs
- document.querySelectorAll("input[name=subid]")
- .forEach(sub=>subIds.push(sub.value));
-
- let cc = "cn";
- if(CC_OVERRIDE.length > 0){
- // 使用覆盖的货币区域
- cc = CC_OVERRIDE;
- } else {
- // 使用默认的的货币区域
- let ccMatch = document.cookie.match(/steamCountry=([a-z]{2})/i);
- if (ccMatch !== null && ccMatch.length == 2) {
- cc = ccMatch[1];
- }
- }
-
- AddLowestPriceTag(appId, type, subIds, STORES.join(","), cc, location.protocol);
-
- // 在商店页添加史低信息
- async function AddLowestPriceTag(appId, type = "app", subIds = [], stores = "steam", cc = "cn", protocol = "https"){
- // 史低信息容器们
- let lowestPriceNodes = {};
-
- // 统计subid
- let findSubIds = [];
- if(type == "bundle"){
- // bundle就一个, 视作subid
- findSubIds.push(appId);
- } else if (type == "app" || type == "sub"){
- // app/sub 可能有好多
- findSubIds = subIds.slice();
- }
-
- // 寻找每一个subid的购买按钮,生成史低信息容器们
- findSubIds.forEach(subId => {
- let gameWrapper = null;
- try{
- gameWrapper = document.querySelector('.game_area_purchase_game input[value="' + subId + '"]');
- switch (INFO_STYLE){
- case 0:
- gameWrapper = gameWrapper.parentNode.parentNode.querySelector('.game_purchase_action');
- break;
- case 1:
- gameWrapper = gameWrapper.parentNode.parentNode.parentNode;
- break;
- }
- } catch (ex) {
- gameWrapper = null;
- }
- if(gameWrapper){
- let lowestInfo = document.createElement("div");
- lowestInfo.className = "game_lowest_price";
- lowestInfo.innerText = "正在读取历史最低价格...";
- if(INFO_STYLE == 1){
- lowestInfo.style.background = 'linear-gradient(to right, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.2) 100%)';
- }
- gameWrapper.prepend(lowestInfo);
- lowestPriceNodes[subId] = lowestInfo;
- }
- });
-
- // 获取sub们的数据
- let data = null;
- try {
- data = JSON.parse( await GettingSteamDBAppInfo(appId, type, subIds, stores, cc, protocol) );
- } catch (err) {
- console.log('[史低]: ' + err);
- }
-
- // console.log('[史低]: app ' + appId + ' data : %o', data); // DEBUG
-
- // 解析data
- let appInfos = [];
- let metaInfo = data ? data[".meta"] : null;
- // 如果是bundle, 除了.meta外只有一个bundle/xxx,否则是一大堆xxx
- if(type == "bundle"){
- appInfos.push({Id:appId, Info:data["bundle/" + appId]});
- } else if(type == "app" || type == "sub"){
- for(let key in data){
- if(!isNaN(key)){
- appInfos.push({Id:key, Info:data[key]});
- }
- }
- }
- // console.log('[史低]: app ' + appId + ' metaInfo: %o', metaInfo); // DEBUG
- // console.log('[史低]: app ' + appId + ' appInfos: %o', appInfos); // DEBUG
-
- // 如果查到info,塞到购买按钮上面去
- if(metaInfo && appInfos.length > 0){
- // 获取整体信息
- let currencySymbol = metaInfo.currency in CURRENCY_SYMBOLS
- ? CURRENCY_SYMBOLS[metaInfo.currency]
- : metaInfo.currency;
-
- // 为每一个sub或bundle添加史低
- appInfos.forEach( app => {
- let lowestInfo = lowestPriceNodes[app.Id];
-
- if(lowestInfo){
- lowestInfo.innerHTML =
- // 历史最低
- '历史最低价是 '
- + '<span style="cursor:help;text-decoration:underline;" title="' + app.Info.lowest.recorded_formatted + '">'
- + new Date(app.Info.lowest.recorded * 1e3).toLocaleDateString()
- + '</span> 时在 '
- + '<a target="_blank" href="' + app.Info.lowest.url + '"> '
- + app.Info.lowest.store
- + '</a> 中的 '
- + '<span class="discount_pct">-' + app.Info.lowest.cut + '%</span>'
- + '<a target="_blank" title="查看价格历史" href="' + app.Info.urls.history + '"> '
- + currencySymbol + ' ' + app.Info.lowest.price
- + '</a>'
-
- // 第二行
- + '<br />'
-
- // 进包次数
- + '进包 <a target="_blank" title="查看进包信息" href="' + app.Info.urls.bundles + '"'
- + (app.Info.bundles && app.Info.bundles.live.length > 0
- ? ' style="color:#0d0" title="' + app.Info.bundles.live.length + ' 个正在销售的慈善包"'
- : '')
- +'> '
- + app.Info.bundles.count
- + ' </a> 次,'
-
- // 当前最低
- + (app.Info.price.price <= app.Info.lowest.price
- ? '<span class="game_purchase_discount_countdown">当前为历史最低价</span>,'
- : '<span>当前最低价是</span>' )
- + '在 '
- + '<a target="_blank" href="' + app.Info.price.url + '"> '
- + app.Info.price.store
- + '</a> 中的 '
- + '<span class="discount_pct">-' + app.Info.price.cut + '%</span>'
- + '<a target="_blank" title="查看价格信息" href="' + app.Info.urls.info + '"> '
- + currencySymbol + ' ' + app.Info.price.price
- + '</a>';
- }
- });
- } else {
- // metaInfo为空,或者appInfos无内容
- console.log('[史低]: get lowest price failed, data = %o', data);
- for(let id in lowestPriceNodes){
- lowestPriceNodes[id].innerHTML = "";
- }
- }
-
- // 返回史低info
- return Promise.resolve(lowestPriceNodes);
- }
-
- // 获取史低信息
- async function GettingSteamDBAppInfo(appId, type = "app", subIds = [], stores = "steam", cc = "cn", protocol = "https"){
- let requestPromise = null;
- let bundleId = "";
-
- if(type == "bundle"){
- bundleId = appId;
- }
- if(!isNaN(appId) && parseInt(appId) > 0){
- let requestUrl = protocol + "//api.enhancedsteam.com/pricev3/?"
- + "bundleid=" + bundleId
- + "&subs=" + subIds.join(',')
- + "&appid=" + appId
- + "&stores=" + stores
- + "&cc=" + cc;
- // console.log('[史低]: requestUrl: ' + requestUrl); // DEBUG
- requestPromise = new Promise( (resolve, reject) => {
- GM_xmlhttpRequest({
- method: "GET",
- url: requestUrl,
- onload: function (response) {
- resolve(response.response);
- },
- onerror: function (error) {
- reject(error);
- }
- });
- });
- } else {
- requestPromise = Promise.reject("Invalid appid");
- }
-
- return requestPromise;
- }