Aria2 RPC Edit 2

Aria2 RPC Library 维护,源脚本 https://greasyfork.org/zh-CN/scripts/402652

目前为 2025-02-27 提交的版本,查看 最新版本

此脚本不应直接安装,它是供其他脚本使用的外部库。如果你需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/528191/1543979/Aria2%20RPC%20Edit%202.js

  1. // ==UserScript==
  2. // @name Aria2 RPC Edit 2
  3. // @namespace https://greasyfork.org/users/667968-pyudng
  4. // @version 0.3.4
  5. // @description Aria2 RPC Library 维护,源脚本 https://greasyfork.org/zh-CN/scripts/402652
  6. // @author PY-DNG
  7. // @original-author moe.jixun, Sonic853
  8. // @original-license MIT, MIT
  9. // @original-script https://greasyfork.org/scripts/5672-aria2-rpc, https://greasyfork.org/zh-CN/scripts/402652
  10. // @license MIT
  11. // @grant GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. /*
  15. Information from original script https://greasyfork.org/zh-CN/scripts/402652:
  16. // Source code: https://github.com/Sonic853/Static_library/blob/master/aria2.ts
  17. // tsc .\aria2.ts --target esnext
  18. // Public Class Aria2 ( options )
  19. */
  20.  
  21. const Aria2AUTH = {
  22. noAuth: 0,
  23. basic: 1,
  24. secret: 2,
  25. 0: 'noAuth',
  26. 1: 'basic',
  27. 2: 'secret'
  28. };
  29.  
  30. class Aria2BATCH {
  31. parent;
  32. data = [];
  33. onSuccess;
  34. onFail;
  35. addRaw(fn, params) {
  36. this.data.push({
  37. method: `aria2.${fn}`,
  38. params
  39. });
  40. }
  41. add(fn, ...args) {
  42. if (this.parent[fn] === undefined)
  43. throw new Error(`Unknown function: ${fn}, please check if you had a typo.`);
  44. return this.addRaw(fn, args);
  45. }
  46. async send() {
  47. let ret = await this.parent.send(true, this.data, this.onSuccess, this.onFail);
  48. this.reset();
  49. return ret;
  50. }
  51. getActions() {
  52. return this.data.slice();
  53. }
  54. setActions(actions) {
  55. if (!actions || !actions.map)
  56. return;
  57. this.data = actions;
  58. }
  59. reset() {
  60. this.onSuccess = this.onFail = null;
  61. this.data = [];
  62. }
  63. constructor(obj, cbSuccess, cbFail) {
  64. this.parent = obj;
  65. this.onSuccess = cbSuccess;
  66. this.onFail = cbFail;
  67. }
  68. }
  69.  
  70. var Aria2 = class AriaBase {
  71. /**
  72. * @constant
  73. * @type {'2.0'}
  74. */
  75. jsonrpc_ver = '2.0';
  76.  
  77. /**
  78. * last aria2 request id
  79. * @type {number}
  80. */
  81. id;
  82.  
  83. /**
  84. * @typedef {Object} Aria2Auth
  85. * @property {string} [secret]
  86. * @property {string} [user]
  87. * @property {string} [pass]
  88. */
  89. /**
  90. * @typedef {Object} Aria2Options
  91. * @property {Aria2Auth} auth
  92. * @property {string} host
  93. * @property {number} port
  94. */
  95. /** @type {Aria2Options} */
  96. options;
  97.  
  98. /**
  99. * @param {Aria2Options} options
  100. */
  101. constructor(options) {
  102. // options
  103. AriaBase.#Assert(!options.host || typeof options.host !== 'string', 'options.host should be string', TypeError);
  104. AriaBase.#Assert(!options.port || typeof options.port !== 'number', 'options.port should be number', TypeError);
  105. this.options = Object.assign({
  106. auth: { type: Aria2AUTH.noAuth },
  107. host: 'localhost',
  108. port: 6800
  109. }, options);
  110.  
  111. // init id
  112. this.id = (+new Date());
  113.  
  114. // warning for not-GM_xmlhttpRequest request
  115. typeof GM_xmlhttpRequest === 'undefined' && console.warn([
  116. 'Warning: You are now using an simple implementation of GM_xmlhttpRequest',
  117. 'Cross-domain request are not avilible unless configured correctly @ target server.',
  118. '',
  119. 'Some of its features are not avilible, such as `username` and `password` field.'
  120. ].join('\n'));
  121.  
  122. // aria2 methods implementation
  123. const isFunction = obj => typeof obj === 'function';
  124. [
  125. "addUri", "addTorrent", "addMetalink", "remove", "forceRemove",
  126. "pause", "pauseAll", "forcePause", "forcePauseAll", "unpause",
  127. "unpauseAll", "tellStatus", "getUris", "getFiles", "getPeers",
  128. "getServers", "tellActive", "tellWaiting", "tellStopped",
  129. "changePosition", "changeUri", "getOption", "changeOption",
  130. "getGlobalOption", "changeGlobalOption", "getGlobalStat",
  131. "purgeDownloadResult", "removeDownloadResult", "getVersion",
  132. "getSessionInfo", "shutdown", "forceShutdown", "saveSession"
  133. ].forEach(sMethod => {
  134. this[sMethod] = async (...args) => {
  135. let cbSuccess, cbError;
  136. if (args.length && isFunction(args[args.length - 1])) {
  137. cbSuccess = args[args.length - 1];
  138. args.splice(-1, 1);
  139. if (args.length && isFunction(args[args.length - 1])) {
  140. cbError = cbSuccess;
  141. cbSuccess = args[args.length - 1];
  142. args.splice(-1, 1);
  143. }
  144. }
  145. return await this.send(false, {
  146. method: `aria2.${sMethod}`,
  147. params: args
  148. }, cbSuccess, cbError);
  149. };
  150. });
  151. }
  152.  
  153. /**
  154. * Get basic authentication header string
  155. * @returns {string}
  156. */
  157. #getBasicAuth() {
  158. return btoa(`${this.options.auth.user}:${this.options.auth.pass}`);
  159. }
  160.  
  161. async send(bIsDataBatch, data, cbSuccess, cbError) {
  162. // update request id
  163. this.id = (+new Date());
  164.  
  165. // construct payload
  166. let srcTaskObj = { jsonrpc: this.jsonrpc_ver, id: this.id };
  167. let payload = {
  168. method: 'POST',
  169. url: `http://${this.options.host}:${this.options.port}/jsonrpc`,
  170. headers: {
  171. 'Content-Type': 'application/json; charset=UTF-8'
  172. },
  173. data: bIsDataBatch
  174. ? data.map(e => { return AriaBase.#merge({}, srcTaskObj, e); })
  175. : AriaBase.#merge({}, srcTaskObj, data),
  176. onload: r => {
  177. if (r.status !== 200) {
  178. cbError && cbError(r);
  179. } else {
  180. let repData;
  181. try {
  182. repData = JSON.parse(r.responseText);
  183. repData.error && cbError(repData);
  184. } catch (error) {
  185. repData = r.responseText;
  186. }
  187. cbSuccess && cbSuccess(repData);
  188. }
  189. },
  190. onerror: cbError ? cbError.bind(null) : null
  191. };
  192.  
  193. // authentication
  194. switch (this.options.auth.type) {
  195. case Aria2AUTH.basic: {
  196. payload.headers.Authorization = 'Basic ' + this.#getBasicAuth();
  197. break;
  198. }
  199. case Aria2AUTH.secret: {
  200. let sToken = `token:${this.options.auth.pass}`;
  201. if (bIsDataBatch) {
  202. for (let i = 0; i < payload.data.length; i++) {
  203. payload.data[i].params.splice(0, 0, sToken);
  204. }
  205. }
  206. else {
  207. if (!payload.data.params)
  208. payload.data.params = [];
  209. payload.data.params.splice(0, 0, sToken);
  210. }
  211. break;
  212. }
  213. case Aria2AUTH.noAuth:
  214. default: {
  215. break;
  216. }
  217. }
  218. return await AriaBase.#doRequest(payload);
  219. }
  220.  
  221. BATCH = new Aria2BATCH(this);
  222.  
  223. /**
  224. * merge moultiple source objects' properties into base object
  225. * @param {object} base
  226. * @param {...object} sources
  227. * @returns
  228. */
  229. static #merge(base, ...sources) {
  230. const isObject = obj => typeof obj === 'object' && obj !== null;
  231. this.#Assert(isObject(base), 'base should be an object', TypeError);
  232. sources.forEach(obj => {
  233. this.#Assert(isObject(obj), 'source should be an object', TypeError);
  234. Object.keys(obj).forEach(key => {
  235. if (isObject(base[key]) && isObject(obj[key])) {
  236. base[key] = AriaBase.#merge(base[key], obj[key]);
  237. } else {
  238. base[key] = obj[key];
  239. }
  240. });
  241. });
  242. return base;
  243. }
  244.  
  245. /**
  246. * throw error when condition not met
  247. * @param {boolean} condition
  248. * @param {string} message
  249. * @param {function} ErrorConstructor
  250. */
  251. static #Assert(condition, message, ErrorConstructor = Error) {
  252. if (!condition) {
  253. throw new ErrorConstructor(message);
  254. }
  255. }
  256.  
  257. static async #doRequest(details) {
  258. if (typeof GM_xmlhttpRequest !== 'undefined') {
  259. return new Promise((resolve, reject) => {
  260. GM_xmlhttpRequest({
  261. url,
  262. method,
  263. data: typeof data === 'string' ? data : JSON.stringify(data),
  264. headers,
  265. onload(r) {
  266. onload && onload(r);
  267. resolve(r);
  268. },
  269. onerror() {
  270. onerror && onerror();
  271. reject();
  272. }
  273. });
  274. });
  275. } else {
  276. const { url, method, data, headers, onload, onerror } = details;
  277. try {
  278. let response = await fetch(url, {
  279. method,
  280. body: typeof data === 'string' ? data : JSON.stringify(data),
  281. headers
  282. });
  283. let responseText = await response.text();
  284. onload && onload(responseText);
  285. return {
  286. readyState: 4,
  287. responseHeaders: response.headers,
  288. status: response.status,
  289. statusText: response.statusText,
  290. response,
  291. responseText,
  292. finalUrl: response.url
  293. };
  294. } catch (error) {
  295. onerror && onerror(error);
  296. throw error;
  297. }
  298. }
  299. }
  300. };