新的提交

This commit is contained in:
cc
2026-01-10 13:01:37 +08:00
commit 01641834de
188 changed files with 34865 additions and 0 deletions

91
src/types/analytics.ts Normal file
View File

@@ -0,0 +1,91 @@
// 分析数据类型定义
// 聊天统计数据
export interface ChatStatistics {
totalMessages: number
textMessages: number
imageMessages: number
voiceMessages: number
videoMessages: number
emojiMessages: number
otherMessages: number
sentMessages: number
receivedMessages: number
firstMessageTime: number | null // Unix timestamp
lastMessageTime: number | null
activeDays: number
messageTypeCounts: Record<number, number>
}
// 时间分布统计
export interface TimeDistribution {
hourlyDistribution: Record<number, number> // 0-23
weekdayDistribution: Record<number, number> // 1-7
monthlyDistribution: Record<string, number> // YYYY-MM
}
// 联系人排名
export interface ContactRanking {
username: string
displayName: string
avatarUrl?: string
messageCount: number
sentCount: number
receivedCount: number
lastMessageTime: number | null
}
// 消息类型标签映射
export const MESSAGE_TYPE_LABELS: Record<number, string> = {
1: '文本',
244813135921: '文本',
3: '图片',
34: '语音',
43: '视频',
47: '表情',
48: '位置',
49: '链接/文件',
42: '名片',
10000: '系统消息',
}
// 星期几名称
export const WEEKDAY_NAMES = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// 获取消息类型分布(用于图表)
export function getMessageTypeDistribution(stats: ChatStatistics): Record<string, number> {
if (Object.keys(stats.messageTypeCounts).length > 0) {
const distribution: Record<string, number> = {}
for (const [type, count] of Object.entries(stats.messageTypeCounts)) {
const typeNum = parseInt(type)
const label = MESSAGE_TYPE_LABELS[typeNum] || '其他'
distribution[label] = (distribution[label] || 0) + count
}
return distribution
}
return {
'文本': stats.textMessages,
'图片': stats.imageMessages,
'语音': stats.voiceMessages,
'视频': stats.videoMessages,
'表情': stats.emojiMessages,
'其他': stats.otherMessages,
}
}
// 计算聊天时长(天数)
export function getChatDurationDays(stats: ChatStatistics): number {
if (!stats.firstMessageTime || !stats.lastMessageTime) return 0
const diffMs = (stats.lastMessageTime - stats.firstMessageTime) * 1000
return Math.floor(diffMs / (1000 * 60 * 60 * 24)) + 1
}
// 平均每天消息数
export function getAverageMessagesPerDay(stats: ChatStatistics): number {
const days = getChatDurationDays(stats)
if (days === 0) return 0
return stats.totalMessages / days
}

329
src/types/electron.d.ts vendored Normal file
View File

@@ -0,0 +1,329 @@
import type { ChatSession, Message, Contact } from './models'
export interface ElectronAPI {
window: {
minimize: () => void
maximize: () => void
close: () => void
openAgreementWindow: () => Promise<boolean>
completeOnboarding: () => Promise<boolean>
openOnboardingWindow: () => Promise<boolean>
setTitleBarOverlay: (options: { symbolColor: string }) => void
}
config: {
get: (key: string) => Promise<unknown>
set: (key: string, value: unknown) => Promise<void>
clear: () => Promise<boolean>
}
dialog: {
openFile: (options?: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>
openDirectory: (options?: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>
saveFile: (options?: Electron.SaveDialogOptions) => Promise<Electron.SaveDialogReturnValue>
}
shell: {
openPath: (path: string) => Promise<string>
openExternal: (url: string) => Promise<void>
}
app: {
getDownloadsPath: () => Promise<string>
getVersion: () => Promise<string>
checkForUpdates: () => Promise<{ hasUpdate: boolean; version?: string; releaseNotes?: string }>
downloadAndInstall: () => Promise<void>
onDownloadProgress: (callback: (progress: number) => void) => () => void
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void
}
log: {
getPath: () => Promise<string>
read: () => Promise<{ success: boolean; content?: string; error?: string }>
}
dbPath: {
autoDetect: () => Promise<{ success: boolean; path?: string; error?: string }>
scanWxids: (rootPath: string) => Promise<WxidInfo[]>
getDefault: () => Promise<string>
}
wcdb: {
testConnection: (dbPath: string, hexKey: string, wxid: string) => Promise<{ success: boolean; error?: string; sessionCount?: number }>
open: (dbPath: string, hexKey: string, wxid: string) => Promise<boolean>
close: () => Promise<boolean>
}
key: {
autoGetDbKey: () => Promise<{ success: boolean; key?: string; error?: string; logs?: string[] }>
autoGetImageKey: (manualDir?: string) => Promise<{ success: boolean; xorKey?: number; aesKey?: string; error?: string }>
onDbKeyStatus: (callback: (payload: { message: string; level: number }) => void) => () => void
onImageKeyStatus: (callback: (payload: { message: string }) => void) => () => void
}
chat: {
connect: () => Promise<{ success: boolean; error?: string }>
getSessions: () => Promise<{ success: boolean; sessions?: ChatSession[]; error?: string }>
getMessages: (sessionId: string, offset?: number, limit?: number) => Promise<{
success: boolean;
messages?: Message[];
hasMore?: boolean;
error?: string
}>
getLatestMessages: (sessionId: string, limit?: number) => Promise<{
success: boolean
messages?: Message[]
error?: string
}>
getContact: (username: string) => Promise<Contact | null>
getContactAvatar: (username: string) => Promise<{ avatarUrl?: string; displayName?: string } | null>
getMyAvatarUrl: () => Promise<{ success: boolean; avatarUrl?: string; error?: string }>
downloadEmoji: (cdnUrl: string, md5?: string) => Promise<{ success: boolean; localPath?: string; error?: string }>
close: () => Promise<boolean>
getSessionDetail: (sessionId: string) => Promise<{
success: boolean
detail?: {
wxid: string
displayName: string
remark?: string
nickName?: string
alias?: string
avatarUrl?: string
messageCount: number
firstMessageTime?: number
latestMessageTime?: number
messageTables: { dbName: string; tableName: string; count: number }[]
}
error?: string
}>
getImageData: (sessionId: string, msgId: string) => Promise<{ success: boolean; data?: string; error?: string }>
getVoiceData: (sessionId: string, msgId: string) => Promise<{ success: boolean; data?: string; error?: string }>
}
image: {
decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => Promise<{ success: boolean; localPath?: string; error?: string }>
resolveCache: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string }) => Promise<{ success: boolean; localPath?: string; hasUpdate?: boolean; error?: string }>
preload: (payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>) => Promise<boolean>
onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => () => void
onCacheResolved: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string; localPath: string }) => void) => () => void
}
analytics: {
getOverallStatistics: () => Promise<{
success: boolean
data?: {
totalMessages: number
textMessages: number
imageMessages: number
voiceMessages: number
videoMessages: number
emojiMessages: number
otherMessages: number
sentMessages: number
receivedMessages: number
firstMessageTime: number | null
lastMessageTime: number | null
activeDays: number
messageTypeCounts: Record<number, number>
}
error?: string
}>
getContactRankings: (limit?: number) => Promise<{
success: boolean
data?: Array<{
username: string
displayName: string
avatarUrl?: string
messageCount: number
sentCount: number
receivedCount: number
lastMessageTime: number | null
}>
error?: string
}>
getTimeDistribution: () => Promise<{
success: boolean
data?: {
hourlyDistribution: Record<number, number>
weekdayDistribution: Record<number, number>
monthlyDistribution: Record<string, number>
}
error?: string
}>
onProgress: (callback: (payload: { status: string; progress: number }) => void) => () => void
}
groupAnalytics: {
getGroupChats: () => Promise<{
success: boolean
data?: Array<{
username: string
displayName: string
memberCount: number
avatarUrl?: string
}>
error?: string
}>
getGroupMembers: (chatroomId: string) => Promise<{
success: boolean
data?: Array<{
username: string
displayName: string
avatarUrl?: string
}>
error?: string
}>
getGroupMessageRanking: (chatroomId: string, limit?: number, startTime?: number, endTime?: number) => Promise<{
success: boolean
data?: Array<{
member: {
username: string
displayName: string
avatarUrl?: string
}
messageCount: number
}>
error?: string
}>
getGroupActiveHours: (chatroomId: string, startTime?: number, endTime?: number) => Promise<{
success: boolean
data?: {
hourlyDistribution: Record<number, number>
}
error?: string
}>
getGroupMediaStats: (chatroomId: string, startTime?: number, endTime?: number) => Promise<{
success: boolean
data?: {
typeCounts: Array<{
type: number
name: string
count: number
}>
total: number
}
error?: string
}>
}
annualReport: {
getAvailableYears: () => Promise<{
success: boolean
data?: number[]
error?: string
}>
generateReport: (year: number) => Promise<{
success: boolean
data?: {
year: number
totalMessages: number
totalFriends: number
coreFriends: Array<{
username: string
displayName: string
avatarUrl?: string
messageCount: number
sentCount: number
receivedCount: number
}>
monthlyTopFriends: Array<{
month: number
displayName: string
avatarUrl?: string
messageCount: number
}>
peakDay: {
date: string
messageCount: number
topFriend?: string
topFriendCount?: number
} | null
longestStreak: {
friendName: string
days: number
startDate: string
endDate: string
} | null
activityHeatmap: {
data: number[][]
}
midnightKing: {
displayName: string
count: number
percentage: number
} | null
selfAvatarUrl?: string
mutualFriend: {
displayName: string
avatarUrl?: string
sentCount: number
receivedCount: number
ratio: number
} | null
socialInitiative: {
initiatedChats: number
receivedChats: number
initiativeRate: number
} | null
responseSpeed: {
avgResponseTime: number
fastestFriend: string
fastestTime: number
} | null
topPhrases: Array<{
phrase: string
count: number
}>
}
error?: string
}>
exportImages: (payload: { baseDir: string; folderName: string; images: Array<{ name: string; dataUrl: string }> }) => Promise<{
success: boolean
dir?: string
error?: string
}>
onProgress: (callback: (payload: { status: string; progress: number }) => void) => () => void
}
export: {
exportSessions: (sessionIds: string[], outputDir: string, options: ExportOptions) => Promise<{
success: boolean
successCount?: number
failCount?: number
error?: string
}>
exportSession: (sessionId: string, outputPath: string, options: ExportOptions) => Promise<{
success: boolean
error?: string
}>
}
}
export interface ExportOptions {
format: 'chatlab' | 'chatlab-jsonl' | 'json' | 'html' | 'txt' | 'excel' | 'sql'
dateRange?: { start: number; end: number } | null
exportMedia?: boolean
exportAvatars?: boolean
}
export interface WxidInfo {
wxid: string
modifiedTime: number
}
declare global {
interface Window {
electronAPI: ElectronAPI
}
// Electron 类型声明
namespace Electron {
interface OpenDialogOptions {
title?: string
defaultPath?: string
filters?: { name: string; extensions: string[] }[]
properties?: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[]
}
interface OpenDialogReturnValue {
canceled: boolean
filePaths: string[]
}
interface SaveDialogOptions {
title?: string
defaultPath?: string
filters?: { name: string; extensions: string[] }[]
}
interface SaveDialogReturnValue {
canceled: boolean
filePath?: string
}
}
}
export { }

55
src/types/models.ts Normal file
View File

@@ -0,0 +1,55 @@
// 聊天会话
export interface ChatSession {
username: string
type: number
unreadCount: number
summary: string
sortTimestamp: number // 用于排序
lastTimestamp: number // 用于显示时间
lastMsgType: number
displayName?: string
avatarUrl?: string
}
// 联系人
export interface Contact {
id: number
username: string
localType: number
alias: string
remark: string
nickName: string
bigHeadUrl: string
smallHeadUrl: string
}
// 消息
export interface Message {
localId: number
serverId: number
localType: number
createTime: number
sortSeq: number
isSend: number | null
senderUsername: string | null
parsedContent: string
imageMd5?: string
imageDatName?: string
emojiCdnUrl?: string
emojiMd5?: string
voiceDurationSeconds?: number
// 引用消息
quotedContent?: string
quotedSender?: string
}
// 分析数据
export interface AnalyticsData {
totalMessages: number
totalDays: number
myMessages: number
otherMessages: number
messagesByType: Record<number, number>
messagesByHour: number[]
messagesByDay: number[]
}