@dcrays/dcgchat 0.4.24 → 0.4.25

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.25",
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
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs'
2
+ import path from 'node:path'
2
3
  import type { ChannelPlugin, OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
3
4
  import { createPluginRuntimeStore, DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk'
4
5
  import type { ResolvedDcgchatAccount, DcgchatConfig } from './types.js'
@@ -9,6 +10,7 @@ import {
9
10
  getDcgchatRuntime,
10
11
  getInfoBySessionKey,
11
12
  getOpenClawConfig,
13
+ getWorkspaceDir,
12
14
  hasSentMediaKey,
13
15
  setCronMessageId
14
16
  } from './utils/global.js'
@@ -104,6 +106,64 @@ function outboundChatId(rawTo: string | undefined, normalizedTo: string): string
104
106
  return raw.indexOf('dcg-cron:') >= 0 ? raw : normalizedTo
105
107
  }
106
108
 
109
+ /**
110
+ * Core / message 工具可能传入:
111
+ * - 本地绝对路径字符串
112
+ * - 工作区虚拟路径(如 `/mobook/xxx`,需落到 `getWorkspaceDir()`)
113
+ * - `mediaUrls` 为 `{ file: string }[]` 或整段 JSON 字符串
114
+ */
115
+ function resolveWorkspaceMediaPath(p: string): string {
116
+ const s = p.trim()
117
+ if (!s) return ''
118
+ if (fs.existsSync(s)) return path.normalize(s)
119
+ const rel = s.replace(/^[\\/]+/, '')
120
+ return path.normalize(path.join(getWorkspaceDir(), rel))
121
+ }
122
+
123
+ function collectOutboundMediaPaths(item: unknown, out: string[]): void {
124
+ if (item == null) return
125
+ if (typeof item === 'string') {
126
+ const t = item.trim()
127
+ if (!t) return
128
+ if (t.startsWith('[')) {
129
+ try {
130
+ const parsed = JSON.parse(t) as unknown
131
+ collectOutboundMediaPaths(parsed, out)
132
+ return
133
+ } catch {
134
+ /* 非 JSON,按普通路径处理 */
135
+ }
136
+ }
137
+ out.push(resolveWorkspaceMediaPath(t))
138
+ return
139
+ }
140
+ if (Array.isArray(item)) {
141
+ for (const el of item) collectOutboundMediaPaths(el, out)
142
+ return
143
+ }
144
+ if (typeof item === 'object') {
145
+ const o = item as Record<string, unknown>
146
+ const raw = o.file ?? o.path ?? o.url
147
+ if (typeof raw === 'string' && raw.trim()) {
148
+ out.push(resolveWorkspaceMediaPath(raw))
149
+ }
150
+ }
151
+ }
152
+
153
+ /** 将出站 media 载荷统一为可 `fs` 访问的本地路径列表(去重保序) */
154
+ export function normalizeOutboundMediaPaths(raw: unknown): string[] {
155
+ const acc: string[] = []
156
+ collectOutboundMediaPaths(raw, acc)
157
+ const seen = new Set<string>()
158
+ const deduped: string[] = []
159
+ for (const p of acc) {
160
+ if (!p || seen.has(p)) continue
161
+ seen.add(p)
162
+ deduped.push(p)
163
+ }
164
+ return deduped
165
+ }
166
+
107
167
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
108
168
  const rawOpt = (opts.sessionKey ?? '').trim()
109
169
  const strippedForCron = rawOpt.replace(/^dcg-cron:/i, '').trim()
@@ -130,7 +190,21 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
130
190
  return
131
191
  }
132
192
 
133
- const mediaUrl = opts.mediaUrl?.trim()
193
+ const expanded = normalizeOutboundMediaPaths(opts.mediaUrl)
194
+ if (expanded.length === 0) {
195
+ dcgLogger(
196
+ `dcgchat: sendMedia skipped (no resolvable path): ${typeof opts.mediaUrl === 'string' ? opts.mediaUrl : JSON.stringify(opts.mediaUrl)} sessionKey=${sessionKey}`,
197
+ 'error'
198
+ )
199
+ return
200
+ }
201
+ if (expanded.length > 1) {
202
+ for (const single of expanded) {
203
+ await sendDcgchatMedia({ ...opts, mediaUrl: single })
204
+ }
205
+ return
206
+ }
207
+ const mediaUrl = expanded[0]
134
208
  if (!mediaUrl || !msgCtx.sessionId) {
135
209
  dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
136
210
  return
@@ -370,11 +444,17 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
370
444
  }
371
445
 
372
446
  dcgLogger(`channel sendMedia to ${ctx.to}`)
373
- await sendDcgchatMedia({
374
- sessionKey: to || '',
375
- mediaUrl: ctx.mediaUrl || '',
376
- ...(isCron ? { messageId } : {})
377
- })
447
+
448
+ const ctxExt = ctx as { mediaUrls?: unknown; mediaUrl?: string }
449
+ const rawMedia = ctxExt.mediaUrls ?? ctxExt.mediaUrl
450
+ const paths = normalizeOutboundMediaPaths(rawMedia)
451
+ for (const mediaUrl of paths) {
452
+ await sendDcgchatMedia({
453
+ sessionKey: to || '',
454
+ mediaUrl,
455
+ ...(isCron ? { messageId } : {})
456
+ })
457
+ }
378
458
  return {
379
459
  channel: "dcgchat",
380
460
  messageId,