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.
- package/LICENSE +21 -0
- package/README.en.md +177 -0
- package/README.ja.md +177 -0
- package/README.ko.md +177 -0
- package/README.md +174 -2
- package/README.zh-TW.md +177 -0
- package/assets/banner.txt +3 -0
- package/dist/cli/argv.js +98 -0
- package/dist/cli/commands/auth.js +11 -0
- package/dist/cli/commands/config.js +23 -0
- package/dist/cli/commands/doctor.js +134 -0
- package/dist/cli/commands/platforms.js +24 -0
- package/dist/cli/commands/version.js +4 -0
- package/dist/cli/confirm.js +22 -0
- package/dist/cli/dispatch.js +440 -0
- package/dist/cli/help.js +223 -0
- package/dist/cli/hints.js +60 -0
- package/dist/cli/main.js +18 -0
- package/dist/cli/output.js +66 -0
- package/dist/core/auth-handlers.js +41 -0
- package/dist/core/auth-store.js +119 -0
- package/dist/core/capabilities.js +163 -0
- package/dist/core/config.js +121 -0
- package/dist/core/cookies.js +164 -0
- package/dist/core/errors.js +37 -0
- package/dist/core/files.js +89 -0
- package/dist/core/fsutil.js +52 -0
- package/dist/core/hash.js +4 -0
- package/dist/core/http.js +331 -0
- package/dist/core/image.js +280 -0
- package/dist/core/log.js +40 -0
- package/dist/core/login.js +191 -0
- package/dist/core/mp4.js +130 -0
- package/dist/core/normalize.js +207 -0
- package/dist/core/options.js +110 -0
- package/dist/core/paths.js +38 -0
- package/dist/core/pb.js +59 -0
- package/dist/core/py.js +181 -0
- package/dist/core/rand.js +131 -0
- package/dist/core/registry.js +129 -0
- package/dist/core/schemas.js +41 -0
- package/dist/core/stream.js +104 -0
- package/dist/core/toolkit.js +35 -0
- package/dist/core/vm.js +73 -0
- package/dist/core/vocab.js +157 -0
- package/dist/platforms/_shared/impaas.js +410 -0
- package/dist/platforms/bilibili/index.js +216 -0
- package/dist/platforms/bilibili/web/api.js +806 -0
- package/dist/platforms/bilibili/web/client.js +311 -0
- package/dist/platforms/bilibili/web/commands.js +783 -0
- package/dist/platforms/bilibili/web/ddddocr.js +342 -0
- package/dist/platforms/bilibili/web/gaia.js +130 -0
- package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
- package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
- package/dist/platforms/bilibili/web/geetest.js +450 -0
- package/dist/platforms/bilibili/web/normalize.js +234 -0
- package/dist/platforms/bilibili/web/profile.js +135 -0
- package/dist/platforms/bilibili/web/resolve.js +112 -0
- package/dist/platforms/bilibili/web/sign.js +223 -0
- package/dist/platforms/bilibili/web/upos.js +102 -0
- package/dist/platforms/douyin/index.js +142 -0
- package/dist/platforms/douyin/web/api.js +863 -0
- package/dist/platforms/douyin/web/client.js +413 -0
- package/dist/platforms/douyin/web/commands.js +712 -0
- package/dist/platforms/douyin/web/creator.js +601 -0
- package/dist/platforms/douyin/web/crypto.js +247 -0
- package/dist/platforms/douyin/web/dtrait.js +190 -0
- package/dist/platforms/douyin/web/im.js +502 -0
- package/dist/platforms/douyin/web/image.js +5 -0
- package/dist/platforms/douyin/web/live.js +225 -0
- package/dist/platforms/douyin/web/mssdk.js +148 -0
- package/dist/platforms/douyin/web/normalize.js +326 -0
- package/dist/platforms/douyin/web/passport.js +1247 -0
- package/dist/platforms/douyin/web/profile.js +221 -0
- package/dist/platforms/douyin/web/proto.js +26 -0
- package/dist/platforms/douyin/web/resolve.js +88 -0
- package/dist/platforms/douyin/web/sign.js +436 -0
- package/dist/platforms/douyin/web/tos.js +78 -0
- package/dist/platforms/index.js +17 -0
- package/dist/platforms/jd/index.js +67 -0
- package/dist/platforms/jd/web/aks.js +115 -0
- package/dist/platforms/jd/web/api.js +549 -0
- package/dist/platforms/jd/web/chat.js +132 -0
- package/dist/platforms/jd/web/client.js +470 -0
- package/dist/platforms/jd/web/commands.js +557 -0
- package/dist/platforms/jd/web/jcap/helper.js +90 -0
- package/dist/platforms/jd/web/jcap/host.js +107 -0
- package/dist/platforms/jd/web/jcap/lsd.js +300 -0
- package/dist/platforms/jd/web/jcap/solver.js +995 -0
- package/dist/platforms/jd/web/jcap.js +150 -0
- package/dist/platforms/jd/web/js.js +420 -0
- package/dist/platforms/jd/web/login.js +775 -0
- package/dist/platforms/jd/web/normalize.js +297 -0
- package/dist/platforms/jd/web/profile.js +256 -0
- package/dist/platforms/jd/web/util.js +175 -0
- package/dist/platforms/jd/web/webm.js +133 -0
- package/dist/platforms/kuaishou/index.js +98 -0
- package/dist/platforms/kuaishou/web/api.js +694 -0
- package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
- package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
- package/dist/platforms/kuaishou/web/captcha.js +273 -0
- package/dist/platforms/kuaishou/web/client.js +487 -0
- package/dist/platforms/kuaishou/web/commands.js +863 -0
- package/dist/platforms/kuaishou/web/gap.js +144 -0
- package/dist/platforms/kuaishou/web/gdfp.js +376 -0
- package/dist/platforms/kuaishou/web/gql.js +11 -0
- package/dist/platforms/kuaishou/web/live.js +125 -0
- package/dist/platforms/kuaishou/web/normalize.js +241 -0
- package/dist/platforms/kuaishou/web/oracle.js +329 -0
- package/dist/platforms/kuaishou/web/profile.js +259 -0
- package/dist/platforms/kuaishou/web/resolve.js +84 -0
- package/dist/platforms/kuaishou/web/session.js +592 -0
- package/dist/platforms/kuaishou/web/sign.js +568 -0
- package/dist/platforms/taobao/index.js +56 -0
- package/dist/platforms/taobao/web/api.js +96 -0
- package/dist/platforms/taobao/web/client.js +102 -0
- package/dist/platforms/taobao/web/commands.js +172 -0
- package/dist/platforms/taobao/web/im.js +68 -0
- package/dist/platforms/taobao/web/normalize.js +103 -0
- package/dist/platforms/taobao/web/profile.js +85 -0
- package/dist/platforms/taobao/web/resolve.js +51 -0
- package/dist/platforms/taobao/web/sign.js +35 -0
- package/dist/platforms/tiktok/index.js +120 -0
- package/dist/platforms/tiktok/web/api.js +1234 -0
- package/dist/platforms/tiktok/web/client.js +362 -0
- package/dist/platforms/tiktok/web/commands.js +821 -0
- package/dist/platforms/tiktok/web/im.js +133 -0
- package/dist/platforms/tiktok/web/jsrun.js +126 -0
- package/dist/platforms/tiktok/web/normalize.js +286 -0
- package/dist/platforms/tiktok/web/profile.js +169 -0
- package/dist/platforms/tiktok/web/resolve.js +156 -0
- package/dist/platforms/tiktok/web/sign.js +585 -0
- package/dist/platforms/tiktok/web/upload.js +805 -0
- package/dist/platforms/tiktok/web/wire.js +490 -0
- package/dist/platforms/weibo/index.js +91 -0
- package/dist/platforms/weibo/web/api.js +206 -0
- package/dist/platforms/weibo/web/client.js +155 -0
- package/dist/platforms/weibo/web/commands.js +202 -0
- package/dist/platforms/weibo/web/normalize.js +137 -0
- package/dist/platforms/weibo/web/profile.js +246 -0
- package/dist/platforms/weibo/web/resolve.js +60 -0
- package/dist/platforms/weibo/web/sign.js +85 -0
- package/dist/platforms/x/index.js +107 -0
- package/dist/platforms/x/web/api.js +443 -0
- package/dist/platforms/x/web/article.js +212 -0
- package/dist/platforms/x/web/client.js +202 -0
- package/dist/platforms/x/web/commands.js +363 -0
- package/dist/platforms/x/web/graphql.js +108 -0
- package/dist/platforms/x/web/normalize.js +235 -0
- package/dist/platforms/x/web/profile.js +99 -0
- package/dist/platforms/x/web/resolve.js +44 -0
- package/dist/platforms/x/web/transaction.js +171 -0
- package/dist/platforms/xhs/index.js +101 -0
- package/dist/platforms/xhs/web/api.js +278 -0
- package/dist/platforms/xhs/web/client.js +540 -0
- package/dist/platforms/xhs/web/commands.js +1046 -0
- package/dist/platforms/xhs/web/creator-api.js +609 -0
- package/dist/platforms/xhs/web/creator.js +203 -0
- package/dist/platforms/xhs/web/js.js +194 -0
- package/dist/platforms/xhs/web/login.js +230 -0
- package/dist/platforms/xhs/web/normalize.js +166 -0
- package/dist/platforms/xhs/web/profile.js +127 -0
- package/dist/platforms/xhs/web/push.js +361 -0
- package/dist/platforms/xhs/web/resolve.js +69 -0
- package/dist/platforms/xhs/web/state.js +368 -0
- package/dist/platforms/xianyu/index.js +59 -0
- package/dist/platforms/xianyu/web/api.js +312 -0
- package/dist/platforms/xianyu/web/client.js +134 -0
- package/dist/platforms/xianyu/web/commands.js +305 -0
- package/dist/platforms/xianyu/web/im.js +73 -0
- package/dist/platforms/xianyu/web/normalize.js +123 -0
- package/dist/platforms/xianyu/web/profile.js +146 -0
- package/dist/platforms/xianyu/web/resolve.js +48 -0
- package/dist/platforms/xianyu/web/sign.js +79 -0
- package/package.json +77 -4
- package/static/douyin/Live.proto +194 -0
- package/static/douyin/Request.proto +89 -0
- package/static/douyin/Response.proto +46 -0
- package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
- package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
- package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
- package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
- package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
- package/static/douyin/challenge_profile.json +801 -0
- package/static/douyin/challenge_template_runner.js +217 -0
- package/static/douyin/dtrait_profile.json +66 -0
- package/static/douyin/mstoken_common_profile.json +1 -0
- package/static/douyin/mstoken_profile.json +214 -0
- package/static/jd/h5st5_env.js +750 -0
- package/static/jd/h5st5_lib.js +5 -0
- package/static/jd/h5st5_server.js +49 -0
- package/static/jd/jcap/env/env_core.js +512 -0
- package/static/jd/jcap/env/run.js +1307 -0
- package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
- package/static/jd/package.json +4 -0
- package/static/jd/pc_tk_lib.js +1 -0
- package/static/jd/pc_tk_server.js +63 -0
- package/static/jd/summer_cryptico_runner.js +50 -0
- package/static/jd/webm/env/run.js +453 -0
- package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
- package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
- package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
- package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
- package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
- package/static/kuaishou/js/cp-kwf.js +1 -0
- package/static/kuaishou/tools/kwf_oracle.js +799 -0
- package/static/kuaishou/tools/kws_oracle.js +885 -0
- package/static/kuaishou/tools/like_token_oracle.js +70 -0
- package/static/taobao/taobao_js_20260407.js +570 -0
- package/static/tiktok/Tiktok_Request.json +597 -0
- package/static/tiktok/package.json +3 -0
- package/static/tiktok/shop_bsid/core.js +1 -0
- package/static/tiktok/shop_bsid/env_core.js +512 -0
- package/static/tiktok/shop_bsid/loader.js +1 -0
- package/static/tiktok/shop_bsid/run.js +753 -0
- package/static/tiktok/shop_bsid/sign.js +100 -0
- package/static/tiktok/shop_bsid/webmssdk.js +1 -0
- package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
- package/static/tiktok/signing/env/env_core.js +512 -0
- package/static/tiktok/signing/env/sign.js +455 -0
- package/static/tiktok/signing/sdk_config.json +230 -0
- package/static/tiktok/signing/source/webmssdk.js +1 -0
- package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
- package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
- package/static/x/graphql.json +7777 -0
- package/static/x/transaction_l1.json +12 -0
- package/static/xhs/package.json +3 -0
- package/static/xhs/xhs_core/js/b1.js +210 -0
- package/static/xhs/xhs_core/js/mns.js +340 -0
- package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
- package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
- package/static/xhs/xhs_core/js/sign.js +225 -0
- package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
- package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
- package/static/xhs/xhs_core/js/xs_common.js +79 -0
- package/static/xhs/xhs_creator/js/profile.js +115 -0
- package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
- package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
- package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
- package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
- package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
- package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
- package/static/xhs/xhs_pc/js/b1.js +9 -0
- package/static/xhs/xhs_pc/js/deflate.js +320 -0
- package/static/xhs/xhs_pc/js/mns.js +4 -0
- package/static/xhs/xhs_pc/js/profile.js +141 -0
- package/static/xhs/xhs_pc/js/rap.js +173 -0
- package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
- package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
- package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
- package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
- package/static/xhs/xhs_pc/js/sign.js +6 -0
- package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
- package/static/xhs/xhs_pc/js/xs_common.js +4 -0
- package/static/xianyu/et_f.js +2 -0
- package/static/xianyu/gen_tfstk.js +410 -0
- package/static/xianyu/goofish_js_version_2.js +568 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { gunzipSync } from 'node:zlib';
|
|
3
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
4
|
+
import { staticFile } from '../../../core/paths.js';
|
|
5
|
+
import { protobuf } from '../../../core/pb.js';
|
|
6
|
+
import { compactJson, jsonLoads } from '../../../core/py.js';
|
|
7
|
+
const enc = new TextEncoder();
|
|
8
|
+
const dec = new TextDecoder();
|
|
9
|
+
function big(value, name) {
|
|
10
|
+
let v;
|
|
11
|
+
try {
|
|
12
|
+
v = typeof value === 'bigint' ? value : BigInt(typeof value === 'number' ? Math.trunc(value) : String(value));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new SpecificAIError('ERROR', `${name} 必须是整数`);
|
|
16
|
+
}
|
|
17
|
+
if (v < 0n)
|
|
18
|
+
throw new SpecificAIError('ERROR', `${name} 不允许为负数`);
|
|
19
|
+
return v;
|
|
20
|
+
}
|
|
21
|
+
export function varint(value, name = 'value') {
|
|
22
|
+
let n = big(value, name);
|
|
23
|
+
const out = [];
|
|
24
|
+
while (n > 0x7fn) {
|
|
25
|
+
out.push(Number(n & 0x7fn) | 0x80);
|
|
26
|
+
n >>= 7n;
|
|
27
|
+
}
|
|
28
|
+
out.push(Number(n));
|
|
29
|
+
return Uint8Array.from(out);
|
|
30
|
+
}
|
|
31
|
+
export function concat(parts) {
|
|
32
|
+
return Uint8Array.from(Buffer.concat(parts));
|
|
33
|
+
}
|
|
34
|
+
export function fieldVarint(field, value, name = 'value') {
|
|
35
|
+
return concat([varint(field << 3, 'field tag'), varint(typeof value === 'boolean' ? Number(value) : value, name)]);
|
|
36
|
+
}
|
|
37
|
+
export function fieldBytes(field, value) {
|
|
38
|
+
return concat([varint((field << 3) | 2, 'field tag'), varint(value.length, 'length'), value]);
|
|
39
|
+
}
|
|
40
|
+
export function fieldString(field, value) {
|
|
41
|
+
return fieldBytes(field, enc.encode(String(value)));
|
|
42
|
+
}
|
|
43
|
+
export const fieldMessage = fieldBytes;
|
|
44
|
+
function fields(raw) {
|
|
45
|
+
const out = new Map();
|
|
46
|
+
let offset = 0;
|
|
47
|
+
const number = () => {
|
|
48
|
+
let value = 0n;
|
|
49
|
+
for (let shift = 0n; shift < 70n; shift += 7n) {
|
|
50
|
+
if (offset >= raw.length)
|
|
51
|
+
throw new SpecificAIError('UPSTREAM', 'protobuf varint 截断');
|
|
52
|
+
const byte = raw[offset++];
|
|
53
|
+
value |= BigInt(byte & 0x7f) << shift;
|
|
54
|
+
if (byte < 0x80)
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
throw new SpecificAIError('UPSTREAM', 'protobuf varint 过长');
|
|
58
|
+
};
|
|
59
|
+
while (offset < raw.length) {
|
|
60
|
+
const tag = number();
|
|
61
|
+
const field = Number(tag >> 3n);
|
|
62
|
+
const wire = Number(tag & 7n);
|
|
63
|
+
if (!field)
|
|
64
|
+
throw new SpecificAIError('UPSTREAM', 'protobuf field 0 无效');
|
|
65
|
+
let value;
|
|
66
|
+
if (wire === 0)
|
|
67
|
+
value = number();
|
|
68
|
+
else if (wire === 2 || wire === 1 || wire === 5) {
|
|
69
|
+
const length = wire === 2 ? Number(number()) : wire === 1 ? 8 : 4;
|
|
70
|
+
if (offset + length > raw.length)
|
|
71
|
+
throw new SpecificAIError('UPSTREAM', 'protobuf bytes 截断');
|
|
72
|
+
value = raw.subarray(offset, offset + length);
|
|
73
|
+
offset += length;
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
throw new SpecificAIError('UPSTREAM', `不支持的 protobuf wire type ${wire}`);
|
|
77
|
+
const list = out.get(field);
|
|
78
|
+
if (list)
|
|
79
|
+
list.push(value);
|
|
80
|
+
else
|
|
81
|
+
out.set(field, [value]);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
const first = (t, k) => t.get(k)?.[0];
|
|
86
|
+
const str = (t, k) => {
|
|
87
|
+
const v = first(t, k);
|
|
88
|
+
return v instanceof Uint8Array ? dec.decode(v) : '';
|
|
89
|
+
};
|
|
90
|
+
const int = (t, k) => {
|
|
91
|
+
const v = first(t, k);
|
|
92
|
+
return typeof v === 'bigint' ? v : 0n;
|
|
93
|
+
};
|
|
94
|
+
const nested = (t, k) => {
|
|
95
|
+
const v = first(t, k);
|
|
96
|
+
return v instanceof Uint8Array ? fields(v) : new Map();
|
|
97
|
+
};
|
|
98
|
+
function liveUser(t) {
|
|
99
|
+
return { id: int(t, 1).toString(), nickname: str(t, 3), display_id: str(t, 38), sec_uid: str(t, 46) };
|
|
100
|
+
}
|
|
101
|
+
/** 一次 HTTP im/fetch 或解压后的 WS LiveResponse。 */
|
|
102
|
+
export function decodeLiveResponse(raw) {
|
|
103
|
+
const tree = fields(raw);
|
|
104
|
+
const events = [];
|
|
105
|
+
for (const item of tree.get(1) ?? []) {
|
|
106
|
+
if (!(item instanceof Uint8Array))
|
|
107
|
+
throw new SpecificAIError('UPSTREAM', 'LiveResponse.messagesList 不是 message');
|
|
108
|
+
const envelope = fields(item);
|
|
109
|
+
const method = str(envelope, 1);
|
|
110
|
+
const payload = first(envelope, 2) ?? new Uint8Array();
|
|
111
|
+
if (!(payload instanceof Uint8Array))
|
|
112
|
+
throw new SpecificAIError('UPSTREAM', '直播事件 payload 不是 bytes');
|
|
113
|
+
const event = { method, message_id: int(envelope, 3).toString(), type: 'other' };
|
|
114
|
+
const body = fields(payload);
|
|
115
|
+
if (method === 'WebcastChatMessage')
|
|
116
|
+
Object.assign(event, { type: 'chat', user: liveUser(nested(body, 2)), text: str(body, 3) });
|
|
117
|
+
else if (method === 'WebcastLikeMessage') {
|
|
118
|
+
Object.assign(event, { type: 'like', count: Number(int(body, 2)), total: Number(int(body, 3)), user: liveUser(nested(body, 5)) });
|
|
119
|
+
}
|
|
120
|
+
else if (method === 'WebcastGiftMessage') {
|
|
121
|
+
const gift = nested(body, 15);
|
|
122
|
+
Object.assign(event, {
|
|
123
|
+
type: 'gift',
|
|
124
|
+
gift_id: int(body, 2).toString(),
|
|
125
|
+
combo_count: Number(int(body, 6)),
|
|
126
|
+
user: liveUser(nested(body, 7)),
|
|
127
|
+
gift: { id: int(gift, 5).toString(), name: str(gift, 16) },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
event.payload_length = payload.length;
|
|
132
|
+
events.push(event);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
events,
|
|
136
|
+
cursor: str(tree, 2),
|
|
137
|
+
internal_ext: str(tree, 5),
|
|
138
|
+
fetch_interval: Number(int(tree, 3)),
|
|
139
|
+
heartbeat_duration: Number(int(tree, 8)),
|
|
140
|
+
need_ack: int(tree, 9) !== 0n,
|
|
141
|
+
raw_length: raw.length,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** WS 的 PushFrame,payload 可能是 gzip 过的 LiveResponse。 */
|
|
145
|
+
export function decodePushFrame(raw) {
|
|
146
|
+
const tree = fields(raw);
|
|
147
|
+
let payload = first(tree, 8) ?? new Uint8Array();
|
|
148
|
+
if (!(payload instanceof Uint8Array))
|
|
149
|
+
throw new SpecificAIError('UPSTREAM', 'PushFrame.payload 不是 bytes');
|
|
150
|
+
const headers = {};
|
|
151
|
+
for (const item of tree.get(5) ?? []) {
|
|
152
|
+
if (item instanceof Uint8Array) {
|
|
153
|
+
const pair = fields(item);
|
|
154
|
+
headers[str(pair, 1)] = str(pair, 2);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (headers.compress_type === 'gzip' || (payload[0] === 0x1f && payload[1] === 0x8b))
|
|
158
|
+
payload = new Uint8Array(gunzipSync(payload));
|
|
159
|
+
const kind = str(tree, 7);
|
|
160
|
+
return {
|
|
161
|
+
seq_id: int(tree, 1).toString(),
|
|
162
|
+
log_id: int(tree, 2),
|
|
163
|
+
payload_type: kind,
|
|
164
|
+
headers,
|
|
165
|
+
response: kind === 'msg' || kind === 'im_enter_room_resp' ? decodeLiveResponse(payload) : null,
|
|
166
|
+
payload_length: payload.length,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function encodeFrame(kind, payload, logId) {
|
|
170
|
+
return concat([logId != null ? fieldVarint(2, logId) : new Uint8Array(), fieldString(6, 'pb'), fieldString(7, kind), fieldBytes(8, payload)]);
|
|
171
|
+
}
|
|
172
|
+
export function encodeHeartbeat(roomId) {
|
|
173
|
+
return encodeFrame('hb', fieldVarint(1, roomId));
|
|
174
|
+
}
|
|
175
|
+
export function encodeEnterRoom(roomId, liveId, cursor) {
|
|
176
|
+
const payload = concat([fieldVarint(1, roomId), fieldVarint(4, liveId), fieldString(5, 'audience'), fieldString(6, cursor), fieldVarint(7, 0), fieldString(9, '0'), fieldVarint(10, 0)]);
|
|
177
|
+
return encodeFrame('im_enter_room', payload);
|
|
178
|
+
}
|
|
179
|
+
// ================================================================ 私信请求(api/tiktok_web.py 的 _im_* 方法)
|
|
180
|
+
export const IM_SDK_VERSION = '1.7.4';
|
|
181
|
+
export const IM_BUILD_NUMBER = 'cb1a69c:feat/im-core-sdk-ooo-push-v2';
|
|
182
|
+
/** Request 外层:body 是 field 8,里面再按命令号嵌一层。 */
|
|
183
|
+
export function imOuterRequest(command, bodyPayload, e) {
|
|
184
|
+
const parts = [
|
|
185
|
+
fieldVarint(1, command),
|
|
186
|
+
fieldVarint(2, e.sequenceId, 'sequence_id'),
|
|
187
|
+
fieldString(3, e.sdkVersion ?? IM_SDK_VERSION),
|
|
188
|
+
fieldString(4, e.token ?? ''),
|
|
189
|
+
fieldVarint(5, e.refer ?? 3),
|
|
190
|
+
fieldVarint(6, e.inboxType ?? 0),
|
|
191
|
+
fieldString(7, e.buildNumber ?? IM_BUILD_NUMBER),
|
|
192
|
+
fieldMessage(8, fieldMessage(command, bodyPayload)),
|
|
193
|
+
fieldString(9, e.deviceId),
|
|
194
|
+
];
|
|
195
|
+
if (e.channel != null)
|
|
196
|
+
parts.push(fieldString(10, e.channel));
|
|
197
|
+
parts.push(fieldString(11, e.devicePlatform ?? 'web'));
|
|
198
|
+
for (const [n, v] of [
|
|
199
|
+
[12, e.deviceType],
|
|
200
|
+
[13, e.osVersion],
|
|
201
|
+
[14, e.versionCode],
|
|
202
|
+
]) {
|
|
203
|
+
if (v != null)
|
|
204
|
+
parts.push(fieldString(n, v));
|
|
205
|
+
}
|
|
206
|
+
for (const [k, v] of e.headers)
|
|
207
|
+
parts.push(fieldMessage(15, concat([fieldString(1, k), fieldString(2, v)])));
|
|
208
|
+
if (e.configId != null)
|
|
209
|
+
parts.push(fieldVarint(16, e.configId, 'config_id'));
|
|
210
|
+
if (e.authType != null)
|
|
211
|
+
parts.push(fieldVarint(18, e.authType, 'auth_type'));
|
|
212
|
+
return concat(parts);
|
|
213
|
+
}
|
|
214
|
+
/** cmd 204:get_by_user_combo(上游 get_im_messages_per_user_combo)。specific-ai 的命令没有用到(msg list 走 203),保留供对拍。 */
|
|
215
|
+
export function imUserComboRequest(inboxes, e, o = {}) {
|
|
216
|
+
if (!inboxes.length)
|
|
217
|
+
throw new SpecificAIError('ERROR', '私信 get_by_user_combo 至少需要一个 inbox');
|
|
218
|
+
const parts = inboxes.map((b) => fieldMessage(1, concat([
|
|
219
|
+
fieldVarint(1, b.inbox_type),
|
|
220
|
+
fieldVarint(2, b.cursor),
|
|
221
|
+
fieldVarint(3, b.limit),
|
|
222
|
+
fieldVarint(4, b.scene),
|
|
223
|
+
...(b.cursor_type != null ? [fieldVarint(5, b.cursor_type)] : []),
|
|
224
|
+
])));
|
|
225
|
+
if (o.statusAdapterMap != null)
|
|
226
|
+
parts.push(fieldVarint(2, o.statusAdapterMap));
|
|
227
|
+
if (o.lastPullTime != null)
|
|
228
|
+
parts.push(fieldVarint(3, o.lastPullTime));
|
|
229
|
+
return imOuterRequest(204, concat(parts), e);
|
|
230
|
+
}
|
|
231
|
+
/** cmd 203:get_by_user_init(私信页初次拉取)。 */
|
|
232
|
+
export function imUserInitRequest(cursor, e, o = {}) {
|
|
233
|
+
const parts = [fieldVarint(1, cursor, 'cursor')];
|
|
234
|
+
if (o.newUser != null)
|
|
235
|
+
parts.push(fieldVarint(2, o.newUser));
|
|
236
|
+
if (o.initSubType != null)
|
|
237
|
+
parts.push(fieldVarint(3, o.initSubType));
|
|
238
|
+
if (o.withEmptyConv != null)
|
|
239
|
+
parts.push(fieldVarint(4, o.withEmptyConv));
|
|
240
|
+
for (const k of o.siderankKeys ?? [])
|
|
241
|
+
parts.push(fieldString(5, k));
|
|
242
|
+
return imOuterRequest(203, concat(parts), e);
|
|
243
|
+
}
|
|
244
|
+
/** cmd 301:get_by_conversation。 */
|
|
245
|
+
export function imConversationRequest(c, e) {
|
|
246
|
+
if (!c.conversationId)
|
|
247
|
+
throw new SpecificAIError('ERROR', '私信 conversation_id 必须是非空字符串');
|
|
248
|
+
const parts = [
|
|
249
|
+
fieldString(1, c.conversationId),
|
|
250
|
+
fieldVarint(2, c.type),
|
|
251
|
+
fieldVarint(3, c.shortId),
|
|
252
|
+
fieldVarint(4, c.direction),
|
|
253
|
+
fieldVarint(5, c.anchorIndex),
|
|
254
|
+
fieldVarint(6, c.limit),
|
|
255
|
+
];
|
|
256
|
+
for (const [k, v] of c.ext ?? [])
|
|
257
|
+
parts.push(fieldMessage(7, concat([fieldString(1, k), fieldString(2, v)])));
|
|
258
|
+
return imOuterRequest(301, concat(parts), e);
|
|
259
|
+
}
|
|
260
|
+
/** cmd 100 的 body:文本消息(content 里的 aweType 是浏览器原样的拼写)。 */
|
|
261
|
+
export function imSendBody(c) {
|
|
262
|
+
if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(c.clientMessageId)) {
|
|
263
|
+
throw new SpecificAIError('ERROR', '私信发送 client_message_id 必须是完整 UUID');
|
|
264
|
+
}
|
|
265
|
+
const content = compactJson({ aweType: 0, text: c.text });
|
|
266
|
+
return concat([
|
|
267
|
+
fieldString(1, c.conversationId),
|
|
268
|
+
fieldVarint(2, c.type),
|
|
269
|
+
fieldVarint(3, c.shortId),
|
|
270
|
+
fieldString(4, content),
|
|
271
|
+
fieldMessage(5, concat([fieldString(1, 's:mentioned_users'), fieldString(2, '')])),
|
|
272
|
+
fieldMessage(5, concat([fieldString(1, 's:client_message_id'), fieldString(2, c.clientMessageId)])),
|
|
273
|
+
fieldVarint(6, c.messageType ?? 7),
|
|
274
|
+
fieldString(7, 'deprecated'),
|
|
275
|
+
fieldString(8, c.clientMessageId),
|
|
276
|
+
]);
|
|
277
|
+
}
|
|
278
|
+
/** WebSocket.send 的完整二进制 Frame。 */
|
|
279
|
+
export function imSendFrame(o) {
|
|
280
|
+
if (o.xBogus.length !== 16)
|
|
281
|
+
throw new SpecificAIError('ERROR', '私信 WebSocket 缺少 16 位 X-Bogus');
|
|
282
|
+
return concat([
|
|
283
|
+
fieldVarint(1, o.sequenceId),
|
|
284
|
+
fieldVarint(2, o.logId),
|
|
285
|
+
fieldVarint(3, 5),
|
|
286
|
+
fieldVarint(4, 1),
|
|
287
|
+
fieldMessage(5, concat([fieldString(1, 'X-Bogus'), fieldString(2, o.xBogus)])),
|
|
288
|
+
...o.frameHeaders.map(([k, v]) => fieldMessage(5, concat([fieldString(1, k), fieldString(2, v)]))),
|
|
289
|
+
fieldString(7, 'pb'),
|
|
290
|
+
fieldMessage(8, o.request),
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
// ================================================================ 私信回包(static/Tiktok_Request_pb2.py 的 descriptor)
|
|
294
|
+
let root = null;
|
|
295
|
+
function imType(name) {
|
|
296
|
+
root ??= protobuf.Root.fromJSON(JSON.parse(readFileSync(staticFile('tiktok', 'Tiktok_Request.json'), 'utf8')));
|
|
297
|
+
return root.lookupType(`im_proto.${name}`);
|
|
298
|
+
}
|
|
299
|
+
const TO_OBJECT = { longs: String, bytes: String, defaults: false };
|
|
300
|
+
/** Frame → Response(protobuf_to_dict 的替代:protobufjs toObject,64 位整数为字符串)。 */
|
|
301
|
+
export function decodeImFrame(raw) {
|
|
302
|
+
try {
|
|
303
|
+
const frame = imType('Frame').toObject(imType('Frame').decode(raw), TO_OBJECT);
|
|
304
|
+
const payload = Buffer.from(frame.payload ?? '', 'base64');
|
|
305
|
+
const response = imType('Response').toObject(imType('Response').decode(payload), TO_OBJECT);
|
|
306
|
+
return { frame, response };
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
throw new SpecificAIError('UPSTREAM', `私信 WebSocket 回包不是可解析 protobuf:${err.message}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** 私信命令 100 的回包(上游 _decode_im_send_response)。 */
|
|
313
|
+
export function decodeImSendResponse(raw) {
|
|
314
|
+
if (typeof raw === 'string')
|
|
315
|
+
return raw === 'hi' ? { heartbeat: true } : { text_frame: true };
|
|
316
|
+
const { frame, response } = decodeImFrame(raw);
|
|
317
|
+
const out = {
|
|
318
|
+
heartbeat: false,
|
|
319
|
+
frame_seqid: String(frame.seqid ?? '0'),
|
|
320
|
+
cmd: Number(response.cmd ?? 0),
|
|
321
|
+
sequence_id: String(response.sequence_id ?? '0'),
|
|
322
|
+
status_code: Number(response.status_code ?? 0),
|
|
323
|
+
error_desc: String(response.error_desc ?? ''),
|
|
324
|
+
};
|
|
325
|
+
const body = response.body?.send_message_body;
|
|
326
|
+
if (out.cmd === 100 && body) {
|
|
327
|
+
Object.assign(out, {
|
|
328
|
+
message_status: Number(body.status ?? 0),
|
|
329
|
+
server_message_id: String(body.server_message_id ?? '0'),
|
|
330
|
+
check_code: String(body.check_code ?? '0'),
|
|
331
|
+
is_async_send: Boolean(body.is_async_send),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
/** cmd 500 的新消息推送;只处理文本(message_type 7),其他返回 null(上游 decode_im_ws_notification)。 */
|
|
337
|
+
export function decodeImNotification(raw) {
|
|
338
|
+
if (typeof raw === 'string') {
|
|
339
|
+
if (raw === 'hi')
|
|
340
|
+
return null;
|
|
341
|
+
throw new SpecificAIError('UPSTREAM', '私信 WS 收到未知文本帧');
|
|
342
|
+
}
|
|
343
|
+
const { frame, response } = decodeImFrame(raw);
|
|
344
|
+
if (Number(response.cmd ?? 0) !== 500)
|
|
345
|
+
return null;
|
|
346
|
+
if (response.status_code)
|
|
347
|
+
throw new SpecificAIError('UPSTREAM', `私信 WS 推送状态异常:${response.status_code}`);
|
|
348
|
+
const notify = response.body?.has_new_message_notify;
|
|
349
|
+
if (!notify)
|
|
350
|
+
throw new SpecificAIError('UPSTREAM', '私信 command-500 缺少通知 body');
|
|
351
|
+
const m = notify.message;
|
|
352
|
+
if (!m || Number(m.message_type ?? 0) !== 7)
|
|
353
|
+
return null;
|
|
354
|
+
let content;
|
|
355
|
+
try {
|
|
356
|
+
content = jsonLoads(m.content);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
throw new SpecificAIError('UPSTREAM', '私信文本推送 content 缺少 text');
|
|
360
|
+
}
|
|
361
|
+
if (typeof content?.text !== 'string')
|
|
362
|
+
throw new SpecificAIError('UPSTREAM', '私信文本推送 content 缺少 text');
|
|
363
|
+
return {
|
|
364
|
+
conversation_id: String(m.conversation_id ?? ''),
|
|
365
|
+
conversation_short_id: String(m.conversation_short_id ?? '0'),
|
|
366
|
+
server_message_id: String(m.server_message_id ?? '0'),
|
|
367
|
+
message_type: Number(m.message_type),
|
|
368
|
+
sender: String(m.sender ?? '0'),
|
|
369
|
+
sec_sender: String(m.sec_sender ?? ''),
|
|
370
|
+
text: content.text,
|
|
371
|
+
content,
|
|
372
|
+
create_time: String(m.create_time ?? '0'),
|
|
373
|
+
frame_seqid: String(frame.seqid ?? '0'),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* 与 blackboxprotobuf 的推断一致:bytes 能完整解析成 protobuf 时当子消息,否则按 UTF-8 文本;
|
|
378
|
+
* 文本像 JSON 时解析成对象。varint 为十进制字符串。重复字段为数组。
|
|
379
|
+
*/
|
|
380
|
+
export function decodeWire(raw) {
|
|
381
|
+
const tree = fields(raw);
|
|
382
|
+
const out = {};
|
|
383
|
+
for (const [k, values] of tree) {
|
|
384
|
+
const decoded = values.map(wireValue);
|
|
385
|
+
out[String(k)] = decoded.length === 1 ? decoded[0] : decoded;
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
const utf8Strict = new TextDecoder('utf-8', { fatal: true });
|
|
390
|
+
/** 可打印的 UTF-8 文本当字符串(像 JSON 时解析),否则尝试当子消息,再不行给 base64。 */
|
|
391
|
+
function wireValue(v) {
|
|
392
|
+
if (typeof v === 'bigint')
|
|
393
|
+
return v.toString();
|
|
394
|
+
let text = null;
|
|
395
|
+
try {
|
|
396
|
+
text = utf8Strict.decode(v);
|
|
397
|
+
}
|
|
398
|
+
catch { }
|
|
399
|
+
if (text != null && !/[\x00-\x08\x0e-\x1f\x7f]/.test(text)) {
|
|
400
|
+
if (text.startsWith('{') || text.startsWith('[')) {
|
|
401
|
+
try {
|
|
402
|
+
return jsonLoads(text);
|
|
403
|
+
}
|
|
404
|
+
catch { }
|
|
405
|
+
}
|
|
406
|
+
return text;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
return decodeWire(v);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return text ?? Buffer.from(v).toString('base64');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** 从拉取的回包里找出文本消息:field 8 是带 text 的 JSON 的节点(上游 visit)。 */
|
|
416
|
+
export function pulledMessages(wire) {
|
|
417
|
+
const out = [];
|
|
418
|
+
const seen = new Set();
|
|
419
|
+
const visit = (value) => {
|
|
420
|
+
if (Array.isArray(value))
|
|
421
|
+
return value.forEach(visit);
|
|
422
|
+
if (!value || typeof value !== 'object')
|
|
423
|
+
return;
|
|
424
|
+
const node = value;
|
|
425
|
+
const content = node['8'];
|
|
426
|
+
if (content && typeof content === 'object' && !Array.isArray(content) && typeof content.text === 'string') {
|
|
427
|
+
const key = [node['3'], node['5'], node['7'], node['10'], content.text].map(String).join('\u0000');
|
|
428
|
+
if (!seen.has(key)) {
|
|
429
|
+
seen.add(key);
|
|
430
|
+
out.push({
|
|
431
|
+
conversation_id: String(node['1'] ?? ''),
|
|
432
|
+
server_message_id: String(node['3'] ?? ''),
|
|
433
|
+
conversation_short_id: String(node['5'] ?? ''),
|
|
434
|
+
message_type: String(node['6'] ?? ''),
|
|
435
|
+
sender: String(node['7'] ?? ''),
|
|
436
|
+
content,
|
|
437
|
+
text: content.text,
|
|
438
|
+
create_time: String(node['10'] ?? ''),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
Object.values(node).forEach(visit);
|
|
443
|
+
};
|
|
444
|
+
visit(wire);
|
|
445
|
+
return out;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 拉取回包的翻页字段(上游不解析)。Response.body(field 6)里按命令号嵌一层:
|
|
449
|
+
* - 203 MessagesPerUserInitV2ResponseBody:messages=1、conversations=2、per_user_cursor=3、next_cursor=4、has_more=5;
|
|
450
|
+
* - 301 MessagesInConversationResponseBody:messages=1、next_cursor=2、has_more=3。
|
|
451
|
+
* 字段号取自字节 IM SDK 的 im_proto(与 static/tiktok/Tiktok_Request.json 同源),没有时当作没有下一页。
|
|
452
|
+
*/
|
|
453
|
+
export function imPullPage(raw, cmd) {
|
|
454
|
+
const [cursorField, moreField] = cmd === 203 ? [4, 5] : [2, 3];
|
|
455
|
+
try {
|
|
456
|
+
const body = nested(nested(fields(raw), 6), cmd);
|
|
457
|
+
const c = first(body, cursorField);
|
|
458
|
+
const more = first(body, moreField);
|
|
459
|
+
const cursor = typeof c === 'bigint' ? c.toString() : null;
|
|
460
|
+
return { cursor, hasMore: typeof more === 'bigint' && more !== 0n && cursor != null };
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return { cursor: null, hasMore: false };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const CONVERSATION_ID = /^\d+:\d+:\d+:\d+$/;
|
|
467
|
+
/** 会话:field 1 是 `0:1:<uid>:<uid>` 形式的会话 ID、field 2 / 3 是短 ID 与类型的节点(ConversationInfo)。 */
|
|
468
|
+
export function pulledConversations(wire) {
|
|
469
|
+
const out = new Map();
|
|
470
|
+
const visit = (value) => {
|
|
471
|
+
if (Array.isArray(value))
|
|
472
|
+
return value.forEach(visit);
|
|
473
|
+
if (!value || typeof value !== 'object')
|
|
474
|
+
return;
|
|
475
|
+
const node = value;
|
|
476
|
+
const id = node['1'];
|
|
477
|
+
if (typeof id === 'string' && CONVERSATION_ID.test(id) && !out.has(id)) {
|
|
478
|
+
// ConversationInfo:1=id 2=short_id 3=type;MessageBody:1=id 2=type 5=short_id
|
|
479
|
+
const isMessage = typeof node['5'] === 'string' && node['8'] !== undefined;
|
|
480
|
+
const shortId = isMessage ? node['5'] : node['2'];
|
|
481
|
+
const type = isMessage ? node['2'] : node['3'];
|
|
482
|
+
if (typeof shortId === 'string' && /^\d+$/.test(shortId)) {
|
|
483
|
+
out.set(id, { conversation_id: id, conversation_short_id: shortId, conversation_type: typeof type === 'string' ? type : '1' });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
Object.values(node).forEach(visit);
|
|
487
|
+
};
|
|
488
|
+
visit(wire);
|
|
489
|
+
return [...out.values()];
|
|
490
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { filter, visibility } from '../../core/options.js';
|
|
3
|
+
import { definePlatform, handlers } from '../../core/registry.js';
|
|
4
|
+
/** 懒加载 web 端的 handler:只有执行到这条命令时才加载实现。 */
|
|
5
|
+
const { h, impl } = handlers(() => import('./web/commands.js'));
|
|
6
|
+
export default definePlatform({
|
|
7
|
+
id: 'weibo',
|
|
8
|
+
name: '微博',
|
|
9
|
+
aliases: ['wb'],
|
|
10
|
+
item: '微博',
|
|
11
|
+
endpoints: {
|
|
12
|
+
web: {
|
|
13
|
+
login: { methods: ['cookie'], default: 'cookie' },
|
|
14
|
+
commands: {
|
|
15
|
+
'auth login': impl('full', 'authLogin'),
|
|
16
|
+
'auth status': impl('full', 'authStatus'),
|
|
17
|
+
'user get': impl('full', 'userGet'),
|
|
18
|
+
'user search': 'none',
|
|
19
|
+
'user items': impl('full', 'userItems'),
|
|
20
|
+
'user likes': 'none',
|
|
21
|
+
'user collects': 'none',
|
|
22
|
+
'user reposts': 'none',
|
|
23
|
+
'user followers': 'none',
|
|
24
|
+
'user following': 'none',
|
|
25
|
+
'user follow': 'none',
|
|
26
|
+
'user unfollow': 'none',
|
|
27
|
+
'item get': impl('full', 'itemGet'),
|
|
28
|
+
'item search': impl('partial', 'itemSearch', { note: '只能取第一页(m.weibo.cn 的访客从第二页起要求登录)' }),
|
|
29
|
+
'item list': 'none',
|
|
30
|
+
'item media': 'none',
|
|
31
|
+
'item download': 'none',
|
|
32
|
+
'item like': 'none',
|
|
33
|
+
'item unlike': 'none',
|
|
34
|
+
'item collect': 'none',
|
|
35
|
+
'item uncollect': 'none',
|
|
36
|
+
'item repost': 'none',
|
|
37
|
+
'item unrepost': 'none',
|
|
38
|
+
// post_weibo 的 visible:0 公开、1 仅自己、6 朋友圈(互相关注)、10 粉丝
|
|
39
|
+
'item publish': impl('full', 'itemPublish', {
|
|
40
|
+
supports: ['text', 'image', 'video', 'topic', 'visibility'],
|
|
41
|
+
options: {
|
|
42
|
+
visibility: visibility('public', 'private', 'friends', 'fans'),
|
|
43
|
+
poiName: z.string().optional().describe('地点名称,发成「#名称[地点]#」的地点标签'),
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
'item delete': 'none',
|
|
47
|
+
'comment list': impl('partial', 'commentList', { note: '只有一级评论' }),
|
|
48
|
+
'comment replies': 'none',
|
|
49
|
+
'comment add': 'none',
|
|
50
|
+
'comment delete': 'none',
|
|
51
|
+
'comment like': 'none',
|
|
52
|
+
'comment unlike': 'none',
|
|
53
|
+
'feed list': { upstream: 'none', options: { kind: filter.kind('recommend', 'hot', 'following') } },
|
|
54
|
+
'live get': 'none',
|
|
55
|
+
'live list': 'none',
|
|
56
|
+
'live search': 'none',
|
|
57
|
+
'live categories': 'none',
|
|
58
|
+
'live listen': 'none',
|
|
59
|
+
'live history': 'none',
|
|
60
|
+
'live send': 'none',
|
|
61
|
+
'live like': 'none',
|
|
62
|
+
'live rank': 'none',
|
|
63
|
+
'live gifts': 'none',
|
|
64
|
+
'live products': 'none',
|
|
65
|
+
'live media': 'none',
|
|
66
|
+
'live replays': 'none',
|
|
67
|
+
'live start': 'none',
|
|
68
|
+
'live stop': 'none',
|
|
69
|
+
'keyword suggest': 'none',
|
|
70
|
+
'keyword hot': 'none',
|
|
71
|
+
'notice list': 'none',
|
|
72
|
+
'notice count': 'none',
|
|
73
|
+
'msg list': 'none',
|
|
74
|
+
'msg history': 'none',
|
|
75
|
+
'msg send': 'none',
|
|
76
|
+
'msg listen': 'none',
|
|
77
|
+
'msg read': 'none',
|
|
78
|
+
'msg revoke': 'none',
|
|
79
|
+
'msg delete': 'none',
|
|
80
|
+
'media upload': impl('full', 'mediaUpload'),
|
|
81
|
+
'folder list': 'none',
|
|
82
|
+
'folder items': 'none',
|
|
83
|
+
'history list': 'none',
|
|
84
|
+
'topic search': 'none',
|
|
85
|
+
'poi search': 'none',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
app: 'planned',
|
|
89
|
+
pc: 'planned',
|
|
90
|
+
},
|
|
91
|
+
});
|