@xmoxmo/bncr 0.4.1 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -153,6 +153,10 @@ function ensurePluginNodeModulesLink(pluginDir: string, targetRoot: string) {
153
153
  }
154
154
 
155
155
  export function resolveBncrRuntimeSourceDir(pluginDir: string) {
156
+ const pluginRoot = resolveBncrPluginRoot(pluginDir);
157
+ const rootSource = path.join(pluginRoot, 'src');
158
+ if (fs.existsSync(path.join(rootSource, 'channel.ts'))) return rootSource;
159
+
156
160
  const direct = path.join(pluginDir, 'src');
157
161
  if (fs.existsSync(path.join(direct, 'channel.ts'))) return direct;
158
162
 
@@ -15,9 +15,30 @@ export type LoadedRuntime = {
15
15
  createBncrChannelPlugin: ChannelModule['createBncrChannelPlugin'];
16
16
  };
17
17
 
18
- export const pluginFile = fileURLToPath(new URL('../../index.ts', import.meta.url));
18
+ export function resolvePluginEntryFileFromModule(moduleUrl: string) {
19
+ const currentFile = fileURLToPath(moduleUrl);
20
+ const pluginRoot = resolveBncrPluginRoot(currentFile);
21
+ const currentDir = path.dirname(currentFile);
22
+ const distEntry = path.join(pluginRoot, 'dist', 'index.js');
23
+ if (currentFile === distEntry && fs.existsSync(distEntry)) return distEntry;
24
+
25
+ const sourceEntry = path.join(pluginRoot, 'index.ts');
26
+ if (fs.existsSync(sourceEntry)) return sourceEntry;
27
+
28
+ if (fs.existsSync(distEntry)) return distEntry;
29
+
30
+ if (path.basename(currentDir) === 'dist') return distEntry;
31
+
32
+ return sourceEntry;
33
+ }
34
+
35
+ function resolvePluginEntryFile() {
36
+ return resolvePluginEntryFileFromModule(import.meta.url);
37
+ }
38
+
39
+ export const pluginFile = resolvePluginEntryFile();
19
40
  export const pluginDir = path.dirname(pluginFile);
20
- export const pluginRequire = createRequire(new URL('../../index.ts', import.meta.url));
41
+ export const pluginRequire = createRequire(pluginFile);
21
42
  export const pluginRoot = resolveBncrPluginRoot(pluginFile);
22
43
 
23
44
  const runtimeSourceDir = resolveBncrRuntimeSourceDir(pluginDir);
@@ -12,6 +12,7 @@ type MinimalBncrSendInput = {
12
12
  media?: string;
13
13
  filePath?: string;
14
14
  mediaUrl?: string;
15
+ mediaUrls?: string[];
15
16
  asVoice?: boolean;
16
17
  audioAsVoice?: boolean;
17
18
  params?: Record<string, unknown>;
@@ -73,13 +74,19 @@ export function buildBncrMessageAction(input: MinimalBncrSendInput): BuiltBncrMe
73
74
  input.filePath,
74
75
  input.mediaUrl,
75
76
  );
77
+ const rawMediaUrls = Array.isArray(paramsObj.mediaUrls)
78
+ ? paramsObj.mediaUrls
79
+ : Array.isArray(input.mediaUrls)
80
+ ? input.mediaUrls
81
+ : undefined;
82
+ const mediaUrls = rawMediaUrls?.map((value) => asString(value || '').trim()).filter(Boolean);
76
83
 
77
84
  const message = pickFirstString(paramsObj.message, input.message) ?? '';
78
85
  const explicitCaption = pickFirstString(paramsObj.caption, input.caption) ?? '';
79
86
  const asVoice = pickFirstBoolean(paramsObj.asVoice, input.asVoice);
80
87
  const audioAsVoice = pickFirstBoolean(paramsObj.audioAsVoice, input.audioAsVoice);
81
88
 
82
- if ((asVoice === true || audioAsVoice === true) && !mediaPath) {
89
+ if ((asVoice === true || audioAsVoice === true) && !mediaPath && !mediaUrls?.length) {
83
90
  throw new Error('bncr voice send requires media path');
84
91
  }
85
92
 
@@ -93,11 +100,20 @@ export function buildBncrMessageAction(input: MinimalBncrSendInput): BuiltBncrMe
93
100
  const finalCaption = explicitCaption || message;
94
101
  if (finalCaption) normalizedParams.caption = finalCaption;
95
102
  delete normalizedParams.message;
103
+ if (mediaUrls?.length) normalizedParams.mediaUrls = mediaUrls;
96
104
  } else {
97
- const finalMessage = message || explicitCaption;
98
- if (!finalMessage.trim()) throw new Error('bncr send requires message or media');
99
- normalizedParams.message = finalMessage;
100
- delete normalizedParams.caption;
105
+ if (mediaUrls?.length) {
106
+ normalizedParams.mediaUrls = mediaUrls;
107
+ const finalCaption = explicitCaption || message;
108
+ if (finalCaption) normalizedParams.caption = finalCaption;
109
+ delete normalizedParams.message;
110
+ delete normalizedParams.path;
111
+ } else {
112
+ const finalMessage = message || explicitCaption;
113
+ if (!finalMessage.trim()) throw new Error('bncr send requires message or media');
114
+ normalizedParams.message = finalMessage;
115
+ delete normalizedParams.caption;
116
+ }
101
117
  }
102
118
 
103
119
  if (asVoice === true) normalizedParams.asVoice = true;
@@ -7,6 +7,7 @@ export type NormalizedBncrSendParams = {
7
7
  message: string;
8
8
  caption: string;
9
9
  mediaUrl?: string;
10
+ mediaUrls?: string[];
10
11
  asVoice: boolean;
11
12
  audioAsVoice: boolean;
12
13
  type?: string;
@@ -33,16 +34,32 @@ export function normalizeBncrSendParams(input: {
33
34
  readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
34
35
  readOpenClawStringParam(paramsObj, 'filePath', { trim: false }) ??
35
36
  readOpenClawStringParam(paramsObj, 'mediaUrl', { trim: false });
37
+ const rawMediaUrls = paramsObj.mediaUrls;
38
+ const mediaUrls = Array.isArray(rawMediaUrls)
39
+ ? Array.from(
40
+ new Set(rawMediaUrls.map((v) => (typeof v === 'string' ? v.trim() : '')).filter(Boolean)),
41
+ )
42
+ : undefined;
43
+ // 如果 mediaUrl 已经在 mediaUrls 中,去重避免重复发送
44
+ const dedupedMediaUrls = mediaUrls?.length
45
+ ? mediaUrl && mediaUrls.includes(mediaUrl)
46
+ ? mediaUrls
47
+ : mediaUrl
48
+ ? [mediaUrl, ...mediaUrls]
49
+ : mediaUrls
50
+ : undefined;
36
51
  const asVoice = readOpenClawBooleanParam(paramsObj, 'asVoice') ?? false;
37
52
  const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
38
53
  const type = readOpenClawStringParam(paramsObj, 'type') || undefined;
39
54
 
40
- if (asVoice && !mediaUrl) throw new Error('send voice requires media path');
55
+ const hasMedia = Boolean(mediaUrl || dedupedMediaUrls?.length);
41
56
 
42
- const normalizedMessage = mediaUrl ? '' : message || caption || '';
43
- const normalizedCaption = mediaUrl ? caption || message || '' : '';
57
+ if (asVoice && !hasMedia) throw new Error('send voice requires media path');
44
58
 
45
- if (!normalizedMessage.trim() && !normalizedCaption.trim() && !mediaUrl) {
59
+ const normalizedMessage = hasMedia ? '' : message || caption || '';
60
+ const normalizedCaption = hasMedia ? caption || message || '' : '';
61
+
62
+ if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia) {
46
63
  throw new Error('send requires message or media');
47
64
  }
48
65
 
@@ -51,7 +68,8 @@ export function normalizeBncrSendParams(input: {
51
68
  accountId: resolvedAccountId,
52
69
  message: normalizedMessage,
53
70
  caption: normalizedCaption,
54
- mediaUrl: mediaUrl || undefined,
71
+ mediaUrl: dedupedMediaUrls?.length ? undefined : mediaUrl || undefined,
72
+ mediaUrls: dedupedMediaUrls,
55
73
  asVoice,
56
74
  audioAsVoice,
57
75
  ...(type ? { type } : {}),
@@ -140,37 +140,39 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
140
140
  const normalized = normalizeBncrSendParams({ params, accountId: accountId || '' });
141
141
 
142
142
  const toolActionBridge = runtime.getToolActionBridge();
143
- const result = normalized.mediaUrl
144
- ? await sendBncrMedia({
145
- channelId: runtime.channelId,
146
- accountId: normalized.accountId,
147
- to: normalized.to,
148
- text: normalized.caption,
149
- mediaUrl: normalized.mediaUrl,
150
- asVoice: normalized.asVoice,
151
- audioAsVoice: normalized.audioAsVoice,
152
- type: normalized.type,
153
- mediaLocalRoots,
154
- resolveVerifiedTarget: (to, accountId) =>
155
- toolActionBridge.resolveVerifiedTarget(to, accountId),
156
- rememberSessionRoute: (sessionKey, accountId, route) =>
157
- toolActionBridge.rememberSessionRoute(sessionKey, accountId, route),
158
- enqueueFromReply: (args) => toolActionBridge.enqueueFromReply(args),
159
- createMessageId: () => randomUUID(),
160
- })
161
- : await sendBncrText({
162
- channelId: runtime.channelId,
163
- accountId: normalized.accountId,
164
- to: normalized.to,
165
- text: normalized.message,
166
- mediaLocalRoots,
167
- resolveVerifiedTarget: (to, accountId) =>
168
- toolActionBridge.resolveVerifiedTarget(to, accountId),
169
- rememberSessionRoute: (sessionKey, accountId, route) =>
170
- toolActionBridge.rememberSessionRoute(sessionKey, accountId, route),
171
- enqueueFromReply: (args) => toolActionBridge.enqueueFromReply(args),
172
- createMessageId: () => randomUUID(),
173
- });
143
+ const result =
144
+ normalized.mediaUrl || normalized.mediaUrls?.length
145
+ ? await sendBncrMedia({
146
+ channelId: runtime.channelId,
147
+ accountId: normalized.accountId,
148
+ to: normalized.to,
149
+ text: normalized.caption,
150
+ mediaUrl: normalized.mediaUrl,
151
+ mediaUrls: normalized.mediaUrls,
152
+ asVoice: normalized.asVoice,
153
+ audioAsVoice: normalized.audioAsVoice,
154
+ type: normalized.type,
155
+ mediaLocalRoots,
156
+ resolveVerifiedTarget: (to, accountId) =>
157
+ toolActionBridge.resolveVerifiedTarget(to, accountId),
158
+ rememberSessionRoute: (sessionKey, accountId, route) =>
159
+ toolActionBridge.rememberSessionRoute(sessionKey, accountId, route),
160
+ enqueueFromReply: (args) => toolActionBridge.enqueueFromReply(args),
161
+ createMessageId: () => randomUUID(),
162
+ })
163
+ : await sendBncrText({
164
+ channelId: runtime.channelId,
165
+ accountId: normalized.accountId,
166
+ to: normalized.to,
167
+ text: normalized.message,
168
+ mediaLocalRoots,
169
+ resolveVerifiedTarget: (to, accountId) =>
170
+ toolActionBridge.resolveVerifiedTarget(to, accountId),
171
+ rememberSessionRoute: (sessionKey, accountId, route) =>
172
+ toolActionBridge.rememberSessionRoute(sessionKey, accountId, route),
173
+ enqueueFromReply: (args) => toolActionBridge.enqueueFromReply(args),
174
+ createMessageId: () => randomUUID(),
175
+ });
174
176
 
175
177
  return runtime.openClawJsonResult({ ok: true, ...result });
176
178
  },