feat: 新增自言自语榜

This commit is contained in:
digua
2025-11-28 18:50:43 +08:00
parent 2f4fbeaae8
commit 642b2b3d17
8 changed files with 589 additions and 1 deletions

View File

@@ -25,6 +25,9 @@ import type {
DragonKingRankItem,
DivingAnalysis,
DivingRankItem,
MonologueAnalysis,
MonologueRankItem,
MaxComboRecord,
} from '../../../src/types/chat'
import { openDatabase } from './core'
@@ -1151,3 +1154,189 @@ export function getDivingAnalysis(sessionId: string, filter?: TimeFilter): Divin
db.close()
}
}
/**
* 获取自言自语分析
* 定义:一个人连续发言超过三次(间隔<=5分钟
* 分类3-4句加特林、5-9句小作文、10+句(无人区广播)
*/
export function getMonologueAnalysis(sessionId: string, filter?: TimeFilter): MonologueAnalysis {
const db = openDatabase(sessionId)
const emptyResult: MonologueAnalysis = {
rank: [],
maxComboRecord: null,
}
if (!db) return emptyResult
try {
const { clause, params } = buildTimeFilter(filter)
// 构建 WHERE 子句:只统计文本消息
let whereClause = clause
if (whereClause.includes('WHERE')) {
whereClause += " AND m.name != '系统消息' AND msg.type = 0"
} else {
whereClause = " WHERE m.name != '系统消息' AND msg.type = 0"
}
// 获取所有文本消息,按时间排序
const messages = db
.prepare(
`
SELECT
msg.id,
msg.sender_id as senderId,
msg.ts,
m.platform_id as platformId,
m.name
FROM message msg
JOIN member m ON msg.sender_id = m.id
${whereClause}
ORDER BY msg.ts ASC
`
)
.all(...params) as Array<{
id: number
senderId: number
ts: number
platformId: string
name: string
}>
if (messages.length === 0) return emptyResult
// 成员信息映射
const memberInfo = new Map<number, { platformId: string; name: string }>()
// 连击统计memberId -> { totalStreaks, maxCombo, lowStreak, midStreak, highStreak }
const memberStats = new Map<
number,
{
totalStreaks: number
maxCombo: number
lowStreak: number
midStreak: number
highStreak: number
}
>()
// 全群最高纪录
let globalMaxCombo: { memberId: number; comboLength: number; startTs: number } | null = null
// 连击间隔限制5分钟 = 300秒
const MAX_INTERVAL = 300
// 当前连击状态
let currentStreak = {
senderId: -1,
count: 0,
startTs: 0,
lastTs: 0,
}
// 处理一个连击结束
const finishStreak = () => {
if (currentStreak.count >= 3) {
const memberId = currentStreak.senderId
// 初始化成员统计
if (!memberStats.has(memberId)) {
memberStats.set(memberId, {
totalStreaks: 0,
maxCombo: 0,
lowStreak: 0,
midStreak: 0,
highStreak: 0,
})
}
const stats = memberStats.get(memberId)!
stats.totalStreaks++
stats.maxCombo = Math.max(stats.maxCombo, currentStreak.count)
// 分类统计3-4句、5-9句、10+句
if (currentStreak.count >= 10) {
stats.highStreak++
} else if (currentStreak.count >= 5) {
stats.midStreak++
} else {
stats.lowStreak++ // 3-4句
}
// 更新全群最高纪录
if (!globalMaxCombo || currentStreak.count > globalMaxCombo.comboLength) {
globalMaxCombo = {
memberId,
comboLength: currentStreak.count,
startTs: currentStreak.startTs,
}
}
}
}
// 遍历所有消息
for (const msg of messages) {
// 记录成员信息
if (!memberInfo.has(msg.senderId)) {
memberInfo.set(msg.senderId, { platformId: msg.platformId, name: msg.name })
}
// 检查是否延续当前连击
const isSameSender = msg.senderId === currentStreak.senderId
const isWithinInterval = msg.ts - currentStreak.lastTs <= MAX_INTERVAL
if (isSameSender && isWithinInterval) {
// 延续连击
currentStreak.count++
currentStreak.lastTs = msg.ts
} else {
// 结束当前连击,开始新的
finishStreak()
currentStreak = {
senderId: msg.senderId,
count: 1,
startTs: msg.ts,
lastTs: msg.ts,
}
}
}
// 处理最后一个连击
finishStreak()
// 构建排行榜(按总连击次数排序)
const rank: MonologueRankItem[] = []
for (const [memberId, stats] of memberStats.entries()) {
const info = memberInfo.get(memberId)!
rank.push({
memberId,
platformId: info.platformId,
name: info.name,
totalStreaks: stats.totalStreaks,
maxCombo: stats.maxCombo,
lowStreak: stats.lowStreak,
midStreak: stats.midStreak,
highStreak: stats.highStreak,
})
}
rank.sort((a, b) => b.totalStreaks - a.totalStreaks)
// 构建全群最高纪录
let maxComboRecord: MaxComboRecord | null = null
if (globalMaxCombo) {
const info = memberInfo.get(globalMaxCombo.memberId)!
maxComboRecord = {
memberId: globalMaxCombo.memberId,
platformId: info.platformId,
memberName: info.name,
comboLength: globalMaxCombo.comboLength,
startTs: globalMaxCombo.startTs,
}
}
return { rank, maxComboRecord }
} finally {
db.close()
}
}

View File

@@ -21,6 +21,7 @@ export {
getNightOwlAnalysis,
getDragonKingAnalysis,
getDivingAnalysis,
getMonologueAnalysis,
} from './analysis'
// 类型导出