mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-18 12:07:01 +08:00
feat: 实现退群监控
This commit is contained in:
+37
-4
@@ -265,19 +265,22 @@ app.whenReady().then(() => {
|
||||
/<appmsg\b|<refermsg\b|<appmsg\b|<refermsg\b/i.test(content)
|
||||
? 49
|
||||
: msgType
|
||||
if ([3, 42, 47, 48, 49, 50].includes(inferredMsgType)) {
|
||||
if ([3, 42, 47, 48, 49, 50, 10000, 10002].includes(inferredMsgType)) {
|
||||
try {
|
||||
const parsed =
|
||||
inferredMsgType === 47
|
||||
? parseStickerMessageFromRow(msg, content)
|
||||
: parseMessageContent(content, inferredMsgType)
|
||||
if (parsed.type !== 'unknown') {
|
||||
if (parsed.type === 'system') {
|
||||
content = parsed.content
|
||||
contentData = parsed
|
||||
} else if (parsed.type !== 'unknown') {
|
||||
content = ''
|
||||
}
|
||||
if (parsed.type === 'image') {
|
||||
const imageDatName = parseImageDatNameFromRow(msg)
|
||||
contentData = { ...parsed, datName: parsed.datName || imageDatName }
|
||||
} else {
|
||||
} else if (parsed.type !== 'system') {
|
||||
if (parsed.type === 'sticker' && !parsed.url && parsed.md5 && wcdb4Client) {
|
||||
parsed.url = wcdb4Client.resolveEmoticonCdnUrl(parsed.md5)
|
||||
}
|
||||
@@ -313,7 +316,7 @@ app.whenReady().then(() => {
|
||||
|
||||
return {
|
||||
id: msg.mesLocalID || Math.random().toString(),
|
||||
from: isMine ? 'assistant' : 'user',
|
||||
from: contentData?.type === 'system' ? 'system' : isMine ? 'assistant' : 'user',
|
||||
type: displayType,
|
||||
datetime: date.toLocaleString('zh-CN', { hour12: false }),
|
||||
content: content,
|
||||
@@ -327,6 +330,36 @@ app.whenReady().then(() => {
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('db:getGroupSnapshot', (_, userMd5: string) => {
|
||||
if (!wechatDb) return null
|
||||
const wcdb4Client = wechatDb.getWcdb4Client()
|
||||
if (!wcdb4Client) return null
|
||||
|
||||
const roomId = wcdb4Client.getUsernameByMd5(userMd5)
|
||||
if (!roomId || !roomId.endsWith('@chatroom')) return null
|
||||
|
||||
const members = wcdb4Client
|
||||
.getGroupMembers(roomId)
|
||||
.filter((member) => member?.m_nsUsrName)
|
||||
.map((member) => ({
|
||||
wxid: member.m_nsUsrName,
|
||||
nickname: member.nickname || '',
|
||||
avatar: member.m_nsHeadImgUrl || ''
|
||||
}))
|
||||
|
||||
console.log(
|
||||
`[GroupSnapshot] roomId=${roomId} memberCount=${members.length} members=${members
|
||||
.map((member) => `${member.nickname || member.wxid}(${member.wxid})`)
|
||||
.join(', ')}`
|
||||
)
|
||||
|
||||
return {
|
||||
roomId,
|
||||
memberCount: members.length,
|
||||
members
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('db:search', (_, keyword: string) => {
|
||||
if (!wechatDb) return null
|
||||
return wechatDb.searchAllMessages(keyword)
|
||||
|
||||
@@ -41,7 +41,7 @@ type QuoteContent = {
|
||||
quotedSender?: string
|
||||
quotedType?: string
|
||||
}
|
||||
type SystemContent = { type: 'system'; content: string }
|
||||
type SystemContent = { type: 'system'; content: string; raw?: string }
|
||||
type UnknownContent = { type: 'unknown'; raw: string }
|
||||
|
||||
export type ParsedContent =
|
||||
@@ -79,12 +79,40 @@ export function parseMessageContent(content: string, messageType: number): Parse
|
||||
return parseVoipMessage(normalized)
|
||||
case 10000:
|
||||
case 10002:
|
||||
return { type: 'system', content: normalized }
|
||||
return parseSystemMessage(normalized)
|
||||
default:
|
||||
return { type: 'text', content: normalized }
|
||||
}
|
||||
}
|
||||
|
||||
function parseSystemMessage(content: string): ParsedContent {
|
||||
const stripped = stripChatroomPrefix(content)
|
||||
const decoded = decodeXmlEntities(stripped)
|
||||
const delChatroomMemberText = extractDelChatroomMemberText(decoded)
|
||||
if (delChatroomMemberText) {
|
||||
return {
|
||||
type: 'system',
|
||||
content: normalizeSystemText(delChatroomMemberText),
|
||||
raw: content
|
||||
}
|
||||
}
|
||||
const plainText =
|
||||
extractXmlNodeText(decoded, 'plain') ||
|
||||
extractXmlNodeText(decoded, 'text') ||
|
||||
extractXmlNodeText(decoded, 'title') ||
|
||||
extractXmlValue(decoded, 'plain') ||
|
||||
extractXmlValue(decoded, 'text') ||
|
||||
extractXmlValue(decoded, 'title') ||
|
||||
''
|
||||
|
||||
const normalized = normalizeSystemText(plainText || fallbackSystemText(decoded))
|
||||
return {
|
||||
type: 'system',
|
||||
content: normalized || '[系统消息]',
|
||||
raw: content
|
||||
}
|
||||
}
|
||||
|
||||
function parseImageMessage(content: string): ParsedContent {
|
||||
// 尝试 XML 格式: <img md5="..." aeskey="..."/>
|
||||
let md5 = extractXmlAttribute(content, 'img', 'md5') || extractXmlValue(content, 'md5') || ''
|
||||
@@ -356,6 +384,20 @@ function sanitizeQuotedContent(content: string): string {
|
||||
return decoded
|
||||
}
|
||||
|
||||
function stripChatroomPrefix(content: string): string {
|
||||
return String(content || '')
|
||||
.replace(/^[0-9a-z_-]+@chatroom:\s*/i, '')
|
||||
.replace(/^wxid_[^:\n]+:\s*/i, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function normalizeSystemText(content: string): string {
|
||||
return String(content || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\s+([,.;!?])/g, '$1')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseVoipMessage(content: string): ParsedContent {
|
||||
const roomTypeStr = extractXmlValue(content, 'room_type')
|
||||
const msg = extractXmlValue(content, 'msg') || ''
|
||||
@@ -416,6 +458,44 @@ function decodeXmlUrl(value: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function decodeXmlEntities(value: string): string {
|
||||
return String(value || '')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, '&')
|
||||
}
|
||||
|
||||
function extractXmlNodeText(xml: string, tagName: string): string {
|
||||
const match = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i').exec(xml)
|
||||
if (!match?.[1]) return ''
|
||||
return normalizeSystemText(
|
||||
decodeXmlEntities(
|
||||
match[1].replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1').replace(/<[^>]+>/g, ' ')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function fallbackSystemText(xml: string): string {
|
||||
return normalizeSystemText(
|
||||
decodeXmlEntities(
|
||||
String(xml || '')
|
||||
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function extractDelChatroomMemberText(xml: string): string {
|
||||
if (!/<sysmsg[^>]+delchatroommember/i.test(xml)) return ''
|
||||
const plainMatch = /<plain[^>]*><!\[CDATA\[([\s\S]*?)\]\]><\/plain>/i.exec(xml)
|
||||
if (plainMatch?.[1]) return plainMatch[1].trim()
|
||||
const textMatch = /<text[^>]*><!\[CDATA\[([\s\S]*?)\]\]><\/text>/i.exec(xml)
|
||||
if (textMatch?.[1]) return textMatch[1].trim()
|
||||
return ''
|
||||
}
|
||||
|
||||
function normalizeMd5(value: unknown): string | undefined {
|
||||
const md5 = String(value || '')
|
||||
.trim()
|
||||
|
||||
Reference in New Issue
Block a user