diff --git a/src/main/index.ts b/src/main/index.ts index f34b042..949d29d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1175,6 +1175,9 @@ app.whenReady().then(async () => { ) ipcMain.handle('group-exit-monitor:checkNow', () => groupExitMonitorService.checkNow()) ipcMain.handle('group-exit-monitor:clearEvents', () => groupExitMonitorService.clearEvents()) + ipcMain.handle('group-exit-monitor:resendEvent', (_, eventId: string) => + groupExitMonitorService.resendEvent(eventId) + ) ipcMain.handle('group-exit-monitor:markRead', (_, readAt?: number) => groupExitMonitorService.markRead(readAt) ) diff --git a/src/main/services/group-exit-monitor-service.ts b/src/main/services/group-exit-monitor-service.ts index ca5604e..1b5772f 100644 --- a/src/main/services/group-exit-monitor-service.ts +++ b/src/main/services/group-exit-monitor-service.ts @@ -602,7 +602,8 @@ class GroupExitMonitorService { private async notifyGroup( group: GroupSnapshotRecord, - event: GroupExitMonitorEvent + event: GroupExitMonitorEvent, + idempotencyKey = `member_left_notification:${event.id}` ): Promise { event.notificationStatus = 'pending' event.notification = { status: 'pending' } @@ -610,7 +611,7 @@ class GroupExitMonitorService { this.broadcast() try { const result = await this.actionGateway.execute({ - idempotencyKey: `member_left_notification:${event.id}`, + idempotencyKey, origin: 'member_monitor', purpose: 'member_left_notification', triggerType: 'automation', @@ -665,6 +666,17 @@ class GroupExitMonitorService { } } + async resendEvent(eventId: string): Promise { + const event = this.events.find((item) => item.id === eventId) + if (!event) throw new Error('退群动态不存在') + await this.notifyGroup( + { roomId: event.roomId, groupName: event.groupName } as GroupSnapshotRecord, + event, + `member_left_notification:${event.id}:retry:${Date.now()}` + ) + return this.getState() + } + private filePath(): string { return path.join(app.getPath('userData'), 'group-exit-monitor.json') } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 28dab2c..e458645 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -275,6 +275,7 @@ declare global { setGroupExitMonitorNotificationTemplate: (template: string) => Promise checkGroupExitMonitorNow: () => Promise clearGroupExitMonitorEvents: () => Promise + resendGroupExitMonitorEvent: (eventId: string) => Promise markGroupExitMonitorRead: (readAt?: number) => Promise listWechatActionLogs: () => Promise onGroupExitMonitorState: (callback: (state: GroupExitMonitorState) => void) => () => void diff --git a/src/preload/index.ts b/src/preload/index.ts index 4ccf673..26a36e8 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -155,6 +155,8 @@ const api = { ipcRenderer.invoke('group-exit-monitor:checkNow'), clearGroupExitMonitorEvents: (): Promise => ipcRenderer.invoke('group-exit-monitor:clearEvents'), + resendGroupExitMonitorEvent: (eventId: string): Promise => + ipcRenderer.invoke('group-exit-monitor:resendEvent', eventId), markGroupExitMonitorRead: (readAt?: number): Promise => ipcRenderer.invoke('group-exit-monitor:markRead', readAt), listWechatActionLogs: (): Promise => diff --git a/src/renderer/src/features/group-exit-monitor/GroupExitMonitorWorkspace.tsx b/src/renderer/src/features/group-exit-monitor/GroupExitMonitorWorkspace.tsx index 5a195f5..41c49f3 100644 --- a/src/renderer/src/features/group-exit-monitor/GroupExitMonitorWorkspace.tsx +++ b/src/renderer/src/features/group-exit-monitor/GroupExitMonitorWorkspace.tsx @@ -85,10 +85,14 @@ const sendCapabilityTone = (capability: PersonalWechatSendCapability | null): st function EventNotificationStatus({ event, - onOpenSendSettings + onOpenSendSettings, + onResend, + resending }: { event: GroupExitMonitorState['events'][number] onOpenSendSettings?: () => void + onResend?: () => void + resending?: boolean }): React.ReactElement { const status = event.notificationStatus || event.notification?.status || 'not_requested' const details = @@ -120,6 +124,11 @@ function EventNotificationStatus({ 去设置 ) : null} + {status !== 'sent' && status !== 'pending' && onResend ? ( + + ) : null} ) } @@ -455,6 +464,7 @@ export function GroupExitMonitorWorkspace({ const [templateError, setTemplateError] = React.useState('') const [selectedRoomId, setSelectedRoomId] = React.useState('all') const [view, setView] = React.useState<'events' | 'manage'>('events') + const [resendingEventId, setResendingEventId] = React.useState(null) const [manageKeyword, setManageKeyword] = React.useState('') const [manageFilter, setManageFilter] = React.useState('all') const [selectedManageRoomIds, setSelectedManageRoomIds] = React.useState>( @@ -594,6 +604,19 @@ export function GroupExitMonitorWorkspace({ } } + const resendEvent = async (eventId: string): Promise => { + if (resendingEventId || typeof window.api.resendGroupExitMonitorEvent !== 'function') return + setResendingEventId(eventId) + setError('') + try { + setState(await window.api.resendGroupExitMonitorEvent(eventId)) + } catch (reason) { + setError(reason instanceof Error ? reason.message : String(reason)) + } finally { + setResendingEventId(null) + } + } + const saveGroups = async (): Promise => { const api = window.api if (saving || !dbReady || typeof api.setGroupExitMonitorGroups !== 'function') return @@ -857,6 +880,8 @@ export function GroupExitMonitorWorkspace({ void resendEvent(event.id)} + resending={resendingEventId === event.id} />