specific-ai-cli 0.0.0-stage → 0.1.1

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.
Files changed (277) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +177 -0
  3. package/README.ja.md +177 -0
  4. package/README.ko.md +177 -0
  5. package/README.md +174 -2
  6. package/README.zh-TW.md +177 -0
  7. package/assets/banner.txt +3 -0
  8. package/dist/cli/argv.js +98 -0
  9. package/dist/cli/commands/auth.js +11 -0
  10. package/dist/cli/commands/config.js +23 -0
  11. package/dist/cli/commands/doctor.js +134 -0
  12. package/dist/cli/commands/platforms.js +24 -0
  13. package/dist/cli/commands/version.js +4 -0
  14. package/dist/cli/confirm.js +22 -0
  15. package/dist/cli/dispatch.js +440 -0
  16. package/dist/cli/help.js +223 -0
  17. package/dist/cli/hints.js +60 -0
  18. package/dist/cli/main.js +18 -0
  19. package/dist/cli/output.js +66 -0
  20. package/dist/core/auth-handlers.js +41 -0
  21. package/dist/core/auth-store.js +119 -0
  22. package/dist/core/capabilities.js +163 -0
  23. package/dist/core/config.js +121 -0
  24. package/dist/core/cookies.js +164 -0
  25. package/dist/core/errors.js +37 -0
  26. package/dist/core/files.js +89 -0
  27. package/dist/core/fsutil.js +52 -0
  28. package/dist/core/hash.js +4 -0
  29. package/dist/core/http.js +331 -0
  30. package/dist/core/image.js +280 -0
  31. package/dist/core/log.js +40 -0
  32. package/dist/core/login.js +191 -0
  33. package/dist/core/mp4.js +130 -0
  34. package/dist/core/normalize.js +207 -0
  35. package/dist/core/options.js +110 -0
  36. package/dist/core/paths.js +38 -0
  37. package/dist/core/pb.js +59 -0
  38. package/dist/core/py.js +181 -0
  39. package/dist/core/rand.js +131 -0
  40. package/dist/core/registry.js +129 -0
  41. package/dist/core/schemas.js +41 -0
  42. package/dist/core/stream.js +104 -0
  43. package/dist/core/toolkit.js +35 -0
  44. package/dist/core/vm.js +73 -0
  45. package/dist/core/vocab.js +157 -0
  46. package/dist/platforms/_shared/impaas.js +410 -0
  47. package/dist/platforms/bilibili/index.js +216 -0
  48. package/dist/platforms/bilibili/web/api.js +806 -0
  49. package/dist/platforms/bilibili/web/client.js +311 -0
  50. package/dist/platforms/bilibili/web/commands.js +783 -0
  51. package/dist/platforms/bilibili/web/ddddocr.js +342 -0
  52. package/dist/platforms/bilibili/web/gaia.js +130 -0
  53. package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
  54. package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
  55. package/dist/platforms/bilibili/web/geetest.js +450 -0
  56. package/dist/platforms/bilibili/web/normalize.js +234 -0
  57. package/dist/platforms/bilibili/web/profile.js +135 -0
  58. package/dist/platforms/bilibili/web/resolve.js +112 -0
  59. package/dist/platforms/bilibili/web/sign.js +223 -0
  60. package/dist/platforms/bilibili/web/upos.js +102 -0
  61. package/dist/platforms/douyin/index.js +142 -0
  62. package/dist/platforms/douyin/web/api.js +863 -0
  63. package/dist/platforms/douyin/web/client.js +413 -0
  64. package/dist/platforms/douyin/web/commands.js +712 -0
  65. package/dist/platforms/douyin/web/creator.js +601 -0
  66. package/dist/platforms/douyin/web/crypto.js +247 -0
  67. package/dist/platforms/douyin/web/dtrait.js +190 -0
  68. package/dist/platforms/douyin/web/im.js +502 -0
  69. package/dist/platforms/douyin/web/image.js +5 -0
  70. package/dist/platforms/douyin/web/live.js +225 -0
  71. package/dist/platforms/douyin/web/mssdk.js +148 -0
  72. package/dist/platforms/douyin/web/normalize.js +326 -0
  73. package/dist/platforms/douyin/web/passport.js +1247 -0
  74. package/dist/platforms/douyin/web/profile.js +221 -0
  75. package/dist/platforms/douyin/web/proto.js +26 -0
  76. package/dist/platforms/douyin/web/resolve.js +88 -0
  77. package/dist/platforms/douyin/web/sign.js +436 -0
  78. package/dist/platforms/douyin/web/tos.js +78 -0
  79. package/dist/platforms/index.js +17 -0
  80. package/dist/platforms/jd/index.js +67 -0
  81. package/dist/platforms/jd/web/aks.js +115 -0
  82. package/dist/platforms/jd/web/api.js +549 -0
  83. package/dist/platforms/jd/web/chat.js +132 -0
  84. package/dist/platforms/jd/web/client.js +470 -0
  85. package/dist/platforms/jd/web/commands.js +557 -0
  86. package/dist/platforms/jd/web/jcap/helper.js +90 -0
  87. package/dist/platforms/jd/web/jcap/host.js +107 -0
  88. package/dist/platforms/jd/web/jcap/lsd.js +300 -0
  89. package/dist/platforms/jd/web/jcap/solver.js +995 -0
  90. package/dist/platforms/jd/web/jcap.js +150 -0
  91. package/dist/platforms/jd/web/js.js +420 -0
  92. package/dist/platforms/jd/web/login.js +775 -0
  93. package/dist/platforms/jd/web/normalize.js +297 -0
  94. package/dist/platforms/jd/web/profile.js +256 -0
  95. package/dist/platforms/jd/web/util.js +175 -0
  96. package/dist/platforms/jd/web/webm.js +133 -0
  97. package/dist/platforms/kuaishou/index.js +98 -0
  98. package/dist/platforms/kuaishou/web/api.js +694 -0
  99. package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
  100. package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
  101. package/dist/platforms/kuaishou/web/captcha.js +273 -0
  102. package/dist/platforms/kuaishou/web/client.js +487 -0
  103. package/dist/platforms/kuaishou/web/commands.js +863 -0
  104. package/dist/platforms/kuaishou/web/gap.js +144 -0
  105. package/dist/platforms/kuaishou/web/gdfp.js +376 -0
  106. package/dist/platforms/kuaishou/web/gql.js +11 -0
  107. package/dist/platforms/kuaishou/web/live.js +125 -0
  108. package/dist/platforms/kuaishou/web/normalize.js +241 -0
  109. package/dist/platforms/kuaishou/web/oracle.js +329 -0
  110. package/dist/platforms/kuaishou/web/profile.js +259 -0
  111. package/dist/platforms/kuaishou/web/resolve.js +84 -0
  112. package/dist/platforms/kuaishou/web/session.js +592 -0
  113. package/dist/platforms/kuaishou/web/sign.js +568 -0
  114. package/dist/platforms/taobao/index.js +56 -0
  115. package/dist/platforms/taobao/web/api.js +96 -0
  116. package/dist/platforms/taobao/web/client.js +102 -0
  117. package/dist/platforms/taobao/web/commands.js +172 -0
  118. package/dist/platforms/taobao/web/im.js +68 -0
  119. package/dist/platforms/taobao/web/normalize.js +103 -0
  120. package/dist/platforms/taobao/web/profile.js +85 -0
  121. package/dist/platforms/taobao/web/resolve.js +51 -0
  122. package/dist/platforms/taobao/web/sign.js +35 -0
  123. package/dist/platforms/tiktok/index.js +120 -0
  124. package/dist/platforms/tiktok/web/api.js +1234 -0
  125. package/dist/platforms/tiktok/web/client.js +362 -0
  126. package/dist/platforms/tiktok/web/commands.js +821 -0
  127. package/dist/platforms/tiktok/web/im.js +133 -0
  128. package/dist/platforms/tiktok/web/jsrun.js +126 -0
  129. package/dist/platforms/tiktok/web/normalize.js +286 -0
  130. package/dist/platforms/tiktok/web/profile.js +169 -0
  131. package/dist/platforms/tiktok/web/resolve.js +156 -0
  132. package/dist/platforms/tiktok/web/sign.js +585 -0
  133. package/dist/platforms/tiktok/web/upload.js +805 -0
  134. package/dist/platforms/tiktok/web/wire.js +490 -0
  135. package/dist/platforms/weibo/index.js +91 -0
  136. package/dist/platforms/weibo/web/api.js +206 -0
  137. package/dist/platforms/weibo/web/client.js +155 -0
  138. package/dist/platforms/weibo/web/commands.js +202 -0
  139. package/dist/platforms/weibo/web/normalize.js +137 -0
  140. package/dist/platforms/weibo/web/profile.js +246 -0
  141. package/dist/platforms/weibo/web/resolve.js +60 -0
  142. package/dist/platforms/weibo/web/sign.js +85 -0
  143. package/dist/platforms/x/index.js +107 -0
  144. package/dist/platforms/x/web/api.js +443 -0
  145. package/dist/platforms/x/web/article.js +212 -0
  146. package/dist/platforms/x/web/client.js +202 -0
  147. package/dist/platforms/x/web/commands.js +363 -0
  148. package/dist/platforms/x/web/graphql.js +108 -0
  149. package/dist/platforms/x/web/normalize.js +235 -0
  150. package/dist/platforms/x/web/profile.js +99 -0
  151. package/dist/platforms/x/web/resolve.js +44 -0
  152. package/dist/platforms/x/web/transaction.js +171 -0
  153. package/dist/platforms/xhs/index.js +101 -0
  154. package/dist/platforms/xhs/web/api.js +278 -0
  155. package/dist/platforms/xhs/web/client.js +540 -0
  156. package/dist/platforms/xhs/web/commands.js +1046 -0
  157. package/dist/platforms/xhs/web/creator-api.js +609 -0
  158. package/dist/platforms/xhs/web/creator.js +203 -0
  159. package/dist/platforms/xhs/web/js.js +194 -0
  160. package/dist/platforms/xhs/web/login.js +230 -0
  161. package/dist/platforms/xhs/web/normalize.js +166 -0
  162. package/dist/platforms/xhs/web/profile.js +127 -0
  163. package/dist/platforms/xhs/web/push.js +361 -0
  164. package/dist/platforms/xhs/web/resolve.js +69 -0
  165. package/dist/platforms/xhs/web/state.js +368 -0
  166. package/dist/platforms/xianyu/index.js +59 -0
  167. package/dist/platforms/xianyu/web/api.js +312 -0
  168. package/dist/platforms/xianyu/web/client.js +134 -0
  169. package/dist/platforms/xianyu/web/commands.js +305 -0
  170. package/dist/platforms/xianyu/web/im.js +73 -0
  171. package/dist/platforms/xianyu/web/normalize.js +123 -0
  172. package/dist/platforms/xianyu/web/profile.js +146 -0
  173. package/dist/platforms/xianyu/web/resolve.js +48 -0
  174. package/dist/platforms/xianyu/web/sign.js +79 -0
  175. package/package.json +77 -4
  176. package/static/douyin/Live.proto +194 -0
  177. package/static/douyin/Request.proto +89 -0
  178. package/static/douyin/Response.proto +46 -0
  179. package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
  180. package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
  181. package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
  182. package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
  183. package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
  184. package/static/douyin/challenge_profile.json +801 -0
  185. package/static/douyin/challenge_template_runner.js +217 -0
  186. package/static/douyin/dtrait_profile.json +66 -0
  187. package/static/douyin/mstoken_common_profile.json +1 -0
  188. package/static/douyin/mstoken_profile.json +214 -0
  189. package/static/jd/h5st5_env.js +750 -0
  190. package/static/jd/h5st5_lib.js +5 -0
  191. package/static/jd/h5st5_server.js +49 -0
  192. package/static/jd/jcap/env/env_core.js +512 -0
  193. package/static/jd/jcap/env/run.js +1307 -0
  194. package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
  195. package/static/jd/package.json +4 -0
  196. package/static/jd/pc_tk_lib.js +1 -0
  197. package/static/jd/pc_tk_server.js +63 -0
  198. package/static/jd/summer_cryptico_runner.js +50 -0
  199. package/static/jd/webm/env/run.js +453 -0
  200. package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
  201. package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
  202. package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
  203. package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
  204. package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
  205. package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
  206. package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
  207. package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
  208. package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
  209. package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
  210. package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
  211. package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
  212. package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
  213. package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
  214. package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
  215. package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
  216. package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
  217. package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
  218. package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
  219. package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
  220. package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
  221. package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
  222. package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
  223. package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
  224. package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
  225. package/static/kuaishou/js/cp-kwf.js +1 -0
  226. package/static/kuaishou/tools/kwf_oracle.js +799 -0
  227. package/static/kuaishou/tools/kws_oracle.js +885 -0
  228. package/static/kuaishou/tools/like_token_oracle.js +70 -0
  229. package/static/taobao/taobao_js_20260407.js +570 -0
  230. package/static/tiktok/Tiktok_Request.json +597 -0
  231. package/static/tiktok/package.json +3 -0
  232. package/static/tiktok/shop_bsid/core.js +1 -0
  233. package/static/tiktok/shop_bsid/env_core.js +512 -0
  234. package/static/tiktok/shop_bsid/loader.js +1 -0
  235. package/static/tiktok/shop_bsid/run.js +753 -0
  236. package/static/tiktok/shop_bsid/sign.js +100 -0
  237. package/static/tiktok/shop_bsid/webmssdk.js +1 -0
  238. package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
  239. package/static/tiktok/signing/env/env_core.js +512 -0
  240. package/static/tiktok/signing/env/sign.js +455 -0
  241. package/static/tiktok/signing/sdk_config.json +230 -0
  242. package/static/tiktok/signing/source/webmssdk.js +1 -0
  243. package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
  244. package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
  245. package/static/x/graphql.json +7777 -0
  246. package/static/x/transaction_l1.json +12 -0
  247. package/static/xhs/package.json +3 -0
  248. package/static/xhs/xhs_core/js/b1.js +210 -0
  249. package/static/xhs/xhs_core/js/mns.js +340 -0
  250. package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
  251. package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
  252. package/static/xhs/xhs_core/js/sign.js +225 -0
  253. package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
  254. package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
  255. package/static/xhs/xhs_core/js/xs_common.js +79 -0
  256. package/static/xhs/xhs_creator/js/profile.js +115 -0
  257. package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
  258. package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
  259. package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
  260. package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
  261. package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
  262. package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
  263. package/static/xhs/xhs_pc/js/b1.js +9 -0
  264. package/static/xhs/xhs_pc/js/deflate.js +320 -0
  265. package/static/xhs/xhs_pc/js/mns.js +4 -0
  266. package/static/xhs/xhs_pc/js/profile.js +141 -0
  267. package/static/xhs/xhs_pc/js/rap.js +173 -0
  268. package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
  269. package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
  270. package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
  271. package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
  272. package/static/xhs/xhs_pc/js/sign.js +6 -0
  273. package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
  274. package/static/xhs/xhs_pc/js/xs_common.js +4 -0
  275. package/static/xianyu/et_f.js +2 -0
  276. package/static/xianyu/gen_tfstk.js +410 -0
  277. package/static/xianyu/goofish_js_version_2.js +568 -0
@@ -0,0 +1,331 @@
1
+ import { TextDecoder } from 'node:util';
2
+ import { createTransport, fetch as wreqFetch, RequestError, } from 'wreq-js';
3
+ import { SpecificAIError } from './errors.js';
4
+ import { jsonDumps, jsonLoads, parseQsl, pyStr, requoteUri, toPairs, urlencode } from './py.js';
5
+ // wreq-js 默认读 HTTP(S)_PROXY 和系统代理。代理只能显式配置(AGENTS 5.5),这里全部关掉,
6
+ // 只通过 transport 的 proxy 传入。cli/main.ts 在加载任何模块前也会设置一次。
7
+ disableEnvProxy();
8
+ export function disableEnvProxy() {
9
+ process.env.NO_PROXY = process.env.no_proxy = '*,0.0.0.0/0,::/0';
10
+ }
11
+ const REDIRECT_STATUS = new Set([301, 302, 303, 307, 308]);
12
+ const transports = new Map();
13
+ async function transportFor(options) {
14
+ const key = `${options.browser}|${options.os}|${options.proxy ?? ''}`;
15
+ let t = transports.get(key);
16
+ if (!t) {
17
+ t = createTransport({ browser: options.browser, os: options.os, ...(options.proxy ? { proxy: options.proxy } : {}) });
18
+ transports.set(key, t);
19
+ }
20
+ return t;
21
+ }
22
+ const wreqSender = async (p, o) => {
23
+ const headers = [...p.headers];
24
+ if (p.cookies.length)
25
+ insertCookieHeader(headers, p.cookies.map(([k, v]) => `${k}=${v}`).join('; '));
26
+ let body;
27
+ if (p.multipart) {
28
+ const form = new FormData();
29
+ for (const part of p.multipart) {
30
+ if (part.filename != null || typeof part.data !== 'string') {
31
+ const data = typeof part.data === 'string' ? Buffer.from(part.data) : new Uint8Array(part.data);
32
+ form.append(part.name, new Blob([data], { type: part.contentType ?? 'application/octet-stream' }), part.filename ?? part.name);
33
+ }
34
+ else
35
+ form.append(part.name, part.data);
36
+ }
37
+ body = form;
38
+ }
39
+ else if (p.body != null)
40
+ body = p.body;
41
+ // wreq-js 会把空路径的 URL(https://host?x=1)发成坏请求,补上 /
42
+ const url = p.url.replace(/^(https?:\/\/[^/?#]+)(?=[?#]|$)/i, '$1/');
43
+ return wreqFetch(url, {
44
+ method: p.method,
45
+ headers,
46
+ body,
47
+ transport: await transportFor(o),
48
+ timeout: o.timeout * 1000,
49
+ redirect: o.redirect,
50
+ disableDefaultHeaders: o.disableDefaultHeaders,
51
+ });
52
+ };
53
+ let sender = wreqSender;
54
+ /** 替换真正发请求的函数,返回恢复函数。只给测试用。 */
55
+ export function mockSender(fn) {
56
+ const prev = sender;
57
+ sender = async (p) => fn(p);
58
+ return () => {
59
+ sender = prev;
60
+ };
61
+ }
62
+ /** 关闭所有连接池,让进程可以退出。 */
63
+ export async function closeTransports() {
64
+ const all = [...transports.values()];
65
+ transports.clear();
66
+ await Promise.allSettled(all.map(async (t) => (await t).close()));
67
+ }
68
+ /** cookie 头放在 priority 之前(Chrome 的位置),没有 priority 时放最后。 */
69
+ function insertCookieHeader(headers, value) {
70
+ const i = headers.findIndex(([k]) => k.toLowerCase() === 'priority');
71
+ headers.splice(i < 0 ? headers.length : i, 0, ['cookie', value]);
72
+ }
73
+ function headerPairs(init) {
74
+ if (!init)
75
+ return [];
76
+ const pairs = Array.isArray(init) ? init : Object.entries(init);
77
+ return pairs.filter((p) => p[1] != null);
78
+ }
79
+ function hasHeader(headers, name) {
80
+ const n = name.toLowerCase();
81
+ return headers.some(([k]) => k.toLowerCase() === n);
82
+ }
83
+ /**
84
+ * curl_cffi 的 `update_url_params`:已有同名参数且新旧都只出现一次时就地替换,否则追加;
85
+ * 布尔值写成 true / false;最后整体 `urlencode` 再 `requote_uri`。
86
+ */
87
+ export function buildUrl(url, query) {
88
+ const pairs = toPairs(query);
89
+ if (!pairs.length)
90
+ return requoteUri(url);
91
+ const hashAt = url.indexOf('#');
92
+ const fragment = hashAt < 0 ? '' : url.slice(hashAt);
93
+ const base = hashAt < 0 ? url : url.slice(0, hashAt);
94
+ const qAt = base.indexOf('?');
95
+ const path = qAt < 0 ? base : base.slice(0, qAt);
96
+ let args = qAt < 0 ? [] : parseQsl(base.slice(qAt + 1));
97
+ const count = (list, key) => list.filter(([k]) => k === key).length;
98
+ for (const [key, raw] of pairs) {
99
+ const value = typeof raw === 'boolean' ? String(raw) : pyStr(raw);
100
+ if (count(args, key) === 1 && count(pairs, key) === 1)
101
+ args = args.map((a) => (a[0] === key ? [key, value] : a));
102
+ else
103
+ args.push([key, value]);
104
+ }
105
+ const query2 = urlencode(args);
106
+ return requoteUri(`${path}${query2 ? '?' + query2 : ''}${fragment}`);
107
+ }
108
+ export class HttpClient {
109
+ jar;
110
+ options;
111
+ constructor(options = {}) {
112
+ this.options = options;
113
+ this.jar = options.jar ?? null;
114
+ }
115
+ /** 组装请求(不发送)。 */
116
+ prepare(req) {
117
+ const method = (req.method ?? 'GET').toUpperCase();
118
+ const url = buildUrl(req.url, req.query);
119
+ const headers = headerPairs(req.headers);
120
+ let body = null;
121
+ if (req.json !== undefined) {
122
+ body = jsonDumps(req.json, { separators: [',', ':'] });
123
+ if (!hasHeader(headers, 'content-type'))
124
+ headers.push(['Content-Type', 'application/json']);
125
+ }
126
+ else if (req.form !== undefined) {
127
+ body = urlencode(req.form);
128
+ if (method !== 'POST' && !hasHeader(headers, 'content-type'))
129
+ headers.push(['Content-Type', 'application/x-www-form-urlencoded']);
130
+ }
131
+ else if (req.body !== undefined) {
132
+ body = req.body;
133
+ if (body.length && !hasHeader(headers, 'content-type'))
134
+ headers.push(['Content-Type', 'application/octet-stream']);
135
+ }
136
+ // 显式给出的 cookie 头原样保留;否则从 cookie 罐取
137
+ let cookies = [];
138
+ const explicit = headers.findIndex(([k]) => k.toLowerCase() === 'cookie');
139
+ if (explicit >= 0) {
140
+ cookies = headers
141
+ .filter(([k]) => k.toLowerCase() === 'cookie')
142
+ .flatMap(([, v]) => v.split(';'))
143
+ .map((c) => c.trim())
144
+ .filter(Boolean)
145
+ .map((c) => {
146
+ // 没有 = 的片段当作空值的 cookie(与 Python 的 SimpleCookie 一致:foo → foo=)
147
+ const i = c.indexOf('=');
148
+ return (i < 0 ? [c, ''] : [c.slice(0, i), c.slice(i + 1)]);
149
+ });
150
+ for (let i = headers.length - 1; i >= 0; i--)
151
+ if (headers[i][0].toLowerCase() === 'cookie')
152
+ headers.splice(i, 1);
153
+ }
154
+ else if (req.cookies !== false) {
155
+ if (req.cookies)
156
+ cookies = Object.entries(req.cookies);
157
+ else if (this.jar) {
158
+ const list = this.jar.forUrl(url);
159
+ const order = this.options.cookieOrder;
160
+ if (order) {
161
+ const index = new Map(order.map((n, i) => [n, i]));
162
+ list.sort((a, b) => (index.get(a.name) ?? order.length) - (index.get(b.name) ?? order.length));
163
+ }
164
+ const seen = new Set();
165
+ for (const c of list)
166
+ if (!seen.has(c.name))
167
+ (seen.add(c.name), cookies.push([c.name, c.value]));
168
+ }
169
+ }
170
+ return { method, url, headers, cookies, body, multipart: req.multipart ?? null };
171
+ }
172
+ /**
173
+ * 发送请求。Set-Cookie 自动写回 cookie 罐;网络错误映射成 NETWORK。
174
+ * 跳转由这里逐跳跟随(最多 10 跳),这样中间跳转设置的 cookie 也会收进 cookie 罐,
175
+ * 后续跳转按 cookie 罐重新取 cookie。
176
+ */
177
+ async request(req) {
178
+ const follow = (req.redirect ?? 'follow') === 'follow';
179
+ let current = req;
180
+ for (let hop = 0;; hop++) {
181
+ const { res, url } = await this.send(current);
182
+ const location = res.headers.get('location');
183
+ if (!follow || !location || !REDIRECT_STATUS.has(res.status) || hop >= 10)
184
+ return res;
185
+ const method = (current.method ?? 'GET').toUpperCase();
186
+ const toGet = res.status === 303 || ((res.status === 301 || res.status === 302) && method === 'POST');
187
+ current = { ...current, url: new URL(location, url).toString(), query: undefined };
188
+ if (toGet) {
189
+ const { form: _f, json: _j, body: _b, multipart: _m, ...rest } = current;
190
+ current = { ...rest, method: 'GET', headers: headerPairs(rest.headers).filter(([k]) => k.toLowerCase() !== 'content-type') };
191
+ }
192
+ }
193
+ }
194
+ async send(req) {
195
+ const prepared = this.prepare(req);
196
+ const o = this.options;
197
+ const sendOptions = {
198
+ browser: o.browser ?? 'chrome',
199
+ os: o.os ?? 'windows',
200
+ proxy: o.proxy ?? null,
201
+ timeout: req.timeout ?? o.timeout ?? 30,
202
+ redirect: 'manual',
203
+ disableDefaultHeaders: o.disableDefaultHeaders ?? true,
204
+ };
205
+ // 表单 POST:libcurl 会自己补 content-type,wreq 不会
206
+ if (req.form !== undefined && prepared.method === 'POST' && !hasHeader(prepared.headers, 'content-type')) {
207
+ prepared.headers.push(['Content-Type', 'application/x-www-form-urlencoded']);
208
+ }
209
+ o.log?.debug(`${prepared.method} ${prepared.url}`);
210
+ let res;
211
+ try {
212
+ res = decodeByCharset(await sender(prepared, sendOptions));
213
+ }
214
+ catch (err) {
215
+ throw toNetworkError(err);
216
+ }
217
+ this.jar?.applySetCookie(prepared.url, res.headers.getSetCookie());
218
+ o.log?.debug(`${res.status} ${prepared.url}`);
219
+ return { res, url: prepared.url };
220
+ }
221
+ async text(req) {
222
+ return (await this.request(req)).text();
223
+ }
224
+ /** 请求并解析 JSON;不是 JSON 时报 UPSTREAM(HTTP 状态码放在 detail 里)。 */
225
+ async json(req) {
226
+ const res = await this.request(req);
227
+ return parseJson(res);
228
+ }
229
+ async bytes(req) {
230
+ const res = await this.request(req);
231
+ return new Uint8Array(await res.arrayBuffer());
232
+ }
233
+ }
234
+ const CHARSET = /charset\s*=\s*["']?([\w-]+)/i;
235
+ /**
236
+ * 响应头声明了 UTF-8 以外的 charset(京东的 `text/json;charset=gbk` 等)时,text() / json() 按它解码,
237
+ * 与上游 requests / curl_cffi 的 `resp.text` 一致。没有声明或声明 UTF-8 时不变。
238
+ */
239
+ function decodeByCharset(res) {
240
+ const charset = CHARSET.exec(res.headers.get('content-type') ?? '')?.[1]?.toLowerCase();
241
+ if (!charset || charset === 'utf-8' || charset === 'utf8')
242
+ return res;
243
+ let decoder;
244
+ try {
245
+ decoder = new TextDecoder(charset);
246
+ }
247
+ catch {
248
+ return res;
249
+ }
250
+ const wrap = (r) => new Proxy(r, {
251
+ get(target, key) {
252
+ if (key === 'text')
253
+ return async () => decoder.decode(await target.arrayBuffer());
254
+ if (key === 'json')
255
+ return async () => JSON.parse(decoder.decode(await target.arrayBuffer()));
256
+ if (key === 'clone')
257
+ return () => wrap(target.clone());
258
+ const value = Reflect.get(target, key);
259
+ return typeof value === 'function' ? value.bind(target) : value;
260
+ },
261
+ });
262
+ return wrap(res);
263
+ }
264
+ export async function parseJson(res) {
265
+ const text = await res.text();
266
+ try {
267
+ return jsonLoads(text);
268
+ }
269
+ catch {
270
+ throw new SpecificAIError('UPSTREAM', `平台返回的不是 JSON(HTTP ${res.status})`, {
271
+ detail: { status: res.status, body: text.slice(0, 300) },
272
+ });
273
+ }
274
+ }
275
+ /** 解开 JSONP 包装:`cb({...})`、`cb({...});` → 对象;不带包装时按 JSON 解。都解不开时为 null。 */
276
+ export function parseJsonp(text) {
277
+ if (text == null)
278
+ return null;
279
+ const t = text.trim();
280
+ const m = /^[^({["]*\(([\s\S]*)\)[;\s]*$/.exec(t);
281
+ try {
282
+ return jsonLoads(m ? m[1] : t);
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ }
288
+ /** 响应体按 JSONP 解析(见 parseJsonp),解不开时报 UPSTREAM,带上状态码和响应开头。 */
289
+ export async function jsonpOf(res) {
290
+ const text = await res.text();
291
+ const data = parseJsonp(text);
292
+ if (data == null) {
293
+ throw new SpecificAIError('UPSTREAM', `平台返回的不是 JSON / JSONP(HTTP ${res.status})`, {
294
+ detail: { status: res.status, body: text.slice(0, 300) },
295
+ });
296
+ }
297
+ return data;
298
+ }
299
+ /** wreq-js 的错误映射成 NETWORK,`detail.kind` 区分超时、代理、连接、DNS、TLS、中止。 */
300
+ export function toNetworkError(err) {
301
+ if (err instanceof SpecificAIError)
302
+ return err;
303
+ const message = err instanceof Error ? err.message : String(err);
304
+ if (!(err instanceof RequestError) && !(err instanceof Error && err.name === 'AbortError')) {
305
+ return new SpecificAIError('ERROR', message, { cause: err });
306
+ }
307
+ const kind = /timed? ?out/i.test(message)
308
+ ? 'timeout'
309
+ : /proxy|tunnel|socks/i.test(message)
310
+ ? 'proxy'
311
+ : /certificate|tls|ssl/i.test(message)
312
+ ? 'tls'
313
+ : /abort/i.test(message)
314
+ ? 'abort'
315
+ : /dns|resolve|lookup|name or service/i.test(message)
316
+ ? 'dns'
317
+ : 'connect';
318
+ return new SpecificAIError('NETWORK', `网络错误:${message}`, {
319
+ hint: kind === 'proxy' ? '检查代理:specific-ai config get proxy' : null,
320
+ detail: { kind },
321
+ cause: err,
322
+ });
323
+ }
324
+ /** 测试用:构造一个响应。 */
325
+ export function fakeResponse(body, init = {}) {
326
+ const text = typeof body === 'string' || body instanceof Uint8Array ? body : JSON.stringify(body);
327
+ const headers = new globalThis.Headers(init.headers ?? []);
328
+ const res = new globalThis.Response(text, { status: init.status ?? 200, headers });
329
+ Object.defineProperty(res, 'url', { value: init.url ?? '' });
330
+ return res;
331
+ }
@@ -0,0 +1,280 @@
1
+ import { createRequire } from 'node:module';
2
+ import { inflateSync } from 'node:zlib';
3
+ /**
4
+ * 图片解码与 OpenCV(@techstark/opencv-js)辅助,给各平台的验证码识别用(京东 JCAP、快手滑块、B 站极验)。
5
+ * PNG 用自带的解码器(与 cv2.imdecode / Pillow 逐像素一致,半透明像素不经过预乘);JPEG / WebP 等交给 @napi-rs/canvas。
6
+ * imdecode 输出 BGR / BGRA 的交错像素(与 cv2 一致)。
7
+ */
8
+ const require = createRequire(import.meta.url);
9
+ let cvPromise = null;
10
+ /** 加载 opencv-js(WASM,首次约几百毫秒)。 */
11
+ export function loadCv() {
12
+ cvPromise ??= (async () => {
13
+ let cv = require('@techstark/opencv-js');
14
+ if (cv instanceof Promise)
15
+ cv = await cv;
16
+ else if (!cv.Mat)
17
+ await new Promise((r) => (cv.onRuntimeInitialized = () => r()));
18
+ return cv;
19
+ })();
20
+ return cvPromise;
21
+ }
22
+ export const img = (width, height, channels, data) => ({ width, height, channels, data });
23
+ // ---------------------------------------------------------------- PNG
24
+ function paeth(a, b, c) {
25
+ const p = a + b - c;
26
+ const pa = Math.abs(p - a);
27
+ const pb = Math.abs(p - b);
28
+ const pc = Math.abs(p - c);
29
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
30
+ }
31
+ /** 非交错 PNG → RGBA8。不是 PNG、或是交错图时返回 null(交给 canvas)。 */
32
+ export function decodePng(buf) {
33
+ const b = Buffer.from(buf);
34
+ if (b.length < 8 || b.readUInt32BE(0) !== 0x89504e47)
35
+ return null;
36
+ let off = 8;
37
+ let width = 0;
38
+ let height = 0;
39
+ let depth = 8;
40
+ let type = 0;
41
+ let interlace = 0;
42
+ let palette = null;
43
+ let trns = null;
44
+ const idat = [];
45
+ while (off + 8 <= b.length) {
46
+ const len = b.readUInt32BE(off);
47
+ const kind = b.toString('latin1', off + 4, off + 8);
48
+ const data = b.subarray(off + 8, off + 8 + len);
49
+ off += 12 + len;
50
+ if (kind === 'IHDR') {
51
+ width = data.readUInt32BE(0);
52
+ height = data.readUInt32BE(4);
53
+ depth = data[8];
54
+ type = data[9];
55
+ interlace = data[12];
56
+ }
57
+ else if (kind === 'PLTE')
58
+ palette = data;
59
+ else if (kind === 'tRNS')
60
+ trns = data;
61
+ else if (kind === 'IDAT')
62
+ idat.push(data);
63
+ else if (kind === 'IEND')
64
+ break;
65
+ }
66
+ if (!width || interlace)
67
+ return null;
68
+ const channels = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }[type];
69
+ if (!channels)
70
+ return null;
71
+ const raw = inflateSync(Buffer.concat(idat));
72
+ const bitsPerPixel = channels * depth;
73
+ const bpp = Math.max(1, bitsPerPixel >> 3);
74
+ const stride = Math.ceil((width * bitsPerPixel) / 8);
75
+ const lines = new Uint8Array(stride * height);
76
+ let prev = new Uint8Array(stride);
77
+ let p = 0;
78
+ for (let y = 0; y < height; y++) {
79
+ const filter = raw[p++];
80
+ const line = lines.subarray(y * stride, (y + 1) * stride);
81
+ for (let i = 0; i < stride; i++) {
82
+ const x = raw[p++];
83
+ const a = i >= bpp ? line[i - bpp] : 0;
84
+ const up = prev[i];
85
+ const c = i >= bpp ? prev[i - bpp] : 0;
86
+ line[i] = (filter === 0 ? x : filter === 1 ? x + a : filter === 2 ? x + up : filter === 3 ? x + ((a + up) >> 1) : x + paeth(a, up, c)) & 0xff;
87
+ }
88
+ prev = line;
89
+ }
90
+ const rgba = new Uint8Array(width * height * 4);
91
+ const sample = (line, idx) => {
92
+ if (depth === 8)
93
+ return line[idx];
94
+ if (depth === 16)
95
+ return line[idx * 2];
96
+ const perByte = 8 / depth;
97
+ const byte = line[Math.floor(idx / perByte)];
98
+ const shift = 8 - depth * ((idx % perByte) + 1);
99
+ return (byte >> shift) & ((1 << depth) - 1);
100
+ };
101
+ const scale = depth < 8 && type !== 3 ? 255 / ((1 << depth) - 1) : 1;
102
+ for (let y = 0; y < height; y++) {
103
+ const line = lines.subarray(y * stride, (y + 1) * stride);
104
+ for (let x = 0; x < width; x++) {
105
+ const o = (y * width + x) * 4;
106
+ if (type === 3) {
107
+ const i = sample(line, x);
108
+ rgba[o] = palette?.[i * 3] ?? 0;
109
+ rgba[o + 1] = palette?.[i * 3 + 1] ?? 0;
110
+ rgba[o + 2] = palette?.[i * 3 + 2] ?? 0;
111
+ rgba[o + 3] = trns && i < trns.length ? trns[i] : 255;
112
+ }
113
+ else if (type === 0 || type === 4) {
114
+ const g = Math.round(sample(line, x * channels) * scale);
115
+ rgba[o] = rgba[o + 1] = rgba[o + 2] = g;
116
+ rgba[o + 3] = type === 4 ? sample(line, x * channels + 1) : 255;
117
+ }
118
+ else {
119
+ rgba[o] = sample(line, x * channels);
120
+ rgba[o + 1] = sample(line, x * channels + 1);
121
+ rgba[o + 2] = sample(line, x * channels + 2);
122
+ rgba[o + 3] = type === 6 ? sample(line, x * channels + 3) : 255;
123
+ }
124
+ }
125
+ }
126
+ return { width, height, rgba, hasAlpha: type === 4 || type === 6 || (type === 3 && trns != null) };
127
+ }
128
+ /** WebP / JPEG 是否带 alpha(与 libwebp 的 WebPGetFeatures 一致);其他格式为 null(按像素判断)。 */
129
+ function formatAlpha(b) {
130
+ if (b.length >= 2 && b[0] === 0xff && b[1] === 0xd8)
131
+ return false;
132
+ if (b.length >= 16 && b.toString('latin1', 0, 4) === 'RIFF' && b.toString('latin1', 8, 12) === 'WEBP') {
133
+ const kind = b.toString('latin1', 12, 16);
134
+ if (kind === 'VP8X' && b.length > 20)
135
+ return (b[20] & 0x10) !== 0;
136
+ if (kind === 'VP8L' && b.length >= 25)
137
+ return ((b.readUInt32LE(21) >>> 28) & 1) === 1;
138
+ return false;
139
+ }
140
+ return null;
141
+ }
142
+ /**
143
+ * PNG 以外(JPEG、WebP 等)交给 @napi-rs/canvas 解码。解码是异步的:要等 loadImage 完成再画,
144
+ * 同步设 `Image.src` 后立刻 drawImage 画出来是全黑的。
145
+ */
146
+ async function decodeCanvas(buf) {
147
+ const { loadImage, createCanvas } = require('@napi-rs/canvas');
148
+ const b = Buffer.from(buf);
149
+ const im = await loadImage(b);
150
+ const c = createCanvas(im.width, im.height);
151
+ const ctx = c.getContext('2d');
152
+ ctx.drawImage(im, 0, 0);
153
+ const d = ctx.getImageData(0, 0, im.width, im.height);
154
+ const rgba = new Uint8Array(d.data.buffer, d.data.byteOffset, d.data.length);
155
+ let hasAlpha = formatAlpha(b);
156
+ if (hasAlpha == null) {
157
+ hasAlpha = false;
158
+ for (let i = 3; i < rgba.length; i += 4) {
159
+ if (rgba[i] !== 255) {
160
+ hasAlpha = true;
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ return { width: im.width, height: im.height, rgba, hasAlpha };
166
+ }
167
+ /** 任意格式 → RGBA8;解码不了(或宽高为 0)时为 null。 */
168
+ export async function decodeImage(buf) {
169
+ try {
170
+ const im = decodePng(buf) ?? (await decodeCanvas(buf));
171
+ return im.width && im.height ? im : null;
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ }
177
+ /**
178
+ * cv2.imdecode:color 模式输出 BGR;unchanged 模式在有 alpha 时输出 BGRA、否则 BGR。
179
+ */
180
+ export async function imdecode(buf, mode = 'color') {
181
+ const png = await decodeImage(buf);
182
+ if (!png)
183
+ return null;
184
+ const alpha = mode === 'unchanged' && png.hasAlpha;
185
+ const ch = alpha ? 4 : 3;
186
+ const out = new Uint8Array(png.width * png.height * ch);
187
+ for (let i = 0, j = 0; i < png.rgba.length; i += 4, j += ch) {
188
+ out[j] = png.rgba[i + 2];
189
+ out[j + 1] = png.rgba[i + 1];
190
+ out[j + 2] = png.rgba[i];
191
+ if (alpha)
192
+ out[j + 3] = png.rgba[i + 3];
193
+ }
194
+ return img(png.width, png.height, ch, out);
195
+ }
196
+ /**
197
+ * 只读文件头取图片宽高:PNG / JPEG / GIF / WebP / BMP(上游用 opencv / Pillow 解码后取尺寸)。读不出来时为 null。
198
+ */
199
+ export function imageSize(data) {
200
+ const d = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
201
+ try {
202
+ if (d.length >= 24 && d.readUInt32BE(0) === 0x89504e47)
203
+ return { width: d.readUInt32BE(16), height: d.readUInt32BE(20) };
204
+ if (d.length >= 10 && d.toString('latin1', 0, 3) === 'GIF')
205
+ return { width: d.readUInt16LE(6), height: d.readUInt16LE(8) };
206
+ if (d.length >= 26 && d.toString('latin1', 0, 2) === 'BM')
207
+ return { width: d.readInt32LE(18), height: Math.abs(d.readInt32LE(22)) };
208
+ if (d.length >= 30 && d.toString('latin1', 0, 4) === 'RIFF' && d.toString('latin1', 8, 12) === 'WEBP') {
209
+ const kind = d.toString('latin1', 12, 16);
210
+ if (kind === 'VP8X')
211
+ return { width: d.readUIntLE(24, 3) + 1, height: d.readUIntLE(27, 3) + 1 };
212
+ if (kind === 'VP8L') {
213
+ const bits = d.readUInt32LE(21);
214
+ return { width: (bits & 0x3fff) + 1, height: ((bits >> 14) & 0x3fff) + 1 };
215
+ }
216
+ return { width: d.readUInt16LE(26) & 0x3fff, height: d.readUInt16LE(28) & 0x3fff };
217
+ }
218
+ if (d.length >= 4 && d[0] === 0xff && d[1] === 0xd8) {
219
+ let o = 2;
220
+ while (o + 9 < d.length) {
221
+ // 段之间可能有填充字节 0xff
222
+ if (d[o] !== 0xff) {
223
+ o++;
224
+ continue;
225
+ }
226
+ const marker = d[o + 1];
227
+ if (marker === 0xff) {
228
+ o++;
229
+ continue;
230
+ }
231
+ const len = d.readUInt16BE(o + 2);
232
+ if (marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker))
233
+ return { width: d.readUInt16BE(o + 7), height: d.readUInt16BE(o + 5) };
234
+ o += 2 + len;
235
+ }
236
+ }
237
+ }
238
+ catch { }
239
+ return null;
240
+ }
241
+ // ---------------------------------------------------------------- 与 cv.Mat 互转
242
+ export function toMat(cv, im) {
243
+ const depth = im.data instanceof Float32Array ? cv.CV_32F : cv.CV_8U;
244
+ const type = depth === cv.CV_32F ? [cv.CV_32FC1, cv.CV_32FC2, cv.CV_32FC3, cv.CV_32FC4][im.channels - 1] : [cv.CV_8UC1, cv.CV_8UC2, cv.CV_8UC3, cv.CV_8UC4][im.channels - 1];
245
+ const mat = new cv.Mat(im.height, im.width, type);
246
+ (depth === cv.CV_32F ? mat.data32F : mat.data).set(im.data);
247
+ return mat;
248
+ }
249
+ export function fromMat(cv, mat) {
250
+ const depth = mat.depth();
251
+ const channels = mat.channels();
252
+ const data = depth === cv.CV_32F ? new Float32Array(mat.data32F) : depth === cv.CV_8U ? new Uint8Array(mat.data) : depth === cv.CV_32S ? Float32Array.from(mat.data32S) : new Float32Array(mat.data32F);
253
+ return img(mat.cols, mat.rows, channels, data);
254
+ }
255
+ /** 在一组 Mat 上跑 fn,结束后统一释放。 */
256
+ export function withMats(fn) {
257
+ const mats = [];
258
+ try {
259
+ return fn((m) => (mats.push(m), m));
260
+ }
261
+ finally {
262
+ for (const m of mats)
263
+ m.delete();
264
+ }
265
+ }
266
+ /** 单通道视图。 */
267
+ export function channel(im, c) {
268
+ const out = im.data instanceof Float32Array ? new Float32Array(im.width * im.height) : new Uint8Array(im.width * im.height);
269
+ for (let i = 0; i < out.length; i++)
270
+ out[i] = im.data[i * im.channels + c];
271
+ return img(im.width, im.height, 1, out);
272
+ }
273
+ export function crop(im, x, y, w, h) {
274
+ const out = im.data instanceof Float32Array ? new Float32Array(w * h * im.channels) : new Uint8Array(w * h * im.channels);
275
+ for (let r = 0; r < h; r++) {
276
+ const s = ((y + r) * im.width + x) * im.channels;
277
+ out.set(im.data.subarray(s, s + w * im.channels), r * w * im.channels);
278
+ }
279
+ return img(w, h, im.channels, out);
280
+ }
@@ -0,0 +1,40 @@
1
+ const SENSITIVE = /cookie|token|authorization|password|secret|ticket|sessdata|ct0/i;
2
+ const URL_USERINFO = /\/\/[^/\s:@]+:[^/\s@]+@/g;
3
+ const COOKIE_HEADER = /\b((?:set-)?cookie)(["']?\s*:\s*)[^\n]*/gi;
4
+ const SENSITIVE_PAIR = /\b(authorization|[\w-]*token|password|secret|ticket|sessdata|ct0)(["']?\s*[:=]\s*)((?:bearer|basic)\s+)?("[^"]*"|'[^']*'|[^\s,;&]+)/gi;
5
+ /**
6
+ * 给凭证打码:对象里键名敏感的值换成 ***;字符串里的 Cookie 头整行打码,key=value / key: value 只打码值,
7
+ * URL 里的用户名密码(代理地址)也打码。
8
+ */
9
+ export function redact(value) {
10
+ if (typeof value === 'string') {
11
+ return value.replace(URL_USERINFO, '//***@').replace(COOKIE_HEADER, '$1$2***').replace(SENSITIVE_PAIR, '$1$2$3***');
12
+ }
13
+ if (Array.isArray(value))
14
+ return value.map(redact);
15
+ if (value && typeof value === 'object') {
16
+ const out = {};
17
+ for (const [k, v] of Object.entries(value))
18
+ out[k] = SENSITIVE.test(k) && v != null ? '***' : redact(v);
19
+ return out;
20
+ }
21
+ return value;
22
+ }
23
+ export function createLogger(options = {}) {
24
+ const write = (line) => process.stderr.write(line + '\n');
25
+ return {
26
+ debug(message, data) {
27
+ if (!options.verbose)
28
+ return;
29
+ const suffix = data === undefined ? '' : ' ' + JSON.stringify(redact(data));
30
+ write(`[specific-ai:debug] ${redact(message)}${suffix}`);
31
+ },
32
+ info(message) {
33
+ if (!options.quiet)
34
+ write(`[specific-ai] ${message}`);
35
+ },
36
+ warn(message) {
37
+ write(`[specific-ai] 警告:${message}`);
38
+ },
39
+ };
40
+ }