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,137 @@
|
|
|
1
|
+
import * as n from '../../../core/normalize.js';
|
|
2
|
+
import { midToBid } from './sign.js';
|
|
3
|
+
/**
|
|
4
|
+
* 微博原始对象 → 归一化类型(AGENTS 6.2)。
|
|
5
|
+
* weibo.com(ajax)与 m.weibo.cn 的微博对象字段大体相同,差别在图片(pic_infos / pics)、视频(page_info)和正文(text_raw / HTML)。
|
|
6
|
+
*/
|
|
7
|
+
export const userUrl = (uid) => `https://weibo.com/u/${uid}`;
|
|
8
|
+
/** 微博的规范链接:weibo.com/<uid>/<mblogid>;不知道作者时用 m.weibo.cn/detail/<mid>。 */
|
|
9
|
+
export function itemUrl(mid, uid, bid) {
|
|
10
|
+
if (uid == null || uid === '')
|
|
11
|
+
return `https://m.weibo.cn/detail/${mid}`;
|
|
12
|
+
return `https://weibo.com/${uid}/${bid || midToBid(mid)}`;
|
|
13
|
+
}
|
|
14
|
+
export function ref(u) {
|
|
15
|
+
const id = u?.idstr ?? u?.id;
|
|
16
|
+
return n.userRef({ id, name: u?.screen_name, url: id == null || id === '' ? null : userUrl(id) });
|
|
17
|
+
}
|
|
18
|
+
/** "21万次播放" / "1,234" → 整数。 */
|
|
19
|
+
function plays(v) {
|
|
20
|
+
if (typeof v === 'number')
|
|
21
|
+
return n.count(v);
|
|
22
|
+
const m = /^\s*([\d.,]+\s*[万亿wW]?)/.exec(String(v ?? ''));
|
|
23
|
+
return m ? n.count(m[1].replace(/\s/g, '')) : null;
|
|
24
|
+
}
|
|
25
|
+
function isVideo(m) {
|
|
26
|
+
return m?.page_info?.type === 'video' || m?.page_info?.object_type === 'video';
|
|
27
|
+
}
|
|
28
|
+
/** 图片:weibo.com 是 pic_infos(按 pic_ids 的顺序),m.weibo.cn 是 pics。 */
|
|
29
|
+
function images(m) {
|
|
30
|
+
if (m?.pic_infos && typeof m.pic_infos === 'object') {
|
|
31
|
+
const ids = Array.isArray(m.pic_ids) && m.pic_ids.length ? m.pic_ids : Object.keys(m.pic_infos);
|
|
32
|
+
return ids
|
|
33
|
+
.map((pid) => m.pic_infos[pid])
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.map((p) => {
|
|
36
|
+
const best = p.largest ?? p.mw2000 ?? p.original ?? p.large ?? p.bmiddle;
|
|
37
|
+
return n.media({ id: n.idOrNull(p.pic_id), type: 'image', url: n.url(best?.url), width: n.count(best?.width), height: n.count(best?.height) }, p);
|
|
38
|
+
})
|
|
39
|
+
.filter((x) => x.url);
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(m?.pics)) {
|
|
42
|
+
return m.pics
|
|
43
|
+
.map((p) => {
|
|
44
|
+
const large = p.large ?? p;
|
|
45
|
+
return n.media({ id: n.idOrNull(p.pid), type: 'image', url: n.url(large.url), width: n.count(large.geo?.width), height: n.count(large.geo?.height) }, p);
|
|
46
|
+
})
|
|
47
|
+
.filter((x) => x.url);
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
/** 视频:weibo.com 的 media_info.playback_list 按清晰度从高到低;m.weibo.cn 是 urls。 */
|
|
52
|
+
function video(m) {
|
|
53
|
+
const p = m.page_info ?? {};
|
|
54
|
+
const info = p.media_info ?? {};
|
|
55
|
+
const best = info.playback_list?.[0]?.play_info;
|
|
56
|
+
const url = best?.url ??
|
|
57
|
+
p.urls?.mp4_720p_mp4 ??
|
|
58
|
+
p.urls?.mp4_hd_mp4 ??
|
|
59
|
+
p.urls?.mp4_ld_mp4 ??
|
|
60
|
+
info.mp4_720p_mp4 ??
|
|
61
|
+
info.mp4_hd_url ??
|
|
62
|
+
info.stream_url_hd ??
|
|
63
|
+
info.stream_url;
|
|
64
|
+
if (!url)
|
|
65
|
+
return null;
|
|
66
|
+
return n.media({
|
|
67
|
+
id: n.idOrNull(info.media_id ?? p.object_id),
|
|
68
|
+
type: 'video',
|
|
69
|
+
url: n.url(url),
|
|
70
|
+
width: n.count(best?.width),
|
|
71
|
+
height: n.count(best?.height),
|
|
72
|
+
duration: n.seconds(best?.duration ?? info.duration),
|
|
73
|
+
}, info);
|
|
74
|
+
}
|
|
75
|
+
const VISIBLE = { 0: 'published', 1: 'private', 6: 'published', 10: 'published' };
|
|
76
|
+
/**
|
|
77
|
+
* 一条微博。own 为 true 时(自己的微博)按 visible.type 给出 status:仅自己可见为 private,其余为 published。
|
|
78
|
+
*/
|
|
79
|
+
export function mblog(m, own = false) {
|
|
80
|
+
const id = n.id(m.idstr ?? m.mid ?? m.id);
|
|
81
|
+
const author = ref(m.user);
|
|
82
|
+
const vid = isVideo(m) ? video(m) : null;
|
|
83
|
+
const pics = images(m);
|
|
84
|
+
const media = vid ? [vid] : pics;
|
|
85
|
+
const kind = vid ? 'video' : m.page_info?.object_type === 'article' || m.page_info?.type === 'article' ? 'article' : pics.length || Number(m.pic_num) > 0 ? 'image' : 'text';
|
|
86
|
+
const cover = vid ? n.url(m.page_info?.page_pic?.url ?? m.page_info?.page_pic) : (pics[0]?.url ?? null);
|
|
87
|
+
return n.item({
|
|
88
|
+
id,
|
|
89
|
+
kind,
|
|
90
|
+
url: itemUrl(id, author?.id, m.mblogid ?? m.bid),
|
|
91
|
+
title: kind === 'article' ? n.str(m.page_info?.page_title ?? m.page_info?.content1) : null,
|
|
92
|
+
text: n.str(m.text_raw) ?? n.plainText(m.text),
|
|
93
|
+
author,
|
|
94
|
+
created_at: n.time(m.created_at),
|
|
95
|
+
cover,
|
|
96
|
+
media,
|
|
97
|
+
stats: {
|
|
98
|
+
views: vid ? plays(m.page_info?.play_count ?? m.page_info?.media_info?.online_users_number) : null,
|
|
99
|
+
likes: n.count(m.attitudes_count),
|
|
100
|
+
comments: n.count(m.comments_count),
|
|
101
|
+
shares: n.count(m.reposts_count),
|
|
102
|
+
},
|
|
103
|
+
status: own ? (VISIBLE[Number(m.visible?.type ?? 0)] ?? 'published') : null,
|
|
104
|
+
}, m);
|
|
105
|
+
}
|
|
106
|
+
/** weibo.com/ajax/profile/info 的 data.user。 */
|
|
107
|
+
export function user(u) {
|
|
108
|
+
const id = n.id(u.idstr ?? u.id);
|
|
109
|
+
return n.user({
|
|
110
|
+
id,
|
|
111
|
+
name: n.str(u.screen_name),
|
|
112
|
+
handle: n.str(u.domain) ?? n.str(u.weihao),
|
|
113
|
+
avatar: n.url(u.avatar_hd ?? u.avatar_large ?? u.profile_image_url),
|
|
114
|
+
url: userUrl(id),
|
|
115
|
+
bio: n.str(u.description),
|
|
116
|
+
stats: {
|
|
117
|
+
followers: n.count(u.followers_count),
|
|
118
|
+
following: n.count(u.friends_count ?? u.follow_count),
|
|
119
|
+
items: n.count(u.statuses_count),
|
|
120
|
+
likes: n.count(u.status_total_counter?.like_cnt),
|
|
121
|
+
},
|
|
122
|
+
}, u);
|
|
123
|
+
}
|
|
124
|
+
/** buildComments 的一条评论。 */
|
|
125
|
+
export function comment(c, itemId) {
|
|
126
|
+
const id = n.id(c.idstr ?? c.id);
|
|
127
|
+
const root = n.idOrNull(c.rootidstr ?? c.rootid);
|
|
128
|
+
return n.comment({
|
|
129
|
+
id,
|
|
130
|
+
item_id: itemId,
|
|
131
|
+
parent_id: root && root !== id ? root : null,
|
|
132
|
+
author: ref(c.user),
|
|
133
|
+
text: n.str(c.text_raw) ?? n.plainText(c.text) ?? '',
|
|
134
|
+
created_at: n.time(c.created_at),
|
|
135
|
+
stats: { likes: n.count(c.like_counts), replies: n.count(c.total_number) },
|
|
136
|
+
}, c);
|
|
137
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求头:逐字照抄上游 utils/weibo_utils.py、weibo_mobile_utils.py、weibo_creator_utils.py,
|
|
3
|
+
* 键名大小写和顺序都不改(对拍测试逐字节比较)。
|
|
4
|
+
*
|
|
5
|
+
* 与上游的差异:上游写死的 `content-length` 不发(上游的 requests 发送前会用真实长度覆盖它,
|
|
6
|
+
* 而 get_post_video_headers 里写的是视频大小、与表单体不符),由 HTTP 库按实际请求体计算。
|
|
7
|
+
*/
|
|
8
|
+
export const WEB = 'https://weibo.com';
|
|
9
|
+
export const MOBILE = 'https://m.weibo.cn';
|
|
10
|
+
export const COOKIE_DOMAIN = '.weibo.com';
|
|
11
|
+
/** 上游 HTML 请求头里的 UA(Chrome 132),访客接口也用它。 */
|
|
12
|
+
export const UA_132 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
|
|
13
|
+
const SEC_CH_UA_132 = '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"';
|
|
14
|
+
const UA_130 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0';
|
|
15
|
+
const SEC_CH_UA_130 = '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"';
|
|
16
|
+
const DOC_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7';
|
|
17
|
+
const XHR_ACCEPT = 'application/json, text/plain, */*';
|
|
18
|
+
// ================================================================ utils/weibo_utils.py
|
|
19
|
+
/** get_common_headers:weibo.com 的 ajax 接口。xsrf 取 cookie 里的 XSRF-TOKEN,游客为空串。 */
|
|
20
|
+
export function commonHeaders(xsrf) {
|
|
21
|
+
return [
|
|
22
|
+
['accept', XHR_ACCEPT],
|
|
23
|
+
['accept-language', 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'],
|
|
24
|
+
['cache-control', 'no-cache'],
|
|
25
|
+
['client-version', 'v2.46.7'],
|
|
26
|
+
['pragma', 'no-cache'],
|
|
27
|
+
['priority', 'u=1, i'],
|
|
28
|
+
['referer', 'https://weibo.com/u/5266778656'],
|
|
29
|
+
['sec-ch-ua', '"Chromium";v="128", "Not;A=Brand";v="24", "Microsoft Edge";v="128"'],
|
|
30
|
+
['sec-ch-ua-mobile', '?0'],
|
|
31
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
32
|
+
['sec-fetch-dest', 'empty'],
|
|
33
|
+
['sec-fetch-mode', 'cors'],
|
|
34
|
+
['sec-fetch-site', 'same-origin'],
|
|
35
|
+
['server-version', 'v2024.08.30.1'],
|
|
36
|
+
['user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0'],
|
|
37
|
+
['x-requested-with', 'XMLHttpRequest'],
|
|
38
|
+
['x-xsrf-token', xsrf],
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
/** get_html_headers:weibo.com 首页(取 $CONFIG)。 */
|
|
42
|
+
export function htmlHeaders() {
|
|
43
|
+
return [
|
|
44
|
+
['accept', DOC_ACCEPT],
|
|
45
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
46
|
+
['cache-control', 'no-cache'],
|
|
47
|
+
['pragma', 'no-cache'],
|
|
48
|
+
['priority', 'u=0, i'],
|
|
49
|
+
['referer', 'https://weibo.com/tv/home'],
|
|
50
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
51
|
+
['sec-ch-ua-mobile', '?0'],
|
|
52
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
53
|
+
['sec-fetch-dest', 'document'],
|
|
54
|
+
['sec-fetch-mode', 'navigate'],
|
|
55
|
+
['sec-fetch-site', 'same-origin'],
|
|
56
|
+
['sec-fetch-user', '?1'],
|
|
57
|
+
['upgrade-insecure-requests', '1'],
|
|
58
|
+
['user-agent', UA_132],
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
// ================================================================ utils/weibo_mobile_utils.py
|
|
62
|
+
/** get_search_headers:m.weibo.cn 的搜索接口。 */
|
|
63
|
+
export function searchHeaders() {
|
|
64
|
+
return [
|
|
65
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
66
|
+
['X-XSRF-TOKEN', 'a1d843'],
|
|
67
|
+
['Referer', 'https://m.weibo.cn/'],
|
|
68
|
+
['sec-ch-ua', SEC_CH_UA_130],
|
|
69
|
+
['sec-ch-ua-mobile', '?0'],
|
|
70
|
+
['MWeibo-Pwa', '1'],
|
|
71
|
+
['X-Requested-With', 'XMLHttpRequest'],
|
|
72
|
+
['User-Agent', UA_130],
|
|
73
|
+
['Accept', XHR_ACCEPT],
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
/** get_detail_headers:m.weibo.cn 的详情页。 */
|
|
77
|
+
export function detailHeaders() {
|
|
78
|
+
return [
|
|
79
|
+
['accept', DOC_ACCEPT],
|
|
80
|
+
['accept-language', 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'],
|
|
81
|
+
['cache-control', 'no-cache'],
|
|
82
|
+
['pragma', 'no-cache'],
|
|
83
|
+
['priority', 'u=0, i'],
|
|
84
|
+
['sec-ch-ua', SEC_CH_UA_130],
|
|
85
|
+
['sec-ch-ua-mobile', '?0'],
|
|
86
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
87
|
+
['sec-fetch-dest', 'document'],
|
|
88
|
+
['sec-fetch-mode', 'navigate'],
|
|
89
|
+
['sec-fetch-site', 'none'],
|
|
90
|
+
['sec-fetch-user', '?1'],
|
|
91
|
+
['upgrade-insecure-requests', '1'],
|
|
92
|
+
['user-agent', UA_130],
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
// ================================================================ utils/weibo_creator_utils.py
|
|
96
|
+
/** generate_upload_image_media_headers:picupload 上传图片。 */
|
|
97
|
+
export function uploadImageHeaders() {
|
|
98
|
+
return [
|
|
99
|
+
['accept', XHR_ACCEPT],
|
|
100
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
101
|
+
['cache-control', 'no-cache'],
|
|
102
|
+
['content-type', 'application/octet-stream'],
|
|
103
|
+
['origin', 'https://weibo.com'],
|
|
104
|
+
['pragma', 'no-cache'],
|
|
105
|
+
['priority', 'u=1, i'],
|
|
106
|
+
['referer', 'https://weibo.com/'],
|
|
107
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
108
|
+
['sec-ch-ua-mobile', '?0'],
|
|
109
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
110
|
+
['sec-fetch-dest', 'empty'],
|
|
111
|
+
['sec-fetch-mode', 'cors'],
|
|
112
|
+
['sec-fetch-site', 'same-site'],
|
|
113
|
+
['user-agent', UA_132],
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
/** generate_video_check_headers:fileplatform check.json。 */
|
|
117
|
+
export function videoCheckHeaders(auth) {
|
|
118
|
+
return [
|
|
119
|
+
['Accept', XHR_ACCEPT],
|
|
120
|
+
['Accept-Language', 'zh-CN,zh;q=0.9'],
|
|
121
|
+
['Cache-Control', 'no-cache'],
|
|
122
|
+
['Connection', 'keep-alive'],
|
|
123
|
+
['Content-Type', 'application/x-www-form-urlencoded'],
|
|
124
|
+
['Origin', 'https://weibo.com'],
|
|
125
|
+
['Pragma', 'no-cache'],
|
|
126
|
+
['Referer', 'https://weibo.com/'],
|
|
127
|
+
['Sec-Fetch-Dest', 'empty'],
|
|
128
|
+
['Sec-Fetch-Mode', 'cors'],
|
|
129
|
+
['Sec-Fetch-Site', 'same-site'],
|
|
130
|
+
['User-Agent', UA_132],
|
|
131
|
+
['X-Up-Auth', auth],
|
|
132
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
133
|
+
['sec-ch-ua-mobile', '?0'],
|
|
134
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
/** generate_video_output_headers:multimedia/output 轮询转码结果。 */
|
|
138
|
+
export function videoOutputHeaders() {
|
|
139
|
+
return [
|
|
140
|
+
['accept', XHR_ACCEPT],
|
|
141
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
142
|
+
['cache-control', 'no-cache'],
|
|
143
|
+
['pragma', 'no-cache'],
|
|
144
|
+
['priority', 'u=1, i'],
|
|
145
|
+
['referer', 'https://weibo.com/upload/channel'],
|
|
146
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
147
|
+
['sec-ch-ua-mobile', '?0'],
|
|
148
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
149
|
+
['sec-fetch-dest', 'empty'],
|
|
150
|
+
['sec-fetch-mode', 'cors'],
|
|
151
|
+
['sec-fetch-site', 'same-origin'],
|
|
152
|
+
['user-agent', UA_132],
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
/** generate_upload_video_media_headers:up.video.weibocdn.com 上传视频(content-length 见文件头注释)。 */
|
|
156
|
+
export function uploadVideoHeaders(auth) {
|
|
157
|
+
return [
|
|
158
|
+
['accept', XHR_ACCEPT],
|
|
159
|
+
['accept-encoding', 'gzip, deflate, br, zstd'],
|
|
160
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
161
|
+
['cache-control', 'no-cache'],
|
|
162
|
+
['content-type', 'application/octet-stream'],
|
|
163
|
+
['host', 'up.video.weibocdn.com'],
|
|
164
|
+
['origin', 'https://weibo.com'],
|
|
165
|
+
['pragma', 'no-cache'],
|
|
166
|
+
['priority', 'u=1, i'],
|
|
167
|
+
['referer', 'https://weibo.com/'],
|
|
168
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
169
|
+
['sec-ch-ua-mobile', '?0'],
|
|
170
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
171
|
+
['sec-fetch-dest', 'empty'],
|
|
172
|
+
['sec-fetch-mode', 'cors'],
|
|
173
|
+
['sec-fetch-site', 'same-site'],
|
|
174
|
+
['user-agent', UA_132],
|
|
175
|
+
['x-up-auth', auth],
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
function postHeaders() {
|
|
179
|
+
return [
|
|
180
|
+
['accept', XHR_ACCEPT],
|
|
181
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
182
|
+
['cache-control', 'no-cache'],
|
|
183
|
+
['client-version', 'v2.47.25'],
|
|
184
|
+
['content-type', 'application/x-www-form-urlencoded'],
|
|
185
|
+
['origin', 'https://weibo.com'],
|
|
186
|
+
['pragma', 'no-cache'],
|
|
187
|
+
['priority', 'u=1, i'],
|
|
188
|
+
['referer', 'https://weibo.com/'],
|
|
189
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
190
|
+
['sec-ch-ua-mobile', '?0'],
|
|
191
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
192
|
+
['sec-fetch-dest', 'empty'],
|
|
193
|
+
['sec-fetch-mode', 'cors'],
|
|
194
|
+
['sec-fetch-site', 'same-origin'],
|
|
195
|
+
['server-version', 'v2025.01.23.1'],
|
|
196
|
+
['user-agent', UA_132],
|
|
197
|
+
['x-requested-with', 'XMLHttpRequest'],
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
/** get_post_image_headers:发图文(statuses/update)。 */
|
|
201
|
+
export function postImageHeaders(xsrf) {
|
|
202
|
+
return [...postHeaders(), ['x-xsrf-token', xsrf]];
|
|
203
|
+
}
|
|
204
|
+
/** get_post_video_headers:发视频(statuses/update)。上游这里不带 x-xsrf-token,照抄。 */
|
|
205
|
+
export function postVideoHeaders() {
|
|
206
|
+
return postHeaders();
|
|
207
|
+
}
|
|
208
|
+
/** get_form_headers:fileplatform init.json,multipart/mixed 的 boundary 带秒级时间戳。 */
|
|
209
|
+
export function formHeaders(t) {
|
|
210
|
+
return [
|
|
211
|
+
['Accept', XHR_ACCEPT],
|
|
212
|
+
['Accept-Language', 'zh-CN,zh;q=0.9'],
|
|
213
|
+
['Cache-Control', 'no-cache'],
|
|
214
|
+
['Connection', 'keep-alive'],
|
|
215
|
+
['Content-Type', `multipart/mixed; boundary=2067456weiboPro${t}`],
|
|
216
|
+
['Origin', 'https://weibo.com'],
|
|
217
|
+
['Pragma', 'no-cache'],
|
|
218
|
+
['Referer', 'https://weibo.com/'],
|
|
219
|
+
['Sec-Fetch-Dest', 'empty'],
|
|
220
|
+
['Sec-Fetch-Mode', 'cors'],
|
|
221
|
+
['Sec-Fetch-Site', 'same-site'],
|
|
222
|
+
['User-Agent', UA_132],
|
|
223
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
224
|
+
['sec-ch-ua-mobile', '?0'],
|
|
225
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
// ================================================================ 访客(上游没有,见 client.ts)
|
|
229
|
+
/** 新浪访客系统 genvisitor2 的 XHR 头:与访客页里 ufp.util.postData 发出的一致。 */
|
|
230
|
+
export function visitorHeaders(origin, referer) {
|
|
231
|
+
return [
|
|
232
|
+
['sec-ch-ua-platform', '"Windows"'],
|
|
233
|
+
['user-agent', UA_132],
|
|
234
|
+
['sec-ch-ua', SEC_CH_UA_132],
|
|
235
|
+
['content-type', 'application/x-www-form-urlencoded'],
|
|
236
|
+
['sec-ch-ua-mobile', '?0'],
|
|
237
|
+
['accept', '*/*'],
|
|
238
|
+
['origin', origin],
|
|
239
|
+
['sec-fetch-site', 'same-origin'],
|
|
240
|
+
['sec-fetch-mode', 'cors'],
|
|
241
|
+
['sec-fetch-dest', 'empty'],
|
|
242
|
+
['referer', referer],
|
|
243
|
+
['accept-language', 'zh-CN,zh;q=0.9'],
|
|
244
|
+
['priority', 'u=1, i'],
|
|
245
|
+
];
|
|
246
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
2
|
+
import * as api from './api.js';
|
|
3
|
+
import { bidToMid } from './sign.js';
|
|
4
|
+
const toMid = (token) => (/^\d+$/.test(token) ? token : bidToMid(token));
|
|
5
|
+
/**
|
|
6
|
+
* 微博:mid(16 位数字)、mblogid(如 OuIv3hbiw),或链接
|
|
7
|
+
* weibo.com/<uid>/<mblogid|mid>、weibo.com/detail/<mid>、m.weibo.cn/detail|status/<mid|mblogid>、m.weibo.cn/<uid>/<mid>。
|
|
8
|
+
*/
|
|
9
|
+
export function resolveItem(input) {
|
|
10
|
+
const s = input.trim();
|
|
11
|
+
if (/^\d+$/.test(s) || /^[0-9A-Za-z]{8,10}$/.test(s))
|
|
12
|
+
return { mid: toMid(s), uid: null };
|
|
13
|
+
let url = null;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(/^https?:\/\//i.test(s) ? s : `https://${s}`);
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
if (url && /(^|\.)weibo\.(com|cn)$/.test(url.hostname)) {
|
|
19
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
20
|
+
if (parts.length >= 2 && /^(detail|status)$/.test(parts[0]) && /^[0-9A-Za-z]+$/.test(parts[1]))
|
|
21
|
+
return { mid: toMid(parts[1]), uid: null };
|
|
22
|
+
if (parts.length >= 2 && /^\d+$/.test(parts[0]) && /^[0-9A-Za-z]+$/.test(parts[1]))
|
|
23
|
+
return { mid: toMid(parts[1]), uid: parts[0] };
|
|
24
|
+
const q = url.searchParams.get('id') ?? url.searchParams.get('mid');
|
|
25
|
+
if (q && /^[0-9A-Za-z]+$/.test(q))
|
|
26
|
+
return { mid: toMid(q), uid: null };
|
|
27
|
+
}
|
|
28
|
+
throw new SpecificAIError('USAGE', `无法识别的微博:${input}`, { hint: '传 mid、mblogid 或微博链接,例如 https://weibo.com/<uid>/<mblogid>' });
|
|
29
|
+
}
|
|
30
|
+
/** 用户:uid、主页链接(weibo.com/u/<uid>、weibo.com/<uid>、m.weibo.cn/u|profile/<uid>)或 `me`。 */
|
|
31
|
+
export async function resolveUser(w, input) {
|
|
32
|
+
const s = input.trim();
|
|
33
|
+
if (s === 'me') {
|
|
34
|
+
const saved = w.ctx.credential.user?.id;
|
|
35
|
+
if (saved)
|
|
36
|
+
return saved;
|
|
37
|
+
const self = await api.selfInfo(w);
|
|
38
|
+
if (!self)
|
|
39
|
+
throw new SpecificAIError('AUTH_REQUIRED', 'me 表示当前账号,需要先登录', { hint: 'specific-ai weibo auth login' });
|
|
40
|
+
return self.uid;
|
|
41
|
+
}
|
|
42
|
+
if (/^\d+$/.test(s))
|
|
43
|
+
return s;
|
|
44
|
+
let url = null;
|
|
45
|
+
try {
|
|
46
|
+
url = new URL(/^https?:\/\//i.test(s) ? s : `https://${s}`);
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
if (url && /(^|\.)weibo\.(com|cn)$/.test(url.hostname)) {
|
|
50
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
51
|
+
if (parts.length >= 2 && /^(u|profile)$/.test(parts[0]) && /^\d+$/.test(parts[1]))
|
|
52
|
+
return parts[1];
|
|
53
|
+
if (parts.length >= 1 && /^\d+$/.test(parts[0]))
|
|
54
|
+
return parts[0];
|
|
55
|
+
const uid = url.searchParams.get('uid');
|
|
56
|
+
if (uid && /^\d+$/.test(uid))
|
|
57
|
+
return uid;
|
|
58
|
+
}
|
|
59
|
+
throw new SpecificAIError('USAGE', `无法识别的用户:${input}`, { hint: '传用户 uid(数字)或主页链接 https://weibo.com/u/<uid>' });
|
|
60
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { crc32 } from 'node:zlib';
|
|
2
|
+
import { md5Hex } from '../../../core/hash.js';
|
|
3
|
+
import { urlencode } from '../../../core/py.js';
|
|
4
|
+
import * as rand from '../../../core/rand.js';
|
|
5
|
+
/** 上传参数(上游 utils/weibo_creator_utils.py)。 */
|
|
6
|
+
export const APP_SOURCE = '339644097';
|
|
7
|
+
export { md5Hex };
|
|
8
|
+
/** generate_params:CRC32(上游手写的查表实现,与标准 CRC32 相同)、MD5、大小。 */
|
|
9
|
+
export function fileParams(file) {
|
|
10
|
+
return { cs: crc32(file), md5: md5Hex(file), fileSize: file.length };
|
|
11
|
+
}
|
|
12
|
+
/** generate_session_id:md5(`毫秒|大小|文件名|video|秒`)。 */
|
|
13
|
+
export function sessionId(fileSize, fileName) {
|
|
14
|
+
return md5Hex(`${rand.now()}|${fileSize}|${fileName}|video|${rand.nowSeconds()}`);
|
|
15
|
+
}
|
|
16
|
+
/** generate_upload_image_media_params,已 urlencode。 */
|
|
17
|
+
export function uploadImageParams(uid, nick, file) {
|
|
18
|
+
const p = fileParams(file);
|
|
19
|
+
const params = [
|
|
20
|
+
['file_source', '1'],
|
|
21
|
+
['cs', String(p.cs)],
|
|
22
|
+
['ent', 'miniblog'],
|
|
23
|
+
['appid', APP_SOURCE],
|
|
24
|
+
['uid', uid],
|
|
25
|
+
['raw_md5', p.md5],
|
|
26
|
+
['ori', '1'],
|
|
27
|
+
['mpos', '1'],
|
|
28
|
+
['nick', nick],
|
|
29
|
+
['pri', '0'],
|
|
30
|
+
['request_id', String(rand.now())],
|
|
31
|
+
['file_size', String(p.fileSize)],
|
|
32
|
+
];
|
|
33
|
+
return urlencode(params);
|
|
34
|
+
}
|
|
35
|
+
/** generate_upload_video_media_params,已 urlencode。整个文件作为一个分片上传。 */
|
|
36
|
+
export function uploadVideoParams(uploadId, mediaId, file) {
|
|
37
|
+
const p = fileParams(file);
|
|
38
|
+
const params = [
|
|
39
|
+
['source', APP_SOURCE],
|
|
40
|
+
['upload_id', uploadId],
|
|
41
|
+
['media_id', mediaId],
|
|
42
|
+
['upload_protocol', 'binary'],
|
|
43
|
+
['type', 'video'],
|
|
44
|
+
['client', 'web'],
|
|
45
|
+
['check', p.md5],
|
|
46
|
+
['index', '0'],
|
|
47
|
+
['size', String(p.fileSize)],
|
|
48
|
+
['start_loc', '0'],
|
|
49
|
+
['count', '1'],
|
|
50
|
+
];
|
|
51
|
+
return urlencode(params);
|
|
52
|
+
}
|
|
53
|
+
// ================================================================ mid ↔ mblogid
|
|
54
|
+
/** 微博的 62 进制:mid 从右往左每 7 位十进制一组,每组转成 4 位 62 进制(最高组不补零)。 */
|
|
55
|
+
const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
56
|
+
function groups(s, size) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (let end = s.length; end > 0; end -= size)
|
|
59
|
+
out.unshift(s.slice(Math.max(0, end - size), end));
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/** 数字 mid → mblogid(URL 里的 9 位短码,如 OuIv3hbiw)。 */
|
|
63
|
+
export function midToBid(mid) {
|
|
64
|
+
return groups(mid, 7)
|
|
65
|
+
.map((g, i) => {
|
|
66
|
+
let n = Number(g);
|
|
67
|
+
let s = '';
|
|
68
|
+
do {
|
|
69
|
+
s = ALPHABET[n % 62] + s;
|
|
70
|
+
n = Math.floor(n / 62);
|
|
71
|
+
} while (n > 0);
|
|
72
|
+
return i === 0 ? s : s.padStart(4, '0');
|
|
73
|
+
})
|
|
74
|
+
.join('');
|
|
75
|
+
}
|
|
76
|
+
/** mblogid → 数字 mid。 */
|
|
77
|
+
export function bidToMid(bid) {
|
|
78
|
+
return groups(bid, 4)
|
|
79
|
+
.map((g, i) => {
|
|
80
|
+
const n = [...g].reduce((acc, ch) => acc * 62 + ALPHABET.indexOf(ch), 0);
|
|
81
|
+
return i === 0 ? String(n) : String(n).padStart(7, '0');
|
|
82
|
+
})
|
|
83
|
+
.join('')
|
|
84
|
+
.replace(/^0+(?=\d)/, '');
|
|
85
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { filter, PUBLISH, QUOTE, 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: 'x',
|
|
8
|
+
name: 'X',
|
|
9
|
+
aliases: ['twitter'],
|
|
10
|
+
item: '推文',
|
|
11
|
+
endpoints: {
|
|
12
|
+
web: {
|
|
13
|
+
// 上游的账密登录依赖 Castle token,不移植:只支持从浏览器导入 cookie
|
|
14
|
+
login: { methods: ['cookie'], default: 'cookie' },
|
|
15
|
+
commands: {
|
|
16
|
+
'auth login': impl('full', 'authLogin'),
|
|
17
|
+
'auth status': impl('full', 'authStatus'),
|
|
18
|
+
'user get': impl('full', 'userGet'),
|
|
19
|
+
'user search': impl('full', 'userSearch'),
|
|
20
|
+
'user items': impl('full', 'userItems'),
|
|
21
|
+
'user likes': 'none',
|
|
22
|
+
'user collects': 'none',
|
|
23
|
+
'user reposts': 'none',
|
|
24
|
+
'user followers': 'none',
|
|
25
|
+
'user following': 'none',
|
|
26
|
+
'user follow': impl('full', 'userFollow'),
|
|
27
|
+
'user unfollow': impl('full', 'userUnfollow'),
|
|
28
|
+
'item get': impl('full', 'itemGet'),
|
|
29
|
+
'item search': impl('full', 'itemSearch', {
|
|
30
|
+
note: '--type video / image 走媒体搜索,按类型过滤,不能和 --sort latest 一起用',
|
|
31
|
+
options: { sort: filter.sort('general', 'latest'), type: filter.type('all', 'video', 'image') },
|
|
32
|
+
}),
|
|
33
|
+
'item media': impl('full', 'itemMedia'),
|
|
34
|
+
'item download': impl('full', 'itemDownload'),
|
|
35
|
+
'item like': impl('full', 'itemLike'),
|
|
36
|
+
'item unlike': impl('full', 'itemUnlike'),
|
|
37
|
+
'item collect': impl('full', 'itemCollect'),
|
|
38
|
+
'item uncollect': impl('full', 'itemUncollect'),
|
|
39
|
+
'item repost': impl('full', 'itemRepost'),
|
|
40
|
+
'item unrepost': impl('full', 'itemUnrepost'),
|
|
41
|
+
'item publish': impl('full', 'itemPublish', {
|
|
42
|
+
note: '正文超过 280 权重时自动按长推发(需要 Premium);--thread 发 thread',
|
|
43
|
+
supports: ['text', 'image', 'video', 'visibility'],
|
|
44
|
+
options: {
|
|
45
|
+
visibility: visibility('public'),
|
|
46
|
+
quote: QUOTE,
|
|
47
|
+
thread: z.array(z.string()).optional().describe('thread 的后续各条,依次回复上一条'),
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
'item delete': impl('full', 'itemDelete'),
|
|
51
|
+
'comment list': impl('full', 'commentList'),
|
|
52
|
+
'comment replies': 'none',
|
|
53
|
+
'comment add': impl('full', 'commentAdd'),
|
|
54
|
+
'comment delete': impl('full', 'commentDelete'),
|
|
55
|
+
'comment like': impl('full', 'commentLike'),
|
|
56
|
+
'comment unlike': impl('full', 'commentUnlike'),
|
|
57
|
+
'feed list': impl('partial', 'feedList', {
|
|
58
|
+
note: 'following 规划中',
|
|
59
|
+
options: { kind: filter.kind('recommend', 'following') },
|
|
60
|
+
}),
|
|
61
|
+
'keyword suggest': 'none',
|
|
62
|
+
'keyword hot': 'none',
|
|
63
|
+
'notice list': 'none',
|
|
64
|
+
'notice count': 'none',
|
|
65
|
+
'msg list': impl('partial', 'msgList', { note: '只取收件箱首页(最近 20 个会话),上游没有翻页' }),
|
|
66
|
+
'msg history': impl('partial', 'msgHistory', { note: '消息端到端加密,只给出占位消息' }),
|
|
67
|
+
// 上游的 send_message 只是占位:X Chat 端到端加密,要先在网页设 passcode、再逆向加密后的发送请求
|
|
68
|
+
'msg send': 'none',
|
|
69
|
+
'msg listen': 'none',
|
|
70
|
+
'msg read': 'none',
|
|
71
|
+
'msg revoke': 'none',
|
|
72
|
+
'msg delete': 'none',
|
|
73
|
+
'media upload': impl('full', 'mediaUpload'),
|
|
74
|
+
'folder list': 'none',
|
|
75
|
+
'folder items': 'none',
|
|
76
|
+
'folder create': 'none',
|
|
77
|
+
'folder update': 'none',
|
|
78
|
+
'folder delete': 'none',
|
|
79
|
+
// 平台扩展(AGENTS 4.7)
|
|
80
|
+
'article publish': {
|
|
81
|
+
upstream: 'full',
|
|
82
|
+
summary: '发文章(Premium 长文):建草稿,写标题、正文、封面,再发布',
|
|
83
|
+
args: [],
|
|
84
|
+
options: {
|
|
85
|
+
title: z.string().optional().describe('标题,不给时取正文第一行的 # 标题'),
|
|
86
|
+
text: z.string().describe('正文(Markdown),@file 表示从文件读取;独占一行的  会上传成插图,相对路径按该文件所在目录解析'),
|
|
87
|
+
cover: PUBLISH.cover,
|
|
88
|
+
},
|
|
89
|
+
auth: 'required',
|
|
90
|
+
output: '{id url}',
|
|
91
|
+
handler: h('articlePublish'),
|
|
92
|
+
},
|
|
93
|
+
'article delete': {
|
|
94
|
+
upstream: 'full',
|
|
95
|
+
summary: '删文章(草稿或已发布的)',
|
|
96
|
+
args: [{ name: 'article', summary: '文章:ID 或 URL' }],
|
|
97
|
+
auth: 'required',
|
|
98
|
+
confirm: true,
|
|
99
|
+
output: '{id}',
|
|
100
|
+
handler: h('articleDelete'),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
app: 'planned',
|
|
105
|
+
pc: 'planned',
|
|
106
|
+
},
|
|
107
|
+
});
|