Fanatical Keys Backup

Displays a text area with game titles and keys so you can copy them out easily.

当前为 2021-10-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Fanatical Keys Backup
  3. // @namespace Lex@GreasyFork
  4. // @version 0.2.3.1
  5. // @description Displays a text area with game titles and keys so you can copy them out easily.
  6. // @author Lex
  7. // @match https://www.fanatical.com/en/orders/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Formats games array to a string to be displayed
  15. // Games is an array [ [title, key], ... ]
  16. function formatGames(games) {
  17. // Ignore games which do not have keys revealed
  18. games = games.filter(e => e[1]);
  19. // Format the output as tab-separated
  20. games = games.map(e => e[0]+"\t"+e[1]);
  21. return games.join("\n");
  22. }
  23.  
  24. function getGames(bundle) {
  25. let is = bundle.querySelectorAll(".new-order-item");
  26. return Array.prototype.map.call(is, i => {
  27. const gameTitleElement = i.getElementsByClassName("game-name");
  28. const gameTitle = gameTitleElement.length > 0 ? gameTitleElement[0].textContent.trim() : "";
  29. const keyElement = i.querySelector("[aria-label='reveal-key']");
  30. const gameKey = keyElement ? keyElement.value : "";
  31. return [gameTitle, gameKey];
  32. });
  33. }
  34.  
  35. function revealAllKeys(bundle) {
  36. const revealButtons = bundle.querySelectorAll(".key-container button.btn-block");
  37. revealButtons.forEach(b => { b.click() });
  38. this.style.display = "none";
  39. }
  40.  
  41. function createRevealButton(bundle) {
  42. let btn = document.createElement("button");
  43. btn.type = "button"; // no default behavior
  44. btn.innerText = "Reveal this bundle's keys";
  45. btn.onclick = revealAllKeys.bind(btn, bundle);
  46. return btn;
  47. }
  48.  
  49. // Adds a textarea to the bottom of the games listing with all the titles and keys
  50. function handleBundle(bundle) {
  51. console.log("Handling bundle", bundle)
  52. const bundleName = bundle.querySelector("div.bundle-name") ? bundle.querySelector("div.bundle-name").textContent.trim() : "No Title";
  53. let games = getGames(bundle);
  54. const gameCount = games.length;
  55. const keyCount = games.filter(e => e[1]).length;
  56. const gameStr = formatGames(games);
  57.  
  58. let notify = bundle.querySelector(".ktt-notify");
  59. if (!notify) {
  60. notify = document.createElement("div");
  61. notify.className = "ktt-notify";
  62. bundle.append(notify);
  63. if (gameCount != keyCount) {
  64. const btn = createRevealButton(bundle);
  65. notify.before(btn);
  66. }
  67. }
  68.  
  69. const color = gameCount == keyCount ? "" : "red";
  70. let newInner = `Dumping keys for ${bundleName}: Found ${gameCount} items and <span style="background-color:${color}">${keyCount} keys</span>.`;
  71. if (gameCount != keyCount) {
  72. newInner += " Are some keys not revealed?";
  73. }
  74. if (notify.innerHTML != newInner) {
  75. notify.innerHTML = newInner;
  76. }
  77.  
  78. let area = bundle.querySelector(".ktt");
  79. if (!area) {
  80. area = document.createElement("textarea");
  81. area.className = "ktt";
  82. area.style.width = "100%";
  83. area.setAttribute('readonly', true);
  84. bundle.append(area);
  85. }
  86. if (area.value != gameStr) {
  87. area.value = gameStr;
  88. // Adjust the height so all the contents are visible
  89. area.style.height = "";
  90. area.style.height = area.scrollHeight + 20 + "px";
  91. }
  92. }
  93.  
  94. var loopCount = 0;
  95. function handleOrderPage() {
  96. // There can be more than one bundle in an order
  97. const bundles = Array.from(document.querySelectorAll(".single-order > div:not([class])"));
  98. if (bundles.length > 0) {
  99. console.log(`Found ${bundles.length} bundle(s)`);
  100. bundles.forEach(handleBundle);
  101. setTimeout(handleOrderPage, 2000);
  102. } else {
  103. if (loopCount++ < 100) {
  104. setTimeout(handleOrderPage, 100);
  105. }
  106. }
  107. }
  108.  
  109. handleOrderPage();
  110. })();