template of QUSL
// ==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!