traQ UserScript Library template

template of QUSL

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         traQ UserScript Library template
// @namespace    https://github.com/TwoSquirrels
// @version      0.2
// @description  template of QUSL
// @author       TwoSquirrels
// @license      MIT
// @match        https://q.trap.jp/*
// @match        https://q.ex.trap.jp/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=trap.jp
// @grant        unsafeWindow
// ==/UserScript==

"use strict";

// ================================================================================================================== //
//  traQ UserScript Library (v0.2)
//    copyright: Copyright (c) 2025 TwoSquirrels
//    license: MIT
//    url: https://greasyfork.org/ja/scripts/523634-traq-userscript-library-template
// ------------------------------------------------------------------------------------------------------------------ //

async function initQusl() {
  unsafeWindow.qusl ??= (async () => {
    class SimpleEventEmitter {
      constructor() {
        this.events = new Map();
      }

      /**
       * Add a listener for a specific event type.
       * @param {string} type - Event type.
       * @param {function} listener - Callback function.
       * @param {boolean} prepend - If true, add the listener at the beginning of the list.
       */
      on(type, listener, prepend = false) {
        const listeners = this.events.get(type);
        if (!listeners) {
          this.events.set(type, [listener]);
          return;
        }
        if (prepend) listeners.unshift(listener);
        else listeners.push(listener);
      }

      /**
       * Add a one-time listener for a specific event type.
       * The listener is automatically removed after it is called.
       * @param {string} type - Event type.
       * @param {function} listener - Callback function.
       * @param {boolean} prepend - If true, add the listener at the beginning of the list.
       */
      once(type, listener, prepend = false) {
        const wrapper = (...args) => {
          listener(...args);
          this.off(type, wrapper);
        };
        this.on(type, wrapper, prepend);
      }

      /**
       * Remove a specific listener for an event type.
       * If no listener is provided, all listeners for the event type are removed.
       * @param {string} type - Event type.
       * @param {function} [listener] - Callback function to remove.
       */
      off(type, listener) {
        const listeners = this.events.get(type);
        if (!listeners) return;
        if (listener) {
          const index = listeners.indexOf(listener);
          if (index >= 0) listeners.splice(index, 1);
        }
        if (!listener || listeners.length <= 0) this.events.delete(type);
      }

      /**
       * Emit an event, calling all associated listeners with the provided arguments.
       * Listeners can cancel further event propagation by returning `true`.
       * @param {string} type - Event type.
       * @param {...any} args - Arguments to pass to listeners.
       * @returns {Promise<boolean>} - True if propagation was canceled, false otherwise.
       */
      async emit(type, ...args) {
        const listeners = this.events.get(type);
        if (!listeners) return;
        for (const listener of listeners) {
          if (await listener(...args)) return true;
        }
        return false;
      }
    }

    const index = Object.values(await import(document.head.querySelector('script[src^="/assets/index-"]').src));

    return Object.assign(new SimpleEventEmitter(), { SimpleEventEmitter, index });
  })();

  const qusl = await unsafeWindow.qusl;

  // APIs
  if (qusl.apis == null) {
    qusl.apis = qusl.index.find((x) => typeof x.postMessage === "function");

    // Hook all methods of the APIs object
    for (const methodName of Object.getOwnPropertyNames(Object.getPrototypeOf(qusl.apis))) {
      if (methodName === "constructor") continue;
      const method = qusl.apis[methodName];
      if (typeof method !== "function") continue;

      // Wrap each method to allow event handling and cancellation
      qusl.apis[methodName] = async function (...args) {
        if (await qusl.emit(`${methodName}`, args)) throw new Error(`apis.${methodName} was canceled by QUSL.`);
        const response = await method.apply(this, args);
        await qusl.emit(`after.${methodName}`, args, response);
        return response;
      };
    }
  }

  return qusl;
}

// ================================================================================================================== //

// Write your code here!