Alienware Arena Filters

Filter out tier-restricted content on Alienware Arena

  1. // ==UserScript==
  2. // @name Alienware Arena Filters
  3. // @namespace http://updownleftdie.com/
  4. // @version 1.1
  5. // @description Filter out tier-restricted content on Alienware Arena
  6. // @author UpDownLeftDie
  7. // @match https://*.alienwarearena.com/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @license AGPL
  11. // ==/UserScript==
  12. (function () {
  13. 'use strict';
  14. // Settings management
  15. const defaultSettings = {
  16. hideClosedGiveaways: true,
  17. hideTierRestricted: true,
  18. autoSyncTier: true,
  19. hideOutOfStock: true,
  20. hideClaimed: true,
  21. };
  22. function getSettings() {
  23. const savedSettings = GM_getValue('filterSettings');
  24. // Start with default settings as base
  25. const settings = { ...defaultSettings };
  26. if (savedSettings) {
  27. try {
  28. const parsed = JSON.parse(savedSettings);
  29. // Merge saved settings with defaults
  30. Object.assign(settings, parsed);
  31. // Ensure userTier is a number or undefined
  32. settings.userTier =
  33. parsed.userTier != null ? Number(parsed.userTier) : undefined;
  34. // If Number() returned NaN, set to undefined
  35. if (Number.isNaN(settings.userTier)) {
  36. settings.userTier = undefined;
  37. }
  38. } catch (e) {
  39. console.error('Error parsing saved settings:', e);
  40. // On error, return defaults
  41. return defaultSettings;
  42. }
  43. }
  44. return settings;
  45. }
  46. function saveSettings(settings) {
  47. const prevSettings = getSettings();
  48. const newSettings = {
  49. ...prevSettings,
  50. ...settings,
  51. };
  52. GM_setValue('filterSettings', JSON.stringify(newSettings));
  53. }
  54. // Function to extract tier number from text
  55. function extractTier(text) {
  56. const match = text.match(/Tier\s*(\d+)/i);
  57. return match ? parseInt(match[1]) : null;
  58. }
  59. // Function to check and store user's tier on control center page
  60. function checkAndStoreTier() {
  61. const tierImg = document.querySelector(
  62. 'img[src*="/images/content/tier-tags/"]',
  63. );
  64. if (tierImg) {
  65. const tierMatch = tierImg.src.match(/tier-tags\/(\d+)\.png/);
  66. if (tierMatch) {
  67. const userTier = parseInt(tierMatch[1]);
  68. saveSettings({ userTier });
  69. console.log('Stored user tier:', userTier);
  70. }
  71. }
  72. }
  73. // Function to filter community giveaways
  74. function filterGiveaways() {
  75. const settings = getSettings();
  76. const userTier = settings.userTier ?? 99;
  77. const giveaways = document.querySelectorAll(
  78. 'div.mb-3.community-giveaways__listing__row',
  79. );
  80. giveaways.forEach((giveaway) => {
  81. const text = giveaway.textContent;
  82. if (settings.hideClosedGiveaways && text.includes('Closed')) {
  83. giveaway.style.display = 'none';
  84. return;
  85. }
  86. if (settings.hideTierRestricted) {
  87. const tierNumber = extractTier(text);
  88. if (tierNumber && tierNumber > userTier) {
  89. giveaway.style.display = 'none';
  90. }
  91. }
  92. });
  93. }
  94. // Function to filter marketplace items
  95. function filterMarketplace() {
  96. const settings = getSettings();
  97. const userTier = settings.userTier ?? 99;
  98. const items = document.querySelectorAll(
  99. '.pointer.marketplace-game-small, .pointer.marketplace-game-large, .product-tile, .featured-tile',
  100. );
  101. items.forEach((item) => {
  102. const text = item.textContent;
  103. if (
  104. settings.hideOutOfStock &&
  105. text.toLowerCase().includes('out of stock')
  106. ) {
  107. item.style.display = 'none';
  108. return;
  109. }
  110. if (settings.hideClaimed && text.toLowerCase().includes('claimed')) {
  111. item.style.display = 'none';
  112. return;
  113. }
  114. if (settings.hideTierRestricted) {
  115. const tierNumber = extractTier(text);
  116. if (tierNumber && tierNumber > userTier) {
  117. item.style.display = 'none';
  118. }
  119. }
  120. });
  121. if (
  122. [...document.querySelectorAll('.row.mt-3 .featured-tile')].every(
  123. (tile) => tile.style.display === 'none',
  124. )
  125. ) {
  126. const flashDealsSection = document.querySelector(
  127. 'div[style*="border-style: solid"][class*="row mt-3"]',
  128. );
  129. if (flashDealsSection) {
  130. flashDealsSection.style.display = 'none';
  131. }
  132. }
  133. }
  134. // Function to create settings menu
  135. function createSettingsMenu() {
  136. const settings = getSettings();
  137. const menuHTML = `
  138. <div
  139. id="alienware-filter-settings"
  140. role="dialog"
  141. aria-labelledby="settings-title"
  142. aria-modal="true">
  143. <div role="document">
  144. <!-- Title -->
  145. <div id="settings-title" role="heading" aria-level="1">Filter Settings</div>
  146. <!-- Settings Form -->
  147. <form>
  148. <!-- Global Settings Section -->
  149. <div class="settings-section" style="margin-bottom: 20px">
  150. <div role="heading" aria-level="2" class="section-heading">
  151. Global Settings
  152. </div>
  153. <div
  154. class="settings-group"
  155. role="group"
  156. aria-label="Global Filter Options">
  157. <div class="setting">
  158. <label class="settingsLabel">
  159. <input type="checkbox" id="hideTierRestricted" ${
  160. settings.hideTierRestricted ? 'checked' : ''
  161. }
  162. aria-describedby="hideTierDesc"> Hide Higher Tier Content
  163. </label>
  164. <span id="hideTierDesc" class="sr-only"
  165. >If checked, content requiring a higher tier than your current
  166. tier will be hidden</span
  167. >
  168. </div>
  169. <div class="setting">
  170. <label class="settingsLabel">
  171. <input type="checkbox" id="autoSyncTier" ${
  172. !settings.hideTierRestricted ? 'disabled' : ''
  173. } ${settings.autoSyncTier ? 'checked' : ''}
  174. aria-describedby="autoSyncTierDesc"> Auto Sync Tier
  175. </label>
  176. <span id="hideTierDesc" class="sr-only"
  177. >If checked, tier restrictions will be automatically synced from
  178. your profile</span
  179. >
  180. </div>
  181. <div class="setting">
  182. <label class="settingsLabel">
  183. User tier:
  184. <input id="manualSetTier" type="text" inputmode="numeric" pattern="[0-9]*" size="1" maxlength="2" ${
  185. settings.autoSyncTier ? 'disabled' : ''
  186. } value="${settings.userTier ? settings.userTier : ''}"
  187. aria-describedby="manualSetTierDesc">
  188. </label>
  189. <span id="manualSetTierDesc" class="sr-only">
  190. The user tier that is used to filter content on the site</span>
  191. </div>
  192. </div>
  193. </div>
  194. <!-- Game Vault and Marketplace Section -->
  195. <div class="settings-section" style="margin-bottom: 20px">
  196. <div role="heading" aria-level="2" class="section-heading">
  197. Marketplace &amp; Game Vault
  198. </div>
  199. <div
  200. class="settings-group"
  201. role="group"
  202. aria-label="Marketplace Options">
  203. <div class="setting">
  204. <label class="settingsLabel">
  205. <input type="checkbox" id="hideOutOfStock" ${
  206. settings.hideOutOfStock ? 'checked' : ''
  207. }
  208. aria-describedby="hideStockDesc"> Hide Out of Stock Items
  209. </label>
  210. <span id="hideStockDesc" class="sr-only"
  211. >If checked, items that are out of stock will be hidden</span
  212. >
  213. </div>
  214. <div class="setting">
  215. <label class="settingsLabel">
  216. <input type="checkbox" id="hideClaimed" ${
  217. settings.hideClaimed ? 'checked' : ''
  218. } aria-describedby="hideClaimedDesc"> Hide Claimed
  219. Items
  220. </label>
  221. <span id="hideClaimedDesc" class="sr-only"
  222. >If checked, items that you have claimed will be hidden</span
  223. >
  224. </div>
  225. </div>
  226. </div>
  227. <!-- Community Giveaways Section -->
  228. <div class="settings-section" style="margin-bottom: 20px">
  229. <div role="heading" aria-level="2" class="section-heading">
  230. Community Giveaways
  231. </div>
  232. <div
  233. class="settings-group"
  234. role="group"
  235. aria-label="Community Giveaway Options">
  236. <div class="setting">
  237. <label class="settingsLabel">
  238. <input type="checkbox" id="hideClosedGiveaways" ${
  239. settings.hideClosedGiveaways ? 'checked' : ''
  240. }
  241. aria-describedby="hideClosedDesc"> Hide Closed Giveaways
  242. </label>
  243. <span id="hideClosedDesc" class="sr-only"
  244. >If checked, giveaways that are already closed will be
  245. hidden</span
  246. >
  247. </div>
  248. </div>
  249. </div>
  250. <!-- Action Buttons -->
  251. <div style="text-align: right">
  252. <button id="saveFilterSettings" type="submit">Save</button>
  253. <button id="closeFilterSettings" type="button">Close</button>
  254. </div>
  255. </form>
  256. </div>
  257. </div>
  258. <style>
  259. #alienware-filter-settings {
  260. display: none;
  261. position: fixed;
  262. top: 50%;
  263. left: 50%;
  264. transform: translate(-50%, -50%);
  265. background: #1a1a1a;
  266. padding: 20px;
  267. border-radius: 8px;
  268. z-index: 10000;
  269. min-width: 300px;
  270. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  271. }
  272. #settings-title {
  273. color: #fff;
  274. font-size: 1.5em;
  275. font-weight: bold;
  276. margin-bottom: 15px;
  277. }
  278. #manualSetTier {
  279. color: white;
  280. padding: 2px;
  281. text-align: center;
  282. }
  283. #manualSetTier:disabled {
  284. color: grey;
  285. }
  286. .section-heading {
  287. color: #00bc8c;
  288. font-size: 1.1em;
  289. margin-bottom: 10px;
  290. font-weight: bold;
  291. }
  292. .setting {
  293. margin-bottom: 10px;
  294. margin-left: 15px;
  295. }
  296. .settingsLabel {
  297. color: #fff;
  298. display: block;
  299. margin-bottom: 5px;
  300. }
  301. #saveFilterSettings {
  302. background: #00bc8c;
  303. color: #fff;
  304. border: none;
  305. padding: 5px 15px;
  306. border-radius: 4px;
  307. cursor: pointer;
  308. }
  309. #closeFilterSettings {
  310. background: #e74c3c;
  311. color: #fff;
  312. border: none;
  313. padding: 5px 15px;
  314. border-radius: 4px;
  315. margin-left: 10px;
  316. cursor: pointer;
  317. }
  318. .sr-only {
  319. position: absolute;
  320. width: 1px;
  321. height: 1px;
  322. padding: 0;
  323. margin: -1px;
  324. overflow: hidden;
  325. clip: rect(0, 0, 0, 0);
  326. border: 0;
  327. }
  328. </style>
  329. `;
  330. // Add menu to page
  331. document.body.insertAdjacentHTML('beforeend', menuHTML);
  332. // Add event listeners
  333. document
  334. .getElementById('saveFilterSettings')
  335. .addEventListener('click', () => {
  336. const hideClosedGiveaways = document.getElementById(
  337. 'hideClosedGiveaways',
  338. ).checked;
  339. const hideTierRestricted =
  340. document.getElementById('hideTierRestricted').checked;
  341. const autoSyncTier = document.getElementById('autoSyncTier').checked;
  342. const hideOutOfStock =
  343. document.getElementById('hideOutOfStock').checked;
  344. const hideClaimed = document.getElementById('hideClaimed').checked;
  345. const newSettings = {
  346. hideClosedGiveaways,
  347. hideTierRestricted,
  348. autoSyncTier,
  349. hideOutOfStock,
  350. hideClaimed,
  351. ...(!autoSyncTier && {
  352. userTier: document.getElementById('manualSetTier').value,
  353. }),
  354. };
  355. saveSettings(newSettings);
  356. document.getElementById('alienware-filter-settings').style.display =
  357. 'none';
  358. location.reload(); // Reload to apply new settings
  359. });
  360. // Add keyboard event listeners for accessibility
  361. const modal = document.getElementById('alienware-filter-settings');
  362. document
  363. .getElementById('closeFilterSettings')
  364. .addEventListener('click', () => {
  365. modal.style.display = 'none';
  366. });
  367. // Handle ESC key to close modal
  368. document.addEventListener('keydown', (e) => {
  369. if (e.key === 'Escape' && modal.style.display === 'block') {
  370. modal.style.display = 'none';
  371. }
  372. });
  373. // Trap focus within modal when it's open
  374. modal.addEventListener('keydown', (e) => {
  375. if (e.key === 'Tab') {
  376. const focusableElements = modal.querySelectorAll(
  377. 'button, input[type="checkbox"]',
  378. );
  379. const firstFocusable = focusableElements[0];
  380. const lastFocusable = focusableElements[focusableElements.length - 1];
  381. if (e.shiftKey) {
  382. if (document.activeElement === firstFocusable) {
  383. lastFocusable.focus();
  384. e.preventDefault();
  385. }
  386. } else {
  387. if (document.activeElement === lastFocusable) {
  388. firstFocusable.focus();
  389. e.preventDefault();
  390. }
  391. }
  392. }
  393. });
  394. }
  395. // Function to add settings button to menu
  396. function addSettingsButton() {
  397. const menuList = document.querySelector(
  398. '.nav-item-mus .dropdown-menu.dropdown-menu-end',
  399. );
  400. if (menuList) {
  401. const settingsItem = document.createElement('a');
  402. settingsItem.className = 'dropdown-item';
  403. settingsItem.href = '#';
  404. settingsItem.textContent = 'Filter Settings';
  405. settingsItem.addEventListener('click', (e) => {
  406. e.preventDefault();
  407. document.getElementById('alienware-filter-settings').style.display =
  408. 'block';
  409. });
  410. menuList.insertBefore(settingsItem, menuList.lastElementChild);
  411. }
  412. }
  413. // Initialize everything based on current page
  414. const currentPath = window.location.pathname;
  415. // Add settings menu to all pages
  416. createSettingsMenu();
  417. addSettingsButton();
  418. const settings = getSettings();
  419. if (settings.autoSyncTier && currentPath === '/control-center') {
  420. checkAndStoreTier();
  421. } else if (currentPath === '/community-giveaways') {
  422. // Add mutation observer for dynamic content loading
  423. const observer = new MutationObserver((mutations, obs) => {
  424. filterGiveaways();
  425. });
  426. observer.observe(document.body, {
  427. childList: true,
  428. subtree: true,
  429. });
  430. } else if (currentPath.startsWith('/marketplace')) {
  431. // Add mutation observer for dynamic content loading
  432. const observer = new MutationObserver((mutations, obs) => {
  433. filterMarketplace();
  434. });
  435. observer.observe(document.body, {
  436. childList: true,
  437. subtree: true,
  438. });
  439. }
  440. })();