mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
DingTalk uses QR registration to store client credentials, then reuses the existing IM pairing and session bridge. The merged main implementation keeps the existing WeChat QR binding path intact while adding DingTalk as a peer IM platform. The default IM workdir now falls back to the local user working directory so a newly bound chat can start immediately even when recent-project history is empty. Constraint: Local main already carries WeChat IM, so the merge keeps WeChat config, QR binding, sidecar args, and unbind behavior intact while adding DingTalk. Rejected: Keep empty defaultProjectDir as project-picker-only | newly bound IM users can hit a dead end with no recent projects. Confidence: high Scope-risk: moderate Directive: Keep IM platform unions synchronized across config, pairing, sidecar args, desktop settings, and docs. Tested: bun test common/ dingtalk/ wechat/; bun test src/server/__tests__/adapters.test.ts; cd adapters && bunx tsc --noEmit; bun run check:policy; bun run check:server; bun run check:desktop; bun run check:adapters; bun run check:native; bun run check:docs Not-tested: quality:pr full gate was blocked by existing local main CLI-core diff requiring allow-cli-core-change maintainer approval; live post-fix DingTalk second-message delivery was not repeated after the merge.
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
export type DingTalkRobotMessage = {
|
|
msgId?: string
|
|
msgtype?: string
|
|
conversationType?: string
|
|
conversationId?: string
|
|
conversationTitle?: string
|
|
senderStaffId?: string
|
|
senderId?: string
|
|
senderNick?: string
|
|
sessionWebhook?: string
|
|
text?: { content?: string }
|
|
markdown?: { text?: string; title?: string }
|
|
content?: unknown
|
|
}
|
|
|
|
export function parseDingTalkPayload(raw: unknown): DingTalkRobotMessage | null {
|
|
if (!raw) return null
|
|
if (typeof raw === 'object') return raw as DingTalkRobotMessage
|
|
if (typeof raw !== 'string') return null
|
|
try {
|
|
const parsed = JSON.parse(raw)
|
|
return parsed && typeof parsed === 'object' ? parsed as DingTalkRobotMessage : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
export function isDingTalkDirectMessage(data: DingTalkRobotMessage): boolean {
|
|
return data.conversationType === '1'
|
|
}
|
|
|
|
export function getDingTalkSenderId(data: DingTalkRobotMessage): string | null {
|
|
const senderId = data.senderStaffId || data.senderId
|
|
return senderId ? String(senderId) : null
|
|
}
|
|
|
|
export function getDingTalkChatId(data: DingTalkRobotMessage): string | null {
|
|
const senderId = getDingTalkSenderId(data)
|
|
if (isDingTalkDirectMessage(data)) {
|
|
return senderId ? `dingtalk:dm:${senderId}` : null
|
|
}
|
|
return data.conversationId ? `dingtalk:group:${data.conversationId}` : null
|
|
}
|
|
|
|
export function extractDingTalkText(data: DingTalkRobotMessage): string {
|
|
if (typeof data.text?.content === 'string') return data.text.content.trim()
|
|
if (typeof data.markdown?.text === 'string') return data.markdown.text.trim()
|
|
|
|
const content = resolveContentObject(data.content)
|
|
if (typeof content?.text === 'string') return content.text.trim()
|
|
if (Array.isArray(content?.richText)) {
|
|
return content.richText
|
|
.map((item: unknown) => {
|
|
if (!item || typeof item !== 'object') return ''
|
|
const text = (item as { text?: unknown }).text
|
|
return typeof text === 'string' ? text : ''
|
|
})
|
|
.join('')
|
|
.trim()
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function resolveContentObject(raw: unknown): Record<string, any> | null {
|
|
if (!raw) return null
|
|
if (typeof raw === 'object') return raw as Record<string, any>
|
|
if (typeof raw !== 'string') return null
|
|
try {
|
|
const parsed = JSON.parse(raw)
|
|
return parsed && typeof parsed === 'object' ? parsed as Record<string, any> : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|