greasetools

Functions and other tools for GreaseMonkey UserScript development.

Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.greasyfork.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__;
/******/ 	
/******/ })()
;