@wecode-ai/weibo-openclaw-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # openclaw-weibo
2
+
3
+ OpenClaw Weibo DM channel plugin - 微博私信通道插件
4
+
5
+
6
+
7
+ ## 使用
8
+ ### 获取凭证
9
+
10
+ 1. 打开微博客户端,私信 [@微博龙虾助手](https://weibo.com/u/6808810981)
11
+ 2. 发送消息:`连接龙虾`
12
+ 3. 收到回复示例:
13
+ ```
14
+ 您的应用凭证信息如下:
15
+
16
+ AppId: your-app-id
17
+ AppSecret: your-app-secret
18
+
19
+ 如需重置凭证,请发送 "重置凭证" 命令。
20
+ ```
21
+
22
+ ### 配置OpenClaw
23
+ #### 安装插件
24
+ ```
25
+ git clone https://gitee.com/wecode-ai/openclaw-weibo.git
26
+ cd openclaw-weibo
27
+ openclaw plugins install .
28
+ openclaw gateway restart
29
+ ```
30
+
31
+ #### 配置凭证
32
+ 使用命令配置:
33
+ ```bash
34
+ openclaw config set 'channels.weibo.appSecret' 'your-appSecret'
35
+ openclaw config set 'channels.weibo.appId' 'your-appId'
36
+ ```
37
+
38
+ 或编辑 `~/.openclaw/openclaw.config.json`:
39
+
40
+ ```json
41
+ {
42
+ ...existing config...
43
+ "channels": {
44
+ ...existing config...
45
+ "weibo": {
46
+ "appId": "your-app-id",
47
+ "appSecret": "your-app-secret"
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## 插件网络访问说明
54
+ * 插件通过域名 `open-im.api.weibo.com` 来调用微博接口。
55
+ * 如在出入口网络受限环境下使用该插件请注意配置访问权限。
56
+
57
+ ## License
58
+
59
+ MIT
package/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { weiboPlugin } from "./src/channel.js";
3
+ import { setWeiboRuntime } from "./src/runtime.js";
4
+ import { reconnectWeiboMonitor } from "./src/monitor.js";
5
+ import { clearClientCache } from "./src/client.js";
6
+ import { clearTokenCache } from "./src/token.js";
7
+ import { registerWeiboSearchTools } from "./src/weibo-search.js";
8
+ import { registerWeiboStatusTools } from "./src/weibo-status.js";
9
+ import { registerWeiboHotSearchTools } from "./src/weibo-hot-search.js";
10
+
11
+ export { monitorWeiboProvider } from "./src/monitor.js";
12
+ export { sendMessageWeibo } from "./src/send.js";
13
+ export { weiboPlugin } from "./src/channel.js";
14
+
15
+ const plugin = {
16
+ id: "weibo",
17
+ name: "Weibo",
18
+ description: "Weibo DM channel plugin",
19
+ configSchema: { type: "object" as const, properties: {} },
20
+ register(api: OpenClawPluginApi) {
21
+ setWeiboRuntime(api.runtime);
22
+ api.registerChannel({ plugin: weiboPlugin });
23
+ registerWeiboSearchTools(api);
24
+ registerWeiboStatusTools(api);
25
+ registerWeiboHotSearchTools(api);
26
+
27
+ // 工具调用钩子
28
+ api.on("before_tool_call", (event) => {
29
+ if (event.toolName.startsWith("weibo_")) {
30
+ console.log(`[微博工具调用] ${event.toolName} 参数: ${JSON.stringify(event.params)}`);
31
+ }
32
+ });
33
+ api.on("after_tool_call", (event) => {
34
+ if (event.toolName.startsWith("weibo_")) {
35
+ if (event.error) {
36
+ console.error(`[微博工具调用失败] ${event.toolName} 错误: ${event.error}`);
37
+ } else {
38
+ console.log(`[微博工具调用成功] ${event.toolName} 耗时: ${event.durationMs}ms`);
39
+ }
40
+ }
41
+ });
42
+
43
+ api.registerGatewayMethod("weibo.reconnect", async ({ params, respond, context }) => {
44
+ const accountId =
45
+ typeof params.accountId === "string" && params.accountId.trim()
46
+ ? params.accountId.trim()
47
+ : undefined;
48
+
49
+ try {
50
+ await reconnectWeiboMonitor(accountId);
51
+ clearClientCache(accountId);
52
+ clearTokenCache(accountId);
53
+ await context.stopChannel("weibo", accountId);
54
+ await context.startChannel("weibo", accountId);
55
+ respond(true, { ok: true, accountId: accountId ?? "default" });
56
+ } catch (err) {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ respond(false, undefined, {
59
+ code: "weibo_reconnect_failed",
60
+ message,
61
+ });
62
+ }
63
+ });
64
+ },
65
+ };
66
+
67
+ export default plugin;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@wecode-ai/weibo-openclaw-plugin",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "OpenClaw Weibo DM channel plugin",
6
+ "scripts": {
7
+ "test:unit": "vitest run",
8
+ "ci:check": "npx tsc --noEmit && npm run test:unit",
9
+ "build": "tsc",
10
+ "dev": "tsx",
11
+ "sim:server": "tsx weibo-server.ts"
12
+ },
13
+ "license": "MIT",
14
+ "files": [
15
+ "index.ts",
16
+ "src/**/*.ts",
17
+ "!src/**/__tests__/**",
18
+ "!src/**/*.test.ts"
19
+ ],
20
+ "openclaw": {
21
+ "extensions": [
22
+ "./index.ts"
23
+ ],
24
+ "channel": {
25
+ "id": "weibo",
26
+ "label": "Weibo",
27
+ "selectionLabel": "Weibo (微博)",
28
+ "docsPath": "/channels/weibo",
29
+ "docsLabel": "weibo",
30
+ "blurb": "Weibo direct message channel.",
31
+ "order": 80
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "@sinclair/typebox": "0.34.48",
36
+ "ws": "^8.18.0",
37
+ "zod": "^4.3.6"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.10",
41
+ "@types/ws": "^8.5.14",
42
+ "@vitest/coverage-v8": "^2.1.8",
43
+ "openclaw": "^2026.3.1",
44
+ "tsx": "^4.21.0",
45
+ "typescript": "^5.7.0",
46
+ "vitest": "^2.1.8"
47
+ },
48
+ "peerDependencies": {
49
+ "openclaw": ">=2026.3.1"
50
+ }
51
+ }
@@ -0,0 +1,134 @@
1
+ import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2
+ import type { WeiboConfig, ResolvedWeiboAccount } from "./types.js";
3
+
4
+ const DEFAULT_ACCOUNT_ID = "default";
5
+ const DEFAULT_WS_ENDPOINT = "ws://open-im.api.weibo.com/ws/stream";
6
+ const DEFAULT_TOKEN_ENDPOINT = "http://open-im.api.weibo.com/open/auth/ws_token";
7
+
8
+ function readOptionalNonBlankString(value: unknown): string | undefined {
9
+ if (typeof value === "number" && !Number.isNaN(value)) {
10
+ return String(value);
11
+ }
12
+ if (typeof value !== "string") {
13
+ return undefined;
14
+ }
15
+ const trimmed = value.trim();
16
+ return trimmed ? trimmed : undefined;
17
+ }
18
+
19
+ export function resolveWeiboAccount({
20
+ cfg,
21
+ accountId = DEFAULT_ACCOUNT_ID,
22
+ }: {
23
+ cfg: ClawdbotConfig;
24
+ accountId?: string;
25
+ }): ResolvedWeiboAccount {
26
+ const weiboCfg = cfg.channels?.weibo as WeiboConfig | undefined;
27
+
28
+ const isDefault = accountId === DEFAULT_ACCOUNT_ID;
29
+ const topLevelAppId = readOptionalNonBlankString(weiboCfg?.appId);
30
+ const topLevelAppSecret = readOptionalNonBlankString(weiboCfg?.appSecret);
31
+ const topLevelWsEndpoint = readOptionalNonBlankString(weiboCfg?.wsEndpoint);
32
+ const topLevelTokenEndpoint = readOptionalNonBlankString(weiboCfg?.tokenEndpoint);
33
+
34
+ if (isDefault && weiboCfg) {
35
+ const hasCredentials = !!(topLevelAppId && topLevelAppSecret);
36
+ return {
37
+ accountId: DEFAULT_ACCOUNT_ID,
38
+ enabled: weiboCfg.enabled ?? true,
39
+ configured: hasCredentials,
40
+ name: "Default",
41
+ appId: topLevelAppId,
42
+ appSecret: topLevelAppSecret,
43
+ wsEndpoint: topLevelWsEndpoint ?? DEFAULT_WS_ENDPOINT,
44
+ tokenEndpoint: topLevelTokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT,
45
+ config: {
46
+ dmPolicy: weiboCfg.dmPolicy ?? "open",
47
+ allowFrom: weiboCfg.allowFrom ?? [],
48
+ tokenEndpoint: topLevelTokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT,
49
+ wsEndpoint: topLevelWsEndpoint ?? DEFAULT_WS_ENDPOINT,
50
+ textChunkLimit: weiboCfg.textChunkLimit,
51
+ chunkMode: weiboCfg.chunkMode ?? "raw",
52
+ blockStreaming: weiboCfg.blockStreaming ?? true,
53
+ tools: weiboCfg.tools,
54
+ },
55
+ };
56
+ }
57
+
58
+ const accountCfg = weiboCfg?.accounts?.[accountId];
59
+ const topLevel = {
60
+ appId: topLevelAppId,
61
+ appSecret: topLevelAppSecret,
62
+ wsEndpoint: topLevelWsEndpoint,
63
+ tokenEndpoint: topLevelTokenEndpoint,
64
+ dmPolicy: weiboCfg?.dmPolicy,
65
+ allowFrom: weiboCfg?.allowFrom,
66
+ textChunkLimit: weiboCfg?.textChunkLimit,
67
+ chunkMode: weiboCfg?.chunkMode,
68
+ blockStreaming: weiboCfg?.blockStreaming,
69
+ tools: weiboCfg?.tools,
70
+ };
71
+
72
+ const merged = {
73
+ appId: readOptionalNonBlankString(accountCfg?.appId) ?? topLevel.appId,
74
+ appSecret: readOptionalNonBlankString(accountCfg?.appSecret) ?? topLevel.appSecret,
75
+ wsEndpoint:
76
+ readOptionalNonBlankString(accountCfg?.wsEndpoint)
77
+ ?? topLevel.wsEndpoint
78
+ ?? DEFAULT_WS_ENDPOINT,
79
+ tokenEndpoint:
80
+ readOptionalNonBlankString(accountCfg?.tokenEndpoint)
81
+ ?? topLevel.tokenEndpoint
82
+ ?? DEFAULT_TOKEN_ENDPOINT,
83
+ dmPolicy: accountCfg?.dmPolicy ?? topLevel.dmPolicy ?? "open",
84
+ allowFrom: accountCfg?.allowFrom ?? topLevel.allowFrom ?? [],
85
+ textChunkLimit: accountCfg?.textChunkLimit ?? topLevel.textChunkLimit,
86
+ chunkMode: accountCfg?.chunkMode ?? topLevel.chunkMode ?? "raw",
87
+ blockStreaming: accountCfg?.blockStreaming ?? topLevel.blockStreaming ?? true,
88
+ tools: accountCfg?.tools ?? topLevel.tools,
89
+ };
90
+
91
+ const hasCredentials = !!(merged.appId && merged.appSecret);
92
+
93
+ return {
94
+ accountId,
95
+ enabled: accountCfg?.enabled ?? weiboCfg?.enabled ?? true,
96
+ configured: hasCredentials,
97
+ name: accountCfg?.name,
98
+ appId: merged.appId,
99
+ appSecret: merged.appSecret,
100
+ wsEndpoint: merged.wsEndpoint,
101
+ tokenEndpoint: merged.tokenEndpoint,
102
+ config: {
103
+ dmPolicy: merged.dmPolicy,
104
+ allowFrom: merged.allowFrom,
105
+ tokenEndpoint: merged.tokenEndpoint,
106
+ wsEndpoint: merged.wsEndpoint,
107
+ textChunkLimit: merged.textChunkLimit,
108
+ chunkMode: merged.chunkMode,
109
+ blockStreaming: merged.blockStreaming,
110
+ tools: merged.tools,
111
+ },
112
+ };
113
+ }
114
+
115
+ export function listWeiboAccountIds(cfg: ClawdbotConfig): string[] {
116
+ const weiboCfg = cfg.channels?.weibo as WeiboConfig | undefined;
117
+ const accounts = weiboCfg?.accounts;
118
+ const ids = [DEFAULT_ACCOUNT_ID];
119
+ if (accounts) {
120
+ ids.push(...Object.keys(accounts));
121
+ }
122
+ return ids;
123
+ }
124
+
125
+ export function resolveDefaultWeiboAccountId(cfg: ClawdbotConfig): string {
126
+ return DEFAULT_ACCOUNT_ID;
127
+ }
128
+
129
+ export function listEnabledWeiboAccounts(cfg: ClawdbotConfig): ResolvedWeiboAccount[] {
130
+ const ids = listWeiboAccountIds(cfg);
131
+ return ids
132
+ .map((id) => resolveWeiboAccount({ cfg, accountId: id }))
133
+ .filter((a) => a.enabled && a.configured);
134
+ }