Greasy Fork is available in English.

TankTrouble Development Library

Shared library for TankTrouble userscript development

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greatest.deepsurf.us/scripts/482092/1309109/TankTrouble%20Development%20Library.js

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
  1. // ==UserScript==
  2. // @name TankTrouble Development Library
  3. // @author commander
  4. // @namespace https://github.com/asger-finding/tanktrouble-userscripts
  5. // @version 1.0.0-beta.1
  6. // @license GPL-3.0
  7. // @description Shared library for TankTrouble userscript development
  8. // @match *://*.tanktrouble.com/*
  9. // @grant none
  10. // @run-at document-start
  11. // @noframes
  12. // ==/UserScript==
  13.  
  14. // eslint-disable-next-line no-unused-vars
  15. class Loader {
  16.  
  17. /**
  18. * Pass a function to a hook with the correct context
  19. * @param context Function context (e.g `window`)
  20. * @param funcName Function identifier in the context
  21. * @param handler Hook to call before the original
  22. * @param attributes Optionally additional descriptors
  23. */
  24. static interceptFunction(context, funcName, handler, attributes) {
  25. const original = Reflect.get(context, funcName);
  26. if (typeof original !== 'function') throw new Error('Item passed is not typeof function');
  27.  
  28. Reflect.defineProperty(context, funcName, {
  29. /**
  30. * Call the handler with the original function bound to its context
  31. * and supply with the arguments list
  32. * @param args Arguments passed from outside
  33. * @returns Original function return value
  34. */
  35. value: (...args) => handler(original.bind(context), ...args),
  36. ...attributes
  37. });
  38. }
  39.  
  40. /**
  41. * Fires when the `main()` function is done on TankTrouble.
  42. * @returns Promise that resolves when Content.init() finishes
  43. */
  44. static whenContentInitialized() {
  45. if (GM.info.script.runAt !== 'document-start') return Loader.#createGameProxy();
  46. return whenContentLoaded().then(() => Loader.#createGameProxy());
  47. }
  48.  
  49. /**
  50. * Fires when the document is readyState `interactive` or `complete`
  51. * @returns Promise that resolves upon content loaded
  52. */
  53. static whenContentLoaded() {
  54. return new Promise(resolve => {
  55. if (document.readyState === 'interactive' || document.readyState === 'complete') resolve();
  56. else document.addEventListener('DOMContentLoaded', () => resolve());
  57. });
  58. }
  59.  
  60. /**
  61. * Apply a hook to the Content.init function which resolves when the promise ends
  62. * @returns Promise when Content.init has finished
  63. * @private
  64. */
  65. static #createGameProxy() {
  66. const functionString = Function.prototype.toString.call(Content.init);
  67. const isAlreadyHooked = /hooked-by-userscript/u.test(functionString);
  68.  
  69. return new Promise(resolve => {
  70. if (isAlreadyHooked) {
  71. const eventListener = document.addEventListener('content-initialized', () => {
  72. document.removeEventListener('content-initialized', eventListener);
  73. resolve();
  74. });
  75. } else {
  76. const event = new Event('content-initialized');
  77.  
  78. const { init } = Content;
  79. Reflect.defineProperty(Content, 'init', {
  80. /**
  81. * Intercept the Content.init function, add a stamp, dispatch the custom event and resolve
  82. * @param args Arguments passed from outside
  83. * @returns Original function return value
  84. */
  85. value: (...args) => {
  86. // Hack that will add the string to
  87. // the return of toString so we can
  88. // lookup if it's already hooked
  89. // eslint-disable-next-line no-void
  90. void 'hooked-by-userscript';
  91.  
  92. const result = init(...args);
  93.  
  94. document.dispatchEvent(event);
  95. resolve();
  96. return result;
  97. },
  98. configurable: true
  99. });
  100. }
  101. });
  102. }
  103.  
  104. }