OpenAPI.js 12 KB

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