@dcrays/dcgchat-test 0.4.24 → 0.4.27

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-test",
3
- "version": "0.4.24",
3
+ "version": "0.4.27",
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,3 +1,4 @@
1
+ import fs from 'node:fs'
1
2
  import type { ChannelPlugin, OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
2
3
  import { createPluginRuntimeStore, DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk'
3
4
  import type { ResolvedDcgchatAccount, DcgchatConfig } from './types.js'
@@ -103,6 +104,53 @@ function outboundChatId(rawTo: string | undefined, normalizedTo: string): string
103
104
  return raw.indexOf('dcg-cron:') >= 0 ? raw : normalizedTo
104
105
  }
105
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
+
106
154
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
107
155
  const rawOpt = (opts.sessionKey ?? '').trim()
108
156
  const strippedForCron = rawOpt.replace(/^dcg-cron:/i, '').trim()
@@ -129,7 +177,34 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
129
177
  return
130
178
  }
131
179
 
132
- const mediaUrl = opts.mediaUrl
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]
195
+ if (!mediaUrl || !msgCtx.sessionId) {
196
+ dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
197
+ return
198
+ }
199
+ // 判断文件存在
200
+ try {
201
+ if (!fs.existsSync(mediaUrl)) {
202
+ dcgLogger(`dcgchat: sendMedia skipped (file not found): ${mediaUrl} sessionKey=${sessionKey}`, 'error')
203
+ return
204
+ }
205
+ } catch (err) {
206
+ dcgLogger(`dcgchat: sendMedia skipped (cannot stat path): ${mediaUrl} ${String(err)} sessionKey=${sessionKey}`, 'error')
207
+ }
133
208
 
134
209
  if (mediaUrl && msgCtx.sessionId) {
135
210
  if (hasSentMediaKey(msgCtx.sessionId, mediaUrl)) {
@@ -143,10 +218,6 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
143
218
  if (!msgCtx.sessionId) {
144
219
  msgCtx.sessionId = sessionId
145
220
  }
146
- if (!mediaUrl || !msgCtx.sessionId) {
147
- dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
148
- return
149
- }
150
221
  const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
151
222
  const notMessageId = `${msgCtx?.messageId}`?.length === 13 || !msgCtx?.messageId
152
223
  try {
@@ -360,11 +431,17 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
360
431
  }
361
432
 
362
433
  dcgLogger(`channel sendMedia to ${ctx.to}`)
363
- await sendDcgchatMedia({
364
- sessionKey: to || '',
365
- mediaUrl: ctx.mediaUrl || '',
366
- ...(isCron ? { messageId } : {})
367
- })
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
+ }
368
445
  return {
369
446
  channel: "dcgchat-test",
370
447
  messageId,
@@ -137,7 +137,7 @@ function patchCronDeliveryInParams(
137
137
  if (agentId) d.accountId = agentId
138
138
  if (announceNoChannel) {
139
139
  d.bestEffort = true
140
- d.channel = 'dcgchat-test'
140
+ d.channel = "dcgchat-test"
141
141
  }
142
142
  }
143
143
 
@@ -191,7 +191,7 @@ export function cronToolCall(event: { toolName: any; params: any; toolCallId: an
191
191
  if (params.command.indexOf('cron create') > -1 || params.command.indexOf('cron add') > -1) {
192
192
  const newParams = JSON.parse(JSON.stringify(params)) as Record<string, unknown>
193
193
  newParams.command =
194
- params.command.replace('--json', '') + ` --session-key ${sk} --channel ${'dcgchat-test'} --to dcg-cron:${sk} --json`
194
+ params.command.replace('--json', '') + ` --session-key ${sk} --channel ${"dcgchat-test"} --to dcg-cron:${sk} --json`
195
195
  return { params: newParams }
196
196
  } else {
197
197
  return undefined