Ukrainian Steam Improvements

Adds (1) the state flag for games that have Ukrainian language, (2) alert emojis for ua and ru -made games, (3) prices at the top, (4) more noticeable score over the game banner.

  1. // ==UserScript==
  2. // @name Ukrainian Steam Improvements
  3. // @description Adds (1) the state flag for games that have Ukrainian language, (2) alert emojis for ua and ru -made games, (3) prices at the top, (4) more noticeable score over the game banner.
  4. // @include https://store.steampowered.com/*
  5. // @grant GM_addStyle
  6. // @icon https://community.akamai.steamstatic.com/public/images/countryflags/ua.gif
  7. // @license https://creativecommons.org/licenses/by-sa/4.0/
  8. // @author Prudten
  9. // @version 1.1.2
  10. // @namespace https://greasyfork.org/users/1235514
  11. // ==/UserScript==
  12. 'use strict';
  13.  
  14. GM_addStyle(`
  15. .btn_red_steamui {
  16. border-radius: 2px;
  17. border: none;
  18. padding: 1px;
  19. display: inline-block;
  20. cursor: pointer;
  21. text-decoration: none !important;
  22. color: #D28BA9; !important;
  23.  
  24. background: transparent; text-shadow: 1px 1px 0px rgba( 0, 0, 0, 0.3 );}
  25.  
  26. .btn_red_steamui > span {
  27. border-radius: 2px;
  28. display: block;
  29.  
  30.  
  31. background: #D34320;
  32. background: -webkit-linear-gradient( top, #D34320 5%, #BC261B 95%);
  33. background: linear-gradient( to bottom, #D34320 5%, #BC261B 95%);
  34. background: linear-gradient( to right, #D94C22 5%, #BC261B 95%); }
  35.  
  36. .btn_red_steamui:not(.btn_disabled):not(:disabled):not(.btn_active):not(.active):hover {
  37. text-decoration: none !important;
  38. color: #fff; !important;
  39.  
  40. background: transparent; }
  41.  
  42. .btn_red_steamui:not(.btn_disabled):not(:disabled):not(.btn_active):not(.active):hover > span {
  43. background: #8E0E29;
  44. background: -webkit-linear-gradient( top, #8E0E29 5%, #CE4221 95%);
  45. background: linear-gradient( to bottom, #8E0E29 5%, #CE4221 95%);
  46. background: linear-gradient( to right, #8E0E29 5%, #CE4221 95%); }
  47.  
  48. .btn_red_steamui.btn_active, btn_red_steamui.active {
  49. text-decoration: none !important;
  50. color: #fff; !important;
  51.  
  52. background: transparent; }
  53.  
  54. .btn_red_steamui.btn_active > span, btn_red_steamui.active > span {
  55. background: #8E0E29;
  56. background: -webkit-linear-gradient( top, #8E0E29 5%, #CE4221 95%);
  57. background: linear-gradient( to bottom, #8E0E29 5%, #CE4221 95%);
  58. background: linear-gradient( to right, #8E0E29 5%, #CE4221 95%); }
  59.  
  60. .discount_block .discount_pct_red, .discount_pct_red {
  61. font-family: "Motiva Sans", Sans-serif;
  62. font-weight: normal; /* normal */
  63.  
  64. font-weight: 500;
  65. color: #FF2611;
  66. background: #B00722;
  67. display: inline-block;
  68. }
  69.  
  70. .game_purchase_discount .discount_pct_red,
  71. .game_purchase_discount .bundle_base_discount {
  72. display: inline-block;
  73. height: 32px;
  74. line-height: 32px;
  75. font-size: 25px;
  76. text-align: center;
  77. overflow: hidden;
  78. padding: 0 6px;
  79. }
  80.  
  81. .image-container {
  82. position: relative;
  83. display: inline-block;
  84. }
  85.  
  86. .overlay-text {
  87. position: absolute;
  88. bottom: 0;
  89. right: 0;
  90. background-color: #A00000;
  91. font-size: 20px;
  92. }
  93.  
  94. .box {
  95. position: absolute;
  96. bottom: 0;
  97. right: 0;
  98. background-color: #FF0000;
  99. float: left;
  100. height: 40px;
  101. width: 40px;
  102. margin-bottom: 1%;
  103. display: flex;
  104. justify-content: center;
  105. align-items: center;
  106. color: #272727;
  107. font-size: 25px;
  108. font-weight: bold;
  109. font-family: HelveticaNeue;
  110. }
  111.  
  112. .score95 {
  113. background-color: #00D25F;
  114. }
  115. .score85 {
  116. background-color: #66CC33;
  117. }
  118. .score75 {
  119. background-color: #98DD75;
  120. }
  121. .score70 {
  122. background-color: #FFCC33;
  123. }
  124. .score55 {
  125. background-color: #FE9640;
  126. }
  127.  
  128. `);
  129.  
  130.  
  131. function insertAfter(newNode, existingNode) {
  132. existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
  133. }
  134.  
  135. function isNumeric(str) {
  136. if (typeof str != "string") return false
  137. return !isNaN(str) && !isNaN(parseFloat(str))
  138. }
  139.  
  140.  
  141. if(window.location.toString().includes("app")) {
  142. // check translations
  143. var cells = document.getElementsByClassName("ellipsis");
  144. var title = document.getElementsByClassName("apphub_AppName")[0];
  145. var oldTitle = title.innerHTML;
  146. var uagif = "<img src=\"https://community.akamai.steamstatic.com/public/images/countryflags/ua.gif\">";
  147.  
  148. var notSupported = ["\n Не підтримується ",
  149. "\n\t\t\t\t\tНе підтримується\t\t\t\t"];
  150. var russian = ["\n Russian ",
  151. "\n\t\t\t\tRussian\t\t\t",
  152. "\n російська ",
  153. "\n\t\t\t\tросійська\t\t\t"];
  154. var ukrainian = ["\n Ukrainian ",
  155. "\n\t\t\t\tUkrainian\t\t\t",
  156. "\n українська ",
  157. "\n\t\t\t\tукраїнська\t\t\t"];
  158.  
  159. var sunflower = "🌻"
  160. Array.prototype.forEach.call(cells, function(cell) {
  161. if (russian.includes(cell.textContent) &&
  162. !notSupported.includes(cell.nextSibling.nextSibling.textContent)) {
  163. sunflower = "";
  164. };
  165. });
  166.  
  167. Array.prototype.forEach.call(cells, function(cell) {
  168. if (ukrainian.includes(cell.textContent) &&
  169. !notSupported.includes(cell.nextSibling.nextSibling.textContent)) {
  170. var original_ua = cell.textContent.split(/(\p{L}.*)/u);
  171. cell.innerHTML = original_ua[0] + uagif + " " + original_ua[1];
  172. title.innerHTML = `${oldTitle} ${sunflower}${uagif}`;
  173. };
  174. });
  175.  
  176. // check if it's Ukrainian
  177. oldTitle = title.innerHTML;
  178. var imgs = document.getElementsByTagName("img");
  179. var ridne = "93f3a9d4a6868bbaadc90dbbeeecfa13770a59a1";
  180. Array.prototype.forEach.call(imgs, function(img) {
  181. var imgName = img.src.split("/").pop()
  182. if ((imgName == `${ridne}.jpg`) ||
  183. (imgName == `${ridne}_medium.jpg`) ||
  184. (imgName == `${ridne}_full.jpg`)) {
  185. title.innerHTML = `💙💛 ${oldTitle}`;
  186. };
  187. });
  188.  
  189. // check if it's made by 🐷🐶
  190. oldTitle = title.innerHTML;
  191. var prices = document.querySelectorAll('.game_purchase_price, .discount_final_price');
  192. var btns = document.querySelectorAll('.btn_green_steamui, .btn_blue_steamui');
  193. var dscs = document.querySelectorAll('.discount_pct');
  194. var roosnya = false;
  195. var niroosni = ["329f37319a1d6a0c79c67a388414278e3b2996c0.jpg",
  196. "329f37319a1d6a0c79c67a388414278e3b2996c0_medium.jpg",
  197. "329f37319a1d6a0c79c67a388414278e3b2996c0_full.jpg",
  198. "992f3008daabfe9a3a795896a407525ce11b1cc5.jpg",
  199. "992f3008daabfe9a3a795896a407525ce11b1cc5_medium.jpg",
  200. "992f3008daabfe9a3a795896a407525ce11b1cc5_full.jpg",
  201. "5d05c9a196c34e3860fcb34a389c8d0cd6801de8.jpg",
  202. "5d05c9a196c34e3860fcb34a389c8d0cd6801de8_medium.jpg",
  203. "5d05c9a196c34e3860fcb34a389c8d0cd6801de8_full.jpg"];
  204.  
  205. Array.prototype.forEach.call(imgs, function(img) {
  206. var imgName = img.src.split("/").pop();
  207. if (!roosnya && niroosni.includes(imgName)) {
  208. roosnya = true;
  209. title.innerHTML = `🐷🐶 <font color="red">${oldTitle}</font>`;
  210. Array.prototype.forEach.call(prices, function(price) {
  211. var oldPrice = price.innerHTML;
  212. price.innerHTML = `<font color="red">${oldPrice}</font> 🐷🐶`;
  213. });
  214. Array.prototype.forEach.call(btns, function(btn) {
  215. btn.classList.remove('btn_green_steamui','btn_blue_steamui');
  216. btn.classList.add('btn_red_steamui');
  217. });
  218. Array.prototype.forEach.call(dscs, function(dsc) {
  219. dsc.classList.remove('discount_pct');
  220. dsc.classList.add('discount_pct_red');
  221. });
  222. };
  223. });
  224.  
  225. // add elements to the top right corner
  226. var hub = document.getElementsByClassName("btnv6_blue_hoverfade")[0];
  227. hub.innerHTML = "\n <span>✚</span>\n ";
  228.  
  229. var price = document.createElement("div");
  230. price.classList.add('btnv6_blue_hoverfade','btn_medium');
  231. insertAfter(price, hub);
  232.  
  233. // check if it's not a demo
  234. if ((document.getElementsByClassName("game_purchase_action").length > 1) &&
  235. (document.getElementById("demoGameBtn"))) {
  236. var priceNode = document.getElementsByClassName("game_purchase_action")[1];
  237. } else {
  238. var priceNode = document.getElementsByClassName("game_purchase_action")[0];
  239. };
  240.  
  241. // check if it's not released yet
  242. if (document.getElementsByClassName("game_area_comingsoon")[0]) {
  243. var priceText = "В розробці";
  244. if (roosnya) {
  245. priceText = `<font color="red">${priceText}</font> 🐷🐶`;
  246. };
  247. // check the price if it's been released
  248. } else if (priceNode.getElementsByClassName("game_purchase_price").length > 0) {
  249. var priceText = priceNode.getElementsByClassName("game_purchase_price")[0].innerHTML;
  250. // check if it's a free game
  251. if (document.getElementById("freeGameBtn")) {
  252. priceText = `<font color="#beee11">${priceText}</font>`;
  253. };
  254. // check if it's on sale
  255. } else if (priceNode.getElementsByClassName("discount_final_price").length > 0) {
  256. var priceText = priceNode.getElementsByClassName("discount_final_price")[0].innerHTML;
  257.  
  258. var discount = document.createElement("div");
  259. discount.classList.add('btnv6_blue_hoverfade','btn_medium');
  260. insertAfter(discount, hub);
  261.  
  262. if (roosnya) {
  263. var discountText = priceNode.getElementsByClassName("discount_pct_red")[0].innerHTML;
  264. discount.innerHTML = `<span><font color="red">${discountText}</font></span>`;
  265. } else {
  266. var discountText = priceNode.getElementsByClassName("discount_pct")[0].innerHTML;
  267. //discount.innerHTML = `<span><font color="#66cc33">${discountText}</font></span>`;
  268. discount.innerHTML = `<span><font color="#a3cf06">${discountText}</font></span>`;
  269. //discount.innerHTML = `<span><font color="#beee11">${discountText}</font></span>`;
  270. };
  271. discount.insertAdjacentText('beforebegin', `\n `);
  272. } else if ((priceNode.getElementsByClassName("game_purchase_price").length === 0) &&
  273. (priceNode.getElementsByClassName("btn_addtocart").length > 0)) {
  274. var priceText = `<font color="#beee11">Безкоштовно</font>`;
  275. };
  276.  
  277. price.innerHTML = `<span>${priceText}</span>`;
  278. price.insertAdjacentText('beforebegin', `\n `);
  279.  
  280. // add score
  281. var banner = document.getElementsByClassName("game_header_image_full")[0];
  282. var banner_parent = banner.parentNode;
  283. var banner_wrapper = document.createElement('div');
  284. banner_wrapper.classList.add("image-container");
  285. banner_parent.replaceChild(banner_wrapper, banner);
  286. banner_wrapper.appendChild(banner);
  287.  
  288. var score_txt = document.getElementsByClassName("user_reviews_summary_row")[1];
  289. if (!score_txt.hasAttribute("data-tooltip-html")) {
  290. score_txt = document.getElementsByClassName("user_reviews_summary_row")[0];
  291. };
  292.  
  293. score_txt = score_txt.getAttribute("data-tooltip-html").split('%', 1);
  294.  
  295. if (isNumeric(score_txt[0])) {
  296. var score_int = parseInt(score_txt);
  297.  
  298. var score = document.createElement("div");
  299. score.classList.add('box');
  300. score.textContent = score_txt;
  301.  
  302. if (score_int > 95) {
  303. score.classList.add('score95');
  304. } else if (score_int >= 85) {
  305. score.classList.add('score85');
  306. } else if (score_int >= 75) {
  307. score.classList.add('score75');
  308. } else if (score_int >= 70) {
  309. score.classList.add('score70');
  310. } else if (score_int >= 55) {
  311. score.classList.add('score55');
  312. };
  313.  
  314. banner_wrapper.appendChild(score);
  315. };
  316. };
  317.  
  318. // insert html inside a text
  319. // and replace specific part of that text
  320. // i.e. insert a tag inside a text node
  321. // from https://stackoverflow.com/a/29301739
  322. // huy znaye yak vono robe ale krasyvo
  323. var matchText = function(node, regex, callback, excludeElements) {
  324.  
  325. excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
  326. var child = node.firstChild;
  327.  
  328. while (child) {
  329. switch (child.nodeType) {
  330. case 1:
  331. if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1)
  332. break;
  333. matchText(child, regex, callback, excludeElements);
  334. break;
  335. case 3:
  336. var bk = 0;
  337. child.data.replace(regex, function(all) {
  338. var args = [].slice.call(arguments),
  339. offset = args[args.length - 2],
  340. newTextNode = child.splitText(offset+bk), tag;
  341. bk -= child.data.length + all.length;
  342.  
  343. newTextNode.data = newTextNode.data.substr(all.length);
  344. tag = callback.apply(window, [child].concat(args));
  345. child.parentNode.insertBefore(tag, newTextNode);
  346. child = newTextNode;
  347. });
  348. regex.lastIndex = 0;
  349. break;
  350. }
  351.  
  352. child = child.nextSibling;
  353. }
  354.  
  355. return node;
  356. };
  357.  
  358. // add spaces before ₴ sign
  359. matchText(document.body, new RegExp("₴", "g"), function(node, match, offset) {
  360. var hryvnia = document.createElement("span");
  361. hryvnia.style.cssText += "font-family: 'Calibri Light'; font-weight: bold; font-size: 110%;";
  362. hryvnia.innerHTML = " ₴";
  363. return hryvnia;
  364. });