diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bbe70d8..9482455 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,13 +39,23 @@ jobs:
npx tsc
npx vite build
- - name: Inject Configuration
- shell: bash
- run: |
- npm pkg set build.releaseInfo.releaseNotes=$'仅适配微信 4.0 及以上版本\n\n修复了一些已知问题\n\n详情前往 Telegram 群查看'
-
- name: Package and Publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- npx electron-builder --publish always
\ No newline at end of file
+ npx electron-builder --publish always
+
+ - name: Update Release Notes
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: bash
+ run: |
+ cat <
-
+
+
一群满了加二群
## 主要功能 diff --git a/electron/main.ts b/electron/main.ts index c59b487..95a06c4 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -673,6 +673,10 @@ function registerIpcHandlers() { return chatService.getMessageById(sessionId, localId) }) + ipcMain.handle('chat:execQuery', async (_, kind: string, path: string | null, sql: string) => { + return chatService.execQuery(kind, path, sql) + }) + ipcMain.handle('sns:getTimeline', async (_, limit: number, offset: number, usernames?: string[], keyword?: string, startTime?: number, endTime?: number) => { return snsService.getTimeline(limit, offset, usernames, keyword, startTime, endTime) }) diff --git a/electron/preload.ts b/electron/preload.ts index 6b02eba..6fa3c36 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -118,7 +118,9 @@ contextBridge.exposeInMainWorld('electronAPI', { const listener = (_: any, payload: { msgId: string; text: string }) => callback(payload) ipcRenderer.on('chat:voiceTranscriptPartial', listener) return () => ipcRenderer.removeListener('chat:voiceTranscriptPartial', listener) - } + }, + execQuery: (kind: string, path: string | null, sql: string) => + ipcRenderer.invoke('chat:execQuery', kind, path, sql) }, diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index 66f6795..2c3c143 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -3384,6 +3384,19 @@ class ChatService { } return parsed } + + async execQuery(kind: string, path: string | null, sql: string): Promise<{ success: boolean; rows?: any[]; error?: string }> { + try { + const connectResult = await this.ensureConnected() + if (!connectResult.success) { + return { success: false, error: connectResult.error || '数据库未连接' } + } + return wcdbService.execQuery(kind, path, sql) + } catch (e) { + console.error('ChatService: 执行自定义查询失败:', e) + return { success: false, error: String(e) } + } + } } export const chatService = new ChatService() diff --git a/electron/services/exportHtml.css b/electron/services/exportHtml.css new file mode 100644 index 0000000..53698d2 --- /dev/null +++ b/electron/services/exportHtml.css @@ -0,0 +1,301 @@ +:root { + color-scheme: light; + --bg: #f6f7fb; + --card: #ffffff; + --text: #1f2a37; + --muted: #6b7280; + --accent: #4f46e5; + --sent: #dbeafe; + --received: #ffffff; + --border: #e5e7eb; + --shadow: 0 12px 30px rgba(15, 23, 42, 0.08); + --radius: 16px; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--text); +} + +.page { + max-width: 1080px; + margin: 32px auto 60px; + padding: 0 20px; +} + +.header { + background: var(--card); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 24px; + margin-bottom: 24px; +} + +.title { + font-size: 24px; + font-weight: 600; + margin: 0 0 8px; +} + +.meta { + color: var(--muted); + font-size: 14px; + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.controls { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-top: 20px; +} + +.control { + display: flex; + flex-direction: column; + gap: 6px; +} + +.control label { + font-size: 13px; + color: var(--muted); +} + +.control input, +.control select, +.control button { + border-radius: 12px; + border: 1px solid var(--border); + padding: 10px 12px; + font-size: 14px; + font-family: inherit; +} + +.control button { + background: var(--accent); + color: #fff; + border: none; + cursor: pointer; + transition: transform 0.1s ease; +} + +.control button:active { + transform: scale(0.98); +} + +.stats { + font-size: 13px; + color: var(--muted); + display: flex; + align-items: flex-end; +} + +.message-list { + display: flex; + flex-direction: column; + gap: 18px; +} + +.message { + display: flex; + flex-direction: column; + gap: 8px; +} + +.message.hidden { + display: none; +} + +.message-time { + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; +} + +.message-row { + display: flex; + gap: 12px; + align-items: flex-end; +} + +.message.sent .message-row { + flex-direction: row-reverse; +} + +.avatar { + width: 40px; + height: 40px; + border-radius: 12px; + background: #eef2ff; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + flex-shrink: 0; + color: #475569; + font-weight: 600; +} + +.avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.bubble { + max-width: min(70%, 720px); + background: var(--received); + border-radius: 18px; + padding: 12px 14px; + border: 1px solid var(--border); + box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06); +} + +.message.sent .bubble { + background: var(--sent); + border-color: transparent; +} + +.sender-name { + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; +} + +.message-content { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 14px; + line-height: 1.6; +} + +.message-text { + word-break: break-word; +} + +.inline-emoji { + width: 22px; + height: 22px; + vertical-align: text-bottom; + margin: 0 2px; +} + +.message-media { + border-radius: 14px; + max-width: 100%; +} + +.previewable { + cursor: zoom-in; +} + +.message-media.image, +.message-media.emoji { + max-height: 260px; + object-fit: contain; + background: #f1f5f9; + padding: 6px; +} + +.message-media.emoji { + max-height: 160px; + width: auto; +} + +.message-media.video { + max-height: 360px; + background: #111827; +} + +.message-media.audio { + width: 260px; +} + +.image-preview { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.7); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 999; +} + +.image-preview.active { + opacity: 1; + pointer-events: auto; +} + +.image-preview img { + max-width: min(90vw, 1200px); + max-height: 90vh; + border-radius: 18px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35); + background: #0f172a; + transition: transform 0.1s ease; + cursor: zoom-out; +} + +body[data-theme="cloud-dancer"] { + --accent: #6b8cff; + --sent: #e0e7ff; + --received: #ffffff; + --border: #d8e0f7; + --bg: #f6f7fb; +} + +body[data-theme="corundum-blue"] { + --accent: #2563eb; + --sent: #dbeafe; + --received: #ffffff; + --border: #c7d2fe; + --bg: #eef2ff; +} + +body[data-theme="kiwi-green"] { + --accent: #16a34a; + --sent: #dcfce7; + --received: #ffffff; + --border: #bbf7d0; + --bg: #f0fdf4; +} + +body[data-theme="spicy-red"] { + --accent: #e11d48; + --sent: #ffe4e6; + --received: #ffffff; + --border: #fecdd3; + --bg: #fff1f2; +} + +body[data-theme="teal-water"] { + --accent: #0f766e; + --sent: #ccfbf1; + --received: #ffffff; + --border: #99f6e4; + --bg: #f0fdfa; +} + +.highlight { + outline: 2px solid var(--accent); + outline-offset: 4px; + border-radius: 18px; +} + +.empty { + text-align: center; + color: var(--muted); + padding: 40px; +} diff --git a/electron/services/exportHtmlStyles.ts b/electron/services/exportHtmlStyles.ts new file mode 100644 index 0000000..adb3e61 --- /dev/null +++ b/electron/services/exportHtmlStyles.ts @@ -0,0 +1,302 @@ +export const EXPORT_HTML_STYLES = `:root { + color-scheme: light; + --bg: #f6f7fb; + --card: #ffffff; + --text: #1f2a37; + --muted: #6b7280; + --accent: #4f46e5; + --sent: #dbeafe; + --received: #ffffff; + --border: #e5e7eb; + --shadow: 0 12px 30px rgba(15, 23, 42, 0.08); + --radius: 16px; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--text); +} + +.page { + max-width: 1080px; + margin: 32px auto 60px; + padding: 0 20px; +} + +.header { + background: var(--card); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 24px; + margin-bottom: 24px; +} + +.title { + font-size: 24px; + font-weight: 600; + margin: 0 0 8px; +} + +.meta { + color: var(--muted); + font-size: 14px; + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.controls { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-top: 20px; +} + +.control { + display: flex; + flex-direction: column; + gap: 6px; +} + +.control label { + font-size: 13px; + color: var(--muted); +} + +.control input, +.control select, +.control button { + border-radius: 12px; + border: 1px solid var(--border); + padding: 10px 12px; + font-size: 14px; + font-family: inherit; +} + +.control button { + background: var(--accent); + color: #fff; + border: none; + cursor: pointer; + transition: transform 0.1s ease; +} + +.control button:active { + transform: scale(0.98); +} + +.stats { + font-size: 13px; + color: var(--muted); + display: flex; + align-items: flex-end; +} + +.message-list { + display: flex; + flex-direction: column; + gap: 18px; +} + +.message { + display: flex; + flex-direction: column; + gap: 8px; +} + +.message.hidden { + display: none; +} + +.message-time { + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; +} + +.message-row { + display: flex; + gap: 12px; + align-items: flex-end; +} + +.message.sent .message-row { + flex-direction: row-reverse; +} + +.avatar { + width: 40px; + height: 40px; + border-radius: 12px; + background: #eef2ff; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + flex-shrink: 0; + color: #475569; + font-weight: 600; +} + +.avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.bubble { + max-width: min(70%, 720px); + background: var(--received); + border-radius: 18px; + padding: 12px 14px; + border: 1px solid var(--border); + box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06); +} + +.message.sent .bubble { + background: var(--sent); + border-color: transparent; +} + +.sender-name { + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; +} + +.message-content { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 14px; + line-height: 1.6; +} + +.message-text { + word-break: break-word; +} + +.inline-emoji { + width: 22px; + height: 22px; + vertical-align: text-bottom; + margin: 0 2px; +} + +.message-media { + border-radius: 14px; + max-width: 100%; +} + +.previewable { + cursor: zoom-in; +} + +.message-media.image, +.message-media.emoji { + max-height: 260px; + object-fit: contain; + background: #f1f5f9; + padding: 6px; +} + +.message-media.emoji { + max-height: 160px; + width: auto; +} + +.message-media.video { + max-height: 360px; + background: #111827; +} + +.message-media.audio { + width: 260px; +} + +.image-preview { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.7); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 999; +} + +.image-preview.active { + opacity: 1; + pointer-events: auto; +} + +.image-preview img { + max-width: min(90vw, 1200px); + max-height: 90vh; + border-radius: 18px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35); + background: #0f172a; + transition: transform 0.1s ease; + cursor: zoom-out; +} + +body[data-theme="cloud-dancer"] { + --accent: #6b8cff; + --sent: #e0e7ff; + --received: #ffffff; + --border: #d8e0f7; + --bg: #f6f7fb; +} + +body[data-theme="corundum-blue"] { + --accent: #2563eb; + --sent: #dbeafe; + --received: #ffffff; + --border: #c7d2fe; + --bg: #eef2ff; +} + +body[data-theme="kiwi-green"] { + --accent: #16a34a; + --sent: #dcfce7; + --received: #ffffff; + --border: #bbf7d0; + --bg: #f0fdf4; +} + +body[data-theme="spicy-red"] { + --accent: #e11d48; + --sent: #ffe4e6; + --received: #ffffff; + --border: #fecdd3; + --bg: #fff1f2; +} + +body[data-theme="teal-water"] { + --accent: #0f766e; + --sent: #ccfbf1; + --received: #ffffff; + --border: #99f6e4; + --bg: #f0fdfa; +} + +.highlight { + outline: 2px solid var(--accent); + outline-offset: 4px; + border-radius: 18px; +} + +.empty { + text-align: center; + color: var(--muted); + padding: 40px; +} +`; diff --git a/electron/services/exportService.ts b/electron/services/exportService.ts index 76c3633..b89e68c 100644 --- a/electron/services/exportService.ts +++ b/electron/services/exportService.ts @@ -4,10 +4,13 @@ import * as http from 'http' import * as https from 'https' import { fileURLToPath } from 'url' import ExcelJS from 'exceljs' +import { getEmojiPath } from 'wechat-emojis' import { ConfigService } from './config' import { wcdbService } from './wcdbService' import { imageDecryptService } from './imageDecryptService' import { chatService } from './chatService' +import { videoService } from './videoService' +import { EXPORT_HTML_STYLES } from './exportHtmlStyles' // ChatLab 格式类型定义 interface ChatLabHeader { @@ -72,12 +75,25 @@ export interface ExportOptions { exportEmojis?: boolean exportVoiceAsText?: boolean excelCompactColumns?: boolean + txtColumns?: string[] sessionLayout?: 'shared' | 'per-session' } +const TXT_COLUMN_DEFINITIONS: Array<{ id: string; label: string }> = [ + { id: 'index', label: '序号' }, + { id: 'time', label: '时间' }, + { id: 'senderRole', label: '发送者身份' }, + { id: 'messageType', label: '消息类型' }, + { id: 'content', label: '内容' }, + { id: 'senderNickname', label: '发送者昵称' }, + { id: 'senderWxid', label: '发送者微信ID' }, + { id: 'senderRemark', label: '发送者备注' } +] + interface MediaExportItem { relativePath: string - kind: 'image' | 'voice' | 'emoji' + kind: 'image' | 'voice' | 'emoji' | 'video' + posterDataUrl?: string } export interface ExportProgress { @@ -115,6 +131,8 @@ async function parallelLimit