您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Functions and other tools for GreaseMonkey UserScript development.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyforks.org/scripts/440463/1021292/greasetools.js
// ==UserScript== // @name greasetools // @description Functions and other tools for GreaseMonkey UserScript development. // @version 0.5.0 // @author Adam Thompson-Sharpe // @license MIT OR Apache-2.0 // @homepageURL https://gitlab.com/MysteryBlokHed/greasetools#greasetools // ==/UserScript== /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./lib/banner.js": /*!***********************!*\ !*** ./lib/banner.js ***! \***********************/ /***/ ((__unused_webpack_module, exports) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.genBanner = void 0; /** * Generate a UserScript metadata comment from an object. * Falsey values will be excluded from the banner, so checking if a value is undefined * before passing is not necessary. * * @param metaValues Properties to add to metadata * @param spacing The amount of spaces between the `@` and the value, including the prop name. * Should be at least 1 greater than the longest prop name * @param start What to put at the start of the banner. Defaults to `'// ==UserScript=='` * @param end What to put at the end of the banner. Defaults to `'// ==/UserScript=='` * @returns A block of comments to be put at the top of a UserScript * including all of the properties passed */ function genBanner(metaValues, spacing = 12, start = '// ==UserScript==', end = '// ==/UserScript==') { let final = `${start}\n`; const format = (prop, value) => `// @${prop}${' '.repeat(spacing - prop.length)}${value}\n`; for (const [key, value] of Object.entries(metaValues)) { if (!value) continue; if (typeof value === 'string') { final += format(key, value); } else { for (const val of value) { if (!val) continue; final += format(key, val); } } } final += `${end}\n`; return final; } exports.genBanner = genBanner; /***/ }), /***/ "./lib/index.js": /*!**********************!*\ !*** ./lib/index.js ***! \**********************/ /***/ (function(__unused_webpack_module, exports, __webpack_require__) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.checkGrants = void 0; __exportStar(__webpack_require__(/*! ./banner */ "./lib/banner.js"), exports); __exportStar(__webpack_require__(/*! ./xhr */ "./lib/xhr.js"), exports); __exportStar(__webpack_require__(/*! ./values */ "./lib/values.js"), exports); /** Used by functions to check if grants are present */ function checkGrants(...grants) { if (!GM) return false; if (grants.some(grant => !(grant in GM))) return false; return true; } exports.checkGrants = checkGrants; /***/ }), /***/ "./lib/values.js": /*!***********************!*\ !*** ./lib/values.js ***! \***********************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.deleteValue = exports.valuesGetProxy = exports.valuesProxy = exports.getAllValues = exports.getValues = void 0; const _1 = __webpack_require__(/*! . */ "./lib/index.js"); /** Ensure that the values passed are all strings for use with `localStorage` */ function ensureString(values) { for (const value of Object.values(values)) { if (typeof value !== 'string') throw TypeError('Only strings are supported for values when localStorage is being used'); } } const prefixKey = (key, prefix) => prefix ? `${prefix}.${key}` : key; /** * Requires the `GM.getValue` grant or falls back to using localStorage. * Retrieves values from GreaseMonkey based on the generic type provided * * @param defaults The default values if they are undefined. * Each option will be set to a key from this if it does not exist * @param id An optional unique identifier for the config. Prefixes all keys with the ID * (eg. `foo` -> `myconfig.foo` for id `myconfig`). This **won't** change the names of the keys * on the returned object * @param setDefaults Whether or not to store the default value from the defaults argument * with `GM.setValue` if it doesn't exist. Requires the `GM.setValue` grant * @returns A Promise that resolves to an object with all of the values * * @example * ```typescript * const values = await getValues({ * somethingEnabled: false, * someNumber: 42, * }) * * console.log(values.somethingEnabled) * console.log(values.someNumber) * * values.someNumber++ // Does NOT modify GM stored value. * // Pass the return of this function to valuesProxy for that functionality * ``` */ function getValues(defaults, id, setDefaults = false) { return new Promise((resolve, reject) => { const values = defaults; const grants = setDefaults ? (0, _1.checkGrants)('getValue') : (0, _1.checkGrants)('getValue', 'setValue'); if (!grants) ensureString(Object.values(values)); if (grants) { /** * Returns a promise with the value returned from GM.getValue. * If no value exists, sets the value to the provided default * and returns that * * @returns A Promise with the original key and the retrieved value */ const getWithDefault = (key, defaultValue, id) => { return new Promise(async (resolve) => { const prefix = prefixKey(key, id); const value = await GM.getValue(prefix); // Resolve with the value if found if (value) return resolve([key, value]); // Set the value if setDefaults argument is passed if (setDefaults) await GM.setValue(key, defaultValue); // Resolve with the default value return resolve([key, defaultValue]); }); }; const promises = []; for (const [key, value] of Object.entries(defaults)) { promises.push(getWithDefault(key, value, id)); } Promise.all(promises) .then(retrievedValues => { const returnedValues = {}; for (const [key, value] of retrievedValues) { returnedValues[key] = value; } resolve(returnedValues); }) .catch(reason => reject(reason)); } else { const returnedValues = {}; for (const [key, defaultValue] of Object.entries(defaults)) { const value = localStorage.getItem(key); if (value === null && setDefaults) localStorage.setItem(key, defaultValue); returnedValues[key] = value !== null && value !== void 0 ? value : defaultValue; } resolve(returnedValues); } }); } exports.getValues = getValues; /** * Requires the `GM.getValue` and `GM.listValues` grants or falls back to using localStorage. * Returns a values object containing every saved value for the UserScript * * @returns A Promise that resolves to the defined values or rejects with nothing. * @example * ```typescript * // Logs all key/value pairs from GreaseMonkey * const allValues = await getAllValues() * for (const [key, value] of Object.entries(allValues)) { * console.log(key, value) * } * ``` */ async function getAllValues() { const valueNames = await (async () => { // Using localStorage if (!(0, _1.checkGrants)('getValue', 'listValues')) return Object.keys(localStorage); // Using GreaseMonkey return GM.listValues(); })(); const defaults = (() => { const emptyDefault = {}; for (const value of valueNames) emptyDefault[value] = ''; return emptyDefault; })(); return getValues(defaults); } exports.getAllValues = getAllValues; /** * Requires the `GM.setValue` grant or falls back to using localStorage. * Get a Proxy that automatically updates values. * There should generally only be one Proxy per option (eg. one proxy that controls `option1` and `option2` * and a different one that controls `option3` and `option4`). * This is because the returned Proxy doesn't update the value on get, only on set. * If multiple Proxies on the same values are being used to set, then a get Proxy * (`valuesGetProxy`) to get values might be a good idea * * @param values A values object, such as the one from `getValues` * @param id An optional unique identifier for the config. Prefixes all keys with the ID * (eg. `foo` -> `myconfig.foo` for id `myconfig`). This **won't** change the names of the keys * on the returned object * @param callback Called with the Promise returned by `GM.setValue` * @returns A Proxy from `values` that updates the GM value on set * @example * ```typescript * const values = valuesProxy( * await getValues({ * message: 'Hello, World!', * }) * ) * * values.message = 'Hello!' // Runs GM.setValue('message', 'Hello!') * console.log(values.message) // Logs 'Hello!'. Does NOT run GM.getValue * ``` */ function valuesProxy(values, id, callback) { const grants = (0, _1.checkGrants)('setValue'); /** Handle sets to the values object */ const handler = { set(target, prop, value) { const prefix = prefixKey(prop, id); if (prop in target) { // Using GreaseMonkey if (grants) { const gmSetPromise = GM.setValue(prefix, value); if (callback) callback(gmSetPromise); // Using localStorage } else { ensureString([value]); localStorage.setItem(prefix, value); } return Reflect.set(target, prop, value); } return false; }, }; return new Proxy(values, handler); } exports.valuesProxy = valuesProxy; /** * Requires the `GM.getValue` grant or falls back to using localStorage. * Get a Proxy that wraps `GM.getValue` for better typing. * Useful when a value may be modified by multiple different sources, * meaning the value will need to be retrieved from GM every time. * This should not be used if values are only being modified by one source * * @param id An optional unique identifier for the config. Prefixes all keys with the ID * (eg. `foo` -> `myconfig.foo` for id `myconfig`). This **won't** change the names of the keys * on the returned object * @param values A values object, such as the one returned from `getValues` * @returns A Proxy using the keys of `values` that wraps `GM.getValue` * @example * ```typescript * const values = valuesProxy( * await getValues({ * message: 'Hello, World!', * }) * ) * * const valuesGet = valuesGetProxy(values) * * console.log(await valuesGet.message) // Logs the result of GM.getValue('message') * ``` */ function valuesGetProxy(values, id) { const grants = (0, _1.checkGrants)('getValue'); /** Handle gets to the values object */ const handler = { get(target, prop) { return new Promise((resolve, reject) => { const prefix = prefixKey(prop, id); // Check if the property is a part of the passed values if (prop in target) { // Using GreaseMonkey if (grants) { GM.getValue(prefix).then(value => { // Resolve with the value if it's defined if (value !== undefined) resolve(value); else reject(); }); // Using localStorage } else { const value = localStorage.getItem(prefix); if (value !== null) resolve(value); else reject(); } } else { reject(); } }); }, /** Proxy isn't meant for setting, so do nothing */ set() { return false; }, }; return new Proxy(values, handler); } exports.valuesGetProxy = valuesGetProxy; /** * Requires the `GM.deleteValue` grant or falls back to localStorage. * Deletes a value from a values object. * This is only useful if you're using TypeScript or your editor has typing support. * If that doesn't describe your use case, then use `GM.deleteValue` instead * * @param values A values object, such as the one returned from `getValues` * @param toDelete The value to delete * @param id An optional unique identifier for the config. Prefixes all keys with the ID * (eg. `foo` -> `myconfig.foo` for id `myconfig`). This **won't** change the names of the keys * on the returned object * @returns A Promise that resolves with a new object without the deleted type, * or rejects with nothing if the deletion failed */ function deleteValue(values, toDelete, id) { return new Promise(async (resolve, reject) => { const prefix = prefixKey(toDelete, id); if (toDelete in values) { // Using GreaseMonkey if ((0, _1.checkGrants)('deleteValue')) await GM.deleteValue(prefix); // Using localStorage else localStorage.removeItem(prefix); delete values[toDelete]; resolve(values); } reject(); }); } exports.deleteValue = deleteValue; /***/ }), /***/ "./lib/xhr.js": /*!********************!*\ !*** ./lib/xhr.js ***! \********************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.xhrPromise = void 0; const _1 = __webpack_require__(/*! . */ "./lib/index.js"); /** * Make a request with GM.xmlHttpRequest using Promises. * Requires the GM.xmlHttpRequest grant * * @param xhrInfo The XHR info * @returns A Promise that resolves with the Greasemonkey Response object * @see {@link https://wiki.greasespot.net/GM.xmlHttpRequest} * * @example * ```typescript * // Make a GET request to https://example.com * const example = await xhrPromise({ * method: 'GET', * url: 'https://example.com', * }) * ``` */ function xhrPromise(xhrInfo) { return new Promise((resolve, reject) => { let lastState = XMLHttpRequest.UNSENT; if ((0, _1.checkGrants)('xmlHttpRequest')) { GM.xmlHttpRequest(Object.assign(Object.assign({}, xhrInfo), { onreadystatechange: response => { if (response.readyState === XMLHttpRequest.DONE) { if (lastState < 3) reject(new Error('XHR failed')); else resolve(response); } lastState = response.readyState; } })); } else reject(new Error('Missing grant GM.xmlHttpRequest')); }); } exports.xhrPromise = xhrPromise; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module is referenced by other modules so it can't be inlined /******/ var __webpack_exports__ = __webpack_require__("./lib/index.js"); /******/ window.GreaseTools = __webpack_exports__; /******/ /******/ })() ;