SteamHistoryLowestPrice

显示游戏在各种商店中当前和历史最低价格及进包次数

  1. // ==UserScript==
  2. // @name SteamHistoryLowestPrice
  3. // @namespace SteamHistoryLowestPrice@Byzod.user.js
  4. // @description 显示游戏在各种商店中当前和历史最低价格及进包次数
  5. // @include /^https?:\/\/store\.steampowered\.com\/(app|sub|bundle)\/\d+/
  6. // @license GPL version 3 or any later version
  7. // @version 2017-7-8
  8. // @grant GM_xmlhttpRequest
  9. // jshint esversion:6
  10. // ==/UserScript==
  11.  
  12. // 显示样式
  13. // 0 = 显示在购买按钮上面
  14. // 1 = 显示在购买信息框上面
  15. const INFO_STYLE = 0;
  16.  
  17. // 货币区域覆盖,两个字母的国家代号,大小写均可
  18. // 空字符("")代表不覆盖,使用steam的cookie中steamCountry的值
  19. // 见 https://zh.wikipedia.org/wiki/ISO_3166-1 或 https://en.wikipedia.org/wiki/ISO_3166-1
  20. // 常用 美国USD:"us", 中国CNY: "cn", 英国GBP: "uk", 日本JPY: "jp", 俄国RUS: "ru"
  21. const CC_OVERRIDE = "";
  22.  
  23. // 货币符号
  24. const CURRENCY_SYMBOLS = {
  25. 'AED': 'DH',
  26. 'AUD': 'A$',
  27. 'BRL': 'R$',
  28. 'CAD': 'CDN$',
  29. 'CHF': 'CHF',
  30. 'CLP': 'CLP$',
  31. 'CNY': '¥', // Chines Yuan
  32. 'COP': 'COL$',
  33. 'CRC': '₡', // Costa Rican Colón
  34. 'EUR': '€', // Euro
  35. 'GBP': '£', // British Pound Sterling
  36. 'HKD': 'HK$',
  37. 'IDR': 'Rp',
  38. 'ILS': '₪', // Israeli New Sheqel
  39. 'INR': '₹', // Indian Rupee
  40. 'JPY': '¥', // Japanese Yen
  41. 'KRW': '₩', // South Korean Won
  42. 'MXN': 'Mex$',
  43. 'MYR': 'RM',
  44. 'NGN': '₦', // Nigerian Naira
  45. 'NOK': 'kr',
  46. 'NZD': 'NZ$',
  47. 'PEN': 'S/.',
  48. 'PHP': '₱', // Philippine Peso
  49. 'PLN': 'zł', // Polish Zloty
  50. 'PYG': '₲', // Paraguayan Guarani
  51. 'RUB': 'pуб',
  52. 'SAR': 'SR',
  53. 'SGD': 'S$',
  54. 'THB': '฿', // Thai Baht
  55. 'TRY': 'TL',
  56. 'TWD': 'NT$',
  57. 'UAH': '₴', // Ukrainian Hryvnia
  58. 'USD': '$', // US Dollar
  59. 'VND': '₫', // Vietnamese Dong
  60. 'ZAR': 'R ',
  61. };
  62.  
  63. // 查询历史低价包括的商店
  64. const STORES = [
  65. "steam",
  66. "amazonus",
  67. "impulse",
  68. "gamersgate",
  69. "direct2drive",
  70. "origin",
  71. "uplay",
  72. "indiegalastore",
  73. "gamesplanet",
  74. "indiegamestand",
  75. "gog",
  76. "nuuvem",
  77. "dlgamer",
  78. "humblestore",
  79. "squenix",
  80. "bundlestars",
  81. "fireflower",
  82. "humblewidgets",
  83. "newegg",
  84. "coinplay",
  85. "wingamestore",
  86. "macgamestore",
  87. "gamebillet",
  88. "silagames",
  89. "itchio",
  90. "gamejolt",
  91. "paradox"
  92. ];
  93.  
  94.  
  95. // 在app页和愿望单页显示史低价格
  96. let urlMatch = location.href.match(/(app|sub|bundle)\/(\d+)/);
  97. let appId = "";
  98. let type = "";
  99. let subIds = [];
  100. if(urlMatch && urlMatch.length == 3){
  101. type = urlMatch[1]
  102. appId = urlMatch[2];
  103. }
  104. // console.log('[史低]: ' + type + ' : ' + appId + ', requesting data info...'); // DEBUG
  105. // 获取subs
  106. document.querySelectorAll("input[name=subid]")
  107. .forEach(sub=>subIds.push(sub.value));
  108. let cc = "cn";
  109. if(CC_OVERRIDE.length > 0){
  110. // 使用覆盖的货币区域
  111. cc = CC_OVERRIDE;
  112. } else {
  113. // 使用默认的的货币区域
  114. let ccMatch = document.cookie.match(/steamCountry=([a-z]{2})/i);
  115. if (ccMatch !== null && ccMatch.length == 2) {
  116. cc = ccMatch[1];
  117. }
  118. }
  119. AddLowestPriceTag(appId, type, subIds, STORES.join(","), cc, location.protocol);
  120. // 在商店页添加史低信息
  121. async function AddLowestPriceTag(appId, type = "app", subIds = [], stores = "steam", cc = "cn", protocol = "https"){
  122. // 史低信息容器们
  123. let lowestPriceNodes = {};
  124. // 统计subid
  125. let findSubIds = [];
  126. if(type == "bundle"){
  127. // bundle就一个, 视作subid
  128. findSubIds.push(appId);
  129. } else if (type == "app" || type == "sub"){
  130. // app/sub 可能有好多
  131. findSubIds = subIds.slice();
  132. }
  133. // 寻找每一个subid的购买按钮,生成史低信息容器们
  134. findSubIds.forEach(subId => {
  135. let gameWrapper = null;
  136. try{
  137. gameWrapper = document.querySelector('.game_area_purchase_game input[value="' + subId + '"]');
  138. switch (INFO_STYLE){
  139. case 0:
  140. gameWrapper = gameWrapper.parentNode.parentNode.querySelector('.game_purchase_action');
  141. break;
  142. case 1:
  143. gameWrapper = gameWrapper.parentNode.parentNode.parentNode;
  144. break;
  145. }
  146. } catch (ex) {
  147. gameWrapper = null;
  148. }
  149. if(gameWrapper){
  150. let lowestInfo = document.createElement("div");
  151. lowestInfo.className = "game_lowest_price";
  152. lowestInfo.innerText = "正在读取历史最低价格...";
  153. if(INFO_STYLE == 1){
  154. lowestInfo.style.background = 'linear-gradient(to right, rgba(0,0,0,0.3) 0%,rgba(0,0,0,0.2) 100%)';
  155. }
  156. gameWrapper.prepend(lowestInfo);
  157. lowestPriceNodes[subId] = lowestInfo;
  158. }
  159. });
  160. // 获取sub们的数据
  161. let data = null;
  162. try {
  163. data = JSON.parse( await GettingSteamDBAppInfo(appId, type, subIds, stores, cc, protocol) );
  164. } catch (err) {
  165. console.log('[史低]: ' + err);
  166. }
  167. // console.log('[史低]: app ' + appId + ' data : %o', data); // DEBUG
  168. // 解析data
  169. let appInfos = [];
  170. let metaInfo = data ? data[".meta"] : null;
  171. // 如果是bundle, 除了.meta外只有一个bundle/xxx,否则是一大堆xxx
  172. if(type == "bundle"){
  173. appInfos.push({Id:appId, Info:data["bundle/" + appId]});
  174. } else if(type == "app" || type == "sub"){
  175. for(let key in data){
  176. if(!isNaN(key)){
  177. appInfos.push({Id:key, Info:data[key]});
  178. }
  179. }
  180. }
  181. // console.log('[史低]: app ' + appId + ' metaInfo: %o', metaInfo); // DEBUG
  182. // console.log('[史低]: app ' + appId + ' appInfos: %o', appInfos); // DEBUG
  183. // 如果查到info,塞到购买按钮上面去
  184. if(metaInfo && appInfos.length > 0){
  185. // 获取整体信息
  186. let currencySymbol = metaInfo.currency in CURRENCY_SYMBOLS
  187. ? CURRENCY_SYMBOLS[metaInfo.currency]
  188. : metaInfo.currency;
  189. // 为每一个sub或bundle添加史低
  190. appInfos.forEach( app => {
  191. let lowestInfo = lowestPriceNodes[app.Id];
  192. if(lowestInfo){
  193. lowestInfo.innerHTML =
  194. // 历史最低
  195. '历史最低价是 '
  196. + '<span style="cursor:help;text-decoration:underline;" title="' + app.Info.lowest.recorded_formatted + '">'
  197. + new Date(app.Info.lowest.recorded * 1e3).toLocaleDateString()
  198. + '</span>&nbsp;时在&nbsp;'
  199. + '<a target="_blank" href="' + app.Info.lowest.url + '"> '
  200. + app.Info.lowest.store
  201. + '</a>&nbsp;中的&nbsp;'
  202. + '<span class="discount_pct">-' + app.Info.lowest.cut + '%</span>'
  203. + '<a target="_blank" title="查看价格历史" href="' + app.Info.urls.history + '"> '
  204. + currencySymbol + ' ' + app.Info.lowest.price
  205. + '</a>'
  206.  
  207. // 第二行
  208. + '<br />'
  209.  
  210. // 进包次数
  211. + '进包&nbsp;<a target="_blank" title="查看进包信息" href="' + app.Info.urls.bundles + '"'
  212. + (app.Info.bundles && app.Info.bundles.live.length > 0
  213. ? ' style="color:#0d0" title="' + app.Info.bundles.live.length + ' 个正在销售的慈善包"'
  214. : '')
  215. +'> '
  216. + app.Info.bundles.count
  217. + ' </a>&nbsp;次,'
  218.  
  219. // 当前最低
  220. + (app.Info.price.price <= app.Info.lowest.price
  221. ? '<span class="game_purchase_discount_countdown">当前为历史最低价</span>,'
  222. : '<span>当前最低价是</span>' )
  223. + '在&nbsp;'
  224. + '<a target="_blank" href="' + app.Info.price.url + '"> '
  225. + app.Info.price.store
  226. + '</a>&nbsp;中的&nbsp;'
  227. + '<span class="discount_pct">-' + app.Info.price.cut + '%</span>'
  228. + '<a target="_blank" title="查看价格信息" href="' + app.Info.urls.info + '"> '
  229. + currencySymbol + ' ' + app.Info.price.price
  230. + '</a>';
  231. }
  232. });
  233. } else {
  234. // metaInfo为空,或者appInfos无内容
  235. console.log('[史低]: get lowest price failed, data = %o', data);
  236. for(let id in lowestPriceNodes){
  237. lowestPriceNodes[id].innerHTML = "";
  238. }
  239. }
  240. // 返回史低info
  241. return Promise.resolve(lowestPriceNodes);
  242. }
  243. // 获取史低信息
  244. async function GettingSteamDBAppInfo(appId, type = "app", subIds = [], stores = "steam", cc = "cn", protocol = "https"){
  245. let requestPromise = null;
  246. let bundleId = "";
  247. if(type == "bundle"){
  248. bundleId = appId;
  249. }
  250. if(!isNaN(appId) && parseInt(appId) > 0){
  251. let requestUrl = protocol + "//api.enhancedsteam.com/pricev3/?"
  252. + "bundleid=" + bundleId
  253. + "&subs=" + subIds.join(',')
  254. + "&appid=" + appId
  255. + "&stores=" + stores
  256. + "&cc=" + cc;
  257. // console.log('[史低]: requestUrl: ' + requestUrl); // DEBUG
  258. requestPromise = new Promise( (resolve, reject) => {
  259. GM_xmlhttpRequest({
  260. method: "GET",
  261. url: requestUrl,
  262. onload: function (response) {
  263. resolve(response.response);
  264. },
  265. onerror: function (error) {
  266. reject(error);
  267. }
  268. });
  269. });
  270. } else {
  271. requestPromise = Promise.reject("Invalid appid");
  272. }
  273. return requestPromise;
  274. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址