feat: 增强评论功能,添加表情和图片支持

This commit is contained in:
ILoveBingLu
2026-03-02 02:52:19 +08:00
parent 732312fc37
commit ee4f580ebf
5 changed files with 333 additions and 26 deletions
+109 -9
View File
@@ -44,6 +44,43 @@ export interface SnsShareInfo {
type?: number
}
export interface SnsCommentEmoji {
url: string
md5: string
width: number
height: number
encryptUrl?: string
aesKey?: string
}
export interface SnsCommentImage {
url: string
token?: string
key?: string
encIdx?: string
thumbUrl?: string
thumbUrlToken?: string
thumbKey?: string
thumbEncIdx?: string
width?: number
height?: number
heightPercentage?: number
fileSize?: number
minArea?: number
mediaId?: string
md5?: string
}
export interface SnsComment {
id: string
nickname: string
content: string
refCommentId: string
refNickname?: string
emojis?: SnsCommentEmoji[]
images?: SnsCommentImage[]
}
export interface SnsPost {
id: string
username: string
@@ -55,7 +92,7 @@ export interface SnsPost {
media: SnsMedia[]
shareInfo?: SnsShareInfo
likes: string[]
comments: { id: string; nickname: string; content: string; refCommentId: string; refNickname?: string; emojis?: { url: string; md5: string; width: number; height: number; encryptUrl?: string; aesKey?: string }[] }[]
comments: SnsComment[]
rawXml?: string
}
@@ -465,10 +502,20 @@ class SnsService {
/**
* 从 XML 中解析评论信息
*/
private parseCommentsFromXml(xml: string): { id: string; nickname: string; content: string; refCommentId: string; refNickname?: string; emojis?: { url: string; md5: string; width: number; height: number }[] }[] {
private parseCommentsFromXml(xml: string): SnsComment[] {
if (!xml) return []
type CommentItem = { id: string; nickname: string; username?: string; content: string; refCommentId: string; refUsername?: string; refNickname?: string; emojis?: { url: string; md5: string; width: number; height: number }[] }
type CommentItem = {
id: string
nickname: string
username?: string
content: string
refCommentId: string
refUsername?: string
refNickname?: string
emojis?: SnsCommentEmoji[]
images?: SnsCommentImage[]
}
const comments: CommentItem[] = []
try {
// 方式1: 查找 <CommentUserList> 标签
@@ -525,7 +572,7 @@ class SnsService {
const refUsernameMatch = commentUserXml.match(/<ref_username>([^<]*)<\/ref_username>/i)
// 提取表情包信息
const emojis: { url: string; md5: string; width: number; height: number; encryptUrl?: string; aesKey?: string }[] = []
const emojis: SnsCommentEmoji[] = []
const emojiRegex = /<emojiinfo>([\s\S]*?)<\/emojiinfo>/gi
let emojiMatch
while ((emojiMatch = emojiRegex.exec(commentUserXml)) !== null) {
@@ -558,8 +605,46 @@ class SnsService {
}
}
// 昵称存在即可(content 可能为空但有表情包
if (nicknameMatch && (contentMatch || emojis.length > 0)) {
// 提取评论图片信息(评论图片走 imagelist/imageinfo
const images: SnsCommentImage[] = []
const imageRegex = /<imageinfo>([\s\S]*?)<\/imageinfo>/gi
let imageMatch
while ((imageMatch = imageRegex.exec(commentUserXml)) !== null) {
const imageXml = imageMatch[1]
const pick = (tag: string) => {
const m = imageXml.match(new RegExp(`<${tag}>([^<]*)<\\/${tag}>`, 'i'))
return m ? m[1].trim().replace(/&amp;/g, '&') : ''
}
const parseNum = (value: string) => {
const n = parseInt(value, 10)
return Number.isFinite(n) ? n : undefined
}
const imageInfo: SnsCommentImage = {
url: pick('url'),
token: pick('token') || undefined,
key: pick('key') || undefined,
encIdx: pick('enc_idx') || undefined,
thumbUrl: pick('thumb_url') || undefined,
thumbUrlToken: pick('thumb_url_token') || undefined,
thumbKey: pick('thumb_key') || undefined,
thumbEncIdx: pick('thumb_enc_idx') || undefined,
width: parseNum(pick('width')),
height: parseNum(pick('height')),
heightPercentage: parseNum(pick('height_percentage')),
fileSize: parseNum(pick('file_size')),
minArea: parseNum(pick('min_area')),
mediaId: pick('media_id') || undefined,
md5: pick('md5') || undefined
}
if (imageInfo.url || imageInfo.thumbUrl) {
images.push(imageInfo)
}
}
// 昵称存在即可(content 可能为空但有表情包/图片)
if (nicknameMatch && (contentMatch || emojis.length > 0 || images.length > 0)) {
const refCommentId = refCommentIdMatch ? refCommentIdMatch[1].trim() : ''
comments.push({
id: idMatch ? idMatch[1].trim() : `comment_${Date.now()}_${Math.random()}`,
@@ -569,7 +654,8 @@ class SnsService {
refCommentId: (refCommentId === '0') ? '' : refCommentId,
refUsername: refUsernameMatch ? refUsernameMatch[1].trim() : undefined,
refNickname: refNicknameMatch ? refNicknameMatch[1].trim() : undefined,
emojis: emojis.length > 0 ? emojis : undefined
emojis: emojis.length > 0 ? emojis : undefined,
images: images.length > 0 ? images : undefined
})
}
}
@@ -593,6 +679,20 @@ class SnsService {
return comments
}
private normalizeComments(comments: SnsComment[]): SnsComment[] {
return comments.map((c) => {
const fixedImages = c.images?.map((img) => ({
...img,
url: fixSnsUrl(img.url || '', img.token, false),
thumbUrl: fixSnsUrl(img.thumbUrl || '', img.thumbUrlToken || img.token, false)
}))
return {
...c,
images: fixedImages
}
})
}
/**
* 从 XML 中解析媒体信息
*/
@@ -1287,7 +1387,7 @@ class SnsService {
})
const likes = this.parseLikesFromXml(xmlContent)
const comments = this.parseCommentsFromXml(xmlContent)
const comments = this.normalizeComments(this.parseCommentsFromXml(xmlContent))
return {
id: idMatch ? idMatch[1] : String(row.tid),
@@ -1418,7 +1518,7 @@ class SnsService {
// 提取点赞和评论
const likes = this.parseLikesFromXml(xmlContent)
const comments = this.parseCommentsFromXml(xmlContent)
const comments = this.normalizeComments(this.parseCommentsFromXml(xmlContent))
return {
id: snsId,
+24 -9
View File
@@ -6,9 +6,9 @@ import vm from 'vm';
let app: any;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
app = require('electron').app;
app = require('electron')?.app;
} catch (e) {
app = { isPackaged: false };
app = null;
}
// This service handles the loading and execution of the WeChat WASM module
@@ -35,13 +35,28 @@ export class WasmService {
this.initPromise = new Promise((resolve, reject) => {
try {
// For dev, files are in electron/assets/wasm
// __dirname in dev (from dist-electron) is .../dist-electron
// So we need to go up one level and then into electron/assets/wasm
const isDev = !app.isPackaged;
const basePath = isDev
? path.join(__dirname, '../electron/assets/wasm')
: path.join(process.resourcesPath, 'assets/wasm'); // Adjust as needed for production build
const isPackaged = !!app && app.isPackaged === true;
const candidates = isPackaged
? [path.join(process.resourcesPath, 'assets/wasm')]
: [
path.join(__dirname, '../assets/wasm'),
path.join(__dirname, '../electron/assets/wasm'),
path.join(process.cwd(), 'electron/assets/wasm')
];
let basePath = '';
for (const p of candidates) {
const wasmCandidate = path.join(p, 'wasm_video_decode.wasm');
const jsCandidate = path.join(p, 'wasm_video_decode.js');
if (fs.existsSync(wasmCandidate) && fs.existsSync(jsCandidate)) {
basePath = p;
break;
}
}
if (!basePath) {
throw new Error(`WASM files not found, checked: ${candidates.join(', ')}`);
}
const wasmPath = path.join(basePath, 'wasm_video_decode.wasm');
const jsPath = path.join(basePath, 'wasm_video_decode.js');