SteamGifts: Look for Similar

Adds buttons to search for similar giveaways and removes redirect from existing links.

  1. // ==UserScript==
  2. // @name SteamGifts: Look for Similar
  3. // @namespace lainscripts_sg_look_for_similar
  4. // @version 4.3
  5. // @description Adds buttons to search for similar giveaways and removes redirect from existing links.
  6. // @author lainverse
  7. // @match *://www.steamgifts.com/
  8. // @match *://www.steamgifts.com/giveaway/*
  9. // @match *://www.steamgifts.com/giveaways/*
  10. // @match *://www.steamgifts.com/discussion/*
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. // jshint esversion: 8
  15. // jshint unused: true
  16. (function () {
  17. 'use strict';
  18.  
  19. // NodeList iterator polyfill (mostly for Safari)
  20. // https://jakearchibald.com/2014/iterators-gonna-iterate/
  21. if (!NodeList.prototype[Symbol.iterator])
  22. NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
  23.  
  24. (function (st) {
  25. st.id = 'sg_look_for_similar_stylesheet';
  26. document.head.appendChild(st);
  27. st.sheet.insertRule(
  28. '.featured__heading .fa-search {' + (
  29. 'font-size: 1.8em'
  30. ) + '}', 0);
  31. st.sheet.insertRule(
  32. '.lfsList {' + (
  33. 'position: relative;' +
  34. 'display: inline-block;' +
  35. 'margin-right: -5px'
  36. ) + '}', 0);
  37. st.sheet.insertRule(
  38. '.lfsMenu { ' + (
  39. 'display: none;' +
  40. 'position: absolute;' +
  41. 'min-width: max-content;' +
  42. 'background-color: #f9f9f9;' +
  43. 'box-shadow: 0px 4px 8px -2px #666;' +
  44. 'padding: 5px 0px'
  45. ) + '}', 0);
  46. st.sheet.insertRule(
  47. '.lfsOpts {' + (
  48. 'display: block;' +
  49. 'font-weight: bold;' +
  50. 'min-width: max-content;' +
  51. 'white-space: nowrap;' +
  52. 'color: rgb(70, 86, 112) !important;' +
  53. 'padding: 5px 15px 5px 5px;' +
  54. 'text-decoration: none !important;' +
  55. 'font-size: 12px !important'
  56. ) + '}', 0);
  57. st.sheet.insertRule(
  58. '.lfsOpts:hover {' + (
  59. 'background-color: #ddd'
  60. ) + '}', 0);
  61. st.sheet.insertRule(
  62. '.lfsList:hover > .lfsMenu {' + (
  63. 'display: inline-block;' +
  64. 'z-index: 100'
  65. ) + '}', 0);
  66. st.sheet.insertRule(
  67. '.lfsList > .fa, .lfsList > a {' + (
  68. 'display: block;' +
  69. 'padding: 2px 5px 0 5px;' +
  70. 'margin-left: 0'
  71. ) + '}', 0);
  72. st.sheet.insertRule(
  73. '.lfsList:hover .fa {' + (
  74. 'color: rgb(70, 86, 112)'
  75. ) + '}', 0);
  76. st.sheet.insertRule(
  77. '.lfsList:hover {' + (
  78. 'background-color: #f9f9f9;' +
  79. 'box-shadow: 0px 4px 8px 0px #666;'
  80. ) + '}', 0);
  81. })(document.createElement('style'));
  82.  
  83. let loc = (location.pathname
  84. .replace(/^\/$/, '/giveaways')
  85. .replace(/^\/giveaway\/.*$/, '/giveaway')
  86. .replace(/\/search$/i, '')
  87. .replace(/^\/discussion\/.*/i, '/discussion')),
  88. type = loc.match(/\/([^/]+)\/?$/i)[1],
  89. locs = {
  90. local: {
  91. title: 'Here',
  92. id: 'lfsInLocal',
  93. loc: loc
  94. },
  95. entered: {
  96. title: 'Entered',
  97. id: 'lfsInEntered',
  98. loc: '/giveaways/entered'
  99. },
  100. giveaways: {
  101. title: 'All Giveaways',
  102. id: 'lfsInGiveaways',
  103. loc: '/giveaways'
  104. }
  105. },
  106. contSearch = {
  107. giveaway: [locs.giveaways, locs.entered],
  108. won: [locs.local, locs.entered],
  109. giveaways: [locs.local, locs.entered],
  110. entered: [locs.local, locs.giveaways],
  111. discussion: [locs.giveaways, locs.entered],
  112. default: [locs.local]
  113. },
  114. clHeader = (
  115. '.giveaway__heading__name,' +
  116. '.featured__heading__medium,' +
  117. 'a.table__column__heading,' +
  118. '.markdown td > strong > a[href]'
  119. ),
  120. clHeaderIns = [
  121. 'giveaway__heading__name',
  122. 'table__column__heading'
  123. ],
  124. caHeaderSib = [
  125. 'giveaway__heading__thin',
  126. 'featured__heading__small'
  127. ];
  128.  
  129. if (!(type in contSearch)) type = 'default';
  130.  
  131. const ofClass = (itm, lst) => lst.some(cls => itm.classList.contains(cls));
  132.  
  133. const creator = document.createElement('outer');
  134. creator.make = html => {
  135. creator.innerHTML = html;
  136. return creator.removeChild(creator.children[0]);
  137. };
  138.  
  139. function appendLFSLink(he) {
  140. let heLink = he.pathname ? he : he.querySelector('a'),
  141. heText = he.textContent;
  142.  
  143. // Do not parse invite-only giveaways, non-giveaway and not-steam links
  144. if (heText == 'Invite Only' ||
  145. (heLink &&
  146. !/^\/giveaway\//i.test(heLink.pathname) &&
  147. !/^store\.steampowered\.com|steamcommunity.com$/i.test(heLink.hostname)))
  148. return;
  149.  
  150. // Do not add LFS button to a node with already existing LFS button
  151. if (he.parentNode.querySelector('.lfsList'))
  152. return;
  153.  
  154. // Append LFS buttons
  155. let ovl = creator.make('<div class="lfsList"></div>'), //document.createElement('div'),
  156. mnu = creator.make('<div class="lfsMenu"></div>');
  157.  
  158. ovl.appendChild(
  159. he.parentNode.querySelector('a[href^="/giveaways/search?"]') ||
  160. creator.make('<i class="giveaway__icon fa fa-search" />')
  161. );
  162. ovl.appendChild(mnu);
  163.  
  164. heText = '/search?q=' + encodeURIComponent(
  165. heText
  166. .replace(/(\s\(\d+(P|\sCopies)\))+$/i, '')
  167. .replace(/\.\.\.$/, '')
  168. );
  169.  
  170. for (let el of contSearch[type])
  171. mnu.appendChild(
  172. creator.make(`<a class="lfsOpts" id="${el.id}" href="${el.loc + heText}">${el.title}</a>`)
  173. );
  174.  
  175. while (he.nextElementSibling && ofClass(he.nextElementSibling, caHeaderSib))
  176. he = he.nextElementSibling;
  177. if (ofClass(he, clHeaderIns))
  178. he.appendChild(ovl);
  179. else
  180. he.parentNode.insertBefore(ovl, he.nextElementSibling);
  181. }
  182.  
  183. for (let header of document.querySelectorAll(clHeader))
  184. appendLFSLink(header);
  185.  
  186. (new MutationObserver(function (ms) {
  187. for (let m of ms)
  188. for (let node of m.addedNodes)
  189. if (node.nodeType === Node.ELEMENT_NODE)
  190. for (let header of node.querySelectorAll(clHeader))
  191. appendLFSLink(header);
  192. })).observe(document.body, {
  193. childList: true,
  194. subtree: true
  195. });
  196.  
  197. document.addEventListener('click', e => {
  198. let lnk = e.target.closest('a');
  199. if (!lnk) return;
  200. if (lnk.pathname === '/redirect' && lnk.search.startsWith('?url='))
  201. lnk.href = decodeURIComponent(lnk.search.slice(5));
  202. });
  203. })();