OpenAPI.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /**
  2. * OpenAPI
  3. * @author: Peng-YM
  4. * https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/api-minified.js
  5. * https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
  6. */
  7. function ENV() {
  8. const isJSBox = typeof require == "function" && typeof $jsbox != "undefined";
  9. return {
  10. isQX: typeof $task !== "undefined",
  11. isLoon: typeof $loon !== "undefined",
  12. isSurge: typeof $httpClient !== "undefined" && typeof $utils !== "undefined",
  13. isBrowser: typeof document !== "undefined",
  14. isNode: typeof require == "function" && !isJSBox,
  15. isJSBox,
  16. isRequest: typeof $request !== "undefined",
  17. isScriptable: typeof importModule !== "undefined",
  18. };
  19. }
  20. function HTTP(defaultOptions = {
  21. baseURL: ""
  22. }) {
  23. const {
  24. isQX,
  25. isLoon,
  26. isSurge,
  27. isScriptable,
  28. isNode,
  29. isBrowser
  30. } = ENV();
  31. const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"];
  32. const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/
  33. function send(method, options) {
  34. options = typeof options === "string" ? {
  35. url: options
  36. } : options;
  37. const baseURL = defaultOptions.baseURL;
  38. if (baseURL && !URL_REGEX.test(options.url || "")) {
  39. options.url = baseURL ? baseURL + options.url : options.url;
  40. }
  41. if (options.body && options.headers && !options.headers['Content-Type']) {
  42. options.headers['Content-Type'] = 'application/x-www-form-urlencoded'
  43. }
  44. options = {
  45. ...defaultOptions,
  46. ...options
  47. };
  48. const timeout = options.timeout;
  49. const events = {
  50. ...{
  51. onRequest: () => {
  52. },
  53. onResponse: (resp) => resp,
  54. onTimeout: () => {
  55. },
  56. },
  57. ...options.events,
  58. };
  59. events.onRequest(method, options);
  60. let worker;
  61. if (isQX) {
  62. worker = $task.fetch({
  63. method,
  64. ...options
  65. });
  66. } else if (isLoon || isSurge || isNode) {
  67. worker = new Promise((resolve, reject) => {
  68. const request = isNode ? require("request") : $httpClient;
  69. request[method.toLowerCase()](options, (err, response, body) => {
  70. if (err) reject(err);
  71. else
  72. resolve({
  73. statusCode: response.status || response.statusCode,
  74. headers: response.headers,
  75. body,
  76. });
  77. });
  78. });
  79. } else if (isScriptable) {
  80. const request = new Request(options.url);
  81. request.method = method;
  82. request.headers = options.headers;
  83. request.body = options.body;
  84. worker = new Promise((resolve, reject) => {
  85. request
  86. .loadString()
  87. .then((body) => {
  88. resolve({
  89. statusCode: request.response.statusCode,
  90. headers: request.response.headers,
  91. body,
  92. });
  93. })
  94. .catch((err) => reject(err));
  95. });
  96. } else if (isBrowser) {
  97. worker = new Promise((resolve, reject) => {
  98. fetch(options.url, {
  99. method,
  100. headers: options.headers,
  101. body: options.body,
  102. })
  103. .then(response => response.json())
  104. .then(response => resolve({
  105. statusCode: response.status,
  106. headers: response.headers,
  107. body: response.data,
  108. })).catch(reject);
  109. });
  110. }
  111. let timeoutid;
  112. const timer = timeout ?
  113. new Promise((_, reject) => {
  114. timeoutid = setTimeout(() => {
  115. events.onTimeout();
  116. return reject(
  117. `${method} URL: ${options.url} exceeds the timeout ${timeout} ms`
  118. );
  119. }, timeout);
  120. }) :
  121. null;
  122. return (timer ?
  123. Promise.race([timer, worker]).then((res) => {
  124. clearTimeout(timeoutid);
  125. return res;
  126. }) :
  127. worker
  128. ).then((resp) => events.onResponse(resp));
  129. }
  130. const http = {};
  131. methods.forEach(
  132. (method) =>
  133. (http[method.toLowerCase()] = (options) => send(method, options))
  134. );
  135. return http;
  136. }
  137. function API(name = "untitled", debug = false) {
  138. const {
  139. isQX,
  140. isLoon,
  141. isSurge,
  142. isNode,
  143. isJSBox,
  144. isScriptable
  145. } = ENV();
  146. return new (class {
  147. constructor(name, debug) {
  148. this.name = name;
  149. this.debug = debug;
  150. this.http = HTTP();
  151. this.env = ENV();
  152. this.node = (() => {
  153. if (isNode) {
  154. const fs = require("fs");
  155. return {
  156. fs,
  157. };
  158. } else {
  159. return null;
  160. }
  161. })();
  162. this.initCache();
  163. const delay = (t, v) =>
  164. new Promise(function (resolve) {
  165. setTimeout(resolve.bind(null, v), t);
  166. });
  167. Promise.prototype.delay = function (t) {
  168. return this.then(function (v) {
  169. return delay(t, v);
  170. });
  171. };
  172. }
  173. // persistence
  174. // initialize cache
  175. initCache() {
  176. if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || "{}");
  177. if (isLoon || isSurge)
  178. this.cache = JSON.parse($persistentStore.read(this.name) || "{}");
  179. if (isNode) {
  180. // create a json for root cache
  181. let fpath = "root.json";
  182. if (!this.node.fs.existsSync(fpath)) {
  183. this.node.fs.writeFileSync(
  184. fpath,
  185. JSON.stringify({}), {
  186. flag: "wx"
  187. },
  188. (err) => console.log(err)
  189. );
  190. }
  191. this.root = {};
  192. // create a json file with the given name if not exists
  193. fpath = `${this.name}.json`;
  194. if (!this.node.fs.existsSync(fpath)) {
  195. this.node.fs.writeFileSync(
  196. fpath,
  197. JSON.stringify({}), {
  198. flag: "wx"
  199. },
  200. (err) => console.log(err)
  201. );
  202. this.cache = {};
  203. } else {
  204. this.cache = JSON.parse(
  205. this.node.fs.readFileSync(`${this.name}.json`)
  206. );
  207. }
  208. }
  209. }
  210. // store cache
  211. persistCache() {
  212. const data = JSON.stringify(this.cache, null, 2);
  213. if (isQX) $prefs.setValueForKey(data, this.name);
  214. if (isLoon || isSurge) $persistentStore.write(data, this.name);
  215. if (isNode) {
  216. this.node.fs.writeFileSync(
  217. `${this.name}.json`,
  218. data, {
  219. flag: "w"
  220. },
  221. (err) => console.log(err)
  222. );
  223. this.node.fs.writeFileSync(
  224. "root.json",
  225. JSON.stringify(this.root, null, 2), {
  226. flag: "w"
  227. },
  228. (err) => console.log(err)
  229. );
  230. }
  231. }
  232. write(data, key) {
  233. this.log(`SET ${key}`);
  234. if (key.indexOf("#") !== -1) {
  235. key = key.substr(1);
  236. if (isSurge || isLoon) {
  237. return $persistentStore.write(data, key);
  238. }
  239. if (isQX) {
  240. return $prefs.setValueForKey(data, key);
  241. }
  242. if (isNode) {
  243. this.root[key] = data;
  244. }
  245. } else {
  246. this.cache[key] = data;
  247. }
  248. this.persistCache();
  249. }
  250. read(key) {
  251. this.log(`READ ${key}`);
  252. if (key.indexOf("#") !== -1) {
  253. key = key.substr(1);
  254. if (isSurge || isLoon) {
  255. return $persistentStore.read(key);
  256. }
  257. if (isQX) {
  258. return $prefs.valueForKey(key);
  259. }
  260. if (isNode) {
  261. return this.root[key];
  262. }
  263. } else {
  264. return this.cache[key];
  265. }
  266. }
  267. delete(key) {
  268. this.log(`DELETE ${key}`);
  269. if (key.indexOf("#") !== -1) {
  270. key = key.substr(1);
  271. if (isSurge || isLoon) {
  272. return $persistentStore.write(null, key);
  273. }
  274. if (isQX) {
  275. return $prefs.removeValueForKey(key);
  276. }
  277. if (isNode) {
  278. delete this.root[key];
  279. }
  280. } else {
  281. delete this.cache[key];
  282. }
  283. this.persistCache();
  284. }
  285. // notification
  286. notify(title, subtitle = "", content = "", options = {}) {
  287. const openURL = options["open-url"];
  288. const mediaURL = options["media-url"];
  289. if (isQX) $notify(title, subtitle, content, options);
  290. if (isSurge) {
  291. $notification.post(
  292. title,
  293. subtitle,
  294. content + `${mediaURL ? "\n多媒体:" + mediaURL : ""}`, {
  295. url: openURL,
  296. }
  297. );
  298. }
  299. if (isLoon) {
  300. let opts = {};
  301. if (openURL) opts["openUrl"] = openURL;
  302. if (mediaURL) opts["mediaUrl"] = mediaURL;
  303. if (JSON.stringify(opts) === "{}") {
  304. $notification.post(title, subtitle, content);
  305. } else {
  306. $notification.post(title, subtitle, content, opts);
  307. }
  308. }
  309. if (isNode || isScriptable) {
  310. const content_ =
  311. content +
  312. (openURL ? `\n点击跳转: ${openURL}` : "") +
  313. (mediaURL ? `\n多媒体: ${mediaURL}` : "");
  314. if (isJSBox) {
  315. const push = require("push");
  316. push.schedule({
  317. title: title,
  318. body: (subtitle ? subtitle + "\n" : "") + content_,
  319. });
  320. } else {
  321. console.log(`${title}\n${subtitle}\n${content_}\n\n`);
  322. }
  323. }
  324. }
  325. // other helper functions
  326. log(msg) {
  327. if (this.debug) console.log(`[${this.name}] LOG: ${this.stringify(msg)}`);
  328. }
  329. info(msg) {
  330. console.log(`[${this.name}] INFO: ${this.stringify(msg)}`);
  331. }
  332. error(msg) {
  333. console.log(`[${this.name}] ERROR: ${this.stringify(msg)}`);
  334. }
  335. wait(millisec) {
  336. return new Promise((resolve) => setTimeout(resolve, millisec));
  337. }
  338. done(value = {}) {
  339. if (isQX || isLoon || isSurge) {
  340. $done(value);
  341. } else if (isNode && !isJSBox) {
  342. if (typeof $context !== "undefined") {
  343. $context.headers = value.headers;
  344. $context.statusCode = value.statusCode;
  345. $context.body = value.body;
  346. }
  347. }
  348. }
  349. stringify(obj_or_str) {
  350. if (typeof obj_or_str === 'string' || obj_or_str instanceof String)
  351. return obj_or_str;
  352. else
  353. try {
  354. return JSON.stringify(obj_or_str, null, 2);
  355. } catch (err) {
  356. return "[object Object]";
  357. }
  358. }
  359. })(name, debug);
  360. }