此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.icu/scripts/405822/820080/Monkey%20Requests.js
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name Monkey Requests
// @namespace https://rafaelgssa.gitlab.io/monkey-scripts
// @version 1.1.3
// @author rafaelgssa
// @description Useful library for sending requests.
// @match *://*/*
// @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @require https://greasyfork.org/scripts/405802-monkey-dom/code/Monkey%20DOM.js
// @require https://greasyfork.org/scripts/405813-monkey-utils/code/Monkey%20Utils.js
// @grant GM.xmlHttpRequest
// @grant GM_xmlhttpRequest
// ==/UserScript==
/* global MonkeyDom, MonkeyUtils */
/**
* @typedef {'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'} RequestMethod
* @typedef {Object} MonkeyResponse
* @property {string} url
* @property {string} text
* @property {Object} [json]
* @property {Document} [dom]
* @typedef {{ [K in RequestMethod]: (url: string, options?: RequestInit | GM.Request) => Promise<MonkeyResponse> }} ShorthandSend
*/
// eslint-disable-next-line
const MonkeyRequests = (() => {
/** @type {RequestMethod[]} */
const methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'];
/**
* @param {RequestMethod} method
* @param {string} url
* @param {RequestInit | GM.Request} [options]
* @returns {Promise<MonkeyResponse>}
*/
const _sendWithMethod = (method, url, options = {}) => {
return send(url, { ...options, method });
};
/**
* @param {string} url
* @param {RequestInit | GM.Request} [options]
* @returns {Promise<MonkeyResponse>}
*/
const send = (url, options = {}) => {
if (_isInternal(url, options)) {
return _sendInternal(url, options);
}
return _sendExternal(url, options);
};
/**
* Checks if the request is internal (uses window.fetch) or external (uses GM.xmlHttpRequest to bypass CORS).
* @param {string} url
* @param {RequestInit | GM.Request} options
* @returns {options is RequestInit}
*/
// eslint-disable-next-line
const _isInternal = (url, options) => {
return url.includes(window.location.host);
};
/**
* @param {string} url
* @param {RequestInit} options
* @returns {Promise<MonkeyResponse>}
*/
const _sendInternal = async (url, options) => {
const [internalFetch, internalOptions] = _getInternalObjects(options);
const fetchResponse = await internalFetch(url, internalOptions);
/** @type {MonkeyResponse} */
const response = {
url: fetchResponse.url,
text: await fetchResponse.text(),
};
return _processResponse(response);
};
/**
* Allows the request to be made in the current Firefox container.
* @param {RequestInit} options
* @returns {[Function, RequestInit]}
*/
const _getInternalObjects = (options) => {
if ('wrappedJSObject' in window) {
window.wrappedJSObject.options = cloneInto(options, window);
return [window.wrappedJSObject.fetch, window.wrappedJSObject.options];
}
return [window.fetch, options];
};
/**
* @param {string} url
* @param {Partial<GM.Request>} options
* @returns {Promise<MonkeyResponse>}
*/
const _sendExternal = (url, options) => {
return new Promise((resolve) => {
GM.xmlHttpRequest({
url,
method: 'GET',
...options,
onload: (gmResponse) => {
/** @type {MonkeyResponse} */
const response = {
url: gmResponse.finalUrl,
text: gmResponse.responseText,
};
resolve(_processResponse(response));
},
});
});
};
/**
* @param {MonkeyResponse} response
* @returns {MonkeyResponse}
*/
const _processResponse = (response) => {
const processedResponse = { ...response };
try {
processedResponse.dom = MonkeyDom.parse(response.text);
} catch (err) {
// Response is not a DOM, just ignore.
}
try {
processedResponse.json = JSON.parse(response.text);
} catch (err) {
// Response is not a JSON, just ignore.
}
return processedResponse;
};
/**
* Parses a query string into an object.
* @param {string} query The query to parse.
* @returns {Record<string, string>} The parsed object.
*/
const parseQuery = (query) => {
/** @type {Record<string, string>} */
const params = {};
const rawQuery = query.startsWith('?') ? query.slice(1) : query;
const parts = rawQuery.split('&').filter(MonkeyUtils.isSet);
for (const part of parts) {
const [key, value] = part.split('=').filter(MonkeyUtils.isSet);
params[key] = value;
}
return params;
};
/**
* Converts an object to a query string.
* @param {Record<string, unknown>} obj The object to convert.
* @returns {string} The converted query string, without '?'.
*/
const convertToQuery = (obj) => {
return Object.entries(obj)
.map((entry) => entry.join('='))
.join('&');
};
const shorthandSend = /** @type {ShorthandSend} */ ({});
for (const method of methods) {
shorthandSend[method] = _sendWithMethod.bind(null, method);
}
return {
...shorthandSend,
parseQuery,
convertToQuery,
send,
};
})();