@nsnanocat/util 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,257 @@
1
+ import { $app } from "../lib/app.mjs";
2
+ import { Lodash as _ } from "./Lodash.mjs";
3
+
4
+ /**
5
+ * Storage
6
+ *
7
+ * @link https://developer.mozilla.org/zh-CN/docs/Web/API/Storage/setItem
8
+ * @export
9
+ * @class Storage
10
+ * @typedef {Storage}
11
+ */
12
+ export class Storage {
13
+ /**
14
+ * data
15
+ *
16
+ * @static
17
+ * @type {file}
18
+ */
19
+ static data = null;
20
+ static dataFile = "box.dat";
21
+ /**
22
+ * nameRegex
23
+ *
24
+ * @static
25
+ * @type {regexp}
26
+ */
27
+ static #nameRegex = /^@(?<key>[^.]+)(?:\.(?<path>.*))?$/;
28
+
29
+ /**
30
+ * getItem
31
+ *
32
+ * @static
33
+ * @param {string} keyName
34
+ * @param {*} [defaultValue]
35
+ * @returns {*}
36
+ */
37
+ static getItem(keyName, defaultValue = null) {
38
+ let keyValue = defaultValue;
39
+ // 如果以 @
40
+ switch (keyName.startsWith("@")) {
41
+ case true: {
42
+ const { key, path } = keyName.match(Storage.#nameRegex)?.groups;
43
+ keyName = key;
44
+ let value = Storage.getItem(keyName, {});
45
+ if (typeof value !== "object") value = {};
46
+ keyValue = _.get(value, path);
47
+ try {
48
+ keyValue = JSON.parse(keyValue);
49
+ } catch (e) {}
50
+ break;
51
+ }
52
+ default:
53
+ switch ($app) {
54
+ case "Surge":
55
+ case "Loon":
56
+ case "Stash":
57
+ case "Egern":
58
+ case "Shadowrocket":
59
+ keyValue = $persistentStore.read(keyName);
60
+ break;
61
+ case "Quantumult X":
62
+ keyValue = $prefs.valueForKey(keyName);
63
+ break;
64
+ case "Node.js":
65
+ Storage.data = Storage.#loaddata(Storage.dataFile);
66
+ keyValue = Storage.data?.[keyName];
67
+ break;
68
+ default:
69
+ keyValue = Storage.data?.[keyName] || null;
70
+ break;
71
+ }
72
+ try {
73
+ keyValue = JSON.parse(keyValue);
74
+ } catch (e) {
75
+ // do nothing
76
+ }
77
+ break;
78
+ }
79
+ return keyValue ?? defaultValue;
80
+ }
81
+
82
+ /**
83
+ * setItem
84
+ *
85
+ * @static
86
+ * @param {string} keyName
87
+ * @param {*} keyValue
88
+ * @returns {boolean}
89
+ */
90
+ static setItem(keyName = new String(), keyValue = new String()) {
91
+ let result = false;
92
+ switch (typeof keyValue) {
93
+ case "object":
94
+ keyValue = JSON.stringify(keyValue);
95
+ break;
96
+ default:
97
+ keyValue = String(keyValue);
98
+ break;
99
+ }
100
+ switch (keyName.startsWith("@")) {
101
+ case true: {
102
+ const { key, path } = keyName.match(Storage.#nameRegex)?.groups;
103
+ keyName = key;
104
+ let value = Storage.getItem(keyName, {});
105
+ if (typeof value !== "object") value = {};
106
+ _.set(value, path, keyValue);
107
+ result = Storage.setItem(keyName, value);
108
+ break;
109
+ }
110
+ default:
111
+ switch ($app) {
112
+ case "Surge":
113
+ case "Loon":
114
+ case "Stash":
115
+ case "Egern":
116
+ case "Shadowrocket":
117
+ result = $persistentStore.write(keyValue, keyName);
118
+ break;
119
+ case "Quantumult X":
120
+ result = $prefs.setValueForKey(keyValue, keyName);
121
+ break;
122
+ case "Node.js":
123
+ Storage.data = Storage.#loaddata(Storage.dataFile);
124
+ Storage.data[keyName] = keyValue;
125
+ Storage.#writedata(Storage.dataFile);
126
+ result = true;
127
+ break;
128
+ default:
129
+ result = Storage.data?.[keyName] || null;
130
+ break;
131
+ }
132
+ break;
133
+ }
134
+ return result;
135
+ }
136
+
137
+ /**
138
+ * removeItem
139
+ *
140
+ * @static
141
+ * @param {string} keyName
142
+ * @returns {boolean}
143
+ */
144
+ static removeItem(keyName) {
145
+ let result = false;
146
+ switch (keyName.startsWith("@")) {
147
+ case true: {
148
+ const { key, path } = keyName.match(Storage.#nameRegex)?.groups;
149
+ keyName = key;
150
+ let value = Storage.getItem(keyName);
151
+ if (typeof value !== "object") value = {};
152
+ keyValue = _.unset(value, path);
153
+ result = Storage.setItem(keyName, value);
154
+ break;
155
+ }
156
+ default:
157
+ switch ($app) {
158
+ case "Surge":
159
+ case "Loon":
160
+ case "Stash":
161
+ case "Egern":
162
+ case "Shadowrocket":
163
+ result = false;
164
+ break;
165
+ case "Quantumult X":
166
+ result = $prefs.removeValueForKey(keyName);
167
+ break;
168
+ case "Node.js":
169
+ result = false;
170
+ break;
171
+ default:
172
+ result = false;
173
+ break;
174
+ }
175
+ break;
176
+ }
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * clear
182
+ *
183
+ * @static
184
+ * @returns {boolean}
185
+ */
186
+ static clear() {
187
+ let result = false;
188
+ switch ($app) {
189
+ case "Surge":
190
+ case "Loon":
191
+ case "Stash":
192
+ case "Egern":
193
+ case "Shadowrocket":
194
+ result = false;
195
+ break;
196
+ case "Quantumult X":
197
+ result = $prefs.removeAllValues();
198
+ break;
199
+ case "Node.js":
200
+ result = false;
201
+ break;
202
+ default:
203
+ result = false;
204
+ break;
205
+ }
206
+ return result;
207
+ }
208
+
209
+ /**
210
+ * #loaddata
211
+ *
212
+ * @param {string} dataFile
213
+ * @returns {*}
214
+ */
215
+ static #loaddata = dataFile => {
216
+ if ($app === "Node.js") {
217
+ this.fs = this.fs ? this.fs : require("node:fs");
218
+ this.path = this.path ? this.path : require("node:path");
219
+ const curDirDataFilePath = this.path.resolve(dataFile);
220
+ const rootDirDataFilePath = this.path.resolve(process.cwd(), dataFile);
221
+ const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath);
222
+ const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath);
223
+ if (isCurDirDataFile || isRootDirDataFile) {
224
+ const datPath = isCurDirDataFile ? curDirDataFilePath : rootDirDataFilePath;
225
+ try {
226
+ return JSON.parse(this.fs.readFileSync(datPath));
227
+ } catch (e) {
228
+ return {};
229
+ }
230
+ } else return {};
231
+ } else return {};
232
+ };
233
+
234
+ /**
235
+ * #writedata
236
+ *
237
+ * @param {string} [dataFile=this.dataFile]
238
+ */
239
+ static #writedata = (dataFile = this.dataFile) => {
240
+ if ($app === "Node.js") {
241
+ this.fs = this.fs ? this.fs : require("node:fs");
242
+ this.path = this.path ? this.path : require("node:path");
243
+ const curDirDataFilePath = this.path.resolve(dataFile);
244
+ const rootDirDataFilePath = this.path.resolve(process.cwd(), dataFile);
245
+ const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath);
246
+ const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath);
247
+ const jsondata = JSON.stringify(this.data);
248
+ if (isCurDirDataFile) {
249
+ this.fs.writeFileSync(curDirDataFilePath, jsondata);
250
+ } else if (isRootDirDataFile) {
251
+ this.fs.writeFileSync(rootDirDataFilePath, jsondata);
252
+ } else {
253
+ this.fs.writeFileSync(curDirDataFilePath, jsondata);
254
+ }
255
+ }
256
+ };
257
+ }
@@ -0,0 +1,176 @@
1
+ import { $app } from "../lib/app.mjs";
2
+ import { Console } from "./Console.mjs";
3
+ import { Lodash as _ } from "./Lodash.mjs";
4
+
5
+ /**
6
+ * fetch
7
+ *
8
+ * @link https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
9
+ * @export
10
+ * @async
11
+ * @param {object|string} request
12
+ * @param {object} [option]
13
+ * @returns {Promise<object>}
14
+ */
15
+ export async function fetch(request, option) {
16
+ // 初始化参数
17
+ switch (request.constructor) {
18
+ case Object:
19
+ request = { ...option, ...request };
20
+ break;
21
+ case String:
22
+ request = { ...option, url: request };
23
+ break;
24
+ }
25
+ // 自动判断请求方法
26
+ if (!request.method) {
27
+ request.method = "GET";
28
+ if (request.body ?? request.bodyBytes) request.method = "POST";
29
+ }
30
+ // 移除请求头中的部分参数, 让其自动生成
31
+ delete request.headers?.Host;
32
+ delete request.headers?.[":authority"];
33
+ delete request.headers?.["Content-Length"];
34
+ delete request.headers?.["content-length"];
35
+ // 定义请求方法(小写)
36
+ const method = request.method.toLocaleLowerCase();
37
+ // 判断平台
38
+ switch ($app) {
39
+ case "Loon":
40
+ case "Surge":
41
+ case "Stash":
42
+ case "Egern":
43
+ case "Shadowrocket":
44
+ default:
45
+ // 转换请求参数
46
+ if (request.timeout) {
47
+ request.timeout = Number.parseInt(request.timeout, 10);
48
+ switch ($app) {
49
+ case "Loon":
50
+ case "Shadowrocket":
51
+ case "Stash":
52
+ case "Egern":
53
+ default:
54
+ request.timeout = request.timeout / 1000;
55
+ break;
56
+ case "Surge":
57
+ break;
58
+ }
59
+ }
60
+ if (request.policy) {
61
+ switch ($app) {
62
+ case "Loon":
63
+ request.node = request.policy;
64
+ break;
65
+ case "Stash":
66
+ _.set(request, "headers.X-Stash-Selected-Proxy", encodeURI(request.policy));
67
+ break;
68
+ case "Shadowrocket":
69
+ _.set(request, "headers.X-Surge-Proxy", request.policy);
70
+ break;
71
+ }
72
+ }
73
+ if (typeof request.redirection === "boolean") request["auto-redirect"] = request.redirection;
74
+ // 转换请求体
75
+ if (request.bodyBytes && !request.body) {
76
+ request.body = request.bodyBytes;
77
+ request.bodyBytes = undefined;
78
+ }
79
+ // 判断是否请求二进制响应体
80
+ switch ((request.headers?.Accept || request.headers?.accept)?.split(";")?.[0]) {
81
+ case "application/protobuf":
82
+ case "application/x-protobuf":
83
+ case "application/vnd.google.protobuf":
84
+ case "application/vnd.apple.flatbuffer":
85
+ case "application/grpc":
86
+ case "application/grpc+proto":
87
+ case "application/octet-stream":
88
+ request["binary-mode"] = true;
89
+ break;
90
+ }
91
+ // 发送请求
92
+ return await new Promise((resolve, reject) => {
93
+ $httpClient[method](request, (error, response, body) => {
94
+ if (error) reject(error);
95
+ else {
96
+ response.ok = /^2\d\d$/.test(response.status);
97
+ response.statusCode = response.status;
98
+ if (body) {
99
+ response.body = body;
100
+ if (request["binary-mode"] == true) response.bodyBytes = body;
101
+ }
102
+ resolve(response);
103
+ }
104
+ });
105
+ });
106
+ case "Quantumult X":
107
+ // 转换请求参数
108
+ if (request.policy) _.set(request, "opts.policy", request.policy);
109
+ if (typeof request["auto-redirect"] === "boolean") _.set(request, "opts.redirection", request["auto-redirect"]);
110
+ // 转换请求体
111
+ if (request.body instanceof ArrayBuffer) {
112
+ request.bodyBytes = request.body;
113
+ request.body = undefined;
114
+ } else if (ArrayBuffer.isView(request.body)) {
115
+ request.bodyBytes = request.body.buffer.slice(request.body.byteOffset, request.body.byteLength + request.body.byteOffset);
116
+ request.body = undefined;
117
+ } else if (request.body) request.bodyBytes = undefined;
118
+ // 发送请求
119
+ return await $task.fetch(request).then(
120
+ response => {
121
+ response.ok = /^2\d\d$/.test(response.statusCode);
122
+ response.status = response.statusCode;
123
+ switch ((response.headers?.["Content-Type"] ?? response.headers?.["content-type"])?.split(";")?.[0]) {
124
+ case "application/protobuf":
125
+ case "application/x-protobuf":
126
+ case "application/vnd.google.protobuf":
127
+ case "application/vnd.apple.flatbuffer":
128
+ case "application/grpc":
129
+ case "application/grpc+proto":
130
+ case "application/octet-stream":
131
+ response.body = response.bodyBytes;
132
+ break;
133
+ case undefined:
134
+ default:
135
+ break;
136
+ }
137
+ response.bodyBytes = undefined;
138
+ return response;
139
+ },
140
+ reason => Promise.reject(reason.error),
141
+ );
142
+ case "Node.js": {
143
+ const iconv = require("iconv-lite");
144
+ const got = globalThis.got ? globalThis.got : require("got");
145
+ const cktough = globalThis.cktough ? globalThis.cktough : require("tough-cookie");
146
+ const ckjar = globalThis.ckjar ? globalThis.ckjar : new cktough.CookieJar();
147
+ if (request) {
148
+ request.headers = request.headers ? request.headers : {};
149
+ if (undefined === request.headers.Cookie && undefined === request.cookieJar) request.cookieJar = ckjar;
150
+ }
151
+ const { url, ...option } = request;
152
+ return await got[method](url, option)
153
+ .on("redirect", (response, nextOpts) => {
154
+ try {
155
+ if (response.headers["set-cookie"]) {
156
+ const ck = response.headers["set-cookie"].map(cktough.Cookie.parse).toString();
157
+ if (ck) ckjar.setCookieSync(ck, null);
158
+ nextOpts.cookieJar = ckjar;
159
+ }
160
+ } catch (e) {
161
+ Console.error(e);
162
+ }
163
+ // ckjar.setCookieSync(response.headers["set-cookie"].map(Cookie.parse).toString())
164
+ })
165
+ .then(
166
+ response => {
167
+ response.statusCode = response.status;
168
+ response.body = iconv.decode(response.rawBody, "utf-8");
169
+ response.bodyBytes = response.rawBody;
170
+ return response;
171
+ },
172
+ error => Promise.reject(error.message),
173
+ );
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./Console.mjs";
2
+ export * from "./fetch.mjs";
3
+ export * from "./Lodash.mjs";
4
+ export * from "./StatusCodes.mjs";
5
+ export * from "./Storage.mjs";