mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-04-24 04:21:01 +08:00
feat: 支持统计龙王和潜水排名
This commit is contained in:
@@ -21,6 +21,10 @@ import type {
|
||||
TimeRankItem,
|
||||
ConsecutiveNightRecord,
|
||||
NightOwlChampion,
|
||||
DragonKingAnalysis,
|
||||
DragonKingRankItem,
|
||||
DivingAnalysis,
|
||||
DivingRankItem,
|
||||
} from '../../../src/types/chat'
|
||||
import { openDatabase } from './core'
|
||||
|
||||
@@ -1012,3 +1016,138 @@ export function getNightOwlAnalysis(sessionId: string, filter?: TimeFilter): Nig
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取龙王排名
|
||||
* 每天发言最多的人+1,统计所有天数
|
||||
*/
|
||||
export function getDragonKingAnalysis(sessionId: string, filter?: TimeFilter): DragonKingAnalysis {
|
||||
const db = openDatabase(sessionId)
|
||||
const emptyResult: DragonKingAnalysis = {
|
||||
rank: [],
|
||||
totalDays: 0,
|
||||
}
|
||||
|
||||
if (!db) return emptyResult
|
||||
|
||||
try {
|
||||
const { clause, params } = buildTimeFilter(filter)
|
||||
const clauseWithSystem = buildSystemMessageFilter(clause)
|
||||
|
||||
// 查询每天每个人的发言数,找出每天的龙王
|
||||
const dailyTopSpeakers = db
|
||||
.prepare(
|
||||
`
|
||||
WITH daily_counts AS (
|
||||
SELECT
|
||||
strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as date,
|
||||
msg.sender_id,
|
||||
m.platform_id,
|
||||
m.name,
|
||||
COUNT(*) as msg_count
|
||||
FROM message msg
|
||||
JOIN member m ON msg.sender_id = m.id
|
||||
${clauseWithSystem}
|
||||
GROUP BY date, msg.sender_id
|
||||
),
|
||||
daily_max AS (
|
||||
SELECT date, MAX(msg_count) as max_count
|
||||
FROM daily_counts
|
||||
GROUP BY date
|
||||
)
|
||||
SELECT dc.sender_id, dc.platform_id, dc.name, COUNT(*) as dragon_days
|
||||
FROM daily_counts dc
|
||||
JOIN daily_max dm ON dc.date = dm.date AND dc.msg_count = dm.max_count
|
||||
GROUP BY dc.sender_id
|
||||
ORDER BY dragon_days DESC
|
||||
`
|
||||
)
|
||||
.all(...params) as Array<{
|
||||
sender_id: number
|
||||
platform_id: string
|
||||
name: string
|
||||
dragon_days: number
|
||||
}>
|
||||
|
||||
// 获取总天数
|
||||
const totalDaysRow = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as total
|
||||
FROM message msg
|
||||
JOIN member m ON msg.sender_id = m.id
|
||||
${clauseWithSystem}
|
||||
`
|
||||
)
|
||||
.get(...params) as { total: number }
|
||||
|
||||
const totalDays = totalDaysRow.total
|
||||
|
||||
const rank: DragonKingRankItem[] = dailyTopSpeakers.map((item) => ({
|
||||
memberId: item.sender_id,
|
||||
platformId: item.platform_id,
|
||||
name: item.name,
|
||||
count: item.dragon_days,
|
||||
percentage: totalDays > 0 ? Math.round((item.dragon_days / totalDays) * 10000) / 100 : 0,
|
||||
}))
|
||||
|
||||
return { rank, totalDays }
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取潜水排名
|
||||
* 所有人的最后一次发言记录,按时间倒序(最久没发言的在前面)
|
||||
*/
|
||||
export function getDivingAnalysis(sessionId: string, filter?: TimeFilter): DivingAnalysis {
|
||||
const db = openDatabase(sessionId)
|
||||
const emptyResult: DivingAnalysis = {
|
||||
rank: [],
|
||||
}
|
||||
|
||||
if (!db) return emptyResult
|
||||
|
||||
try {
|
||||
const { clause, params } = buildTimeFilter(filter)
|
||||
const clauseWithSystem = buildSystemMessageFilter(clause)
|
||||
|
||||
// 查询每个成员的最后发言时间
|
||||
const lastMessages = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
m.id as member_id,
|
||||
m.platform_id,
|
||||
m.name,
|
||||
MAX(msg.ts) as last_ts
|
||||
FROM member m
|
||||
JOIN message msg ON m.id = msg.sender_id
|
||||
${clauseWithSystem.replace('msg.', 'msg.')}
|
||||
GROUP BY m.id
|
||||
ORDER BY last_ts ASC
|
||||
`
|
||||
)
|
||||
.all(...params) as Array<{
|
||||
member_id: number
|
||||
platform_id: string
|
||||
name: string
|
||||
last_ts: number
|
||||
}>
|
||||
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
|
||||
const rank: DivingRankItem[] = lastMessages.map((item) => ({
|
||||
memberId: item.member_id,
|
||||
platformId: item.platform_id,
|
||||
name: item.name,
|
||||
lastMessageTs: item.last_ts,
|
||||
daysSinceLastMessage: Math.floor((now - item.last_ts) / 86400),
|
||||
}))
|
||||
|
||||
return { rank }
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export {
|
||||
getRepeatAnalysis,
|
||||
getCatchphraseAnalysis,
|
||||
getNightOwlAnalysis,
|
||||
getDragonKingAnalysis,
|
||||
getDivingAnalysis,
|
||||
} from './analysis'
|
||||
|
||||
// 类型导出
|
||||
|
||||
@@ -427,6 +427,36 @@ const mainIpcMain = (win: BrowserWindow) => {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取龙王分析数据
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'chat:getDragonKingAnalysis',
|
||||
async (_, sessionId: string, filter?: { startTs?: number; endTs?: number }) => {
|
||||
try {
|
||||
return database.getDragonKingAnalysis(sessionId, filter)
|
||||
} catch (error) {
|
||||
console.error('获取龙王分析失败:', error)
|
||||
return { rank: [], totalDays: 0 }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取潜水分析数据
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'chat:getDivingAnalysis',
|
||||
async (_, sessionId: string, filter?: { startTs?: number; endTs?: number }) => {
|
||||
try {
|
||||
return database.getDivingAnalysis(sessionId, filter)
|
||||
} catch (error) {
|
||||
console.error('获取潜水分析失败:', error)
|
||||
return { rank: [] }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default mainIpcMain
|
||||
|
||||
4
electron/preload/index.d.ts
vendored
4
electron/preload/index.d.ts
vendored
@@ -11,6 +11,8 @@ import type {
|
||||
RepeatAnalysis,
|
||||
CatchphraseAnalysis,
|
||||
NightOwlAnalysis,
|
||||
DragonKingAnalysis,
|
||||
DivingAnalysis,
|
||||
} from '../../src/types/chat'
|
||||
|
||||
interface TimeFilter {
|
||||
@@ -41,6 +43,8 @@ interface ChatApi {
|
||||
getRepeatAnalysis: (sessionId: string, filter?: TimeFilter) => Promise<RepeatAnalysis>
|
||||
getCatchphraseAnalysis: (sessionId: string, filter?: TimeFilter) => Promise<CatchphraseAnalysis>
|
||||
getNightOwlAnalysis: (sessionId: string, filter?: TimeFilter) => Promise<NightOwlAnalysis>
|
||||
getDragonKingAnalysis: (sessionId: string, filter?: TimeFilter) => Promise<DragonKingAnalysis>
|
||||
getDivingAnalysis: (sessionId: string, filter?: TimeFilter) => Promise<DivingAnalysis>
|
||||
}
|
||||
|
||||
interface Api {
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {
|
||||
RepeatAnalysis,
|
||||
CatchphraseAnalysis,
|
||||
NightOwlAnalysis,
|
||||
DragonKingAnalysis,
|
||||
DivingAnalysis,
|
||||
} from '../../src/types/chat'
|
||||
|
||||
// Custom APIs for renderer
|
||||
@@ -190,6 +192,26 @@ const chatApi = {
|
||||
): Promise<NightOwlAnalysis> => {
|
||||
return ipcRenderer.invoke('chat:getNightOwlAnalysis', sessionId, filter)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取龙王分析数据
|
||||
*/
|
||||
getDragonKingAnalysis: (
|
||||
sessionId: string,
|
||||
filter?: { startTs?: number; endTs?: number }
|
||||
): Promise<DragonKingAnalysis> => {
|
||||
return ipcRenderer.invoke('chat:getDragonKingAnalysis', sessionId, filter)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取潜水分析数据
|
||||
*/
|
||||
getDivingAnalysis: (
|
||||
sessionId: string,
|
||||
filter?: { startTs?: number; endTs?: number }
|
||||
): Promise<DivingAnalysis> => {
|
||||
return ipcRenderer.invoke('chat:getDivingAnalysis', sessionId, filter)
|
||||
},
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
Reference in New Issue
Block a user