@dcrays/dcgchat 0.4.24 → 0.4.26

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": "@dcrays/dcgchat",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getWorkspaceDir,
12
12
  setMsgStatus
13
13
  } from './utils/global.js'
14
- import { resolveAccount, sendDcgchatMedia } from './channel.js'
14
+ import { normalizeOutboundMediaPaths, resolveAccount, sendDcgchatMedia } from './channel.js'
15
15
  import { generateSignUrl } from './request/api.js'
16
16
  import { sendChunk, sendFinal, sendText as sendTextMsg, sendError, wsSendRaw, sendText } from './transport.js'
17
17
  import { dcgLogger } from './utils/log.js'
@@ -143,8 +143,11 @@ function buildMediaPayload(mediaList: MediaInfo[]): MediaPayload {
143
143
  }
144
144
 
145
145
  function resolveReplyMediaList(payload: ReplyPayload): string[] {
146
- if (payload.mediaUrls?.length) return payload.mediaUrls.filter(Boolean)
147
- return payload.mediaUrl ? [payload.mediaUrl] : []
146
+ const p = payload as { mediaUrls?: unknown[]; mediaUrl?: unknown }
147
+ if (p.mediaUrls != null && Array.isArray(p.mediaUrls) && p.mediaUrls.length > 0) {
148
+ return normalizeOutboundMediaPaths(p.mediaUrls)
149
+ }
150
+ return normalizeOutboundMediaPaths(p.mediaUrl ?? null)
148
151
  }
149
152
 
150
153
  const typingCallbacks = createTypingCallbacks({
package/src/channel.ts CHANGED
@@ -104,6 +104,53 @@ function outboundChatId(rawTo: string | undefined, normalizedTo: string): string
104
104
  return raw.indexOf('dcg-cron:') >= 0 ? raw : normalizedTo
105
105
  }
106
106
 
107
+ /**
108
+ * 仅从 JSON / `{ file | path | url }` 等结构里取出路径字符串,不做改写(不拼 workspace、不 normalize)。
109
+ */
110
+ function collectOutboundMediaPaths(item: unknown, out: string[]): void {
111
+ if (item == null) return
112
+ if (typeof item === 'string') {
113
+ const t = item.trim()
114
+ if (!t) return
115
+ if (t.startsWith('[')) {
116
+ try {
117
+ const parsed = JSON.parse(t) as unknown
118
+ collectOutboundMediaPaths(parsed, out)
119
+ return
120
+ } catch {
121
+ /* 非 JSON,按普通路径处理 */
122
+ }
123
+ }
124
+ out.push(t)
125
+ return
126
+ }
127
+ if (Array.isArray(item)) {
128
+ for (const el of item) collectOutboundMediaPaths(el, out)
129
+ return
130
+ }
131
+ if (typeof item === 'object') {
132
+ const o = item as Record<string, unknown>
133
+ const raw = o.file ?? o.path ?? o.url
134
+ if (typeof raw === 'string' && raw.trim()) {
135
+ out.push(raw.trim())
136
+ }
137
+ }
138
+ }
139
+
140
+ /** 将出站 media 展平为路径字符串列表(去重保序;路径保持 Core 原样) */
141
+ export function normalizeOutboundMediaPaths(raw: unknown): string[] {
142
+ const acc: string[] = []
143
+ collectOutboundMediaPaths(raw, acc)
144
+ const seen = new Set<string>()
145
+ const deduped: string[] = []
146
+ for (const p of acc) {
147
+ if (!p || seen.has(p)) continue
148
+ seen.add(p)
149
+ deduped.push(p)
150
+ }
151
+ return deduped
152
+ }
153
+
107
154
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
108
155
  const rawOpt = (opts.sessionKey ?? '').trim()
109
156
  const strippedForCron = rawOpt.replace(/^dcg-cron:/i, '').trim()
@@ -130,7 +177,21 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
130
177
  return
131
178
  }
132
179
 
133
- const mediaUrl = opts.mediaUrl?.trim()
180
+ const expanded = normalizeOutboundMediaPaths(opts.mediaUrl)
181
+ if (expanded.length === 0) {
182
+ dcgLogger(
183
+ `dcgchat: sendMedia skipped (no resolvable path): ${typeof opts.mediaUrl === 'string' ? opts.mediaUrl : JSON.stringify(opts.mediaUrl)} sessionKey=${sessionKey}`,
184
+ 'error'
185
+ )
186
+ return
187
+ }
188
+ if (expanded.length > 1) {
189
+ for (const single of expanded) {
190
+ await sendDcgchatMedia({ ...opts, mediaUrl: single })
191
+ }
192
+ return
193
+ }
194
+ const mediaUrl = expanded[0]
134
195
  if (!mediaUrl || !msgCtx.sessionId) {
135
196
  dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
136
197
  return
@@ -370,11 +431,17 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
370
431
  }
371
432
 
372
433
  dcgLogger(`channel sendMedia to ${ctx.to}`)
373
- await sendDcgchatMedia({
374
- sessionKey: to || '',
375
- mediaUrl: ctx.mediaUrl || '',
376
- ...(isCron ? { messageId } : {})
377
- })
434
+
435
+ const ctxExt = ctx as { mediaUrls?: unknown; mediaUrl?: string }
436
+ const rawMedia = ctxExt.mediaUrls ?? ctxExt.mediaUrl
437
+ const paths = normalizeOutboundMediaPaths(rawMedia)
438
+ for (const mediaUrl of paths) {
439
+ await sendDcgchatMedia({
440
+ sessionKey: to || '',
441
+ mediaUrl,
442
+ ...(isCron ? { messageId } : {})
443
+ })
444
+ }
378
445
  return {
379
446
  channel: "dcgchat",
380
447
  messageId,