feat: 所有数据解析完全后台进行以解决页面未响应的问题;优化了头像渲染逻辑以提升渲染速度

fix: 修复了虚拟机上无法索引到wxkey的问题;修复图片密钥扫描的问题;修复年度报告错误;修复了年度报告和数据分析中的发送者错误问题;修复了部分页面偶发的未渲染名称问题;修复了头像偶发渲染失败的问题;修复了部分图片无法解密的问题
This commit is contained in:
cc committed 2026-01-14 22:43:42 +08:00
1 parent 3151f79ee7
commit 2e41a03c96
32 files changed
+2772 -4130

No files matched your search

Submodule
+1
Submodule echotrace added at 98280f0d0d.
+8
View File
@@ -62,6 +62,12 @@ function isThumbnailDat(fileName: string): boolean {
return fileName.includes('.t.dat') || fileName.includes('_t.dat')
}
function isHdDat(fileName: string): boolean {
const lower = fileName.toLowerCase()
const base = lower.endsWith('.dat') ? lower.slice(0, -4) : lower
return base.endsWith('_hd') || base.endsWith('_h')
}
function walkForDat(
root: string,
datName: string,
@@ -101,6 +107,8 @@ function walkForDat(
if (!isLikelyImageDatBase(baseLower)) continue
if (!hasXVariant(baseLower)) continue
if (!matchesDatName(lower, datName)) continue
// 排除高清图片格式 (_hd, _h)
if (isHdDat(lower)) continue
matchedBases.add(baseLower)
const isThumb = isThumbnailDat(lower)
if (!allowThumbnail && isThumb) continue
+4 -2
View File
@@ -382,6 +382,8 @@ function registerIpcHandlers() {
return true
})
// 聊天相关
ipcMain.handle('chat:connect', async () => {
return chatService.connect()
@@ -468,8 +470,8 @@ function registerIpcHandlers() {
})
// 数据分析相关
ipcMain.handle('analytics:getOverallStatistics', async () => {
return analyticsService.getOverallStatistics()
ipcMain.handle('analytics:getOverallStatistics', async (_, force?: boolean) => {
return analyticsService.getOverallStatistics(force)
})
ipcMain.handle('analytics:getContactRankings', async (_, limit?: number) => {
+2 -1
View File
@@ -69,7 +69,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
ipcRenderer.invoke('wcdb:testConnection', dbPath, hexKey, wxid),
open: (dbPath: string, hexKey: string, wxid: string) =>
ipcRenderer.invoke('wcdb:open', dbPath, hexKey, wxid),
close: () => ipcRenderer.invoke('wcdb:close')
close: () => ipcRenderer.invoke('wcdb:close'),
},
// 密钥获取
+50 -7
View File
@@ -1,5 +1,8 @@
import { ConfigService } from './config'
import { wcdbService } from './wcdbService'
import { join } from 'path'
import { readFile, writeFile } from 'fs/promises'
import { app } from 'electron'
export interface ChatStatistics {
totalMessages: number
@@ -253,15 +256,31 @@ class AnalyticsService {
sessionIds: string[],
beginTimestamp = 0,
endTimestamp = 0,
window?: any
window?: any,
force = false
): Promise<{ success: boolean; data?: any; source?: string; error?: string }> {
const cacheKey = this.buildAggregateCacheKey(sessionIds, beginTimestamp, endTimestamp)
if (this.aggregateCache && this.aggregateCache.key === cacheKey) {
if (force) {
if (this.aggregateCache) this.aggregateCache = null
if (this.fallbackAggregateCache) this.fallbackAggregateCache = null
}
if (!force && this.aggregateCache && this.aggregateCache.key === cacheKey) {
if (Date.now() - this.aggregateCache.updatedAt < 5 * 60 * 1000) {
return { success: true, data: this.aggregateCache.data, source: 'cache' }
}
}
// 尝试从文件加载缓存
if (!force) {
const fileCache = await this.loadCacheFromFile()
if (fileCache && fileCache.key === cacheKey) {
this.aggregateCache = fileCache
return { success: true, data: fileCache.data, source: 'file-cache' }
}
}
if (this.aggregatePromise && this.aggregatePromise.key === cacheKey) {
return this.aggregatePromise.promise
}
@@ -291,7 +310,12 @@ class AnalyticsService {
this.aggregatePromise = { key: cacheKey, promise }
try {
return await promise
const result = await promise
// 如果计算成功,同时写入此文件缓存
if (result.success && result.data && result.source !== 'cache') {
this.saveCacheToFile({ key: cacheKey, data: this.aggregateCache?.data, updatedAt: Date.now() })
}
return result
} finally {
if (this.aggregatePromise && this.aggregatePromise.key === cacheKey) {
this.aggregatePromise = null
@@ -299,6 +323,25 @@ class AnalyticsService {
}
}
private getCacheFilePath(): string {
return join(app.getPath('userData'), 'analytics_cache.json')
}
private async loadCacheFromFile(): Promise<{ key: string; data: any; updatedAt: number } | null> {
try {
const raw = await readFile(this.getCacheFilePath(), 'utf-8')
return JSON.parse(raw)
} catch { return null }
}
private async saveCacheToFile(data: any) {
try {
await writeFile(this.getCacheFilePath(), JSON.stringify(data))
} catch (e) {
console.error('保存统计缓存失败:', e)
}
}
private normalizeAggregateSessions(
sessions: Record<string, any> | undefined,
idMap: Record<string, string> | undefined
@@ -326,7 +369,7 @@ class AnalyticsService {
void results
}
async getOverallStatistics(): Promise<{ success: boolean; data?: ChatStatistics; error?: string }> {
async getOverallStatistics(force = false): Promise<{ success: boolean; data?: ChatStatistics; error?: string }> {
try {
const conn = await this.ensureConnected()
if (!conn.success || !conn.cleanedWxid) return { success: false, error: conn.error }
@@ -340,7 +383,7 @@ class AnalyticsService {
const win = BrowserWindow.getAllWindows()[0]
this.setProgress(win, '正在执行原生数据聚合...', 30)
const result = await this.getAggregateWithFallback(sessionInfo.usernames, 0, 0, win)
const result = await this.getAggregateWithFallback(sessionInfo.usernames, 0, 0, win, force)
if (!result.success || !result.data) {
return { success: false, error: result.error || '聚合统计失败' }
@@ -458,8 +501,8 @@ class AnalyticsService {
const d = result.data
// SQLite strftime('%w') 返回 0=Sun, 1=Mon...6=Sat
// 前端期望 1=Mon...7=Sun
// SQLite strftime('%w') 返回 0=周日, 1=周一...6=周六
// 前端期望 1=周一...7=周日
const weekdayDistribution: Record<number, number> = {}
for (const [w, count] of Object.entries(d.weekday)) {
const sqliteW = parseInt(w, 10)
+46 -26
View File
@@ -667,24 +667,24 @@ class ChatService {
const messages: Message[] = []
for (const row of rows) {
const content = this.decodeMessageContent(
this.getRowField(row, [
'message_content',
'messageContent',
'content',
'msg_content',
'msgContent',
'WCDB_CT_message_content',
'WCDB_CT_messageContent'
]),
this.getRowField(row, [
'compress_content',
'compressContent',
'compressed_content',
'WCDB_CT_compress_content',
'WCDB_CT_compressContent'
])
)
const rawMessageContent = this.getRowField(row, [
'message_content',
'messageContent',
'content',
'msg_content',
'msgContent',
'WCDB_CT_message_content',
'WCDB_CT_messageContent'
]);
const rawCompressContent = this.getRowField(row, [
'compress_content',
'compressContent',
'compressed_content',
'WCDB_CT_compress_content',
'WCDB_CT_compressContent'
]);
const content = this.decodeMessageContent(rawMessageContent, rawCompressContent);
const localType = this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 1)
const isSendRaw = this.getRowField(row, ['computed_is_send', 'computedIsSend', 'is_send', 'isSend', 'WCDB_CT_is_send'])
let isSend = isSendRaw === null ? null : parseInt(isSendRaw, 10)
@@ -696,6 +696,16 @@ class ChatService {
const expectedIsSend = (senderLower === myWxidLower || senderLower === cleanedWxidLower) ? 1 : 0
if (isSend === null) {
isSend = expectedIsSend
// [DEBUG] Issue #34: 记录 isSend 推断过程
if (expectedIsSend === 0 && localType === 1) {
// 仅在被判为接收且是文本消息时记录,避免刷屏
// console.log(`[ChatService] inferred isSend=0: sender=${senderUsername}, myWxid=${myWxid} (cleaned=${cleanedWxid})`)
}
}
} else if (senderUsername && !myWxid) {
// [DEBUG] Issue #34: 未配置 myWxid,无法判断是否发送
if (messages.length < 5) {
console.warn(`[ChatService] Warning: myWxid not set. Cannot determine if message is sent by me. sender=${senderUsername}`)
}
}
@@ -1481,9 +1491,9 @@ class ChatService {
*/
private decodeMessageContent(messageContent: any, compressContent: any): string {
// 优先使用 compress_content
let content = this.decodeMaybeCompressed(compressContent)
let content = this.decodeMaybeCompressed(compressContent, 'compress_content')
if (!content || content.length === 0) {
content = this.decodeMaybeCompressed(messageContent)
content = this.decodeMaybeCompressed(messageContent, 'message_content')
}
return content
}
@@ -1491,12 +1501,14 @@ class ChatService {
/**
* 尝试解码可能压缩的内容
*/
private decodeMaybeCompressed(raw: any): string {
private decodeMaybeCompressed(raw: any, fieldName: string = 'unknown'): string {
if (!raw) return ''
// console.log(`[ChatService] Decoding ${fieldName}: type=${typeof raw}`, raw)
// 如果是 Buffer/Uint8Array
if (Buffer.isBuffer(raw) || raw instanceof Uint8Array) {
return this.decodeBinaryContent(Buffer.from(raw))
return this.decodeBinaryContent(Buffer.from(raw), String(raw))
}
// 如果是字符串
@@ -1507,7 +1519,9 @@ class ChatService {
if (this.looksLikeHex(raw)) {
const bytes = Buffer.from(raw, 'hex')
if (bytes.length > 0) {
return this.decodeBinaryContent(bytes)
const result = this.decodeBinaryContent(bytes, raw)
// console.log(`[ChatService] HEX decoded result: ${result}`)
return result
}
}
@@ -1515,7 +1529,7 @@ class ChatService {
if (this.looksLikeBase64(raw)) {
try {
const bytes = Buffer.from(raw, 'base64')
return this.decodeBinaryContent(bytes)
return this.decodeBinaryContent(bytes, raw)
} catch { }
}
@@ -1529,7 +1543,7 @@ class ChatService {
/**
* 解码二进制内容(处理 zstd 压缩)
*/
private decodeBinaryContent(data: Buffer): string {
private decodeBinaryContent(data: Buffer, fallbackValue?: string): string {
if (data.length === 0) return ''
try {
@@ -1556,10 +1570,16 @@ class ChatService {
return decoded.replace(/\uFFFD/g, '')
}
// 如果提供了 fallbackValue,且解码结果看起来像二进制垃圾,则返回 fallbackValue
if (fallbackValue && replacementCount > 0) {
// console.log(`[ChatService] Binary garbage detected, using fallback: ${fallbackValue}`)
return fallbackValue
}
// 尝试 latin1 解码
return data.toString('latin1')
} catch {
return ''
return fallbackValue || ''
}
}
+43 -4
View File
@@ -459,7 +459,7 @@ class ExportService {
const [displayNames, avatarUrls] = await Promise.all([
wcdbService.getDisplayNames(Array.from(lookupUsernames)),
includeAvatars ? wcdbService.getAvatarUrls(Array.from(lookupUsernames)) : Promise.resolve({ success: true, map: {} })
includeAvatars ? wcdbService.getAvatarUrls(Array.from(lookupUsernames)) : Promise.resolve({ success: true, map: {} as Record<string, string> })
])
for (const member of rawMembers) {
@@ -590,7 +590,11 @@ class ExportService {
}
}
if (!data) continue
const finalMime = mime || this.inferImageMime(fileInfo.ext)
// 优先使用内容检测出的 MIME 类型
const detectedMime = this.detectMimeType(data)
const finalMime = detectedMime || mime || this.inferImageMime(fileInfo.ext)
const base64 = data.toString('base64')
result.set(member.username, `data:${finalMime};base64,${base64}`)
} catch {
@@ -601,6 +605,39 @@ class ExportService {
return result
}
private detectMimeType(buffer: Buffer): string | null {
if (buffer.length < 4) return null
// PNG: 89 50 4E 47
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
return 'image/png'
}
// JPEG: FF D8 FF
if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
return 'image/jpeg'
}
// GIF: 47 49 46 38
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
return 'image/gif'
}
// WEBP: RIFF ... WEBP
if (buffer.length >= 12 &&
buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
return 'image/webp'
}
// BMP: 42 4D
if (buffer[0] === 0x42 && buffer[1] === 0x4D) {
return 'image/bmp'
}
return null
}
private inferImageMime(ext: string): string {
switch (ext.toLowerCase()) {
case '.png':
@@ -659,7 +696,8 @@ class ExportService {
const chatLabMessages: ChatLabMessage[] = allMessages.map((msg) => {
const memberInfo = collected.memberSet.get(msg.senderUsername)?.member || {
platformId: msg.senderUsername,
accountName: msg.senderUsername
accountName: msg.senderUsername,
groupNickname: undefined
}
return {
sender: msg.senderUsername,
@@ -811,7 +849,8 @@ class ExportService {
displayName: sessionInfo.displayName,
type: isGroup ? '群聊' : '私聊',
lastTimestamp: collected.lastTime,
messageCount: allMessages.length
messageCount: allMessages.length,
avatar: undefined as string | undefined
},
messages: allMessages
}
@@ -181,6 +181,8 @@ class GroupAnalyticsService {
}
}
async getGroupActiveHours(chatroomId: string, startTime?: number, endTime?: number): Promise<{ success: boolean; data?: GroupActiveHours; error?: string }> {
try {
const conn = await this.ensureConnected()
+80 -28
View File
@@ -1,14 +1,14 @@
import { app, BrowserWindow } from 'electron'
import { basename, dirname, extname, join } from 'path'
import { pathToFileURL } from 'url'
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'fs'
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, appendFileSync } from 'fs'
import { writeFile } from 'fs/promises'
import crypto from 'crypto'
import { Worker } from 'worker_threads'
import { ConfigService } from './config'
import { wcdbService } from './wcdbService'
type DecryptResult = {
type DecryptResult = {
success: boolean
localPath?: string
error?: string
@@ -32,11 +32,45 @@ export class ImageDecryptService {
private logInfo(message: string, meta?: Record<string, unknown>): void {
if (!this.configService.get('logEnabled')) return
const timestamp = new Date().toISOString()
const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''
const logLine = `[${timestamp}] [ImageDecrypt] ${message}${metaStr}\n`
// 同时输出到控制台
if (meta) {
console.info(message, meta)
} else {
console.info(message)
}
// 写入日志文件
this.writeLog(logLine)
}
private logError(message: string, error?: unknown, meta?: Record<string, unknown>): void {
if (!this.configService.get('logEnabled')) return
const timestamp = new Date().toISOString()
const errorStr = error ? ` Error: ${String(error)}` : ''
const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''
const logLine = `[${timestamp}] [ImageDecrypt] ERROR: ${message}${errorStr}${metaStr}\n`
// 同时输出到控制台
console.error(message, error, meta)
// 写入日志文件
this.writeLog(logLine)
}
private writeLog(line: string): void {
try {
const logDir = join(app.getPath('userData'), 'logs')
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true })
}
appendFileSync(join(logDir, 'wcdb.log'), line, { encoding: 'utf8' })
} catch (err) {
console.error('写入日志失败:', err)
}
}
async resolveCachedImage(payload: { sessionId?: string; imageMd5?: string; imageDatName?: string }): Promise<DecryptResult & { hasUpdate?: boolean }> {
@@ -49,6 +83,7 @@ export class ImageDecryptService {
for (const key of cacheKeys) {
const cached = this.resolvedCache.get(key)
if (cached && existsSync(cached) && this.isImageFile(cached)) {
this.logInfo('缓存命中(从Map)', { key, path: cached, isThumb: this.isThumbnailPath(cached) })
const dataUrl = this.fileToDataUrl(cached)
const isThumb = this.isThumbnailPath(cached)
const hasUpdate = isThumb ? (this.updateFlags.get(key) ?? false) : false
@@ -68,6 +103,7 @@ export class ImageDecryptService {
for (const key of cacheKeys) {
const existing = this.findCachedOutput(key, false, payload.sessionId)
if (existing) {
this.logInfo('缓存命中(文件系统)', { key, path: existing, isThumb: this.isThumbnailPath(existing) })
this.cacheResolvedPaths(key, payload.imageMd5, payload.imageDatName, existing)
const dataUrl = this.fileToDataUrl(existing)
const isThumb = this.isThumbnailPath(existing)
@@ -81,6 +117,7 @@ export class ImageDecryptService {
return { success: true, localPath: dataUrl || this.filePathToUrl(existing), hasUpdate }
}
}
this.logInfo('未找到缓存', { md5: payload.imageMd5, datName: payload.imageDatName })
return { success: false, error: '未找到缓存图片' }
}
@@ -120,15 +157,18 @@ export class ImageDecryptService {
payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean },
cacheKey: string
): Promise<DecryptResult> {
this.logInfo('开始解密图片', { md5: payload.imageMd5, datName: payload.imageDatName, force: payload.force })
try {
const wxid = this.configService.get('myWxid')
const dbPath = this.configService.get('dbPath')
if (!wxid || !dbPath) {
this.logError('配置缺失', undefined, { wxid: !!wxid, dbPath: !!dbPath })
return { success: false, error: '未配置账号或数据库路径' }
}
const accountDir = this.resolveAccountDir(dbPath, wxid)
if (!accountDir) {
this.logError('未找到账号目录', undefined, { dbPath, wxid })
return { success: false, error: '未找到账号目录' }
}
@@ -139,15 +179,19 @@ export class ImageDecryptService {
payload.sessionId,
{ allowThumbnail: !payload.force, skipResolvedCache: Boolean(payload.force) }
)
// 如果要求高清图但没找到,直接返回提示
if (!datPath && payload.force) {
this.logError('未找到高清图', undefined, { md5: payload.imageMd5, datName: payload.imageDatName })
return { success: false, error: '未找到高清图,请在微信中点开该图片查看后重试' }
}
if (!datPath) {
this.logError('未找到DAT文件', undefined, { md5: payload.imageMd5, datName: payload.imageDatName })
return { success: false, error: '未找到图片文件' }
}
this.logInfo('找到DAT文件', { datPath })
if (!extname(datPath).toLowerCase().includes('dat')) {
this.cacheResolvedPaths(cacheKey, payload.imageMd5, payload.imageDatName, datPath)
const dataUrl = this.fileToDataUrl(datPath)
@@ -160,6 +204,7 @@ export class ImageDecryptService {
// 查找已缓存的解密文件
const existing = this.findCachedOutput(cacheKey, payload.force, payload.sessionId)
if (existing) {
this.logInfo('找到已解密文件', { existing, isHd: this.isHdPath(existing) })
const isHd = this.isHdPath(existing)
// 如果要求高清但找到的是缩略图,继续解密高清图
if (!(payload.force && !isHd)) {
@@ -192,13 +237,15 @@ export class ImageDecryptService {
const aesKeyRaw = this.configService.get('imageAesKey')
const aesKey = this.resolveAesKey(aesKeyRaw)
this.logInfo('开始解密DAT文件', { datPath, xorKey, hasAesKey: !!aesKey })
const decrypted = await this.decryptDatAuto(datPath, xorKey, aesKey)
const ext = this.detectImageExtension(decrypted) || '.jpg'
const outputPath = this.getCacheOutputPathFromDat(datPath, ext, payload.sessionId)
await writeFile(outputPath, decrypted)
this.logInfo('解密成功', { outputPath, size: decrypted.length })
const isThumb = this.isThumbnailPath(datPath)
this.cacheResolvedPaths(cacheKey, payload.imageMd5, payload.imageDatName, outputPath)
if (!isThumb) {
@@ -209,6 +256,7 @@ export class ImageDecryptService {
this.emitCacheResolved(payload, cacheKey, localPath)
return { success: true, localPath, isThumb }
} catch (e) {
this.logError('解密失败', e, { md5: payload.imageMd5, datName: payload.imageDatName })
return { success: false, error: String(e) }
}
}
@@ -233,7 +281,7 @@ export class ImageDecryptService {
if (this.isAccountDir(entryPath)) return entryPath
}
}
} catch {}
} catch { }
return null
}
@@ -244,10 +292,10 @@ export class ImageDecryptService {
private getDecryptedCacheDir(wxid: string): string | null {
const cachePath = this.configService.get('cachePath')
if (!cachePath) return null
const cleanedWxid = this.cleanAccountDirName(wxid)
const cacheAccountDir = join(cachePath, cleanedWxid)
// 检查缓存目录下是否有 hardlink.db
if (existsSync(join(cacheAccountDir, 'hardlink.db'))) {
return cacheAccountDir
@@ -312,7 +360,7 @@ export class ImageDecryptService {
allowThumbnail,
skipResolvedCache
})
// 优先通过 hardlink.db 查询
if (imageMd5) {
this.logInfo('[ImageDecrypt] hardlink lookup (md5)', { imageMd5, sessionId })
@@ -474,7 +522,7 @@ export class ImageDecryptService {
if (!hasUpdate) return
this.updateFlags.set(cacheKey, true)
this.emitImageUpdate(payload, cacheKey)
}).catch(() => {})
}).catch(() => { })
}
private looksLikeMd5(value: string): boolean {
@@ -528,7 +576,7 @@ export class ImageDecryptService {
this.logInfo('[ImageDecrypt] hardlink row miss', { md5, table: state.imageTable })
return null
}
const dir1 = this.getRowValue(row, 'dir1')
const dir2 = this.getRowValue(row, 'dir2')
const fileName = this.getRowValue(row, 'file_name') ?? this.getRowValue(row, 'fileName')
@@ -549,7 +597,7 @@ export class ImageDecryptService {
// dir1 和 dir2 是 rowid,需要从 dir2id 表查询对应的目录名
let dir1Name: string | null = null
let dir2Name: string | null = null
if (state.dirTable) {
try {
// 通过 rowid 查询目录名
@@ -562,7 +610,7 @@ export class ImageDecryptService {
const value = this.getRowValue(dir1Result.rows[0], 'username')
if (value) dir1Name = String(value)
}
const dir2Result = await wcdbService.execQuery(
'media',
hardlinkPath,
@@ -588,14 +636,14 @@ export class ImageDecryptService {
join(accountDir, 'msg', 'attach', dir1Name, dir2Name, 'mg', fileName),
join(accountDir, 'msg', 'attach', dir1Name, dir2Name, fileName),
]
for (const fullPath of possiblePaths) {
if (existsSync(fullPath)) {
this.logInfo('[ImageDecrypt] hardlink path hit', { fullPath })
return fullPath
}
}
this.logInfo('[ImageDecrypt] hardlink path miss', { possiblePaths })
return null
} catch {
@@ -829,14 +877,14 @@ export class ImageDecryptService {
}
}
}
// 新目录结构: Images/{normalizedKey}/{normalizedKey}_thumb.jpg 或 _hd.jpg
const imageDir = join(root, normalizedKey)
if (existsSync(imageDir)) {
const hit = this.findCachedOutputInDir(imageDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
// 兼容旧的平铺结构
for (const ext of extensions) {
const candidate = join(root, `${cacheKey}${ext}`)
@@ -846,7 +894,7 @@ export class ImageDecryptService {
const candidate = join(root, `${cacheKey}_t${ext}`)
if (existsSync(candidate)) return candidate
}
return null
}
@@ -863,6 +911,8 @@ export class ImageDecryptService {
}
const thumbPath = join(dirPath, `${normalizedKey}_thumb${ext}`)
if (existsSync(thumbPath)) return thumbPath
// 允许返回 _hd 格式(因为它有 _hd 变体后缀)
if (!preferHd) {
const hdPath = join(dirPath, `${normalizedKey}_hd${ext}`)
if (existsSync(hdPath)) return hdPath
@@ -875,14 +925,14 @@ export class ImageDecryptService {
const name = basename(datPath)
const lower = name.toLowerCase()
const base = lower.endsWith('.dat') ? name.slice(0, -4) : name
// 提取基础名称(去掉 _t, _h 等后缀)
const normalizedBase = this.normalizeDatBase(base)
// 判断是缩略图还是高清图
const isThumb = this.isThumbnailDat(lower)
const suffix = isThumb ? '_thumb' : '_hd'
const contactDir = this.sanitizeDirName(sessionId || 'unknown')
const timeDir = this.resolveTimeDir(datPath)
const outputDir = join(this.getCacheRoot(), contactDir, timeDir)
@@ -960,8 +1010,9 @@ export class ImageDecryptService {
const lower = entry.toLowerCase()
if (!lower.endsWith('.dat')) continue
if (this.isThumbnailDat(lower)) continue
if (!this.hasXVariant(lower.slice(0, -4))) continue
const baseLower = lower.slice(0, -4)
// 只排除没有 _x 变体后缀的文件(允许 _hd、_h 等所有带变体的)
if (!this.hasXVariant(baseLower)) continue
if (this.normalizeDatBase(baseLower) !== target) continue
return join(dirPath, entry)
}
@@ -973,6 +1024,7 @@ export class ImageDecryptService {
if (!lower.endsWith('.dat')) return false
if (this.isThumbnailDat(lower)) return false
const baseLower = lower.slice(0, -4)
// 只检查是否有 _x 变体后缀(允许 _hd、_h 等所有带变体的)
return this.hasXVariant(baseLower)
}
@@ -1079,7 +1131,7 @@ export class ImageDecryptService {
private async decryptDatAuto(datPath: string, xorKey: number, aesKey: Buffer | null): Promise<Buffer> {
const version = this.getDatVersion(datPath)
if (version === 0) {
return this.decryptDatV3(datPath, xorKey)
}
@@ -1136,7 +1188,7 @@ export class ImageDecryptService {
// 当 aesSize % 16 === 0 时,仍需要额外 16 字节的填充
const remainder = ((aesSize % 16) + 16) % 16
const alignedAesSize = aesSize + (16 - remainder)
if (alignedAesSize > data.length) {
throw new Error('文件格式异常:AES 数据长度超过文件实际长度')
}
@@ -1147,7 +1199,7 @@ export class ImageDecryptService {
const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null)
decipher.setAutoPadding(false)
const decrypted = Buffer.concat([decipher.update(aesData), decipher.final()])
// 使用 PKCS7 填充移除
unpadded = this.strictRemovePadding(decrypted)
}
@@ -1214,7 +1266,7 @@ export class ImageDecryptService {
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) return '.png'
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return '.jpg'
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
return '.webp'
}
return null
@@ -1332,10 +1384,10 @@ export class ImageDecryptService {
keyCount.set(key, (keyCount.get(key) || 0) + 1)
filesChecked++
}
} catch {}
} catch { }
}
}
} catch {}
} catch { }
}
scanDir(dirPath)
+126 -14
View File
@@ -1,9 +1,10 @@
import { app } from 'electron'
import { join, dirname, basename } from 'path'
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
import { existsSync, readdirSync, readFileSync, statSync, copyFileSync, mkdirSync } from 'fs'
import { execFile, spawn } from 'child_process'
import { promisify } from 'util'
import crypto from 'crypto'
import os from 'os'
const execFileAsync = promisify(execFile)
@@ -57,18 +58,94 @@ export class KeyService {
private readonly ERROR_SUCCESS = 0
private getDllPath(): string {
const resourcesPath = app.isPackaged
? join(process.resourcesPath, 'resources')
: join(app.getAppPath(), 'resources')
return join(resourcesPath, 'wx_key.dll')
const isPackaged = typeof app !== 'undefined' && app ? app.isPackaged : process.env.NODE_ENV === 'production'
// 候选路径列表
const candidates: string[] = []
// 1. 显式环境变量 (最高优先级)
if (process.env.WX_KEY_DLL_PATH) {
candidates.push(process.env.WX_KEY_DLL_PATH)
}
if (isPackaged) {
// 生产环境: 通常在 resources 目录下,但也可能直接在 resources 根目录
candidates.push(join(process.resourcesPath, 'resources', 'wx_key.dll'))
candidates.push(join(process.resourcesPath, 'wx_key.dll'))
} else {
// 开发环境
const cwd = process.cwd()
candidates.push(join(cwd, 'resources', 'wx_key.dll'))
candidates.push(join(app.getAppPath(), 'resources', 'wx_key.dll'))
}
// 检查并返回第一个存在的路径
for (const path of candidates) {
if (existsSync(path)) {
return path
}
}
// 如果都没找到,返回最可能的路径以便报错信息有参考
return candidates[0]
}
// 检查路径是否为 UNC 路径或网络路径
private isNetworkPath(path: string): boolean {
// UNC 路径以 \\ 开头
if (path.startsWith('\\\\')) {
return true
}
// 检查是否为网络映射驱动器(简化检测:A: 表示驱动器)
// 注意:这是一个启发式检测,更准确的方式需要调用 GetDriveType Windows API
// 但对于大多数 VM 共享场景,UNC 路径检测已足够
return false
}
// 将 DLL 复制到本地临时目录
private localizeNetworkDll(originalPath: string): string {
try {
const tempDir = join(os.tmpdir(), 'weflow_dll_cache')
if (!existsSync(tempDir)) {
mkdirSync(tempDir, { recursive: true })
}
const localPath = join(tempDir, 'wx_key.dll')
// 检查是否已经有本地副本,如果有就使用它
if (existsSync(localPath)) {
console.log(`使用已存在的 DLL 本地副本: ${localPath}`)
return localPath
}
console.log(`检测到网络路径 DLL,正在复制到本地: ${originalPath} -> ${localPath}`)
copyFileSync(originalPath, localPath)
console.log('DLL 本地化成功')
return localPath
} catch (e) {
console.error('DLL 本地化失败:', e)
// 如果本地化失败,返回原路径
return originalPath
}
}
private ensureLoaded(): boolean {
if (this.initialized) return true
let dllPath = ''
try {
this.koffi = require('koffi')
const dllPath = this.getDllPath()
if (!existsSync(dllPath)) return false
dllPath = this.getDllPath()
if (!existsSync(dllPath)) {
console.error(`wx_key.dll 不存在于路径: ${dllPath}`)
return false
}
// 检查是否为网络路径,如果是则本地化
if (this.isNetworkPath(dllPath)) {
console.log('检测到网络路径,将进行本地化处理')
dllPath = this.localizeNetworkDll(dllPath)
}
this.lib = this.koffi.load(dllPath)
this.initHook = this.lib.func('bool InitializeHook(uint32 targetPid)')
@@ -80,7 +157,14 @@ export class KeyService {
this.initialized = true
return true
} catch (e) {
console.error('加载 wx_key.dll 失败:', e)
const errorMsg = e instanceof Error ? e.message : String(e)
const errorStack = e instanceof Error ? e.stack : ''
console.error(`加载 wx_key.dll 失败`)
console.error(` 路径: ${dllPath}`)
console.error(` 错误: ${errorMsg}`)
if (errorStack) {
console.error(` 堆栈: ${errorStack}`)
}
return false
}
}
@@ -831,17 +915,40 @@ export class KeyService {
return buffer.subarray(0, bytesRead[0])
}
private async getAesKeyFromMemory(pid: number, ciphertext: Buffer): Promise<string | null> {
private async getAesKeyFromMemory(
pid: number,
ciphertext: Buffer,
onProgress?: (current: number, total: number, message: string) => void
): Promise<string | null> {
if (!this.ensureKernel32()) return null
const hProcess = this.OpenProcess(this.PROCESS_ALL_ACCESS, false, pid)
if (!hProcess) return null
try {
const regions = this.getMemoryRegions(hProcess)
const chunkSize = 4 * 1024 * 1024
const allRegions = this.getMemoryRegions(hProcess)
// 优化1: 只保留小内存区域(< 10MB)- 密钥通常在小区域,可大幅减少扫描时间
const filteredRegions = allRegions.filter(([_, size]) => size <= 10 * 1024 * 1024)
// 优化2: 优先级排序 - 按大小升序,先扫描小区域(密钥通常在较小区域)
const sortedRegions = filteredRegions.sort((a, b) => a[1] - b[1])
// 优化3: 计算总字节数用于精确进度报告
const totalBytes = sortedRegions.reduce((sum, [_, size]) => sum + size, 0)
let processedBytes = 0
// 优化4: 减小分块大小到 1MB(参考 wx_key 项目)
const chunkSize = 1 * 1024 * 1024
const overlap = 65
for (const [baseAddress, regionSize] of regions) {
if (regionSize > 100 * 1024 * 1024) continue
let currentRegion = 0
for (const [baseAddress, regionSize] of sortedRegions) {
currentRegion++
const progress = totalBytes > 0 ? Math.floor((processedBytes / totalBytes) * 100) : 0
onProgress?.(progress, 100, `扫描内存 ${progress}% (${currentRegion}/${sortedRegions.length})`)
// 每个区域都让出主线程,确保UI流畅
await new Promise(resolve => setImmediate(resolve))
let offset = 0
let trailing: Buffer | null = null
while (offset < regionSize) {
@@ -896,6 +1003,9 @@ export class KeyService {
trailing = dataToScan.subarray(start < 0 ? 0 : start)
offset += currentChunkSize
}
// 更新已处理字节数
processedBytes += regionSize
}
return null
} finally {
@@ -933,7 +1043,9 @@ export class KeyService {
if (!pid) return { success: false, error: '未检测到微信进程' }
onProgress?.('正在扫描内存获取 AES 密钥...')
const aesKey = await this.getAesKeyFromMemory(pid, ciphertext)
const aesKey = await this.getAesKeyFromMemory(pid, ciphertext, (current, total, msg) => {
onProgress?.(`${msg} (${current}/${total})`)
})
if (!aesKey) {
return {
success: false,
+1317
View File
@@ -0,0 +1,1317 @@
import { join, dirname, basename } from 'path'
import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, readFileSync } from 'fs'
export class WcdbCore {
private resourcesPath: string | null = null
private userDataPath: string | null = null
private logEnabled = false
private lib: any = null
private koffi: any = null
private initialized = false
private handle: number | null = null
private currentPath: string | null = null
private currentKey: string | null = null
private currentWxid: string | null = null
// 函数引用
private wcdbInit: any = null
private wcdbShutdown: any = null
private wcdbOpenAccount: any = null
private wcdbCloseAccount: any = null
private wcdbSetMyWxid: any = null
private wcdbFreeString: any = null
private wcdbGetSessions: any = null
private wcdbGetMessages: any = null
private wcdbGetMessageCount: any = null
private wcdbGetDisplayNames: any = null
private wcdbGetAvatarUrls: any = null
private wcdbGetGroupMemberCount: any = null
private wcdbGetGroupMemberCounts: any = null
private wcdbGetGroupMembers: any = null
private wcdbGetMessageTables: any = null
private wcdbGetMessageMeta: any = null
private wcdbGetContact: any = null
private wcdbGetMessageTableStats: any = null
private wcdbGetAggregateStats: any = null
private wcdbGetAvailableYears: any = null
private wcdbGetAnnualReportStats: any = null
private wcdbGetAnnualReportExtras: any = null
private wcdbGetGroupStats: any = null
private wcdbOpenMessageCursor: any = null
private wcdbOpenMessageCursorLite: any = null
private wcdbFetchMessageBatch: any = null
private wcdbCloseMessageCursor: any = null
private wcdbGetLogs: any = null
private wcdbExecQuery: any = null
private wcdbListMessageDbs: any = null
private wcdbListMediaDbs: any = null
private wcdbGetMessageById: any = null
private wcdbGetEmoticonCdnUrl: any = null
private wcdbGetDbStatus: any = null
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
private readonly avatarCacheTtlMs = 10 * 60 * 1000
private logTimer: NodeJS.Timeout | null = null
private lastLogTail: string | null = null
setPaths(resourcesPath: string, userDataPath: string): void {
this.resourcesPath = resourcesPath
this.userDataPath = userDataPath
}
setLogEnabled(enabled: boolean): void {
this.logEnabled = enabled
if (this.isLogEnabled() && this.initialized) {
this.startLogPolling()
} else {
this.stopLogPolling()
}
}
/**
* 获取 DLL 路径
*/
private getDllPath(): string {
const envDllPath = process.env.WCDB_DLL_PATH
if (envDllPath && envDllPath.length > 0) {
return envDllPath
}
// 基础路径探测
const isPackaged = typeof process['resourcesPath'] !== 'undefined'
const resourcesPath = isPackaged ? process.resourcesPath : join(process.cwd(), 'resources')
const candidates = [
// 环境变量指定 resource 目录
process.env.WCDB_RESOURCES_PATH ? join(process.env.WCDB_RESOURCES_PATH, 'wcdb_api.dll') : null,
// 显式 setPaths 设置的路径
this.resourcesPath ? join(this.resourcesPath, 'wcdb_api.dll') : null,
// text/resources/wcdb_api.dll (打包常见结构)
join(resourcesPath, 'resources', 'wcdb_api.dll'),
// items/resourcesPath/wcdb_api.dll (扁平结构)
join(resourcesPath, 'wcdb_api.dll'),
// CWD fallback
join(process.cwd(), 'resources', 'wcdb_api.dll')
].filter(Boolean) as string[]
for (const path of candidates) {
if (existsSync(path)) return path
}
return candidates[0] || 'wcdb_api.dll'
}
private isLogEnabled(): boolean {
if (process.env.WEFLOW_WORKER === '1') return false
if (process.env.WCDB_LOG_ENABLED === '1') return true
return this.logEnabled
}
private writeLog(message: string, force = false): void {
if (!force && !this.isLogEnabled()) return
try {
const base = this.userDataPath || process.env.WCDB_LOG_DIR || process.cwd()
const dir = join(base, 'logs')
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
const line = `[${new Date().toISOString()}] ${message}\n`
appendFileSync(join(dir, 'wcdb.log'), line, { encoding: 'utf8' })
} catch { }
}
/**
* 递归查找 session.db 文件
*/
private findSessionDb(dir: string, depth = 0): string | null {
if (depth > 5) return null
try {
const entries = readdirSync(dir)
for (const entry of entries) {
if (entry.toLowerCase() === 'session.db') {
const fullPath = join(dir, entry)
if (statSync(fullPath).isFile()) {
return fullPath
}
}
}
for (const entry of entries) {
const fullPath = join(dir, entry)
try {
if (statSync(fullPath).isDirectory()) {
const found = this.findSessionDb(fullPath, depth + 1)
if (found) return found
}
} catch { }
}
} catch (e) {
console.error('查找 session.db 失败:', e)
}
return null
}
private resolveDbStoragePath(basePath: string, wxid: string): string | null {
if (!basePath) return null
const normalized = basePath.replace(/[\\\\/]+$/, '')
if (normalized.toLowerCase().endsWith('db_storage') && existsSync(normalized)) {
return normalized
}
const direct = join(normalized, 'db_storage')
if (existsSync(direct)) {
return direct
}
if (wxid) {
const viaWxid = join(normalized, wxid, 'db_storage')
if (existsSync(viaWxid)) {
return viaWxid
}
// 兼容目录名包含额外后缀(如 wxid_xxx_1234)
try {
const entries = readdirSync(normalized)
const lowerWxid = wxid.toLowerCase()
const candidates = entries.filter((entry) => {
const entryPath = join(normalized, entry)
try {
if (!statSync(entryPath).isDirectory()) return false
} catch {
return false
}
const lowerEntry = entry.toLowerCase()
return lowerEntry === lowerWxid || lowerEntry.startsWith(`${lowerWxid}_`)
})
for (const entry of candidates) {
const candidate = join(normalized, entry, 'db_storage')
if (existsSync(candidate)) {
return candidate
}
}
} catch { }
}
return null
}
/**
* 初始化 WCDB
*/
async initialize(): Promise<boolean> {
if (this.initialized) return true
try {
this.koffi = require('koffi')
const dllPath = this.getDllPath()
if (!existsSync(dllPath)) {
console.error('WCDB DLL 不存在:', dllPath)
return false
}
this.lib = this.koffi.load(dllPath)
// 定义类型
// wcdb_status wcdb_init()
this.wcdbInit = this.lib.func('int32 wcdb_init()')
// wcdb_status wcdb_shutdown()
this.wcdbShutdown = this.lib.func('int32 wcdb_shutdown()')
// wcdb_status wcdb_open_account(const char* session_db_path, const char* hex_key, wcdb_handle* out_handle)
// wcdb_handle 是 int64_t
this.wcdbOpenAccount = this.lib.func('int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)')
// wcdb_status wcdb_close_account(wcdb_handle handle)
// C 接口是 int64, koffi 返回 handle 是 number 类型
this.wcdbCloseAccount = this.lib.func('int32 wcdb_close_account(int64 handle)')
// wcdb_status wcdb_set_my_wxid(wcdb_handle handle, const char* wxid)
try {
this.wcdbSetMyWxid = this.lib.func('int32 wcdb_set_my_wxid(int64 handle, const char* wxid)')
} catch {
this.wcdbSetMyWxid = null
}
// void wcdb_free_string(char* ptr)
this.wcdbFreeString = this.lib.func('void wcdb_free_string(void* ptr)')
// wcdb_status wcdb_get_sessions(wcdb_handle handle, char** out_json)
this.wcdbGetSessions = this.lib.func('int32 wcdb_get_sessions(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_get_messages(wcdb_handle handle, const char* username, int32_t limit, int32_t offset, char** out_json)
this.wcdbGetMessages = this.lib.func('int32 wcdb_get_messages(int64 handle, const char* username, int32 limit, int32 offset, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_count(wcdb_handle handle, const char* username, int32_t* out_count)
this.wcdbGetMessageCount = this.lib.func('int32 wcdb_get_message_count(int64 handle, const char* username, _Out_ int32* outCount)')
// wcdb_status wcdb_get_display_names(wcdb_handle handle, const char* usernames_json, char** out_json)
this.wcdbGetDisplayNames = this.lib.func('int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
// wcdb_status wcdb_get_avatar_urls(wcdb_handle handle, const char* usernames_json, char** out_json)
this.wcdbGetAvatarUrls = this.lib.func('int32 wcdb_get_avatar_urls(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
// wcdb_status wcdb_get_group_member_count(wcdb_handle handle, const char* chatroom_id, int32_t* out_count)
this.wcdbGetGroupMemberCount = this.lib.func('int32 wcdb_get_group_member_count(int64 handle, const char* chatroomId, _Out_ int32* outCount)')
// wcdb_status wcdb_get_group_member_counts(wcdb_handle handle, const char* chatroom_ids_json, char** out_json)
try {
this.wcdbGetGroupMemberCounts = this.lib.func('int32 wcdb_get_group_member_counts(int64 handle, const char* chatroomIdsJson, _Out_ void** outJson)')
} catch {
this.wcdbGetGroupMemberCounts = null
}
// wcdb_status wcdb_get_group_members(wcdb_handle handle, const char* chatroom_id, char** out_json)
this.wcdbGetGroupMembers = this.lib.func('int32 wcdb_get_group_members(int64 handle, const char* chatroomId, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_tables(wcdb_handle handle, const char* session_id, char** out_json)
this.wcdbGetMessageTables = this.lib.func('int32 wcdb_get_message_tables(int64 handle, const char* sessionId, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_meta(wcdb_handle handle, const char* db_path, const char* table_name, int32_t limit, int32_t offset, char** out_json)
this.wcdbGetMessageMeta = this.lib.func('int32 wcdb_get_message_meta(int64 handle, const char* dbPath, const char* tableName, int32 limit, int32 offset, _Out_ void** outJson)')
// wcdb_status wcdb_get_contact(wcdb_handle handle, const char* username, char** out_json)
this.wcdbGetContact = this.lib.func('int32 wcdb_get_contact(int64 handle, const char* username, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_table_stats(wcdb_handle handle, const char* session_id, char** out_json)
this.wcdbGetMessageTableStats = this.lib.func('int32 wcdb_get_message_table_stats(int64 handle, const char* sessionId, _Out_ void** outJson)')
// wcdb_status wcdb_get_aggregate_stats(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
this.wcdbGetAggregateStats = this.lib.func('int32 wcdb_get_aggregate_stats(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, _Out_ void** outJson)')
// wcdb_status wcdb_get_available_years(wcdb_handle handle, const char* session_ids_json, char** out_json)
try {
this.wcdbGetAvailableYears = this.lib.func('int32 wcdb_get_available_years(int64 handle, const char* sessionIdsJson, _Out_ void** outJson)')
} catch {
this.wcdbGetAvailableYears = null
}
// wcdb_status wcdb_get_annual_report_stats(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
try {
this.wcdbGetAnnualReportStats = this.lib.func('int32 wcdb_get_annual_report_stats(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, _Out_ void** outJson)')
} catch {
this.wcdbGetAnnualReportStats = null
}
// wcdb_status wcdb_get_annual_report_extras(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, int32_t peak_day_begin, int32_t peak_day_end, char** out_json)
try {
this.wcdbGetAnnualReportExtras = this.lib.func('int32 wcdb_get_annual_report_extras(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, int32 peakBegin, int32 peakEnd, _Out_ void** outJson)')
} catch {
this.wcdbGetAnnualReportExtras = null
}
// wcdb_status wcdb_get_group_stats(wcdb_handle handle, const char* chatroom_id, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
try {
this.wcdbGetGroupStats = this.lib.func('int32 wcdb_get_group_stats(int64 handle, const char* chatroomId, int32 begin, int32 end, _Out_ void** outJson)')
} catch {
this.wcdbGetGroupStats = null
}
// wcdb_status wcdb_open_message_cursor(wcdb_handle handle, const char* session_id, int32_t batch_size, int32_t ascending, int32_t begin_timestamp, int32_t end_timestamp, wcdb_cursor* out_cursor)
this.wcdbOpenMessageCursor = this.lib.func('int32 wcdb_open_message_cursor(int64 handle, const char* sessionId, int32 batchSize, int32 ascending, int32 beginTimestamp, int32 endTimestamp, _Out_ int64* outCursor)')
// wcdb_status wcdb_open_message_cursor_lite(wcdb_handle handle, const char* session_id, int32_t batch_size, int32_t ascending, int32_t begin_timestamp, int32_t end_timestamp, wcdb_cursor* out_cursor)
try {
this.wcdbOpenMessageCursorLite = this.lib.func('int32 wcdb_open_message_cursor_lite(int64 handle, const char* sessionId, int32 batchSize, int32 ascending, int32 beginTimestamp, int32 endTimestamp, _Out_ int64* outCursor)')
} catch {
this.wcdbOpenMessageCursorLite = null
}
// wcdb_status wcdb_fetch_message_batch(wcdb_handle handle, wcdb_cursor cursor, char** out_json, int32_t* out_has_more)
this.wcdbFetchMessageBatch = this.lib.func('int32 wcdb_fetch_message_batch(int64 handle, int64 cursor, _Out_ void** outJson, _Out_ int32* outHasMore)')
// wcdb_status wcdb_close_message_cursor(wcdb_handle handle, wcdb_cursor cursor)
this.wcdbCloseMessageCursor = this.lib.func('int32 wcdb_close_message_cursor(int64 handle, int64 cursor)')
// wcdb_status wcdb_get_logs(char** out_json)
this.wcdbGetLogs = this.lib.func('int32 wcdb_get_logs(_Out_ void** outJson)')
// wcdb_status wcdb_exec_query(wcdb_handle handle, const char* db_kind, const char* db_path, const char* sql, char** out_json)
this.wcdbExecQuery = this.lib.func('int32 wcdb_exec_query(int64 handle, const char* kind, const char* path, const char* sql, _Out_ void** outJson)')
// wcdb_status wcdb_get_emoticon_cdn_url(wcdb_handle handle, const char* db_path, const char* md5, char** out_url)
this.wcdbGetEmoticonCdnUrl = this.lib.func('int32 wcdb_get_emoticon_cdn_url(int64 handle, const char* dbPath, const char* md5, _Out_ void** outUrl)')
// wcdb_status wcdb_list_message_dbs(wcdb_handle handle, char** out_json)
this.wcdbListMessageDbs = this.lib.func('int32 wcdb_list_message_dbs(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_list_media_dbs(wcdb_handle handle, char** out_json)
this.wcdbListMediaDbs = this.lib.func('int32 wcdb_list_media_dbs(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_by_id(wcdb_handle handle, const char* session_id, int32 local_id, char** out_json)
this.wcdbGetMessageById = this.lib.func('int32 wcdb_get_message_by_id(int64 handle, const char* sessionId, int32 localId, _Out_ void** outJson)')
// wcdb_status wcdb_get_db_status(wcdb_handle handle, char** out_json)
try {
this.wcdbGetDbStatus = this.lib.func('int32 wcdb_get_db_status(int64 handle, _Out_ void** outJson)')
} catch {
this.wcdbGetDbStatus = null
}
// 初始化
const initResult = this.wcdbInit()
if (initResult !== 0) {
console.error('WCDB 初始化失败:', initResult)
return false
}
this.initialized = true
return true
} catch (e) {
console.error('WCDB 初始化异常:', e)
return false
}
}
/**
* 测试数据库连接
*/
async testConnection(dbPath: string, hexKey: string, wxid: string): Promise<{ success: boolean; error?: string; sessionCount?: number }> {
try {
// 如果当前已经有相同参数的活动连接,直接返回成功
if (this.handle !== null &&
this.currentPath === dbPath &&
this.currentKey === hexKey &&
this.currentWxid === wxid) {
return { success: true, sessionCount: 0 }
}
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) {
return { success: false, error: 'WCDB 初始化失败' }
}
}
// 构建 db_storage 目录路径
const dbStoragePath = this.resolveDbStoragePath(dbPath, wxid)
this.writeLog(`testConnection dbPath=${dbPath} wxid=${wxid} dbStorage=${dbStoragePath || 'null'}`)
if (!dbStoragePath || !existsSync(dbStoragePath)) {
return { success: false, error: `数据库目录不存在: ${dbPath}` }
}
// 递归查找 session.db
const sessionDbPath = this.findSessionDb(dbStoragePath)
this.writeLog(`testConnection sessionDb=${sessionDbPath || 'null'}`)
if (!sessionDbPath) {
return { success: false, error: `未找到 session.db 文件` }
}
// 分配输出参数内存
const handleOut = [0]
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
await this.printLogs()
let errorMsg = '数据库打开失败'
if (result === -1) errorMsg = '参数错误'
else if (result === -2) errorMsg = '密钥错误'
else if (result === -3) errorMsg = '数据库打开失败'
this.writeLog(`testConnection openAccount failed code=${result}`)
return { success: false, error: `${errorMsg} (错误码: ${result})` }
}
const tempHandle = handleOut[0]
if (tempHandle <= 0) {
return { success: false, error: '无效的数据库句柄' }
}
// 测试成功,使用 shutdown 清理所有资源(包括测试句柄)
// 这会中断当前活动连接,但 testConnection 本应该是独立测试
try {
this.wcdbShutdown()
this.handle = null
this.currentPath = null
this.currentKey = null
this.currentWxid = null
this.initialized = false
} catch (closeErr) {
console.error('关闭测试数据库时出错:', closeErr)
}
return { success: true, sessionCount: 0 }
} catch (e) {
console.error('测试连接异常:', e)
this.writeLog(`testConnection exception: ${String(e)}`)
return { success: false, error: String(e) }
}
}
/**
* 打印 DLL 内部日志(仅在出错时调用)
*/
private async printLogs(force = false): Promise<void> {
try {
if (!this.wcdbGetLogs) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result === 0 && outPtr[0]) {
try {
const jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
this.writeLog(`wcdb_logs: ${jsonStr}`, force)
this.wcdbFreeString(outPtr[0])
} catch (e) {
// ignore
}
}
} catch (e) {
console.error('获取日志失败:', e)
this.writeLog(`wcdb_logs failed: ${String(e)}`, force)
}
}
private startLogPolling(): void {
if (this.logTimer || !this.isLogEnabled()) return
this.logTimer = setInterval(() => {
void this.pollLogs()
}, 2000)
}
private stopLogPolling(): void {
if (this.logTimer) {
clearInterval(this.logTimer)
this.logTimer = null
}
this.lastLogTail = null
}
private async pollLogs(): Promise<void> {
try {
if (!this.wcdbGetLogs || !this.isLogEnabled()) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result !== 0 || !outPtr[0]) return
let jsonStr = ''
try {
jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
} finally {
try { this.wcdbFreeString(outPtr[0]) } catch { }
}
const logs = JSON.parse(jsonStr) as string[]
if (!Array.isArray(logs) || logs.length === 0) return
let startIdx = 0
if (this.lastLogTail) {
const idx = logs.lastIndexOf(this.lastLogTail)
if (idx >= 0) startIdx = idx + 1
}
for (let i = startIdx; i < logs.length; i += 1) {
this.writeLog(`wcdb: ${logs[i]}`)
}
this.lastLogTail = logs[logs.length - 1]
} catch (e) {
// ignore polling errors
}
}
private decodeJsonPtr(outPtr: any): string | null {
if (!outPtr) return null
try {
const jsonStr = this.koffi.decode(outPtr, 'char', -1)
this.wcdbFreeString(outPtr)
return jsonStr
} catch (e) {
try { this.wcdbFreeString(outPtr) } catch { }
return null
}
}
private ensureReady(): boolean {
return this.initialized && this.handle !== null
}
private normalizeTimestamp(input: number): number {
if (!input || input <= 0) return 0
const asNumber = Number(input)
if (!Number.isFinite(asNumber)) return 0
// Treat >1e12 as milliseconds.
const seconds = asNumber > 1e12 ? Math.floor(asNumber / 1000) : Math.floor(asNumber)
const maxInt32 = 2147483647
return Math.min(Math.max(seconds, 0), maxInt32)
}
private normalizeRange(beginTimestamp: number, endTimestamp: number): { begin: number; end: number } {
const normalizedBegin = this.normalizeTimestamp(beginTimestamp)
let normalizedEnd = this.normalizeTimestamp(endTimestamp)
if (normalizedEnd <= 0) {
normalizedEnd = this.normalizeTimestamp(Date.now())
}
if (normalizedBegin > 0 && normalizedEnd < normalizedBegin) {
normalizedEnd = normalizedBegin
}
return { begin: normalizedBegin, end: normalizedEnd }
}
isReady(): boolean {
return this.ensureReady()
}
/**
* 打开数据库
*/
async open(dbPath: string, hexKey: string, wxid: string): Promise<boolean> {
try {
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) return false
}
// 检查是否已经是当前连接的参数,如果是则直接返回成功,实现"始终保持链接"
if (this.handle !== null &&
this.currentPath === dbPath &&
this.currentKey === hexKey &&
this.currentWxid === wxid) {
return true
}
// 如果参数不同,则先关闭原来的连接
if (this.handle !== null) {
this.close()
// 重新初始化,因为 close 呼叫了 shutdown
const initOk = await this.initialize()
if (!initOk) return false
}
const dbStoragePath = this.resolveDbStoragePath(dbPath, wxid)
this.writeLog(`open dbPath=${dbPath} wxid=${wxid} dbStorage=${dbStoragePath || 'null'}`)
if (!dbStoragePath || !existsSync(dbStoragePath)) {
console.error('数据库目录不存在:', dbPath)
this.writeLog(`open failed: dbStorage not found for ${dbPath}`)
return false
}
const sessionDbPath = this.findSessionDb(dbStoragePath)
this.writeLog(`open sessionDb=${sessionDbPath || 'null'}`)
if (!sessionDbPath) {
console.error('未找到 session.db 文件')
this.writeLog('open failed: session.db not found')
return false
}
const handleOut = [0]
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
console.error('打开数据库失败:', result)
await this.printLogs()
this.writeLog(`open failed: openAccount code=${result}`)
return false
}
const handle = handleOut[0]
if (handle <= 0) {
return false
}
this.handle = handle
this.currentPath = dbPath
this.currentKey = hexKey
this.currentWxid = wxid
this.initialized = true
if (this.wcdbSetMyWxid && wxid) {
try {
this.wcdbSetMyWxid(this.handle, wxid)
} catch (e) {
console.warn('设置 wxid 失败:', e)
}
}
if (this.isLogEnabled()) {
this.startLogPolling()
}
this.writeLog(`open ok handle=${handle}`)
return true
} catch (e) {
console.error('打开数据库异常:', e)
this.writeLog(`open exception: ${String(e)}`)
return false
}
}
/**
* 关闭数据库
* 注意:wcdb_close_account 可能导致崩溃,使用 shutdown 代替
*/
close(): void {
if (this.handle !== null || this.initialized) {
try {
// 不调用 closeAccount,直接 shutdown
this.wcdbShutdown()
} catch (e) {
console.error('WCDB shutdown 出错:', e)
}
this.handle = null
this.currentPath = null
this.currentKey = null
this.currentWxid = null
this.initialized = false
this.stopLogPolling()
}
}
/**
* 关闭服务(与 close 相同)
*/
shutdown(): void {
this.close()
}
/**
* 检查是否已连接
*/
isConnected(): boolean {
return this.initialized && this.handle !== null
}
async getSessions(): Promise<{ success: boolean; sessions?: any[]; error?: string }> {
if (!this.ensureReady()) {
this.writeLog('getSessions skipped: not connected')
return { success: false, error: 'WCDB 未连接' }
}
try {
// 使用 setImmediate 让事件循环有机会处理其他任务,避免长时间阻塞
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetSessions(this.handle, outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
this.writeLog(`getSessions failed: code=${result}`)
return { success: false, error: `获取会话失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析会话失败' }
this.writeLog(`getSessions ok size=${jsonStr.length}`)
const sessions = JSON.parse(jsonStr)
return { success: true, sessions }
} catch (e) {
this.writeLog(`getSessions exception: ${String(e)}`)
return { success: false, error: String(e) }
}
}
async getMessages(sessionId: string, limit: number, offset: number): Promise<{ success: boolean; messages?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessages(this.handle, sessionId, limit, offset, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息失败' }
const messages = JSON.parse(jsonStr)
return { success: true, messages }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageCount(sessionId: string): Promise<{ success: boolean; count?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCount = [0]
const result = this.wcdbGetMessageCount(this.handle, sessionId, outCount)
if (result !== 0) {
return { success: false, error: `获取消息总数失败: ${result}` }
}
return { success: true, count: outCount[0] }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getDisplayNames(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (usernames.length === 0) return { success: true, map: {} }
try {
// 让出控制权,避免阻塞事件循环
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetDisplayNames(this.handle, JSON.stringify(usernames), outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取昵称失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析昵称失败' }
const map = JSON.parse(jsonStr)
return { success: true, map }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getAvatarUrls(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (usernames.length === 0) return { success: true, map: {} }
try {
const now = Date.now()
const resultMap: Record<string, string> = {}
const toFetch: string[] = []
const seen = new Set<string>()
for (const username of usernames) {
if (!username || seen.has(username)) continue
seen.add(username)
const cached = this.avatarUrlCache.get(username)
// 只使用有效的缓存(URL不为空)
if (cached && cached.url && cached.url.trim() && now - cached.updatedAt < this.avatarCacheTtlMs) {
resultMap[username] = cached.url
continue
}
toFetch.push(username)
}
if (toFetch.length === 0) {
return { success: true, map: resultMap }
}
// 让出控制权,避免阻塞事件循环
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetAvatarUrls(this.handle, JSON.stringify(toFetch), outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
console.warn(`[wcdbCore] getAvatarUrls DLL调用失败: result=${result}, usernames=${toFetch.length}`)
if (Object.keys(resultMap).length > 0) {
return { success: true, map: resultMap, error: `获取头像失败: ${result}` }
}
return { success: false, error: `获取头像失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) {
console.error('[wcdbCore] getAvatarUrls 解析JSON失败')
return { success: false, error: '解析头像失败' }
}
const map = JSON.parse(jsonStr) as Record<string, string>
let successCount = 0
let emptyCount = 0
for (const username of toFetch) {
const url = map[username]
if (url && url.trim()) {
resultMap[username] = url
// 只缓存有效的URL
this.avatarUrlCache.set(username, { url, updatedAt: now })
successCount++
} else {
emptyCount++
// 不缓存空URL,下次可以重新尝试
}
}
console.log(`[wcdbCore] getAvatarUrls 成功: ${successCount}个, 空结果: ${emptyCount}个, 总请求: ${toFetch.length}`)
return { success: true, map: resultMap }
} catch (e) {
console.error('[wcdbCore] getAvatarUrls 异常:', e)
return { success: false, error: String(e) }
}
}
async getGroupMemberCount(chatroomId: string): Promise<{ success: boolean; count?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCount = [0]
const result = this.wcdbGetGroupMemberCount(this.handle, chatroomId, outCount)
if (result !== 0) {
return { success: false, error: `获取群成员数量失败: ${result}` }
}
return { success: true, count: outCount[0] }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getGroupMemberCounts(chatroomIds: string[]): Promise<{ success: boolean; map?: Record<string, number>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (chatroomIds.length === 0) return { success: true, map: {} }
if (!this.wcdbGetGroupMemberCounts) {
const map: Record<string, number> = {}
for (const chatroomId of chatroomIds) {
const result = await this.getGroupMemberCount(chatroomId)
if (result.success && typeof result.count === 'number') {
map[chatroomId] = result.count
}
}
return { success: true, map }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetGroupMemberCounts(this.handle, JSON.stringify(chatroomIds), outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群成员数量失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群成员数量失败' }
const map = JSON.parse(jsonStr)
return { success: true, map }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getGroupMembers(chatroomId: string): Promise<{ success: boolean; members?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetGroupMembers(this.handle, chatroomId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群成员失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群成员失败' }
const members = JSON.parse(jsonStr)
return { success: true, members }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageTables(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageTables(this.handle, sessionId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息表失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息表失败' }
const tables = JSON.parse(jsonStr)
return { success: true, tables }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageTableStats(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageTableStats(this.handle, sessionId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取表统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析表统计失败' }
const tables = JSON.parse(jsonStr)
return { success: true, tables }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageMeta(dbPath: string, tableName: string, limit: number, offset: number): Promise<{ success: boolean; rows?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageMeta(this.handle, dbPath, tableName, limit, offset, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息元数据失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息元数据失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getContact(username: string): Promise<{ success: boolean; contact?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetContact(this.handle, username, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取联系人失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析联系人失败' }
const contact = JSON.parse(jsonStr)
return { success: true, contact }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getAggregateStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const normalizedBegin = this.normalizeTimestamp(beginTimestamp)
let normalizedEnd = this.normalizeTimestamp(endTimestamp)
if (normalizedEnd <= 0) {
normalizedEnd = this.normalizeTimestamp(Date.now())
}
if (normalizedBegin > 0 && normalizedEnd < normalizedBegin) {
normalizedEnd = normalizedBegin
}
const callAggregate = (ids: string[]) => {
const idsAreNumeric = ids.length > 0 && ids.every((id) => /^\d+$/.test(id))
const payloadIds = idsAreNumeric ? ids.map((id) => Number(id)) : ids
const outPtr = [null as any]
const result = this.wcdbGetAggregateStats(this.handle, JSON.stringify(payloadIds), normalizedBegin, normalizedEnd, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取聚合统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) {
return { success: false, error: '解析聚合统计失败' }
}
const data = JSON.parse(jsonStr)
return { success: true, data }
}
let result = callAggregate(sessionIds)
if (result.success && result.data && result.data.total === 0 && result.data.idMap) {
const idMap = result.data.idMap as Record<string, string>
const reverseMap: Record<string, string> = {}
for (const [id, name] of Object.entries(idMap)) {
if (!name) continue
reverseMap[name] = id
}
const numericIds = sessionIds
.map((id) => reverseMap[id])
.filter((id) => typeof id === 'string' && /^\d+$/.test(id))
if (numericIds.length > 0) {
const retry = callAggregate(numericIds)
if (retry.success && retry.data) {
result = retry
}
}
}
return result
} catch (e) {
return { success: false, error: String(e) }
}
}
async getAvailableYears(sessionIds: string[]): Promise<{ success: boolean; data?: number[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAvailableYears) {
return { success: false, error: '未支持获取年度列表' }
}
if (sessionIds.length === 0) return { success: true, data: [] }
try {
const outPtr = [null as any]
const result = this.wcdbGetAvailableYears(this.handle, JSON.stringify(sessionIds), outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度列表失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getAnnualReportStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAnnualReportStats) {
return this.getAggregateStats(sessionIds, beginTimestamp, endTimestamp)
}
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetAnnualReportStats(this.handle, JSON.stringify(sessionIds), begin, end, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getAnnualReportExtras(
sessionIds: string[],
beginTimestamp: number = 0,
endTimestamp: number = 0,
peakDayBegin: number = 0,
peakDayEnd: number = 0
): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAnnualReportExtras) {
return { success: false, error: '未支持年度扩展统计' }
}
if (sessionIds.length === 0) return { success: true, data: {} }
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetAnnualReportExtras(
this.handle,
JSON.stringify(sessionIds),
begin,
end,
this.normalizeTimestamp(peakDayBegin),
this.normalizeTimestamp(peakDayEnd),
outPtr
)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度扩展统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度扩展统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getGroupStats(chatroomId: string, beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetGroupStats) {
return this.getAggregateStats([chatroomId], beginTimestamp, endTimestamp)
}
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetGroupStats(this.handle, chatroomId, begin, end, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群聊统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群聊统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async openMessageCursor(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCursor = [0]
const result = this.wcdbOpenMessageCursor(
this.handle,
sessionId,
batchSize,
ascending ? 1 : 0,
beginTimestamp,
endTimestamp,
outCursor
)
if (result !== 0 || outCursor[0] <= 0) {
await this.printLogs(true)
this.writeLog(
`openMessageCursor failed: sessionId=${sessionId} batchSize=${batchSize} ascending=${ascending ? 1 : 0} begin=${beginTimestamp} end=${endTimestamp} result=${result} cursor=${outCursor[0]}`,
true
)
return { success: false, error: `创建游标失败: ${result},请查看日志` }
}
return { success: true, cursor: outCursor[0] }
} catch (e) {
await this.printLogs(true)
this.writeLog(`openMessageCursor exception: ${String(e)}`, true)
return { success: false, error: '创建游标异常,请查看日志' }
}
}
async openMessageCursorLite(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbOpenMessageCursorLite) {
return this.openMessageCursor(sessionId, batchSize, ascending, beginTimestamp, endTimestamp)
}
try {
const outCursor = [0]
const result = this.wcdbOpenMessageCursorLite(
this.handle,
sessionId,
batchSize,
ascending ? 1 : 0,
beginTimestamp,
endTimestamp,
outCursor
)
if (result !== 0 || outCursor[0] <= 0) {
await this.printLogs(true)
this.writeLog(
`openMessageCursorLite failed: sessionId=${sessionId} batchSize=${batchSize} ascending=${ascending ? 1 : 0} begin=${beginTimestamp} end=${endTimestamp} result=${result} cursor=${outCursor[0]}`,
true
)
return { success: false, error: `创建游标失败: ${result},请查看日志` }
}
return { success: true, cursor: outCursor[0] }
} catch (e) {
await this.printLogs(true)
this.writeLog(`openMessageCursorLite exception: ${String(e)}`, true)
return { success: false, error: '创建游标异常,请查看日志' }
}
}
async fetchMessageBatch(cursor: number): Promise<{ success: boolean; rows?: any[]; hasMore?: boolean; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const outHasMore = [0]
const result = this.wcdbFetchMessageBatch(this.handle, cursor, outPtr, outHasMore)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取批次失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析批次失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows, hasMore: outHasMore[0] === 1 }
} catch (e) {
return { success: false, error: String(e) }
}
}
async closeMessageCursor(cursor: number): Promise<{ success: boolean; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const result = this.wcdbCloseMessageCursor(this.handle, cursor)
if (result !== 0) {
return { success: false, error: `关闭游标失败: ${result}` }
}
return { success: true }
} catch (e) {
return { success: false, error: String(e) }
}
}
async execQuery(kind: string, path: string | null, sql: string): Promise<{ success: boolean; rows?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbExecQuery(this.handle, kind, path, sql, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `执行查询失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析查询结果失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getEmoticonCdnUrl(dbPath: string, md5: string): Promise<{ success: boolean; url?: string; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetEmoticonCdnUrl(this.handle, dbPath, md5, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取表情 URL 失败: ${result}` }
}
const urlStr = this.decodeJsonPtr(outPtr[0])
if (urlStr === null) return { success: false, error: '解析表情 URL 失败' }
return { success: true, url: urlStr || undefined }
} catch (e) {
return { success: false, error: String(e) }
}
}
async listMessageDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbListMessageDbs(this.handle, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `获取消息库列表失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息库列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async listMediaDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbListMediaDbs(this.handle, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `获取媒体库列表失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析媒体库列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageById(sessionId: string, localId: number): Promise<{ success: boolean; message?: any; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageById(this.handle, sessionId, localId, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `查询消息失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息失败' }
const message = JSON.parse(jsonStr)
// 处理 wcdb_get_message_by_id 返回空对象的情况
if (Object.keys(message).length === 0) return { success: false, error: '未找到消息' }
return { success: true, message }
} catch (e) {
return { success: false, error: String(e) }
}
}
}
+220 -1159
View File
@@ -1,1285 +1,346 @@
import { join, dirname, basename } from 'path'
import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, readFileSync } from 'fs'
import { Worker } from 'worker_threads'
import { join } from 'path'
import { existsSync } from 'fs'
/**
* Worker 消息接口
*/
interface WorkerMessage {
id: number
result?: any
error?: string
}
/**
* WCDB 服务 (客户端代理)
* 负责与后台 Worker 线程通信,执行数据库操作
* 避免主进程阻塞
*/
export class WcdbService {
private worker: Worker | null = null
private messageId = 0
private pending = new Map<number, { resolve: (val: any) => void; reject: (err: any) => void }>()
private resourcesPath: string | null = null
private userDataPath: string | null = null
private logEnabled = false
private lib: any = null
private koffi: any = null
private initialized = false
private handle: number | null = null
private currentPath: string | null = null
private currentKey: string | null = null
private currentWxid: string | null = null
// 函数引用
private wcdbInit: any = null
private wcdbShutdown: any = null
private wcdbOpenAccount: any = null
private wcdbCloseAccount: any = null
private wcdbSetMyWxid: any = null
private wcdbFreeString: any = null
private wcdbGetSessions: any = null
private wcdbGetMessages: any = null
private wcdbGetMessageCount: any = null
private wcdbGetDisplayNames: any = null
private wcdbGetAvatarUrls: any = null
private wcdbGetGroupMemberCount: any = null
private wcdbGetGroupMemberCounts: any = null
private wcdbGetGroupMembers: any = null
private wcdbGetMessageTables: any = null
private wcdbGetMessageMeta: any = null
private wcdbGetContact: any = null
private wcdbGetMessageTableStats: any = null
private wcdbGetAggregateStats: any = null
private wcdbGetAvailableYears: any = null
private wcdbGetAnnualReportStats: any = null
private wcdbGetAnnualReportExtras: any = null
private wcdbGetGroupStats: any = null
private wcdbOpenMessageCursor: any = null
private wcdbOpenMessageCursorLite: any = null
private wcdbFetchMessageBatch: any = null
private wcdbCloseMessageCursor: any = null
private wcdbGetLogs: any = null
private wcdbExecQuery: any = null
private wcdbListMessageDbs: any = null
private wcdbListMediaDbs: any = null
private wcdbGetMessageById: any = null
private wcdbGetEmoticonCdnUrl: any = null
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
private readonly avatarCacheTtlMs = 10 * 60 * 1000
private logTimer: NodeJS.Timeout | null = null
private lastLogTail: string | null = null
constructor() {
this.initWorker()
}
/**
* 初始化 Worker 线程
*/
private initWorker() {
if (this.worker) return
const isDev = process.env.NODE_ENV === 'development'
const workerPath = isDev
? join(__dirname, '../dist-electron/wcdbWorker.js')
: join(__dirname, 'wcdbWorker.js')
let finalPath = workerPath
if (isDev && !existsSync(finalPath)) {
finalPath = join(__dirname, 'wcdbWorker.js')
}
try {
this.worker = new Worker(finalPath)
this.worker.on('message', (msg: WorkerMessage) => {
const { id, result, error } = msg
const p = this.pending.get(id)
if (p) {
this.pending.delete(id)
if (error) p.reject(new Error(error))
else p.resolve(result)
}
})
this.worker.on('error', (err) => {
console.error('WCDB Worker 错误:', err)
})
this.worker.on('exit', (code) => {
if (code !== 0) console.error(`WCDB Worker 异常退出,退出码: ${code}`)
this.worker = null
})
// 如果已有路径配置,重新发送给新的 worker
if (this.resourcesPath && this.userDataPath) {
this.setPaths(this.resourcesPath, this.userDataPath)
}
this.setLogEnabled(this.logEnabled)
} catch (e) {
console.error('创建 WCDB Worker 失败:', e)
}
}
/**
* 发送消息到 Worker 并等待响应
*/
private callWorker<T>(type: string, payload: any = {}): Promise<T> {
if (!this.worker) this.initWorker()
if (!this.worker) return Promise.reject(new Error('WCDB Worker 不可用'))
return new Promise((resolve, reject) => {
const id = ++this.messageId
this.pending.set(id, { resolve, reject })
this.worker!.postMessage({ id, type, payload })
})
}
/**
* 设置资源路径
*/
setPaths(resourcesPath: string, userDataPath: string): void {
this.resourcesPath = resourcesPath
this.userDataPath = userDataPath
this.callWorker('setPaths', { resourcesPath, userDataPath }).catch(console.error)
}
/**
* 启用/禁用日志
*/
setLogEnabled(enabled: boolean): void {
this.logEnabled = enabled
if (this.isLogEnabled() && this.initialized) {
this.startLogPolling()
} else {
this.stopLogPolling()
}
this.callWorker('setLogEnabled', { enabled }).catch(console.error)
}
/**
* 获取 DLL 路径
* 检查服务是否就绪
*/
private getDllPath(): string {
const envDllPath = process.env.WCDB_DLL_PATH
if (envDllPath && envDllPath.length > 0) {
return envDllPath
}
const envResourcesPath = process.env.WCDB_RESOURCES_PATH
if (envResourcesPath && envResourcesPath.length > 0) {
return join(envResourcesPath, 'wcdb_api.dll')
}
if (this.resourcesPath && this.resourcesPath.length > 0) {
return join(this.resourcesPath, 'wcdb_api.dll')
}
const fallbackBase = process.resourcesPath || join(process.cwd(), 'resources')
return join(fallbackBase, 'wcdb_api.dll')
isReady(): boolean {
return !!this.worker
}
private isLogEnabled(): boolean {
if (process.env.WEFLOW_WORKER === '1') return false
if (process.env.WCDB_LOG_ENABLED === '1') return true
return this.logEnabled
}
private writeLog(message: string, force = false): void {
if (!force && !this.isLogEnabled()) return
try {
const base = this.userDataPath || process.env.WCDB_LOG_DIR || process.cwd()
const dir = join(base, 'logs')
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
const line = `[${new Date().toISOString()}] ${message}\n`
appendFileSync(join(dir, 'wcdb.log'), line, { encoding: 'utf8' })
} catch { }
}
/**
* 递归查找 session.db 文件
*/
private findSessionDb(dir: string, depth = 0): string | null {
if (depth > 5) return null
try {
const entries = readdirSync(dir)
for (const entry of entries) {
if (entry.toLowerCase() === 'session.db') {
const fullPath = join(dir, entry)
if (statSync(fullPath).isFile()) {
return fullPath
}
}
}
for (const entry of entries) {
const fullPath = join(dir, entry)
try {
if (statSync(fullPath).isDirectory()) {
const found = this.findSessionDb(fullPath, depth + 1)
if (found) return found
}
} catch { }
}
} catch (e) {
console.error('查找 session.db 失败:', e)
}
return null
}
private resolveDbStoragePath(basePath: string, wxid: string): string | null {
if (!basePath) return null
const normalized = basePath.replace(/[\\\\/]+$/, '')
if (normalized.toLowerCase().endsWith('db_storage') && existsSync(normalized)) {
return normalized
}
const direct = join(normalized, 'db_storage')
if (existsSync(direct)) {
return direct
}
if (wxid) {
const viaWxid = join(normalized, wxid, 'db_storage')
if (existsSync(viaWxid)) {
return viaWxid
}
// 兼容目录名包含额外后缀(如 wxid_xxx_1234)
try {
const entries = readdirSync(normalized)
const lowerWxid = wxid.toLowerCase()
const candidates = entries.filter((entry) => {
const entryPath = join(normalized, entry)
try {
if (!statSync(entryPath).isDirectory()) return false
} catch {
return false
}
const lowerEntry = entry.toLowerCase()
return lowerEntry === lowerWxid || lowerEntry.startsWith(`${lowerWxid}_`)
})
for (const entry of candidates) {
const candidate = join(normalized, entry, 'db_storage')
if (existsSync(candidate)) {
return candidate
}
}
} catch { }
}
return null
}
/**
* 初始化 WCDB
*/
async initialize(): Promise<boolean> {
if (this.initialized) return true
try {
this.koffi = require('koffi')
const dllPath = this.getDllPath()
if (!existsSync(dllPath)) {
console.error('WCDB DLL 不存在:', dllPath)
return false
}
this.lib = this.koffi.load(dllPath)
// 定义类型
// wcdb_status wcdb_init()
this.wcdbInit = this.lib.func('int32 wcdb_init()')
// wcdb_status wcdb_shutdown()
this.wcdbShutdown = this.lib.func('int32 wcdb_shutdown()')
// wcdb_status wcdb_open_account(const char* session_db_path, const char* hex_key, wcdb_handle* out_handle)
// wcdb_handle 是 int64_t
this.wcdbOpenAccount = this.lib.func('int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)')
// wcdb_status wcdb_close_account(wcdb_handle handle)
// C 接口是 int64, koffi 返回 handle 是 number 类型
this.wcdbCloseAccount = this.lib.func('int32 wcdb_close_account(int64 handle)')
// wcdb_status wcdb_set_my_wxid(wcdb_handle handle, const char* wxid)
try {
this.wcdbSetMyWxid = this.lib.func('int32 wcdb_set_my_wxid(int64 handle, const char* wxid)')
} catch {
this.wcdbSetMyWxid = null
}
// void wcdb_free_string(char* ptr)
this.wcdbFreeString = this.lib.func('void wcdb_free_string(void* ptr)')
// wcdb_status wcdb_get_sessions(wcdb_handle handle, char** out_json)
this.wcdbGetSessions = this.lib.func('int32 wcdb_get_sessions(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_get_messages(wcdb_handle handle, const char* username, int32_t limit, int32_t offset, char** out_json)
this.wcdbGetMessages = this.lib.func('int32 wcdb_get_messages(int64 handle, const char* username, int32 limit, int32 offset, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_count(wcdb_handle handle, const char* username, int32_t* out_count)
this.wcdbGetMessageCount = this.lib.func('int32 wcdb_get_message_count(int64 handle, const char* username, _Out_ int32* outCount)')
// wcdb_status wcdb_get_display_names(wcdb_handle handle, const char* usernames_json, char** out_json)
this.wcdbGetDisplayNames = this.lib.func('int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
// wcdb_status wcdb_get_avatar_urls(wcdb_handle handle, const char* usernames_json, char** out_json)
this.wcdbGetAvatarUrls = this.lib.func('int32 wcdb_get_avatar_urls(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
// wcdb_status wcdb_get_group_member_count(wcdb_handle handle, const char* chatroom_id, int32_t* out_count)
this.wcdbGetGroupMemberCount = this.lib.func('int32 wcdb_get_group_member_count(int64 handle, const char* chatroomId, _Out_ int32* outCount)')
// wcdb_status wcdb_get_group_member_counts(wcdb_handle handle, const char* chatroom_ids_json, char** out_json)
try {
this.wcdbGetGroupMemberCounts = this.lib.func('int32 wcdb_get_group_member_counts(int64 handle, const char* chatroomIdsJson, _Out_ void** outJson)')
} catch {
this.wcdbGetGroupMemberCounts = null
}
// wcdb_status wcdb_get_group_members(wcdb_handle handle, const char* chatroom_id, char** out_json)
this.wcdbGetGroupMembers = this.lib.func('int32 wcdb_get_group_members(int64 handle, const char* chatroomId, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_tables(wcdb_handle handle, const char* session_id, char** out_json)
this.wcdbGetMessageTables = this.lib.func('int32 wcdb_get_message_tables(int64 handle, const char* sessionId, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_meta(wcdb_handle handle, const char* db_path, const char* table_name, int32_t limit, int32_t offset, char** out_json)
this.wcdbGetMessageMeta = this.lib.func('int32 wcdb_get_message_meta(int64 handle, const char* dbPath, const char* tableName, int32 limit, int32 offset, _Out_ void** outJson)')
// wcdb_status wcdb_get_contact(wcdb_handle handle, const char* username, char** out_json)
this.wcdbGetContact = this.lib.func('int32 wcdb_get_contact(int64 handle, const char* username, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_table_stats(wcdb_handle handle, const char* session_id, char** out_json)
this.wcdbGetMessageTableStats = this.lib.func('int32 wcdb_get_message_table_stats(int64 handle, const char* sessionId, _Out_ void** outJson)')
// wcdb_status wcdb_get_aggregate_stats(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
this.wcdbGetAggregateStats = this.lib.func('int32 wcdb_get_aggregate_stats(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, _Out_ void** outJson)')
// wcdb_status wcdb_get_available_years(wcdb_handle handle, const char* session_ids_json, char** out_json)
try {
this.wcdbGetAvailableYears = this.lib.func('int32 wcdb_get_available_years(int64 handle, const char* sessionIdsJson, _Out_ void** outJson)')
} catch {
this.wcdbGetAvailableYears = null
}
// wcdb_status wcdb_get_annual_report_stats(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
try {
this.wcdbGetAnnualReportStats = this.lib.func('int32 wcdb_get_annual_report_stats(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, _Out_ void** outJson)')
} catch {
this.wcdbGetAnnualReportStats = null
}
// wcdb_status wcdb_get_annual_report_extras(wcdb_handle handle, const char* session_ids_json, int32_t begin_timestamp, int32_t end_timestamp, int32_t peak_day_begin, int32_t peak_day_end, char** out_json)
try {
this.wcdbGetAnnualReportExtras = this.lib.func('int32 wcdb_get_annual_report_extras(int64 handle, const char* sessionIdsJson, int32 begin, int32 end, int32 peakBegin, int32 peakEnd, _Out_ void** outJson)')
} catch {
this.wcdbGetAnnualReportExtras = null
}
// wcdb_status wcdb_get_group_stats(wcdb_handle handle, const char* chatroom_id, int32_t begin_timestamp, int32_t end_timestamp, char** out_json)
try {
this.wcdbGetGroupStats = this.lib.func('int32 wcdb_get_group_stats(int64 handle, const char* chatroomId, int32 begin, int32 end, _Out_ void** outJson)')
} catch {
this.wcdbGetGroupStats = null
}
// wcdb_status wcdb_open_message_cursor(wcdb_handle handle, const char* session_id, int32_t batch_size, int32_t ascending, int32_t begin_timestamp, int32_t end_timestamp, wcdb_cursor* out_cursor)
this.wcdbOpenMessageCursor = this.lib.func('int32 wcdb_open_message_cursor(int64 handle, const char* sessionId, int32 batchSize, int32 ascending, int32 beginTimestamp, int32 endTimestamp, _Out_ int64* outCursor)')
// wcdb_status wcdb_open_message_cursor_lite(wcdb_handle handle, const char* session_id, int32_t batch_size, int32_t ascending, int32_t begin_timestamp, int32_t end_timestamp, wcdb_cursor* out_cursor)
try {
this.wcdbOpenMessageCursorLite = this.lib.func('int32 wcdb_open_message_cursor_lite(int64 handle, const char* sessionId, int32 batchSize, int32 ascending, int32 beginTimestamp, int32 endTimestamp, _Out_ int64* outCursor)')
} catch {
this.wcdbOpenMessageCursorLite = null
}
// wcdb_status wcdb_fetch_message_batch(wcdb_handle handle, wcdb_cursor cursor, char** out_json, int32_t* out_has_more)
this.wcdbFetchMessageBatch = this.lib.func('int32 wcdb_fetch_message_batch(int64 handle, int64 cursor, _Out_ void** outJson, _Out_ int32* outHasMore)')
// wcdb_status wcdb_close_message_cursor(wcdb_handle handle, wcdb_cursor cursor)
this.wcdbCloseMessageCursor = this.lib.func('int32 wcdb_close_message_cursor(int64 handle, int64 cursor)')
// wcdb_status wcdb_get_logs(char** out_json)
this.wcdbGetLogs = this.lib.func('int32 wcdb_get_logs(_Out_ void** outJson)')
// wcdb_status wcdb_exec_query(wcdb_handle handle, const char* db_kind, const char* db_path, const char* sql, char** out_json)
this.wcdbExecQuery = this.lib.func('int32 wcdb_exec_query(int64 handle, const char* kind, const char* path, const char* sql, _Out_ void** outJson)')
// wcdb_status wcdb_get_emoticon_cdn_url(wcdb_handle handle, const char* db_path, const char* md5, char** out_url)
this.wcdbGetEmoticonCdnUrl = this.lib.func('int32 wcdb_get_emoticon_cdn_url(int64 handle, const char* dbPath, const char* md5, _Out_ void** outUrl)')
// wcdb_status wcdb_list_message_dbs(wcdb_handle handle, char** out_json)
this.wcdbListMessageDbs = this.lib.func('int32 wcdb_list_message_dbs(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_list_media_dbs(wcdb_handle handle, char** out_json)
this.wcdbListMediaDbs = this.lib.func('int32 wcdb_list_media_dbs(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_get_message_by_id(wcdb_handle handle, const char* session_id, int32 local_id, char** out_json)
this.wcdbGetMessageById = this.lib.func('int32 wcdb_get_message_by_id(int64 handle, const char* sessionId, int32 localId, _Out_ void** outJson)')
// 初始化
const initResult = this.wcdbInit()
if (initResult !== 0) {
console.error('WCDB 初始化失败:', initResult)
return false
}
this.initialized = true
return true
} catch (e) {
console.error('WCDB 初始化异常:', e)
return false
}
}
// ==========================================
// 代理方法 (Proxy Methods)
// ==========================================
/**
* 测试数据库连接
*/
async testConnection(dbPath: string, hexKey: string, wxid: string): Promise<{ success: boolean; error?: string; sessionCount?: number }> {
try {
// 如果当前已经有相同参数的活动连接,直接返回成功
if (this.handle !== null &&
this.currentPath === dbPath &&
this.currentKey === hexKey &&
this.currentWxid === wxid) {
return { success: true, sessionCount: 0 }
}
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) {
return { success: false, error: 'WCDB 初始化失败' }
}
}
// 构建 db_storage 目录路径
const dbStoragePath = this.resolveDbStoragePath(dbPath, wxid)
this.writeLog(`testConnection dbPath=${dbPath} wxid=${wxid} dbStorage=${dbStoragePath || 'null'}`)
if (!dbStoragePath || !existsSync(dbStoragePath)) {
return { success: false, error: `数据库目录不存在: ${dbPath}` }
}
// 递归查找 session.db
const sessionDbPath = this.findSessionDb(dbStoragePath)
this.writeLog(`testConnection sessionDb=${sessionDbPath || 'null'}`)
if (!sessionDbPath) {
return { success: false, error: `未找到 session.db 文件` }
}
// 分配输出参数内存
const handleOut = [0]
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
await this.printLogs()
let errorMsg = '数据库打开失败'
if (result === -1) errorMsg = '参数错误'
else if (result === -2) errorMsg = '密钥错误'
else if (result === -3) errorMsg = '数据库打开失败'
this.writeLog(`testConnection openAccount failed code=${result}`)
return { success: false, error: `${errorMsg} (错误码: ${result})` }
}
const tempHandle = handleOut[0]
if (tempHandle <= 0) {
return { success: false, error: '无效的数据库句柄' }
}
// 测试成功,使用 shutdown 清理所有资源(包括测试句柄)
// 这会中断当前活动连接,但 testConnection 本应该是独立测试
try {
this.wcdbShutdown()
this.handle = null
this.currentPath = null
this.currentKey = null
this.currentWxid = null
this.initialized = false
} catch (closeErr) {
console.error('关闭测试数据库时出错:', closeErr)
}
return { success: true, sessionCount: 0 }
} catch (e) {
console.error('测试连接异常:', e)
this.writeLog(`testConnection exception: ${String(e)}`)
return { success: false, error: String(e) }
}
}
/**
* 打印 DLL 内部日志(仅在出错时调用)
*/
private async printLogs(force = false): Promise<void> {
try {
if (!this.wcdbGetLogs) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result === 0 && outPtr[0]) {
try {
const jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
this.writeLog(`wcdb_logs: ${jsonStr}`, force)
this.wcdbFreeString(outPtr[0])
} catch (e) {
// ignore
}
}
} catch (e) {
console.error('获取日志失败:', e)
this.writeLog(`wcdb_logs failed: ${String(e)}`, force)
}
}
private startLogPolling(): void {
if (this.logTimer || !this.isLogEnabled()) return
this.logTimer = setInterval(() => {
void this.pollLogs()
}, 2000)
}
private stopLogPolling(): void {
if (this.logTimer) {
clearInterval(this.logTimer)
this.logTimer = null
}
this.lastLogTail = null
}
private async pollLogs(): Promise<void> {
try {
if (!this.wcdbGetLogs || !this.isLogEnabled()) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result !== 0 || !outPtr[0]) return
let jsonStr = ''
try {
jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
} finally {
try { this.wcdbFreeString(outPtr[0]) } catch { }
}
const logs = JSON.parse(jsonStr) as string[]
if (!Array.isArray(logs) || logs.length === 0) return
let startIdx = 0
if (this.lastLogTail) {
const idx = logs.lastIndexOf(this.lastLogTail)
if (idx >= 0) startIdx = idx + 1
}
for (let i = startIdx; i < logs.length; i += 1) {
this.writeLog(`wcdb: ${logs[i]}`)
}
this.lastLogTail = logs[logs.length - 1]
} catch (e) {
// ignore polling errors
}
}
private decodeJsonPtr(outPtr: any): string | null {
if (!outPtr) return null
try {
const jsonStr = this.koffi.decode(outPtr, 'char', -1)
this.wcdbFreeString(outPtr)
return jsonStr
} catch (e) {
try { this.wcdbFreeString(outPtr) } catch { }
return null
}
}
private ensureReady(): boolean {
return this.initialized && this.handle !== null
}
private normalizeTimestamp(input: number): number {
if (!input || input <= 0) return 0
const asNumber = Number(input)
if (!Number.isFinite(asNumber)) return 0
// Treat >1e12 as milliseconds.
const seconds = asNumber > 1e12 ? Math.floor(asNumber / 1000) : Math.floor(asNumber)
const maxInt32 = 2147483647
return Math.min(Math.max(seconds, 0), maxInt32)
}
private normalizeRange(beginTimestamp: number, endTimestamp: number): { begin: number; end: number } {
const normalizedBegin = this.normalizeTimestamp(beginTimestamp)
let normalizedEnd = this.normalizeTimestamp(endTimestamp)
if (normalizedEnd <= 0) {
normalizedEnd = this.normalizeTimestamp(Date.now())
}
if (normalizedBegin > 0 && normalizedEnd < normalizedBegin) {
normalizedEnd = normalizedBegin
}
return { begin: normalizedBegin, end: normalizedEnd }
}
isReady(): boolean {
return this.ensureReady()
return this.callWorker('testConnection', { dbPath, hexKey, wxid })
}
/**
* 打开数据库
*/
async open(dbPath: string, hexKey: string, wxid: string): Promise<boolean> {
try {
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) return false
}
// 检查是否已经是当前连接的参数,如果是则直接返回成功,实现"始终保持链接"
if (this.handle !== null &&
this.currentPath === dbPath &&
this.currentKey === hexKey &&
this.currentWxid === wxid) {
return true
}
// 如果参数不同,则先关闭原来的连接
if (this.handle !== null) {
this.close()
// 重新初始化,因为 close 呼叫了 shutdown
const initOk = await this.initialize()
if (!initOk) return false
}
const dbStoragePath = this.resolveDbStoragePath(dbPath, wxid)
this.writeLog(`open dbPath=${dbPath} wxid=${wxid} dbStorage=${dbStoragePath || 'null'}`)
if (!dbStoragePath || !existsSync(dbStoragePath)) {
console.error('数据库目录不存在:', dbPath)
this.writeLog(`open failed: dbStorage not found for ${dbPath}`)
return false
}
const sessionDbPath = this.findSessionDb(dbStoragePath)
this.writeLog(`open sessionDb=${sessionDbPath || 'null'}`)
if (!sessionDbPath) {
console.error('未找到 session.db 文件')
this.writeLog('open failed: session.db not found')
return false
}
const handleOut = [0]
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
console.error('打开数据库失败:', result)
await this.printLogs()
this.writeLog(`open failed: openAccount code=${result}`)
return false
}
const handle = handleOut[0]
if (handle <= 0) {
return false
}
this.handle = handle
this.currentPath = dbPath
this.currentKey = hexKey
this.currentWxid = wxid
this.initialized = true
if (this.wcdbSetMyWxid && wxid) {
try {
this.wcdbSetMyWxid(this.handle, wxid)
} catch (e) {
console.warn('设置 wxid 失败:', e)
}
}
if (this.isLogEnabled()) {
this.startLogPolling()
}
this.writeLog(`open ok handle=${handle}`)
return true
} catch (e) {
console.error('打开数据库异常:', e)
this.writeLog(`open exception: ${String(e)}`)
return false
}
return this.callWorker('open', { dbPath, hexKey, wxid })
}
/**
* 关闭数据库
* 注意:wcdb_close_account 可能导致崩溃,使用 shutdown 代替
* 关闭数据库连接
*/
close(): void {
if (this.handle !== null || this.initialized) {
try {
// 不调用 closeAccount,直接 shutdown
this.wcdbShutdown()
} catch (e) {
console.error('WCDB shutdown 出错:', e)
}
this.handle = null
this.currentPath = null
this.currentKey = null
this.currentWxid = null
this.initialized = false
this.stopLogPolling()
}
async close(): Promise<void> {
return this.callWorker('close')
}
/**
* 关闭服务(与 close 相同)
* 关闭服务
*/
shutdown(): void {
this.close()
if (this.worker) {
this.worker.terminate()
this.worker = null
}
}
/**
* 检查是否已连接
* 获取数据库连接状态
* 注意:此方法现在是异步的
*/
isConnected(): boolean {
return this.initialized && this.handle !== null
async isConnected(): Promise<boolean> {
return this.callWorker('isConnected')
}
/**
* 获取会话列表
*/
async getSessions(): Promise<{ success: boolean; sessions?: any[]; error?: string }> {
if (!this.ensureReady()) {
this.writeLog('getSessions skipped: not connected')
return { success: false, error: 'WCDB 未连接' }
}
try {
// 使用 setImmediate 让事件循环有机会处理其他任务,避免长时间阻塞
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetSessions(this.handle, outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
this.writeLog(`getSessions failed: code=${result}`)
return { success: false, error: `获取会话失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析会话失败' }
this.writeLog(`getSessions ok size=${jsonStr.length}`)
const sessions = JSON.parse(jsonStr)
return { success: true, sessions }
} catch (e) {
this.writeLog(`getSessions exception: ${String(e)}`)
return { success: false, error: String(e) }
}
return this.callWorker('getSessions')
}
/**
* 获取消息列表
*/
async getMessages(sessionId: string, limit: number, offset: number): Promise<{ success: boolean; messages?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessages(this.handle, sessionId, limit, offset, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息失败' }
const messages = JSON.parse(jsonStr)
return { success: true, messages }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessages', { sessionId, limit, offset })
}
/**
* 获取消息总数
*/
async getMessageCount(sessionId: string): Promise<{ success: boolean; count?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCount = [0]
const result = this.wcdbGetMessageCount(this.handle, sessionId, outCount)
if (result !== 0) {
return { success: false, error: `获取消息总数失败: ${result}` }
}
return { success: true, count: outCount[0] }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessageCount', { sessionId })
}
/**
* 获取联系人昵称
*/
async getDisplayNames(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (usernames.length === 0) return { success: true, map: {} }
try {
// 让出控制权,避免阻塞事件循环
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetDisplayNames(this.handle, JSON.stringify(usernames), outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取昵称失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析昵称失败' }
const map = JSON.parse(jsonStr)
return { success: true, map }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getDisplayNames', { usernames })
}
/**
* 获取头像 URL
*/
async getAvatarUrls(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (usernames.length === 0) return { success: true, map: {} }
try {
const now = Date.now()
const resultMap: Record<string, string> = {}
const toFetch: string[] = []
const seen = new Set<string>()
for (const username of usernames) {
if (!username || seen.has(username)) continue
seen.add(username)
const cached = this.avatarUrlCache.get(username)
if (cached && cached.url && now - cached.updatedAt < this.avatarCacheTtlMs) {
resultMap[username] = cached.url
continue
}
toFetch.push(username)
}
if (toFetch.length === 0) {
return { success: true, map: resultMap }
}
// 让出控制权,避免阻塞事件循环
await new Promise(resolve => setImmediate(resolve))
const outPtr = [null as any]
const result = this.wcdbGetAvatarUrls(this.handle, JSON.stringify(toFetch), outPtr)
// DLL 调用后再次让出控制权
await new Promise(resolve => setImmediate(resolve))
if (result !== 0 || !outPtr[0]) {
if (Object.keys(resultMap).length > 0) {
return { success: true, map: resultMap, error: `获取头像失败: ${result}` }
}
return { success: false, error: `获取头像失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析头像失败' }
const map = JSON.parse(jsonStr) as Record<string, string>
for (const username of toFetch) {
const url = map[username]
if (url) {
resultMap[username] = url
this.avatarUrlCache.set(username, { url, updatedAt: now })
}
}
return { success: true, map: resultMap }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getAvatarUrls', { usernames })
}
/**
* 获取群成员数量
*/
async getGroupMemberCount(chatroomId: string): Promise<{ success: boolean; count?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCount = [0]
const result = this.wcdbGetGroupMemberCount(this.handle, chatroomId, outCount)
if (result !== 0) {
return { success: false, error: `获取群成员数量失败: ${result}` }
}
return { success: true, count: outCount[0] }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getGroupMemberCount', { chatroomId })
}
/**
* 批量获取群成员数量
*/
async getGroupMemberCounts(chatroomIds: string[]): Promise<{ success: boolean; map?: Record<string, number>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (chatroomIds.length === 0) return { success: true, map: {} }
if (!this.wcdbGetGroupMemberCounts) {
const map: Record<string, number> = {}
for (const chatroomId of chatroomIds) {
const result = await this.getGroupMemberCount(chatroomId)
if (result.success && typeof result.count === 'number') {
map[chatroomId] = result.count
}
}
return { success: true, map }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetGroupMemberCounts(this.handle, JSON.stringify(chatroomIds), outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群成员数量失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群成员数量失败' }
const map = JSON.parse(jsonStr)
return { success: true, map }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getGroupMemberCounts', { chatroomIds })
}
/**
* 获取群成员列表
*/
async getGroupMembers(chatroomId: string): Promise<{ success: boolean; members?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetGroupMembers(this.handle, chatroomId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群成员失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群成员失败' }
const members = JSON.parse(jsonStr)
return { success: true, members }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getGroupMembers', { chatroomId })
}
/**
* 获取消息表列表
*/
async getMessageTables(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageTables(this.handle, sessionId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息表失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息表失败' }
const tables = JSON.parse(jsonStr)
return { success: true, tables }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessageTables', { sessionId })
}
/**
* 获取消息表统计
*/
async getMessageTableStats(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageTableStats(this.handle, sessionId, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取表统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析表统计失败' }
const tables = JSON.parse(jsonStr)
return { success: true, tables }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessageTableStats', { sessionId })
}
/**
* 获取消息元数据
*/
async getMessageMeta(dbPath: string, tableName: string, limit: number, offset: number): Promise<{ success: boolean; rows?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageMeta(this.handle, dbPath, tableName, limit, offset, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取消息元数据失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息元数据失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessageMeta', { dbPath, tableName, limit, offset })
}
/**
* 获取联系人详情
*/
async getContact(username: string): Promise<{ success: boolean; contact?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetContact(this.handle, username, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取联系人失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析联系人失败' }
const contact = JSON.parse(jsonStr)
return { success: true, contact }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getContact', { username })
}
/**
* 获取聚合统计数据
*/
async getAggregateStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const normalizedBegin = this.normalizeTimestamp(beginTimestamp)
let normalizedEnd = this.normalizeTimestamp(endTimestamp)
if (normalizedEnd <= 0) {
normalizedEnd = this.normalizeTimestamp(Date.now())
}
if (normalizedBegin > 0 && normalizedEnd < normalizedBegin) {
normalizedEnd = normalizedBegin
}
const callAggregate = (ids: string[]) => {
const idsAreNumeric = ids.length > 0 && ids.every((id) => /^\d+$/.test(id))
const payloadIds = idsAreNumeric ? ids.map((id) => Number(id)) : ids
const outPtr = [null as any]
const result = this.wcdbGetAggregateStats(this.handle, JSON.stringify(payloadIds), normalizedBegin, normalizedEnd, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取聚合统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) {
return { success: false, error: '解析聚合统计失败' }
}
const data = JSON.parse(jsonStr)
return { success: true, data }
}
let result = callAggregate(sessionIds)
if (result.success && result.data && result.data.total === 0 && result.data.idMap) {
const idMap = result.data.idMap as Record<string, string>
const reverseMap: Record<string, string> = {}
for (const [id, name] of Object.entries(idMap)) {
if (!name) continue
reverseMap[name] = id
}
const numericIds = sessionIds
.map((id) => reverseMap[id])
.filter((id) => typeof id === 'string' && /^\d+$/.test(id))
if (numericIds.length > 0) {
const retry = callAggregate(numericIds)
if (retry.success && retry.data) {
result = retry
}
}
}
return result
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getAggregateStats', { sessionIds, beginTimestamp, endTimestamp })
}
/**
* 获取可用年份
*/
async getAvailableYears(sessionIds: string[]): Promise<{ success: boolean; data?: number[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAvailableYears) {
return { success: false, error: '未支持获取年度列表' }
}
if (sessionIds.length === 0) return { success: true, data: [] }
try {
const outPtr = [null as any]
const result = this.wcdbGetAvailableYears(this.handle, JSON.stringify(sessionIds), outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度列表失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getAvailableYears', { sessionIds })
}
/**
* 获取年度报告统计
*/
async getAnnualReportStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAnnualReportStats) {
return this.getAggregateStats(sessionIds, beginTimestamp, endTimestamp)
}
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetAnnualReportStats(this.handle, JSON.stringify(sessionIds), begin, end, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getAnnualReportStats', { sessionIds, beginTimestamp, endTimestamp })
}
async getAnnualReportExtras(
sessionIds: string[],
beginTimestamp: number = 0,
endTimestamp: number = 0,
peakDayBegin: number = 0,
peakDayEnd: number = 0
): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetAnnualReportExtras) {
return { success: false, error: '未支持年度扩展统计' }
}
if (sessionIds.length === 0) return { success: true, data: {} }
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetAnnualReportExtras(
this.handle,
JSON.stringify(sessionIds),
begin,
end,
this.normalizeTimestamp(peakDayBegin),
this.normalizeTimestamp(peakDayEnd),
outPtr
)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取年度扩展统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析年度扩展统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
/**
* 获取年度报告扩展数据
*/
async getAnnualReportExtras(sessionIds: string[], beginTimestamp: number, endTimestamp: number, peakDayBegin: number, peakDayEnd: number): Promise<{ success: boolean; data?: any; error?: string }> {
return this.callWorker('getAnnualReportExtras', { sessionIds, beginTimestamp, endTimestamp, peakDayBegin, peakDayEnd })
}
/**
* 获取群聊统计
*/
async getGroupStats(chatroomId: string, beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbGetGroupStats) {
return this.getAggregateStats([chatroomId], beginTimestamp, endTimestamp)
}
try {
const { begin, end } = this.normalizeRange(beginTimestamp, endTimestamp)
const outPtr = [null as any]
const result = this.wcdbGetGroupStats(this.handle, chatroomId, begin, end, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取群聊统计失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析群聊统计失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getGroupStats', { chatroomId, beginTimestamp, endTimestamp })
}
/**
* 打开消息游标
*/
async openMessageCursor(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outCursor = [0]
const result = this.wcdbOpenMessageCursor(
this.handle,
sessionId,
batchSize,
ascending ? 1 : 0,
beginTimestamp,
endTimestamp,
outCursor
)
if (result !== 0 || outCursor[0] <= 0) {
await this.printLogs(true)
this.writeLog(
`openMessageCursor failed: sessionId=${sessionId} batchSize=${batchSize} ascending=${ascending ? 1 : 0} begin=${beginTimestamp} end=${endTimestamp} result=${result} cursor=${outCursor[0]}`,
true
)
return { success: false, error: `创建游标失败: ${result},请查看日志` }
}
return { success: true, cursor: outCursor[0] }
} catch (e) {
await this.printLogs(true)
this.writeLog(`openMessageCursor exception: ${String(e)}`, true)
return { success: false, error: '创建游标异常,请查看日志' }
}
return this.callWorker('openMessageCursor', { sessionId, batchSize, ascending, beginTimestamp, endTimestamp })
}
/**
* 打开轻量级消息游标
*/
async openMessageCursorLite(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
if (!this.wcdbOpenMessageCursorLite) {
return this.openMessageCursor(sessionId, batchSize, ascending, beginTimestamp, endTimestamp)
}
try {
const outCursor = [0]
const result = this.wcdbOpenMessageCursorLite(
this.handle,
sessionId,
batchSize,
ascending ? 1 : 0,
beginTimestamp,
endTimestamp,
outCursor
)
if (result !== 0 || outCursor[0] <= 0) {
await this.printLogs(true)
this.writeLog(
`openMessageCursorLite failed: sessionId=${sessionId} batchSize=${batchSize} ascending=${ascending ? 1 : 0} begin=${beginTimestamp} end=${endTimestamp} result=${result} cursor=${outCursor[0]}`,
true
)
return { success: false, error: `创建游标失败: ${result},请查看日志` }
}
return { success: true, cursor: outCursor[0] }
} catch (e) {
await this.printLogs(true)
this.writeLog(`openMessageCursorLite exception: ${String(e)}`, true)
return { success: false, error: '创建游标异常,请查看日志' }
}
return this.callWorker('openMessageCursorLite', { sessionId, batchSize, ascending, beginTimestamp, endTimestamp })
}
/**
* 获取下一批消息
*/
async fetchMessageBatch(cursor: number): Promise<{ success: boolean; rows?: any[]; hasMore?: boolean; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const outHasMore = [0]
const result = this.wcdbFetchMessageBatch(this.handle, cursor, outPtr, outHasMore)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取批次失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析批次失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows, hasMore: outHasMore[0] === 1 }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('fetchMessageBatch', { cursor })
}
/**
* 关闭消息游标
*/
async closeMessageCursor(cursor: number): Promise<{ success: boolean; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const result = this.wcdbCloseMessageCursor(this.handle, cursor)
if (result !== 0) {
return { success: false, error: `关闭游标失败: ${result}` }
}
return { success: true }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('closeMessageCursor', { cursor })
}
/**
* 执行 SQL 查询
*/
async execQuery(kind: string, path: string | null, sql: string): Promise<{ success: boolean; rows?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbExecQuery(this.handle, kind, path, sql, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `执行查询失败: ${result}` }
}
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析查询结果失败' }
const rows = JSON.parse(jsonStr)
return { success: true, rows }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('execQuery', { kind, path, sql })
}
/**
* 获取表情包 CDN URL
*/
async getEmoticonCdnUrl(dbPath: string, md5: string): Promise<{ success: boolean; url?: string; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
const outPtr = [null as any]
const result = this.wcdbGetEmoticonCdnUrl(this.handle, dbPath, md5, outPtr)
if (result !== 0 || !outPtr[0]) {
return { success: false, error: `获取表情 URL 失败: ${result}` }
}
const urlStr = this.decodeJsonPtr(outPtr[0])
if (urlStr === null) return { success: false, error: '解析表情 URL 失败' }
return { success: true, url: urlStr || undefined }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getEmoticonCdnUrl', { dbPath, md5 })
}
/**
* 列出消息数据库
*/
async listMessageDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbListMessageDbs(this.handle, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `获取消息库列表失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息库列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('listMessageDbs')
}
/**
* 列出媒体数据库
*/
async listMediaDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbListMediaDbs(this.handle, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `获取媒体库列表失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析媒体库列表失败' }
const data = JSON.parse(jsonStr)
return { success: true, data }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('listMediaDbs')
}
/**
* 根据 ID 获取消息
*/
async getMessageById(sessionId: string, localId: number): Promise<{ success: boolean; message?: any; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
try {
const outPtr = [null as any]
const result = this.wcdbGetMessageById(this.handle, sessionId, localId, outPtr)
if (result !== 0 || !outPtr[0]) return { success: false, error: `查询消息失败: ${result}` }
const jsonStr = this.decodeJsonPtr(outPtr[0])
if (!jsonStr) return { success: false, error: '解析消息失败' }
const message = JSON.parse(jsonStr)
// 处理 wcdb_get_message_by_id 返回空对象的情况
if (Object.keys(message).length === 0) return { success: false, error: '未找到消息' }
return { success: true, message }
} catch (e) {
return { success: false, error: String(e) }
}
return this.callWorker('getMessageById', { sessionId, localId })
}
}
export const wcdbService = new WcdbService()
+122
View File
@@ -0,0 +1,122 @@
import { parentPort, workerData } from 'worker_threads'
import { WcdbCore } from './services/wcdbCore'
const core = new WcdbCore()
if (parentPort) {
parentPort.on('message', async (msg) => {
const { id, type, payload } = msg
try {
let result: any
switch (type) {
case 'setPaths':
core.setPaths(payload.resourcesPath, payload.userDataPath)
result = { success: true }
break
case 'setLogEnabled':
core.setLogEnabled(payload.enabled)
result = { success: true }
break
case 'testConnection':
result = await core.testConnection(payload.dbPath, payload.hexKey, payload.wxid)
break
case 'open':
result = await core.open(payload.dbPath, payload.hexKey, payload.wxid)
break
case 'close':
core.close()
result = { success: true }
break
case 'isConnected':
result = core.isConnected()
break
case 'getSessions':
result = await core.getSessions()
break
case 'getMessages':
result = await core.getMessages(payload.sessionId, payload.limit, payload.offset)
break
case 'getMessageCount':
result = await core.getMessageCount(payload.sessionId)
break
case 'getDisplayNames':
result = await core.getDisplayNames(payload.usernames)
break
case 'getAvatarUrls':
result = await core.getAvatarUrls(payload.usernames)
break
case 'getGroupMemberCount':
result = await core.getGroupMemberCount(payload.chatroomId)
break
case 'getGroupMemberCounts':
result = await core.getGroupMemberCounts(payload.chatroomIds)
break
case 'getGroupMembers':
result = await core.getGroupMembers(payload.chatroomId)
break
case 'getMessageTables':
result = await core.getMessageTables(payload.sessionId)
break
case 'getMessageTableStats':
result = await core.getMessageTableStats(payload.sessionId)
break
case 'getMessageMeta':
result = await core.getMessageMeta(payload.dbPath, payload.tableName, payload.limit, payload.offset)
break
case 'getContact':
result = await core.getContact(payload.username)
break
case 'getAggregateStats':
result = await core.getAggregateStats(payload.sessionIds, payload.beginTimestamp, payload.endTimestamp)
break
case 'getAvailableYears':
result = await core.getAvailableYears(payload.sessionIds)
break
case 'getAnnualReportStats':
result = await core.getAnnualReportStats(payload.sessionIds, payload.beginTimestamp, payload.endTimestamp)
break
case 'getAnnualReportExtras':
result = await core.getAnnualReportExtras(payload.sessionIds, payload.beginTimestamp, payload.endTimestamp, payload.peakDayBegin, payload.peakDayEnd)
break
case 'getGroupStats':
result = await core.getGroupStats(payload.chatroomId, payload.beginTimestamp, payload.endTimestamp)
break
case 'openMessageCursor':
result = await core.openMessageCursor(payload.sessionId, payload.batchSize, payload.ascending, payload.beginTimestamp, payload.endTimestamp)
break
case 'openMessageCursorLite':
result = await core.openMessageCursorLite(payload.sessionId, payload.batchSize, payload.ascending, payload.beginTimestamp, payload.endTimestamp)
break
case 'fetchMessageBatch':
result = await core.fetchMessageBatch(payload.cursor)
break
case 'closeMessageCursor':
result = await core.closeMessageCursor(payload.cursor)
break
case 'execQuery':
result = await core.execQuery(payload.kind, payload.path, payload.sql)
break
case 'getEmoticonCdnUrl':
result = await core.getEmoticonCdnUrl(payload.dbPath, payload.md5)
break
case 'listMessageDbs':
result = await core.listMessageDbs()
break
case 'listMediaDbs':
result = await core.listMediaDbs()
break
case 'getMessageById':
result = await core.getMessageById(payload.sessionId, payload.localId)
break
default:
result = { success: false, error: `Unknown method: ${type}` }
}
parentPort!.postMessage({ id, result })
} catch (e) {
parentPort!.postMessage({ id, error: String(e) })
}
})
}
+110 -2611
View File
@@ -9,9 +9,6 @@
"version": "1.0.4",
"hasInstallScript": true,
"dependencies": {
"@lancedb/lancedb": "^0.23.1-beta.1",
"@lancedb/lancedb-win32-x64-msvc": "^0.22.3",
"@xenova/transformers": "^2.17.2",
"better-sqlite3": "^12.5.0",
"echarts": "^5.5.1",
"echarts-for-react": "^3.0.2",
@@ -23,8 +20,6 @@
"jszip": "^3.10.1",
"koffi": "^2.9.0",
"lucide-react": "^0.562.0",
"node-llama-cpp": "^3.1.0",
"onnxruntime-node": "^1.16.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router-dom": "^7.1.1",
@@ -1136,15 +1131,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@huggingface/jinja": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/@huggingface/jinja/-/jinja-0.2.2.tgz",
"integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/colour": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.0.0.tgz",
@@ -1824,94 +1810,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT"
},
"node_modules/@lancedb/lancedb": {
"version": "0.23.1-beta.1",
"resolved": "https://registry.npmmirror.com/@lancedb/lancedb/-/lancedb-0.23.1-beta.1.tgz",
"integrity": "sha512-l7mLIA6qGlQHM6YfjUxvph8XzTvkBF8nqGT47nI7xVl1SuJiqv70J+cSHwkj0TXex716UCyiLM94EO3c33m98A==",
"cpu": [
"x64",
"arm64"
],
"license": "Apache-2.0",
"os": [
"darwin",
"linux",
"win32"
],
"dependencies": {
"reflect-metadata": "^0.2.2"
},
"engines": {
"node": ">= 18"
},
"optionalDependencies": {
"@lancedb/lancedb-darwin-arm64": "0.23.1-beta.1",
"@lancedb/lancedb-darwin-x64": "0.23.1-beta.1",
"@lancedb/lancedb-linux-arm64-gnu": "0.23.1-beta.1",
"@lancedb/lancedb-linux-arm64-musl": "0.23.1-beta.1",
"@lancedb/lancedb-linux-x64-gnu": "0.23.1-beta.1",
"@lancedb/lancedb-linux-x64-musl": "0.23.1-beta.1",
"@lancedb/lancedb-win32-arm64-msvc": "0.23.1-beta.1",
"@lancedb/lancedb-win32-x64-msvc": "0.23.1-beta.1"
},
"peerDependencies": {
"apache-arrow": ">=15.0.0 <=18.1.0"
}
},
"node_modules/@lancedb/lancedb-win32-x64-msvc": {
"version": "0.22.3",
"resolved": "https://registry.npmmirror.com/@lancedb/lancedb-win32-x64-msvc/-/lancedb-win32-x64-msvc-0.22.3.tgz",
"integrity": "sha512-/1feFnjz5MIhzXOEU4+1OeGwpAFYczGfefuOGZRsmGWDdt4V6/fza7Hkkxyb2OnTzqpBfy6BdW2+iBguE1JMyQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"os": [
"win32"
],
"engines": {
"node": ">= 18"
}
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-darwin-arm64": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-darwin-x64": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-linux-arm64-gnu": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-linux-arm64-musl": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-linux-x64-gnu": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-linux-x64-musl": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-win32-arm64-msvc": {
"optional": true
},
"node_modules/@lancedb/lancedb/node_modules/@lancedb/lancedb-win32-x64-msvc": {
"optional": true
},
"node_modules/@malept/cross-spawn-promise": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
@@ -1990,138 +1888,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/@node-llama-cpp/linux-arm64": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/linux-arm64/-/linux-arm64-3.15.0.tgz",
"integrity": "sha512-IaHIllWlj6tGjhhCtyp1w6xA7AHaGJiVaXAZ+78hDs8X1SL9ySBN2Qceju8AQJALePtynbAfjgjTqjQ7Hyk+IQ==",
"cpu": [
"arm64",
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/linux-armv7l": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/linux-armv7l/-/linux-armv7l-3.15.0.tgz",
"integrity": "sha512-ZuZ3q6mejQnEP4o22la7zBv7jNR+IZfgItDm3KjAl04HUXTKJ43HpNwjnf9GyYYd+dEgtoX0MESvWz4RnGH8Jw==",
"cpu": [
"arm",
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/linux-x64": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/linux-x64/-/linux-x64-3.15.0.tgz",
"integrity": "sha512-etUuTqSyNefRObqc5+JZviNTkuef2XEtHcQLaamEIWwjI1dj7nTD2YMZPBP7H3M3E55HSIY82vqCQ1bp6ZILiA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/linux-x64-vulkan": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/linux-x64-vulkan/-/linux-x64-vulkan-3.15.0.tgz",
"integrity": "sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/mac-arm64-metal": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/mac-arm64-metal/-/mac-arm64-metal-3.15.0.tgz",
"integrity": "sha512-3Vkq6bpyQZaIzoaLLP7H2Tt8ty5BS0zxUY2pX0ox2S9P4fp8Au0CCJuUJF4V+EKi+/PTn70A6R1QCkRMfMQJig==",
"cpu": [
"arm64",
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/mac-x64": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/mac-x64/-/mac-x64-3.15.0.tgz",
"integrity": "sha512-BUrmLu0ySveEYv2YzFIjqnWWAqjTZfRHuzoFLaZwqIJ86Jzycm9tzxJub4wfJCj6ixeuWyI1sUdNGIw4/2E03Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/win-arm64": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/win-arm64/-/win-arm64-3.15.0.tgz",
"integrity": "sha512-GwhqaPNpbtGDmw0Ex13hwq4jqzSZr7hw5QpRWhSKB1dHiYj6C1NLM1Vz5xiDZX+69WI/ndb+FEqGiIYFQpfmiQ==",
"cpu": [
"arm64",
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@node-llama-cpp/win-x64": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/@node-llama-cpp/win-x64/-/win-x64-3.15.0.tgz",
"integrity": "sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@npmcli/agent": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/@npmcli/agent/-/agent-3.0.0.tgz",
@@ -2174,347 +1940,6 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/@octokit/app": {
"version": "16.1.2",
"resolved": "https://registry.npmmirror.com/@octokit/app/-/app-16.1.2.tgz",
"integrity": "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==",
"license": "MIT",
"dependencies": {
"@octokit/auth-app": "^8.1.2",
"@octokit/auth-unauthenticated": "^7.0.3",
"@octokit/core": "^7.0.6",
"@octokit/oauth-app": "^8.0.3",
"@octokit/plugin-paginate-rest": "^14.0.0",
"@octokit/types": "^16.0.0",
"@octokit/webhooks": "^14.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-app": {
"version": "8.1.2",
"resolved": "https://registry.npmmirror.com/@octokit/auth-app/-/auth-app-8.1.2.tgz",
"integrity": "sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-app": "^9.0.3",
"@octokit/auth-oauth-user": "^6.0.2",
"@octokit/request": "^10.0.6",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"toad-cache": "^3.7.0",
"universal-github-app-jwt": "^2.2.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-app": {
"version": "9.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz",
"integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-device": "^8.0.3",
"@octokit/auth-oauth-user": "^6.0.2",
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-device": {
"version": "8.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz",
"integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==",
"license": "MIT",
"dependencies": {
"@octokit/oauth-methods": "^6.0.2",
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-oauth-user": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz",
"integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-device": "^8.0.3",
"@octokit/oauth-methods": "^6.0.2",
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-token": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/auth-token/-/auth-token-6.0.0.tgz",
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/auth-unauthenticated": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.3.tgz",
"integrity": "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==",
"license": "MIT",
"dependencies": {
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/core": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/@octokit/core/-/core-7.0.6.tgz",
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.3",
"@octokit/request": "^10.0.6",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"before-after-hook": "^4.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/endpoint": {
"version": "11.0.2",
"resolved": "https://registry.npmmirror.com/@octokit/endpoint/-/endpoint-11.0.2.tgz",
"integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql": {
"version": "9.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/graphql/-/graphql-9.0.3.tgz",
"integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-app": {
"version": "8.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/oauth-app/-/oauth-app-8.0.3.tgz",
"integrity": "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-oauth-app": "^9.0.2",
"@octokit/auth-oauth-user": "^6.0.1",
"@octokit/auth-unauthenticated": "^7.0.2",
"@octokit/core": "^7.0.5",
"@octokit/oauth-authorization-url": "^8.0.0",
"@octokit/oauth-methods": "^6.0.1",
"@types/aws-lambda": "^8.10.83",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-authorization-url": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz",
"integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/oauth-methods": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz",
"integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==",
"license": "MIT",
"dependencies": {
"@octokit/oauth-authorization-url": "^8.0.0",
"@octokit/request": "^10.0.6",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/openapi-types": {
"version": "27.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
"license": "MIT"
},
"node_modules/@octokit/openapi-webhooks-types": {
"version": "12.1.0",
"resolved": "https://registry.npmmirror.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz",
"integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-graphql": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz",
"integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "14.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz",
"integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "17.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz",
"integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-retry": {
"version": "8.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz",
"integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==",
"license": "MIT",
"dependencies": {
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=7"
}
},
"node_modules/@octokit/plugin-throttling": {
"version": "11.0.3",
"resolved": "https://registry.npmmirror.com/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz",
"integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": "^7.0.0"
}
},
"node_modules/@octokit/request": {
"version": "10.0.7",
"resolved": "https://registry.npmmirror.com/@octokit/request/-/request-10.0.7.tgz",
"integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.2",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"fast-content-type-parse": "^3.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/request-error": {
"version": "7.1.0",
"resolved": "https://registry.npmmirror.com/@octokit/request-error/-/request-error-7.1.0.tgz",
"integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/types": {
"version": "16.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/types/-/types-16.0.0.tgz",
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^27.0.0"
}
},
"node_modules/@octokit/webhooks": {
"version": "14.2.0",
"resolved": "https://registry.npmmirror.com/@octokit/webhooks/-/webhooks-14.2.0.tgz",
"integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-webhooks-types": "12.1.0",
"@octokit/request-error": "^7.0.0",
"@octokit/webhooks-methods": "^6.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/webhooks-methods": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz",
"integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
@@ -2858,218 +2283,6 @@
"node": ">=14"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@reflink/reflink": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink/-/reflink-0.1.19.tgz",
"integrity": "sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@reflink/reflink-darwin-arm64": "0.1.19",
"@reflink/reflink-darwin-x64": "0.1.19",
"@reflink/reflink-linux-arm64-gnu": "0.1.19",
"@reflink/reflink-linux-arm64-musl": "0.1.19",
"@reflink/reflink-linux-x64-gnu": "0.1.19",
"@reflink/reflink-linux-x64-musl": "0.1.19",
"@reflink/reflink-win32-arm64-msvc": "0.1.19",
"@reflink/reflink-win32-x64-msvc": "0.1.19"
}
},
"node_modules/@reflink/reflink-darwin-arm64": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-darwin-arm64/-/reflink-darwin-arm64-0.1.19.tgz",
"integrity": "sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-darwin-x64": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-darwin-x64/-/reflink-darwin-x64-0.1.19.tgz",
"integrity": "sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-arm64-gnu": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-linux-arm64-gnu/-/reflink-linux-arm64-gnu-0.1.19.tgz",
"integrity": "sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-arm64-musl": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-linux-arm64-musl/-/reflink-linux-arm64-musl-0.1.19.tgz",
"integrity": "sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-x64-gnu": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-linux-x64-gnu/-/reflink-linux-x64-gnu-0.1.19.tgz",
"integrity": "sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-x64-musl": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-linux-x64-musl/-/reflink-linux-x64-musl-0.1.19.tgz",
"integrity": "sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-win32-arm64-msvc": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-win32-arm64-msvc/-/reflink-win32-arm64-msvc-0.1.19.tgz",
"integrity": "sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-win32-x64-msvc": {
"version": "0.1.19",
"resolved": "https://registry.npmmirror.com/@reflink/reflink-win32-x64-msvc/-/reflink-win32-x64-msvc-0.1.19.tgz",
"integrity": "sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -3440,23 +2653,6 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.18",
"resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.18.tgz",
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@swc/helpers/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"peer": true
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@@ -3470,19 +2666,6 @@
"node": ">=10"
}
},
"node_modules/@tinyhttp/content-disposition": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/@tinyhttp/content-disposition/-/content-disposition-2.2.2.tgz",
"integrity": "sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
}
},
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz",
@@ -3493,12 +2676,6 @@
"node": ">= 10"
}
},
"node_modules/@types/aws-lambda": {
"version": "8.10.159",
"resolved": "https://registry.npmmirror.com/@types/aws-lambda/-/aws-lambda-8.10.159.tgz",
"integrity": "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg==",
"license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3567,20 +2744,6 @@
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/command-line-args": {
"version": "5.2.3",
"resolved": "https://registry.npmmirror.com/@types/command-line-args/-/command-line-args-5.2.3.tgz",
"integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==",
"license": "MIT",
"peer": true
},
"node_modules/@types/command-line-usage": {
"version": "5.0.4",
"resolved": "https://registry.npmmirror.com/@types/command-line-usage/-/command-line-usage-5.0.4.tgz",
"integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==",
"license": "MIT",
"peer": true
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@@ -3625,12 +2788,6 @@
"@types/node": "*"
}
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz",
@@ -3642,6 +2799,7 @@
"version": "25.0.3",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz",
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
@@ -3729,89 +2887,6 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/@xenova/transformers": {
"version": "2.17.2",
"resolved": "https://registry.npmmirror.com/@xenova/transformers/-/transformers-2.17.2.tgz",
"integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==",
"license": "Apache-2.0",
"dependencies": {
"@huggingface/jinja": "^0.2.2",
"onnxruntime-web": "1.14.0",
"sharp": "^0.32.0"
},
"optionalDependencies": {
"onnxruntime-node": "1.14.0"
}
},
"node_modules/@xenova/transformers/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"license": "MIT"
},
"node_modules/@xenova/transformers/node_modules/onnxruntime-node": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz",
"integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==",
"license": "MIT",
"optional": true,
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"onnxruntime-common": "~1.14.0"
}
},
"node_modules/@xenova/transformers/node_modules/sharp": {
"version": "0.32.6",
"resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.32.6.tgz",
"integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
},
"engines": {
"node": ">=14.15.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@xenova/transformers/node_modules/tar-fs": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-3.1.1.tgz",
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
},
"optionalDependencies": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0"
}
},
"node_modules/@xenova/transformers/node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
@@ -3839,15 +2914,6 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
@@ -3951,22 +3017,11 @@
"ajv": "^6.9.1"
}
},
"node_modules/ansi-escapes": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
"integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -3976,6 +3031,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -3987,51 +3043,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/apache-arrow": {
"version": "18.1.0",
"resolved": "https://registry.npmmirror.com/apache-arrow/-/apache-arrow-18.1.0.tgz",
"integrity": "sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/helpers": "^0.5.11",
"@types/command-line-args": "^5.2.3",
"@types/command-line-usage": "^5.0.4",
"@types/node": "^20.13.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^7.0.1",
"flatbuffers": "^24.3.25",
"json-bignum": "^0.0.3",
"tslib": "^2.6.2"
},
"bin": {
"arrow2csv": "bin/arrow2csv.js"
}
},
"node_modules/apache-arrow/node_modules/@types/node": {
"version": "20.19.28",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.28.tgz",
"integrity": "sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/apache-arrow/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"peer": true
},
"node_modules/apache-arrow/node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT",
"peer": true
},
"node_modules/app-builder-bin": {
"version": "5.0.0-alpha.10",
"resolved": "https://registry.npmmirror.com/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz",
@@ -4546,6 +3557,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz",
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
"dev": true,
"license": "ISC"
},
"node_modules/archiver": {
@@ -4632,6 +3644,7 @@
"resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
"integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
"deprecated": "This package is no longer supported.",
"dev": true,
"license": "ISC",
"dependencies": {
"delegates": "^1.0.0",
@@ -4647,16 +3660,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-back": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/array-back/-/array-back-3.1.0.tgz",
"integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz",
@@ -4696,28 +3699,11 @@
"node": ">=0.12.0"
}
},
"node_modules/async-retry": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/async-retry/-/async-retry-1.3.3.tgz",
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
"license": "MIT",
"dependencies": {
"retry": "0.13.1"
}
},
"node_modules/async-retry/node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/at-least-node": {
@@ -4740,31 +3726,6 @@
"when-exit": "^2.1.4"
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/b4a": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/b4a/-/b4a-1.7.3.tgz",
"integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -4772,97 +3733,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmmirror.com/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.5.2",
"resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.5.2.tgz",
"integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.6.2",
"resolved": "https://registry.npmmirror.com/bare-os/-/bare-os-3.6.2.tgz",
"integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/bare-stream/-/bare-stream-2.7.0.tgz",
"integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"streamx": "^2.21.0"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/bare-url/-/bare-url-2.3.2.tgz",
"integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-path": "^3.0.0"
}
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
@@ -4902,12 +3772,6 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/before-after-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/before-after-hook/-/before-after-hook-4.0.0.tgz",
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
"license": "Apache-2.0"
},
"node_modules/better-sqlite3": {
"version": "12.5.0",
"resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.5.0.tgz",
@@ -4964,13 +3828,9 @@
"resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT"
},
"node_modules/bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmmirror.com/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
"license": "MIT"
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/brace-expansion": {
"version": "1.1.12",
@@ -5149,15 +4009,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/cacache": {
"version": "19.0.1",
"resolved": "https://registry.npmmirror.com/cacache/-/cacache-19.0.1.tgz",
@@ -5306,6 +4157,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -5340,6 +4192,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
@@ -5352,28 +4205,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"license": "MIT",
"peer": true,
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/chmodrp": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/chmodrp/-/chmodrp-1.0.2.tgz",
"integrity": "sha512-TdngOlFV1FLTzU0o1w8MB6/BFywhtLC0SzRTGJU7T9lmdjlCWeMRt1iVo0Ki+ldwNk0BqNiKoc8xpLZEQ8mY1w==",
"license": "MIT"
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
@@ -5394,6 +4225,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
@@ -5449,6 +4281,7 @@
"version": "2.9.2",
"resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5479,6 +4312,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
@@ -5512,84 +4346,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cmake-js": {
"version": "7.4.0",
"resolved": "https://registry.npmmirror.com/cmake-js/-/cmake-js-7.4.0.tgz",
"integrity": "sha512-Lw0JxEHrmk+qNj1n9W9d4IvkDdYTBn7l2BW6XmtLj7WPpIo2shvxUy+YokfjMxAAOELNonQwX3stkPhM5xSC2Q==",
"license": "MIT",
"dependencies": {
"axios": "^1.6.5",
"debug": "^4",
"fs-extra": "^11.2.0",
"memory-stream": "^1.0.0",
"node-api-headers": "^1.1.0",
"npmlog": "^6.0.2",
"rc": "^1.2.7",
"semver": "^7.5.4",
"tar": "^6.2.0",
"url-join": "^4.0.1",
"which": "^2.0.2",
"yargs": "^17.7.2"
},
"bin": {
"cmake-js": "bin/cmake-js"
},
"engines": {
"node": ">= 14.15.0"
}
},
"node_modules/cmake-js/node_modules/fs-extra": {
"version": "11.3.3",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.3.tgz",
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/cmake-js/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/cmake-js/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@@ -5602,22 +4363,14 @@
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"dev": true,
"license": "ISC",
"bin": {
"color-support": "bin.js"
@@ -5627,6 +4380,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
@@ -5635,58 +4389,6 @@
"node": ">= 0.8"
}
},
"node_modules/command-line-args": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/command-line-args/-/command-line-args-5.2.1.tgz",
"integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
"license": "MIT",
"peer": true,
"dependencies": {
"array-back": "^3.1.0",
"find-replace": "^3.0.0",
"lodash.camelcase": "^4.3.0",
"typical": "^4.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/command-line-usage": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/command-line-usage/-/command-line-usage-7.0.3.tgz",
"integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"array-back": "^6.2.2",
"chalk-template": "^0.4.0",
"table-layout": "^4.1.0",
"typical": "^7.1.1"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/command-line-usage/node_modules/array-back": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/array-back/-/array-back-6.2.2.tgz",
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.17"
}
},
"node_modules/command-line-usage/node_modules/typical": {
"version": "7.3.0",
"resolved": "https://registry.npmmirror.com/typical/-/typical-7.3.0.tgz",
"integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.17"
}
},
"node_modules/commander": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz",
@@ -5850,6 +4552,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"dev": true,
"license": "ISC"
},
"node_modules/convert-source-map": {
@@ -5922,6 +4625,7 @@
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -6043,7 +4747,9 @@
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@@ -6060,7 +4766,9 @@
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
@@ -6077,6 +4785,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@@ -6086,6 +4795,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/detect-libc": {
@@ -6101,7 +4811,9 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT"
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/dir-compare": {
"version": "4.2.0",
@@ -6258,6 +4970,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -6615,6 +5328,7 @@
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/encoding": {
@@ -6647,15 +5361,6 @@
"node": ">=6"
}
},
"node_modules/env-var": {
"version": "7.5.0",
"resolved": "https://registry.npmmirror.com/env-var/-/env-var-7.5.0.tgz",
"integrity": "sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz",
@@ -6667,6 +5372,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6676,6 +5382,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6685,6 +5392,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -6697,6 +5405,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -6712,7 +5421,9 @@
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"license": "MIT"
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/esbuild": {
"version": "0.25.12",
@@ -6760,6 +5471,7 @@
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -6769,7 +5481,9 @@
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -6777,21 +5491,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
@@ -6840,34 +5539,12 @@
"license": "MIT",
"optional": true
},
"node_modules/fast-content-type-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz",
"integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -6940,33 +5617,6 @@
"node": ">=10"
}
},
"node_modules/filename-reserved-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz",
"integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/filenamify": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/filenamify/-/filenamify-6.0.0.tgz",
"integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==",
"license": "MIT",
"dependencies": {
"filename-reserved-regex": "^3.0.0"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
@@ -6981,46 +5631,6 @@
"node": ">=8"
}
},
"node_modules/find-replace": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/find-replace/-/find-replace-3.0.0.tgz",
"integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"array-back": "^3.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/flatbuffers": {
"version": "24.12.23",
"resolved": "https://registry.npmmirror.com/flatbuffers/-/flatbuffers-24.12.23.tgz",
"integrity": "sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -7055,6 +5665,7 @@
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -7127,6 +5738,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7143,6 +5755,7 @@
"resolved": "https://registry.npmmirror.com/gauge/-/gauge-4.0.4.tgz",
"integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
"deprecated": "This package is no longer supported.",
"dev": true,
"license": "ISC",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
@@ -7172,27 +5785,17 @@
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -7217,6 +5820,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -7287,7 +5891,9 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
@@ -7304,7 +5910,9 @@
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"define-properties": "^1.2.1",
"gopd": "^1.0.1"
@@ -7320,6 +5928,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -7360,16 +5969,11 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/guid-typescript": {
"version": "1.0.9",
"resolved": "https://registry.npmmirror.com/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -7379,7 +5983,9 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@@ -7391,6 +5997,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -7403,6 +6010,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -7418,12 +6026,14 @@
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"dev": true,
"license": "ISC"
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -7588,15 +6198,6 @@
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
@@ -7671,238 +6272,6 @@
"node": ">= 12"
}
},
"node_modules/ipull": {
"version": "3.9.3",
"resolved": "https://registry.npmmirror.com/ipull/-/ipull-3.9.3.tgz",
"integrity": "sha512-ZMkxaopfwKHwmEuGDYx7giNBdLxbHbRCWcQVA1D2eqE4crUguupfxej6s7UqbidYEwT69dkyumYkY8DPHIxF9g==",
"license": "MIT",
"dependencies": {
"@tinyhttp/content-disposition": "^2.2.0",
"async-retry": "^1.3.3",
"chalk": "^5.3.0",
"ci-info": "^4.0.0",
"cli-spinners": "^2.9.2",
"commander": "^10.0.0",
"eventemitter3": "^5.0.1",
"filenamify": "^6.0.0",
"fs-extra": "^11.1.1",
"is-unicode-supported": "^2.0.0",
"lifecycle-utils": "^2.0.1",
"lodash.debounce": "^4.0.8",
"lowdb": "^7.0.1",
"pretty-bytes": "^6.1.0",
"pretty-ms": "^8.0.0",
"sleep-promise": "^9.1.0",
"slice-ansi": "^7.1.0",
"stdout-update": "^4.0.1",
"strip-ansi": "^7.1.0"
},
"bin": {
"ipull": "dist/cli/cli.js"
},
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/ido-pluto/ipull?sponsor=1"
},
"optionalDependencies": {
"@reflink/reflink": "^0.1.16"
}
},
"node_modules/ipull/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ipull/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ipull/node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ipull/node_modules/ci-info": {
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.3.1.tgz",
"integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ipull/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/ipull/node_modules/fs-extra": {
"version": "11.3.3",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.3.tgz",
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/ipull/node_modules/is-fullwidth-code-point": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ipull/node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ipull/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/ipull/node_modules/lifecycle-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/lifecycle-utils/-/lifecycle-utils-2.1.0.tgz",
"integrity": "sha512-AnrXnE2/OF9PHCyFg0RSqsnQTzV991XaZA/buhFDoc58xU7rhSCDgCz/09Lqpsn4MpoPHt7TRAXV1kWZypFVsA==",
"license": "MIT"
},
"node_modules/ipull/node_modules/parse-ms": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-3.0.0.tgz",
"integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ipull/node_modules/pretty-ms": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-8.0.0.tgz",
"integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==",
"license": "MIT",
"dependencies": {
"parse-ms": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ipull/node_modules/slice-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.2.tgz",
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/ipull/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/ipull/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/is-ci": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/is-ci/-/is-ci-3.0.1.tgz",
@@ -7931,6 +6300,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -8014,6 +6384,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/jackspeak": {
@@ -8088,15 +6459,6 @@
"node": ">=6"
}
},
"node_modules/json-bignum": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/json-bignum/-/json-bignum-0.0.3.tgz",
"integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==",
"peer": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -8121,7 +6483,9 @@
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/json5": {
"version": "2.2.3",
@@ -8273,12 +6637,6 @@
"immediate": "~3.0.5"
}
},
"node_modules/lifecycle-utils": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/lifecycle-utils/-/lifecycle-utils-3.0.1.tgz",
"integrity": "sha512-Qt/Jl5dsNIsyCAZsHB6x3mbwHFn0HJbdmvF49sVX/bHgX2cW7+G+U+I67Zw+TPM1Sr21Gb2nfJMd2g6iUcI1EQ==",
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
@@ -8286,19 +6644,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"license": "MIT",
"peer": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -8369,27 +6714,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"license": "Apache-2.0"
},
"node_modules/lowdb": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/lowdb/-/lowdb-7.0.1.tgz",
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
"license": "MIT",
"dependencies": {
"steno": "^4.0.2"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -8446,7 +6770,9 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
@@ -8458,20 +6784,12 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/memory-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/memory-stream/-/memory-stream-1.0.0.tgz",
"integrity": "sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==",
"license": "MIT",
"dependencies": {
"readable-stream": "^3.4.0"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
@@ -8504,6 +6822,7 @@
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -8513,6 +6832,7 @@
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
@@ -8735,6 +7055,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
@@ -8811,12 +7132,6 @@
"license": "MIT",
"optional": true
},
"node_modules/node-api-headers": {
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/node-api-headers/-/node-api-headers-1.7.0.tgz",
"integrity": "sha512-uJMGdkhVwu9+I3UsVvI3KW6ICAy/yDfsu5Br9rSnTtY3WpoaComXvKloiV5wtx0Md2rn0B9n29Ys2WMNwWxj9A==",
"license": "MIT"
},
"node_modules/node-api-version": {
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.2.1.tgz",
@@ -8915,442 +7230,6 @@
"node": ">=18"
}
},
"node_modules/node-llama-cpp": {
"version": "3.15.0",
"resolved": "https://registry.npmmirror.com/node-llama-cpp/-/node-llama-cpp-3.15.0.tgz",
"integrity": "sha512-xQKl+MvKiA5QNi/CTwqLKMos7hefhRVyzJuNIAEwl7zvOoF+gNMOXEsR4Ojwl7qvgpcjsVeGKWSK3Rb6zoUP1w==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@huggingface/jinja": "^0.5.3",
"async-retry": "^1.3.3",
"bytes": "^3.1.2",
"chalk": "^5.4.1",
"chmodrp": "^1.0.2",
"cmake-js": "^7.4.0",
"cross-spawn": "^7.0.6",
"env-var": "^7.5.0",
"filenamify": "^6.0.0",
"fs-extra": "^11.3.0",
"ignore": "^7.0.4",
"ipull": "^3.9.2",
"is-unicode-supported": "^2.1.0",
"lifecycle-utils": "^3.0.1",
"log-symbols": "^7.0.0",
"nanoid": "^5.1.5",
"node-addon-api": "^8.3.1",
"octokit": "^5.0.3",
"ora": "^8.2.0",
"pretty-ms": "^9.2.0",
"proper-lockfile": "^4.1.2",
"semver": "^7.7.1",
"simple-git": "^3.27.0",
"slice-ansi": "^7.1.0",
"stdout-update": "^4.0.1",
"strip-ansi": "^7.1.0",
"validate-npm-package-name": "^6.0.0",
"which": "^5.0.0",
"yargs": "^17.7.2"
},
"bin": {
"nlc": "dist/cli/cli.js",
"node-llama-cpp": "dist/cli/cli.js"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/giladgd"
},
"optionalDependencies": {
"@node-llama-cpp/linux-arm64": "3.15.0",
"@node-llama-cpp/linux-armv7l": "3.15.0",
"@node-llama-cpp/linux-x64": "3.15.0",
"@node-llama-cpp/linux-x64-cuda": "3.15.0",
"@node-llama-cpp/linux-x64-cuda-ext": "3.15.0",
"@node-llama-cpp/linux-x64-vulkan": "3.15.0",
"@node-llama-cpp/mac-arm64-metal": "3.15.0",
"@node-llama-cpp/mac-x64": "3.15.0",
"@node-llama-cpp/win-arm64": "3.15.0",
"@node-llama-cpp/win-x64": "3.15.0",
"@node-llama-cpp/win-x64-cuda": "3.15.0",
"@node-llama-cpp/win-x64-cuda-ext": "3.15.0",
"@node-llama-cpp/win-x64-vulkan": "3.15.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/node-llama-cpp/node_modules/@huggingface/jinja": {
"version": "0.5.3",
"resolved": "https://registry.npmmirror.com/@huggingface/jinja/-/jinja-0.5.3.tgz",
"integrity": "sha512-asqfZ4GQS0hD876Uw4qiUb7Tr/V5Q+JZuo2L+BtdrD4U40QU58nIRq3ZSgAzJgT874VLjhGVacaYfrdpXtEvtA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/node-llama-cpp/node_modules/@node-llama-cpp/linux-x64-cuda": {
"optional": true
},
"node_modules/node-llama-cpp/node_modules/@node-llama-cpp/linux-x64-cuda-ext": {
"optional": true
},
"node_modules/node-llama-cpp/node_modules/@node-llama-cpp/win-x64-cuda": {
"optional": true
},
"node_modules/node-llama-cpp/node_modules/@node-llama-cpp/win-x64-cuda-ext": {
"optional": true
},
"node_modules/node-llama-cpp/node_modules/@node-llama-cpp/win-x64-vulkan": {
"optional": true
},
"node_modules/node-llama-cpp/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/node-llama-cpp/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/node-llama-cpp/node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/node-llama-cpp/node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"license": "MIT"
},
"node_modules/node-llama-cpp/node_modules/fs-extra": {
"version": "11.3.3",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.3.tgz",
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/node-llama-cpp/node_modules/is-fullwidth-code-point": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz",
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"license": "ISC",
"engines": {
"node": ">=16"
}
},
"node_modules/node-llama-cpp/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/node-llama-cpp/node_modules/log-symbols": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-7.0.1.tgz",
"integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==",
"license": "MIT",
"dependencies": {
"is-unicode-supported": "^2.0.0",
"yoctocolors": "^2.1.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/nanoid": {
"version": "5.1.6",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/node-llama-cpp/node_modules/node-addon-api": {
"version": "8.5.0",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-8.5.0.tgz",
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-llama-cpp/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/ora": {
"version": "8.2.0",
"resolved": "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz",
"integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"cli-cursor": "^5.0.0",
"cli-spinners": "^2.9.2",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"stdin-discarder": "^0.2.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/ora/node_modules/log-symbols": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz",
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"is-unicode-supported": "^1.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/node-llama-cpp/node_modules/slice-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.2.tgz",
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/node-llama-cpp/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-llama-cpp/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/node-llama-cpp/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/node-llama-cpp/node_modules/which": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/which/-/which-5.0.0.tgz",
"integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
"license": "ISC",
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
@@ -9403,6 +7282,7 @@
"resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-6.0.2.tgz",
"integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
"deprecated": "This package is no longer supported.",
"dev": true,
"license": "ISC",
"dependencies": {
"are-we-there-yet": "^3.0.0",
@@ -9418,33 +7298,13 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/octokit": {
"version": "5.0.5",
"resolved": "https://registry.npmmirror.com/octokit/-/octokit-5.0.5.tgz",
"integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==",
"license": "MIT",
"dependencies": {
"@octokit/app": "^16.1.2",
"@octokit/core": "^7.0.6",
"@octokit/oauth-app": "^8.0.3",
"@octokit/plugin-paginate-graphql": "^6.0.0",
"@octokit/plugin-paginate-rest": "^14.0.0",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"@octokit/plugin-retry": "^8.0.3",
"@octokit/plugin-throttling": "^11.0.3",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"@octokit/webhooks": "^14.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
@@ -9470,64 +7330,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/onnx-proto": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/onnx-proto/-/onnx-proto-4.0.4.tgz",
"integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==",
"license": "MIT",
"dependencies": {
"protobufjs": "^6.8.8"
}
},
"node_modules/onnxruntime-common": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
"integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==",
"license": "MIT"
},
"node_modules/onnxruntime-node": {
"version": "1.23.2",
"resolved": "https://registry.npmmirror.com/onnxruntime-node/-/onnxruntime-node-1.23.2.tgz",
"integrity": "sha512-OBTsG0W8ddBVOeVVVychpVBS87A9YV5sa2hJ6lc025T97Le+J4v++PwSC4XFs1C62SWyNdof0Mh4KvnZgtt4aw==",
"hasInstallScript": true,
"license": "MIT",
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^3.0.0",
"onnxruntime-common": "1.23.2"
}
},
"node_modules/onnxruntime-node/node_modules/onnxruntime-common": {
"version": "1.23.2",
"resolved": "https://registry.npmmirror.com/onnxruntime-common/-/onnxruntime-common-1.23.2.tgz",
"integrity": "sha512-5LFsC9Dukzp2WV6kNHYLNzp8sT6V02IubLCbzw2Xd6X5GOlr65gAX6xiJwyi2URJol/s71gaQLC5F2C25AAR2w==",
"license": "MIT"
},
"node_modules/onnxruntime-web": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz",
"integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==",
"license": "MIT",
"dependencies": {
"flatbuffers": "^1.12.0",
"guid-typescript": "^1.0.9",
"long": "^4.0.0",
"onnx-proto": "^4.0.4",
"onnxruntime-common": "~1.14.0",
"platform": "^1.3.6"
}
},
"node_modules/onnxruntime-web/node_modules/flatbuffers": {
"version": "1.12.0",
"resolved": "https://registry.npmmirror.com/flatbuffers/-/flatbuffers-1.12.0.tgz",
"integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==",
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/ora": {
"version": "5.4.1",
"resolved": "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz",
@@ -9604,18 +7406,6 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parse-ms": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz",
"integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -9630,6 +7420,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -9702,12 +7493,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/platform": {
"version": "1.3.6",
"resolved": "https://registry.npmmirror.com/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"license": "MIT"
},
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz",
@@ -9790,33 +7575,6 @@
"node": ">=10"
}
},
"node_modules/pretty-bytes": {
"version": "6.1.1",
"resolved": "https://registry.npmmirror.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
"integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==",
"license": "MIT",
"engines": {
"node": "^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-ms": {
"version": "9.3.0",
"resolved": "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.3.0.tgz",
"integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
"license": "MIT",
"dependencies": {
"parse-ms": "^4.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/proc-log": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz",
@@ -9864,49 +7622,6 @@
"node": ">=10"
}
},
"node_modules/proper-lockfile": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"retry": "^0.12.0",
"signal-exit": "^3.0.2"
}
},
"node_modules/protobufjs": {
"version": "6.11.4",
"resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-6.11.4.tgz",
"integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/long": "^4.0.1",
"@types/node": ">=13.7.0",
"long": "^4.0.0"
},
"bin": {
"pbjs": "bin/pbjs",
"pbts": "bin/pbts"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz",
@@ -10101,16 +7816,11 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/reflect-metadata": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"license": "Apache-2.0"
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -10181,6 +7891,7 @@
"version": "0.12.0",
"resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
@@ -10207,7 +7918,9 @@
"version": "2.15.4",
"resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
@@ -10351,13 +8064,17 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT"
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/serialize-error": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
@@ -10372,7 +8089,9 @@
"version": "0.13.1",
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -10384,6 +8103,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true,
"license": "ISC"
},
"node_modules/set-cookie-parser": {
@@ -10447,6 +8167,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -10459,6 +8180,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -10468,6 +8190,7 @@
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true,
"license": "ISC"
},
"node_modules/simple-concat": {
@@ -10515,30 +8238,6 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-git": {
"version": "3.30.0",
"resolved": "https://registry.npmmirror.com/simple-git/-/simple-git-3.30.0.tgz",
"integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==",
"license": "MIT",
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/steveukx/git-js?sponsor=1"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -10558,12 +8257,6 @@
"integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==",
"license": "ISC"
},
"node_modules/sleep-promise": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/sleep-promise/-/sleep-promise-9.1.0.tgz",
"integrity": "sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==",
"license": "MIT"
},
"node_modules/slice-ansi": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz",
@@ -10656,7 +8349,9 @@
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
"dev": true,
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/ssri": {
"version": "12.0.0",
@@ -10681,118 +8376,6 @@
"node": ">= 6"
}
},
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/stdout-update": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/stdout-update/-/stdout-update-4.0.1.tgz",
"integrity": "sha512-wiS21Jthlvl1to+oorePvcyrIkiG/6M3D3VTmDUlJm7Cy6SbFhKkAvX+YBuHLxck/tO3mrdpC/cNesigQc3+UQ==",
"license": "MIT",
"dependencies": {
"ansi-escapes": "^6.2.0",
"ansi-styles": "^6.2.1",
"string-width": "^7.1.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/stdout-update/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/stdout-update/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/stdout-update/node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"license": "MIT"
},
"node_modules/stdout-update/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/stdout-update/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/steno": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/steno/-/steno-4.0.2.tgz",
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/streamx": {
"version": "2.23.0",
"resolved": "https://registry.npmmirror.com/streamx/-/streamx-2.23.0.tgz",
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -10806,6 +8389,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -10836,6 +8420,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -10899,6 +8484,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -10907,34 +8493,11 @@
"node": ">=8"
}
},
"node_modules/table-layout": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/table-layout/-/table-layout-4.1.1.tgz",
"integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
"license": "MIT",
"peer": true,
"dependencies": {
"array-back": "^6.2.2",
"wordwrapjs": "^5.1.0"
},
"engines": {
"node": ">=12.17"
}
},
"node_modules/table-layout/node_modules/array-back": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/array-back/-/array-back-6.2.2.tgz",
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.17"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dev": true,
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
@@ -10986,6 +8549,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
@@ -10998,6 +8562,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
@@ -11010,6 +8575,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=8"
@@ -11019,6 +8585,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
@@ -11032,6 +8599,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
@@ -11044,6 +8612,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC"
},
"node_modules/temp-file": {
@@ -11095,15 +8664,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/text-decoder": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/text-decoder/-/text-decoder-1.2.3.tgz",
"integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
@@ -11201,15 +8761,6 @@
"node": ">=8.0"
}
},
"node_modules/toad-cache": {
"version": "3.7.0",
"resolved": "https://registry.npmmirror.com/toad-cache/-/toad-cache-3.7.0.tgz",
"integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -11254,7 +8805,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -11264,16 +8815,6 @@
"node": ">=14.17"
}
},
"node_modules/typical": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/typical/-/typical-4.0.0.tgz",
"integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmmirror.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
@@ -11290,6 +8831,7 @@
"version": "7.16.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/unique-filename": {
@@ -11318,18 +8860,6 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/universal-github-app-jwt": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz",
"integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==",
"license": "MIT"
},
"node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz",
@@ -11381,12 +8911,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"license": "MIT"
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
@@ -11409,15 +8933,6 @@
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/validate-npm-package-name": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz",
"integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==",
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz",
@@ -11591,6 +9106,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -11606,25 +9122,17 @@
"version": "1.1.5",
"resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wordwrapjs": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/wordwrapjs/-/wordwrapjs-5.1.1.tgz",
"integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.17"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -11677,6 +9185,7 @@
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
@@ -11693,6 +9202,7 @@
"version": "17.7.2",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
@@ -11711,6 +9221,7 @@
"version": "21.1.1",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -11740,18 +9251,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yoctocolors": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.2.tgz",
"integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zip-stream": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz",
+4 -11
View File
@@ -1,12 +1,12 @@
{
"name": "weflow",
"version": "1.0.4",
"description": "WeFlow - 微信聊天记录查看工具",
"version": "1.1.0",
"description": "WeFlow",
"main": "dist-electron/main.js",
"author": "cc",
"scripts": {
"postinstall": "electron-rebuild -f -w @lancedb/lancedb,onnxruntime-node",
"rebuild": "electron-rebuild -f -w @lancedb/lancedb,onnxruntime-node",
"postinstall": "echo 'No native modules to rebuild'",
"rebuild": "echo 'No native modules to rebuild'",
"dev": "vite",
"build": "vue-tsc && vite build && electron-builder",
"preview": "vite preview",
@@ -14,8 +14,6 @@
"electron:build": "npm run build"
},
"dependencies": {
"@lancedb/lancedb": "^0.23.1-beta.1",
"@lancedb/lancedb-win32-x64-msvc": "^0.22.3",
"better-sqlite3": "^12.5.0",
"echarts": "^5.5.1",
"echarts-for-react": "^3.0.2",
@@ -27,7 +25,6 @@
"jszip": "^3.10.1",
"koffi": "^2.9.0",
"lucide-react": "^0.562.0",
"onnxruntime-node": "^1.16.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router-dom": "^7.1.1",
@@ -51,10 +48,6 @@
},
"build": {
"appId": "com.WeFlow.app",
"asarUnpack": [
"**/node_modules/@lancedb/lancedb/**/*",
"**/node_modules/onnxruntime-node/**/*"
],
"publish": {
"provider": "github",
"releaseType": "release"
Binary file not shown.
+3 -1
View File
@@ -7,6 +7,7 @@ import WelcomePage from './pages/WelcomePage'
import HomePage from './pages/HomePage'
import ChatPage from './pages/ChatPage'
import AnalyticsPage from './pages/AnalyticsPage'
import AnalyticsWelcomePage from './pages/AnalyticsWelcomePage'
import AnnualReportPage from './pages/AnnualReportPage'
import AnnualReportWindow from './pages/AnnualReportWindow'
import AgreementPage from './pages/AgreementPage'
@@ -308,7 +309,8 @@ function App() {
<Route path="/" element={<HomePage />} />
<Route path="/home" element={<HomePage />} />
<Route path="/chat" element={<ChatPage />} />
<Route path="/analytics" element={<AnalyticsPage />} />
<Route path="/analytics" element={<AnalyticsWelcomePage />} />
<Route path="/analytics/view" element={<AnalyticsPage />} />
<Route path="/group-analytics" element={<GroupAnalyticsPage />} />
<Route path="/annual-report" element={<AnnualReportPage />} />
<Route path="/annual-report/view" element={<AnnualReportWindow />} />
+79
View File
@@ -0,0 +1,79 @@
.avatar-component {
position: relative;
display: inline-block;
overflow: hidden;
background-color: var(--bg-tertiary, #f5f5f5);
flex-shrink: 0;
border-radius: 4px;
/* Default radius */
&.circle {
border-radius: 50%;
}
&.rounded {
border-radius: 6px;
}
/* Image styling */
img.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.3s ease-in-out;
border-radius: inherit;
&.loaded {
opacity: 1;
}
&.instant {
transition: none !important;
opacity: 1 !important;
}
}
/* Placeholder/Letter styling */
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
color: var(--text-secondary, #666);
background-color: var(--bg-tertiary, #e0e0e0);
font-size: 1.2em;
text-transform: uppercase;
user-select: none;
border-radius: inherit;
}
/* Loading Skeleton */
.avatar-skeleton {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
var(--bg-tertiary, #f0f0f0) 25%,
var(--bg-secondary, #e0e0e0) 50%,
var(--bg-tertiary, #f0f0f0) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
z-index: 1;
border-radius: inherit;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
}
+129
View File
@@ -0,0 +1,129 @@
import React, { useState, useEffect, useRef, useMemo } from 'react'
import { User } from 'lucide-react'
import { avatarLoadQueue } from '../utils/AvatarLoadQueue'
import './Avatar.scss'
// 全局缓存已成功加载过的头像 URL,用于控制后续是否显示动画
const loadedAvatarCache = new Set<string>()
interface AvatarProps {
src?: string
name?: string
size?: number | string
shape?: 'circle' | 'square' | 'rounded'
className?: string
lazy?: boolean
onClick?: () => void
}
export const Avatar = React.memo(function Avatar({
src,
name,
size = 48,
shape = 'rounded',
className = '',
lazy = true,
onClick
}: AvatarProps) {
// 如果 URL 已在缓存中,则直接标记为已加载,不显示骨架屏和淡入动画
const isCached = useMemo(() => src ? loadedAvatarCache.has(src) : false, [src])
const [imageLoaded, setImageLoaded] = useState(isCached)
const [imageError, setImageError] = useState(false)
const [shouldLoad, setShouldLoad] = useState(!lazy || isCached)
const [isInQueue, setIsInQueue] = useState(false)
const imgRef = useRef<HTMLImageElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const getAvatarLetter = (): string => {
if (!name) return '?'
const chars = [...name]
return chars[0] || '?'
}
// Intersection Observer for lazy loading
useEffect(() => {
if (!lazy || shouldLoad || isInQueue || !src || !containerRef.current || isCached) return
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isInQueue) {
setIsInQueue(true)
avatarLoadQueue.enqueue(src).then(() => {
setShouldLoad(true)
}).catch(() => {
// 加载失败不要立刻显示错误,让浏览器渲染去报错
setShouldLoad(true)
}).finally(() => {
setIsInQueue(false)
})
observer.disconnect()
}
})
},
{ rootMargin: '100px' }
)
observer.observe(containerRef.current)
return () => observer.disconnect()
}, [src, lazy, shouldLoad, isInQueue, isCached])
// Reset state when src changes
useEffect(() => {
const cached = src ? loadedAvatarCache.has(src) : false
setImageLoaded(cached)
setImageError(false)
if (lazy && !cached) {
setShouldLoad(false)
setIsInQueue(false)
} else {
setShouldLoad(true)
}
}, [src, lazy])
// Check if image is already cached/loaded
useEffect(() => {
if (shouldLoad && imgRef.current?.complete && imgRef.current?.naturalWidth > 0) {
setImageLoaded(true)
}
}, [src, shouldLoad])
const style = {
width: typeof size === 'number' ? `${size}px` : size,
height: typeof size === 'number' ? `${size}px` : size,
}
const hasValidUrl = !!src && !imageError && shouldLoad
return (
<div
ref={containerRef}
className={`avatar-component ${shape} ${className}`}
style={style}
onClick={onClick}
>
{hasValidUrl ? (
<>
{!imageLoaded && <div className="avatar-skeleton" />}
<img
ref={imgRef}
src={src}
alt={name || 'avatar'}
className={`avatar-image ${imageLoaded ? 'loaded' : ''} ${isCached ? 'instant' : ''}`}
onLoad={() => {
if (src) loadedAvatarCache.add(src)
setImageLoaded(true)
}}
onError={() => setImageError(true)}
loading={lazy ? "lazy" : "eager"}
/>
</>
) : (
<div className="avatar-placeholder">
{name ? <span className="avatar-letter">{getAvatarLetter()}</span> : <User size="50%" />}
</div>
)}
</div>
)
})
+10 -3
View File
@@ -1,10 +1,12 @@
import { useState, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { Users, Clock, MessageSquare, Send, Inbox, Calendar, Loader2, RefreshCw, User, Medal } from 'lucide-react'
import ReactECharts from 'echarts-for-react'
import { useAnalyticsStore } from '../stores/analyticsStore'
import { useThemeStore } from '../stores/themeStore'
import './AnalyticsPage.scss'
import './DataManagementPage.scss'
import { Avatar } from '../components/Avatar'
function AnalyticsPage() {
const [isLoading, setIsLoading] = useState(false)
@@ -28,7 +30,7 @@ function AnalyticsPage() {
try {
setLoadingStatus('正在统计消息数据...')
const statsResult = await window.electronAPI.analytics.getOverallStatistics()
const statsResult = await window.electronAPI.analytics.getOverallStatistics(forceRefresh)
if (statsResult.success && statsResult.data) {
setStatistics(statsResult.data)
} else {
@@ -55,7 +57,12 @@ function AnalyticsPage() {
}
}
useEffect(() => { loadData() }, [])
const location = useLocation()
useEffect(() => {
const force = location.state?.forceRefresh === true
loadData(force)
}, [location.state])
const handleRefresh = () => loadData(true)
@@ -289,7 +296,7 @@ function AnalyticsPage() {
<div key={contact.username} className="ranking-item">
<span className={`rank ${index < 3 ? 'top' : ''}`}>{index + 1}</span>
<div className="contact-avatar">
{contact.avatarUrl ? <img src={contact.avatarUrl} alt="" /> : <div className="avatar-placeholder"><User size={20} /></div>}
<Avatar src={contact.avatarUrl} name={contact.displayName} size={36} />
{index < 3 && <div className={`medal medal-${index + 1}`}><Medal size={10} /></div>}
</div>
<div className="contact-info">
+119
View File
@@ -0,0 +1,119 @@
.analytics-welcome-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 40px;
background: var(--bg-primary);
color: var(--text-primary);
animation: fadeIn 0.4s ease-out;
.welcome-content {
text-align: center;
max-width: 600px;
.icon-wrapper {
width: 80px;
height: 80px;
margin: 0 auto 24px;
background: rgba(7, 193, 96, 0.1);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
color: #07c160;
svg {
width: 40px;
height: 40px;
}
}
h1 {
font-size: 28px;
margin-bottom: 12px;
font-weight: 600;
}
p {
color: var(--text-secondary);
margin-bottom: 40px;
font-size: 16px;
line-height: 1.6;
}
.action-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
button {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 20px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
&:hover:not(:disabled) {
transform: translateY(-2px);
border-color: #07c160;
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.1);
.card-icon {
color: #07c160;
background: rgba(7, 193, 96, 0.1);
}
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
filter: grayscale(100%);
}
.card-icon {
width: 50px;
height: 50px;
border-radius: 12px;
background: var(--bg-tertiary);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
color: var(--text-secondary);
transition: all 0.2s ease;
}
h3 {
font-size: 18px;
margin-bottom: 8px;
color: var(--text-primary);
}
span {
font-size: 13px;
color: var(--text-tertiary);
}
}
}
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
+63
View File
@@ -0,0 +1,63 @@
import { useNavigate } from 'react-router-dom'
import { BarChart2, History, RefreshCcw } from 'lucide-react'
import { useAnalyticsStore } from '../stores/analyticsStore'
import './AnalyticsWelcomePage.scss'
function AnalyticsWelcomePage() {
const navigate = useNavigate()
// 检查是否有任何缓存数据加载或基本的存储状态表明它已准备好。
// 实际上,如果 store 没有持久化,`isLoaded` 可能会在应用刷新时重置。
// 如果用户点击“加载缓存”但缓存为空,AnalyticsPage 的逻辑(loadData 不带 force)将尝试从后端缓存加载。
// 如果后端缓存也为空,则会重新计算。
// 我们也可以检查 `lastLoadTime` 来显示“上次更新:xxx”(如果已持久化)。
const { lastLoadTime } = useAnalyticsStore()
const handleLoadCache = () => {
navigate('/analytics/view')
}
const handleNewAnalysis = () => {
navigate('/analytics/view', { state: { forceRefresh: true } })
}
const formatLastTime = (ts: number | null) => {
if (!ts) return '无记录'
return new Date(ts).toLocaleString()
}
return (
<div className="analytics-welcome-container">
<div className="welcome-content">
<div className="icon-wrapper">
<BarChart2 size={40} />
</div>
<h1>私聊数据分析</h1>
<p>
WeFlow 可以分析您的聊天记录,生成详细的统计报表。<br />
您可以选择加载上次的分析结果(速度快),或者开始新的分析(数据最新)。
</p>
<div className="action-cards">
<button onClick={handleLoadCache}>
<div className="card-icon">
<History size={24} />
</div>
<h3>加载缓存</h3>
<span>查看上次分析结果<br />(上次更新: {formatLastTime(lastLoadTime)})</span>
</button>
<button onClick={handleNewAnalysis}>
<div className="card-icon">
<RefreshCcw size={24} />
</div>
<h3>新的分析</h3>
<span>重新扫描并计算数据<br />(可能需要几分钟)</span>
</button>
</div>
</div>
</div>
)
}
export default AnalyticsWelcomePage
+3 -1
View File
@@ -1426,10 +1426,12 @@
height: 6px;
opacity: 0.5;
}
50% {
height: 16px;
opacity: 1;
}
100% {
height: 6px;
opacity: 0.5;
@@ -1875,4 +1877,4 @@
opacity: 1;
transform: translateX(0);
}
}
}
+91 -181
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, Info, Calendar, Database, Hash, Play, Pause, Image as ImageIcon } from 'lucide-react'
import { createPortal } from 'react-dom'
import { useChatStore } from '../stores/chatStore'
import type { ChatSession, Message } from '../types/models'
import { getEmojiPath } from 'wechat-emojis'
@@ -23,65 +24,10 @@ interface SessionDetail {
messageTables: { dbName: string; tableName: string; count: number }[]
}
// 全局头像加载队列管理器(限制并发,避免卡顿)
class AvatarLoadQueue {
private queue: Array<{ url: string; resolve: () => void; reject: () => void }> = []
private loading = new Set<string>()
private readonly maxConcurrent = 1 // 一次只加载1个头像,避免卡顿
private readonly delayBetweenBatches = 100 // 批次间延迟100ms,给UI喘息时间
async enqueue(url: string): Promise<void> {
// 如果已经在加载中,直接返回
if (this.loading.has(url)) {
return Promise.resolve()
}
return new Promise((resolve, reject) => {
this.queue.push({ url, resolve, reject })
this.processQueue()
})
}
private async processQueue() {
// 如果已达到最大并发数,等待
if (this.loading.size >= this.maxConcurrent) {
return
}
// 如果队列为空,返回
if (this.queue.length === 0) {
return
}
// 取出一个任务
const task = this.queue.shift()
if (!task) return
this.loading.add(task.url)
// 加载图片
const img = new Image()
img.onload = () => {
this.loading.delete(task.url)
task.resolve()
// 延迟一下再处理下一个,避免一次性加载太多
setTimeout(() => this.processQueue(), this.delayBetweenBatches)
}
img.onerror = () => {
this.loading.delete(task.url)
task.reject()
setTimeout(() => this.processQueue(), this.delayBetweenBatches)
}
img.src = task.url
}
clear() {
this.queue = []
this.loading.clear()
}
}
const avatarLoadQueue = new AvatarLoadQueue()
// 全局头像加载队列管理器已移至 src/utils/AvatarLoadQueue.ts
// 全局头像加载队列管理器已移至 src/utils/AvatarLoadQueue.ts
import { avatarLoadQueue } from '../utils/AvatarLoadQueue'
import { Avatar } from '../components/Avatar'
// 头像组件 - 支持骨架屏加载和懒加载(优化:限制并发,使用 memo 避免不必要的重渲染)
// 会话项组件(使用 memo 优化,避免不必要的重渲染)
@@ -107,7 +53,12 @@ const SessionItem = React.memo(function SessionItem({
className={`session-item ${isActive ? 'active' : ''}`}
onClick={() => onSelect(session)}
>
<SessionAvatar session={session} size={48} />
<Avatar
src={session.avatarUrl}
name={session.displayName || session.username}
size={48}
className={session.username.includes('@chatroom') ? 'group' : ''}
/>
<div className="session-info">
<div className="session-top">
<span className="session-name">{session.displayName || session.username}</span>
@@ -138,109 +89,7 @@ const SessionItem = React.memo(function SessionItem({
)
})
const SessionAvatar = React.memo(function SessionAvatar({ session, size = 48 }: { session: ChatSession; size?: number }) {
const [imageLoaded, setImageLoaded] = useState(false)
const [imageError, setImageError] = useState(false)
const [shouldLoad, setShouldLoad] = useState(false)
const [isInQueue, setIsInQueue] = useState(false)
const imgRef = useRef<HTMLImageElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const isGroup = session.username.includes('@chatroom')
const getAvatarLetter = (): string => {
const name = session.displayName || session.username
if (!name) return '?'
const chars = [...name]
return chars[0] || '?'
}
// 使用 Intersection Observer 实现懒加载(优化性能)
useEffect(() => {
if (!containerRef.current || shouldLoad || isInQueue) return
if (!session.avatarUrl) {
// 没有头像URL,不需要加载
return
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isInQueue) {
// 加入加载队列,而不是立即加载
setIsInQueue(true)
avatarLoadQueue.enqueue(session.avatarUrl!).then(() => {
setShouldLoad(true)
}).catch(() => {
setImageError(true)
}).finally(() => {
setIsInQueue(false)
})
observer.disconnect()
}
})
},
{
rootMargin: '50px' // 减少预加载距离,只提前50px
}
)
observer.observe(containerRef.current)
return () => {
observer.disconnect()
}
}, [session.avatarUrl, shouldLoad, isInQueue])
// 当 avatarUrl 变化时重置状态
useEffect(() => {
setImageLoaded(false)
setImageError(false)
setShouldLoad(false)
setIsInQueue(false)
}, [session.avatarUrl])
// 检查图片是否已经从缓存加载完成
useEffect(() => {
if (shouldLoad && imgRef.current?.complete && imgRef.current?.naturalWidth > 0) {
setImageLoaded(true)
}
}, [session.avatarUrl, shouldLoad])
const hasValidUrl = session.avatarUrl && !imageError && shouldLoad
return (
<div
ref={containerRef}
className={`session-avatar ${isGroup ? 'group' : ''} ${hasValidUrl && !imageLoaded ? 'loading' : ''}`}
style={{ width: size, height: size }}
>
{hasValidUrl ? (
<>
{!imageLoaded && <div className="avatar-skeleton" />}
<img
ref={imgRef}
src={session.avatarUrl}
alt=""
className={imageLoaded ? 'loaded' : ''}
onLoad={() => setImageLoaded(true)}
onError={() => setImageError(true)}
loading="lazy"
/>
</>
) : (
<span className="avatar-letter">{getAvatarLetter()}</span>
)}
</div>
)
}, (prevProps, nextProps) => {
// 自定义比较函数,只在关键属性变化时重渲染
return (
prevProps.session.username === nextProps.session.username &&
prevProps.session.displayName === nextProps.session.displayName &&
prevProps.session.avatarUrl === nextProps.session.avatarUrl &&
prevProps.size === nextProps.size
)
})
function ChatPage(_props: ChatPageProps) {
const {
@@ -380,10 +229,8 @@ function ChatPage(_props: ChatPageProps) {
// 确保 nextSessions 也是数组
if (Array.isArray(nextSessions)) {
setSessions(nextSessions)
// 延迟启动联系人信息加载,确保UI先渲染完成
setTimeout(() => {
void enrichSessionsContactInfo(nextSessions)
}, 500)
// 立即启动联系人信息加载,不再延迟 500ms
void enrichSessionsContactInfo(nextSessions)
} else {
console.error('mergeSessions returned non-array:', nextSessions)
setSessions(sessionsArray)
@@ -420,8 +267,7 @@ function ChatPage(_props: ChatPageProps) {
console.log(`[性能监控] 开始加载联系人信息,会话数: ${sessions.length}`)
const totalStart = performance.now()
// 延迟启动,等待UI渲染完成
await new Promise(resolve => setTimeout(resolve, 500))
// 移除初始 500ms 延迟,让后台加载与 UI 渲染并行
// 检查是否被取消
if (enrichCancelledRef.current) {
@@ -430,8 +276,8 @@ function ChatPage(_props: ChatPageProps) {
}
try {
// 找出需要加载联系人信息的会话(没有缓存的)
const needEnrich = sessions.filter(s => !s.avatarUrl && (!s.displayName || s.displayName === s.username))
// 找出需要加载联系人信息的会话(没有头像或者没有显示名称的)
const needEnrich = sessions.filter(s => !s.avatarUrl || !s.displayName || s.displayName === s.username)
if (needEnrich.length === 0) {
console.log('[性能监控] 所有联系人信息已缓存,跳过加载')
isEnrichingRef.current = false
@@ -584,9 +430,15 @@ function ChatPage(_props: ChatPageProps) {
}
if (result.success && result.contacts) {
// 将更新加入队列,而不是立即更新
// 将更新加入队列,用于侧边栏更新
for (const [username, contact] of Object.entries(result.contacts)) {
contactUpdateQueueRef.current.set(username, contact)
// 【核心优化】同步更新全局发送者头像缓存,供 MessageBubble 使用
senderAvatarCache.set(username, {
avatarUrl: contact.avatarUrl,
displayName: contact.displayName
})
}
// 触发批量更新
flushContactUpdates()
@@ -660,6 +512,31 @@ function ChatPage(_props: ChatPageProps) {
if (result.success && result.messages) {
if (offset === 0) {
setMessages(result.messages)
// 预取发送者信息:在关闭加载遮罩前处理
const unreadCount = session?.unreadCount ?? 0
const isGroup = sessionId.includes('@chatroom')
if (isGroup && result.messages.length > 0) {
const unknownSenders = [...new Set(result.messages
.filter(m => m.isSend !== 1 && m.senderUsername && !senderAvatarCache.has(m.senderUsername))
.map(m => m.senderUsername as string)
)]
if (unknownSenders.length > 0) {
console.log(`[性能监控] 预取消息发送者信息: ${unknownSenders.length} 个`)
// 在批量请求前,先将这些发送者标记为加载中,防止 MessageBubble 触发重复请求
const batchPromise = loadContactInfoBatch(unknownSenders)
unknownSenders.forEach(username => {
if (!senderAvatarLoading.has(username)) {
senderAvatarLoading.set(username, batchPromise.then(() => senderAvatarCache.get(username) || null))
}
})
// 确保在请求完成后清理 loading 状态
batchPromise.finally(() => {
unknownSenders.forEach(username => senderAvatarLoading.delete(username))
})
}
}
// 首次加载滚动到底部
requestAnimationFrame(() => {
if (messageListRef.current) {
@@ -668,6 +545,27 @@ function ChatPage(_props: ChatPageProps) {
})
} else {
appendMessages(result.messages, true)
// 加载更多也同样处理发送者信息预取
const isGroup = sessionId.includes('@chatroom')
if (isGroup) {
const unknownSenders = [...new Set(result.messages
.filter(m => m.isSend !== 1 && m.senderUsername && !senderAvatarCache.has(m.senderUsername))
.map(m => m.senderUsername as string)
)]
if (unknownSenders.length > 0) {
const batchPromise = loadContactInfoBatch(unknownSenders)
unknownSenders.forEach(username => {
if (!senderAvatarLoading.has(username)) {
senderAvatarLoading.set(username, batchPromise.then(() => senderAvatarCache.get(username) || null))
}
})
batchPromise.finally(() => {
unknownSenders.forEach(username => senderAvatarLoading.delete(username))
})
}
}
// 加载更多后保持位置:让之前的第一条消息保持在原来的视觉位置
if (firstMsgEl && listEl) {
requestAnimationFrame(() => {
@@ -1101,8 +999,10 @@ function ChatPage(_props: ChatPageProps) {
</div>
)}
{/* ... (previous content) ... */}
{isLoadingSessions ? (
<div className="loading-sessions">
{/* ... (skeleton items) ... */}
{[1, 2, 3, 4, 5].map(i => (
<div key={i} className="skeleton-item">
<div className="skeleton-avatar" />
@@ -1118,12 +1018,10 @@ function ChatPage(_props: ChatPageProps) {
className="session-list"
ref={sessionListRef}
onScroll={() => {
// 标记正在滚动,暂停联系人信息加载
isScrollingRef.current = true
if (sessionScrollTimeoutRef.current) {
clearTimeout(sessionScrollTimeoutRef.current)
}
// 滚动结束后200ms才认为滚动停止
sessionScrollTimeoutRef.current = window.setTimeout(() => {
isScrollingRef.current = false
sessionScrollTimeoutRef.current = null
@@ -1147,6 +1045,8 @@ function ChatPage(_props: ChatPageProps) {
<p className="hint">请先在数据管理页面解密数据库</p>
</div>
)}
</div>
{/* 拖动调节条 */}
@@ -1157,7 +1057,12 @@ function ChatPage(_props: ChatPageProps) {
{currentSession ? (
<>
<div className="message-header">
<SessionAvatar session={currentSession} size={40} />
<Avatar
src={currentSession.avatarUrl}
name={currentSession.displayName || currentSession.username}
size={40}
className={isGroupChat(currentSession.username) ? 'group session-avatar' : 'session-avatar'}
/>
<div className="header-info">
<h3>{currentSession.displayName || currentSession.username}</h3>
{isGroupChat(currentSession.username) && (
@@ -1767,13 +1672,14 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
</button>
)}
</div>
{showImagePreview && (
{showImagePreview && createPortal(
<div className="image-preview-overlay" onClick={() => setShowImagePreview(false)}>
<img src={imageLocalPath} alt="图片预览" onClick={(e) => e.stopPropagation()} />
<button className="image-preview-close" onClick={() => setShowImagePreview(false)}>
<X size={16} />
</button>
</div>
</div>,
document.body
)}
</>
)
@@ -1920,11 +1826,15 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
)}
<div className={`message-bubble ${bubbleClass} ${isEmoji && message.emojiCdnUrl && !emojiError ? 'emoji' : ''} ${isImage ? 'image' : ''} ${isVoice ? 'voice' : ''}`}>
<div className="bubble-avatar">
{avatarUrl ? (
<img src={avatarUrl} alt="" />
) : (
<span className="avatar-letter">{avatarLetter}</span>
)}
<Avatar
src={avatarUrl}
name={!isSent ? (isGroupChat ? (senderName || message.senderUsername || '?') : (session.displayName || session.username)) : '我'}
size={36}
className="bubble-avatar"
// If it's sent by me (isSent), we might not want 'group' class even if it's a group chat.
// But 'group' class mainly handles default avatar icon.
// Let's rely on standard Avatar behavior.
/>
</div>
<div className="bubble-body">
{/* 群聊中显示发送者名称 */}
+11 -14
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import { Users, BarChart3, Clock, Image, Loader2, RefreshCw, User, Medal, Search, X, ChevronLeft, Copy, Check } from 'lucide-react'
import { Avatar } from '../components/Avatar'
import ReactECharts from 'echarts-for-react'
import DateRangePicker from '../components/DateRangePicker'
import './GroupAnalyticsPage.scss'
@@ -177,7 +178,7 @@ function GroupAnalyticsPage() {
const getMediaOption = () => {
if (!mediaStats || mediaStats.typeCounts.length === 0) return {}
// 定义颜色映射
const colorMap: Record<number, string> = {
1: '#3b82f6', // 文本 - 蓝色
@@ -188,13 +189,13 @@ function GroupAnalyticsPage() {
49: '#14b8a6', // 链接/文件 - 青色
[-1]: '#6b7280', // 其他 - 灰色
}
const data = mediaStats.typeCounts.map(item => ({
name: item.name,
value: item.count,
itemStyle: { color: colorMap[item.type] || '#6b7280' }
}))
return {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
series: [{
@@ -202,8 +203,8 @@ function GroupAnalyticsPage() {
radius: ['40%', '70%'],
center: ['50%', '50%'],
itemStyle: { borderRadius: 8, borderColor: 'rgba(255,255,255,0.1)', borderWidth: 2 },
label: {
show: true,
label: {
show: true,
formatter: (params: { name: string; percent: number }) => {
// 只显示占比大于3%的标签
return params.percent > 3 ? `${params.name}\n${params.percent.toFixed(1)}%` : ''
@@ -256,11 +257,7 @@ function GroupAnalyticsPage() {
</button>
<div className="modal-content">
<div className="member-avatar large">
{selectedMember.avatarUrl ? (
<img src={selectedMember.avatarUrl} alt="" />
) : (
<div className="avatar-placeholder"><User size={48} /></div>
)}
<Avatar src={selectedMember.avatarUrl} name={selectedMember.displayName} size={96} />
</div>
<h3 className="member-display-name">{selectedMember.displayName}</h3>
<div className="member-details">
@@ -334,7 +331,7 @@ function GroupAnalyticsPage() {
onClick={() => handleGroupSelect(group)}
>
<div className="group-avatar">
{group.avatarUrl ? <img src={group.avatarUrl} alt="" /> : <div className="avatar-placeholder"><Users size={20} /></div>}
<Avatar src={group.avatarUrl} name={group.displayName} size={44} />
</div>
<div className="group-info">
<span className="group-name">{group.displayName}</span>
@@ -352,7 +349,7 @@ function GroupAnalyticsPage() {
<div className="function-menu">
<div className="selected-group-info">
<div className="group-avatar large">
{selectedGroup?.avatarUrl ? <img src={selectedGroup.avatarUrl} alt="" /> : <div className="avatar-placeholder"><Users size={40} /></div>}
<Avatar src={selectedGroup?.avatarUrl} name={selectedGroup?.displayName} size={80} />
</div>
<h2>{selectedGroup?.displayName}</h2>
<p>{selectedGroup?.memberCount} 位成员</p>
@@ -424,7 +421,7 @@ function GroupAnalyticsPage() {
{members.map(member => (
<div key={member.username} className="member-card" onClick={() => handleMemberClick(member)}>
<div className="member-avatar">
{member.avatarUrl ? <img src={member.avatarUrl} alt="" /> : <div className="avatar-placeholder"><User size={20} /></div>}
<Avatar src={member.avatarUrl} name={member.displayName} size={48} />
</div>
<span className="member-name">{member.displayName}</span>
</div>
@@ -437,7 +434,7 @@ function GroupAnalyticsPage() {
<div key={item.member.username} className="ranking-item">
<span className={`rank ${index < 3 ? 'top' : ''}`}>{index + 1}</span>
<div className="contact-avatar">
{item.member.avatarUrl ? <img src={item.member.avatarUrl} alt="" /> : <div className="avatar-placeholder"><User size={20} /></div>}
<Avatar src={item.member.avatarUrl} name={item.member.displayName} size={40} />
{index < 3 && <div className={`medal medal-${index + 1}`}><Medal size={10} /></div>}
</div>
<div className="contact-info">
+1
View File
@@ -484,6 +484,7 @@ function SettingsPage() {
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
</button>
{imageKeyStatus && <div className="form-hint status-text">{imageKeyStatus}</div>}
{isFetchingImageKey && <div className="form-hint status-text">正在扫描内存,请稍候...</div>}
</div>
<div className="form-group">
+2 -1
View File
@@ -506,7 +506,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
{dbKeyStatus && <div className="field-hint status-text">{dbKeyStatus}</div>}
<div className="field-hint">获取密钥会自动识别最近登录的账号</div>
<div className="field-hint">如果获取秘钥失败 请在微信打开后等待10秒后再登录</div>
<div className="field-hint">点击自动获取后微信将重新启动,当页面提示可以登录微信了再点击登录</div>
</div>
)}
@@ -533,6 +533,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
</button>
{imageKeyStatus && <div className="field-hint status-text">{imageKeyStatus}</div>}
<div className="field-hint">如获取失败,请先打开朋友圈图片再重试</div>
{isFetchingImageKey && <div className="field-hint status-text">正在扫描内存,请稍候...</div>}
</div>
)}
+28 -20
View File
@@ -1,4 +1,5 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface ChatStatistics {
totalMessages: number
@@ -36,11 +37,11 @@ interface AnalyticsState {
statistics: ChatStatistics | null
rankings: ContactRanking[]
timeDistribution: TimeDistribution | null
// 状态
isLoaded: boolean
lastLoadTime: number | null
// Actions
setStatistics: (data: ChatStatistics) => void
setRankings: (data: ContactRanking[]) => void
@@ -49,22 +50,29 @@ interface AnalyticsState {
clearCache: () => void
}
export const useAnalyticsStore = create<AnalyticsState>((set) => ({
statistics: null,
rankings: [],
timeDistribution: null,
isLoaded: false,
lastLoadTime: null,
export const useAnalyticsStore = create<AnalyticsState>()(
persist(
(set) => ({
statistics: null,
rankings: [],
timeDistribution: null,
isLoaded: false,
lastLoadTime: null,
setStatistics: (data) => set({ statistics: data }),
setRankings: (data) => set({ rankings: data }),
setTimeDistribution: (data) => set({ timeDistribution: data }),
markLoaded: () => set({ isLoaded: true, lastLoadTime: Date.now() }),
clearCache: () => set({
statistics: null,
rankings: [],
timeDistribution: null,
isLoaded: false,
lastLoadTime: null
}),
}))
setStatistics: (data) => set({ statistics: data }),
setRankings: (data) => set({ rankings: data }),
setTimeDistribution: (data) => set({ timeDistribution: data }),
markLoaded: () => set({ isLoaded: true, lastLoadTime: Date.now() }),
clearCache: () => set({
statistics: null,
rankings: [],
timeDistribution: null,
isLoaded: false,
lastLoadTime: null
}),
}),
{
name: 'analytics-storage',
}
)
)
+2 -1
View File
@@ -45,6 +45,7 @@ export interface ElectronAPI {
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[] }>
@@ -104,7 +105,7 @@ export interface ElectronAPI {
onCacheResolved: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string; localPath: string }) => void) => () => void
}
analytics: {
getOverallStatistics: () => Promise<{
getOverallStatistics: (force?: boolean) => Promise<{
success: boolean
data?: {
totalMessages: number
+74
View File
@@ -0,0 +1,74 @@
// 全局头像加载队列管理器(限制并发,避免卡顿)
export class AvatarLoadQueue {
private queue: Array<{ url: string; resolve: () => void; reject: (error: Error) => void }> = []
private loading = new Map<string, Promise<void>>()
private activeCount = 0
private readonly maxConcurrent = 3
private readonly delayBetweenBatches = 10
private static instance: AvatarLoadQueue
public static getInstance(): AvatarLoadQueue {
if (!AvatarLoadQueue.instance) {
AvatarLoadQueue.instance = new AvatarLoadQueue()
}
return AvatarLoadQueue.instance
}
async enqueue(url: string): Promise<void> {
if (!url) return Promise.resolve()
// 核心修复:防止重复并发请求同一个 URL
const existingPromise = this.loading.get(url)
if (existingPromise) {
return existingPromise
}
const loadPromise = new Promise<void>((resolve, reject) => {
this.queue.push({ url, resolve, reject })
this.processQueue()
})
this.loading.set(url, loadPromise)
loadPromise.finally(() => {
this.loading.delete(url)
})
return loadPromise
}
private async processQueue() {
if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) {
return
}
const task = this.queue.shift()
if (!task) return
this.activeCount++
const img = new Image()
img.onload = () => {
this.activeCount--
task.resolve()
setTimeout(() => this.processQueue(), this.delayBetweenBatches)
}
img.onerror = () => {
this.activeCount--
task.reject(new Error(`Failed: ${task.url}`))
setTimeout(() => this.processQueue(), this.delayBetweenBatches)
}
img.src = task.url
this.processQueue()
}
clear() {
this.queue = []
this.loading.clear()
this.activeCount = 0
}
}
export const avatarLoadQueue = AvatarLoadQueue.getInstance()
+21 -45
View File
@@ -16,14 +16,7 @@ export default defineConfig({
}
},
optimizeDeps: {
exclude: [
'@lancedb/lancedb',
'@lancedb/lancedb-win32-x64-msvc',
'node-llama-cpp',
'onnxruntime-node',
'@xenova/transformers',
'@huggingface/transformers'
]
exclude: []
},
plugins: [
react(),
@@ -33,16 +26,10 @@ export default defineConfig({
vite: {
build: {
outDir: 'dist-electron',
rollupOptions: {
rollupOptions: {
external: [
'better-sqlite3',
'koffi',
'node-llama-cpp',
'@lancedb/lancedb',
'@lancedb/lancedb-win32-x64-msvc',
'onnxruntime-node',
'@xenova/transformers',
'@huggingface/transformers',
'fsevents'
]
}
@@ -57,12 +44,6 @@ export default defineConfig({
rollupOptions: {
external: [
'koffi',
'node-llama-cpp',
'@lancedb/lancedb',
'@lancedb/lancedb-win32-x64-msvc',
'onnxruntime-node',
'@xenova/transformers',
'@huggingface/transformers',
'fsevents'
],
output: {
@@ -73,30 +54,6 @@ export default defineConfig({
}
}
},
{
entry: 'electron/cloneEmbeddingWorker.ts',
vite: {
build: {
outDir: 'dist-electron',
rollupOptions: {
external: [
'koffi',
'node-llama-cpp',
'@lancedb/lancedb',
'@lancedb/lancedb-win32-x64-msvc',
'onnxruntime-node',
'@xenova/transformers',
'@huggingface/transformers',
'fsevents'
],
output: {
entryFileNames: 'cloneEmbeddingWorker.js',
inlineDynamicImports: true
}
}
}
}
},
{
entry: 'electron/imageSearchWorker.ts',
vite: {
@@ -111,6 +68,25 @@ export default defineConfig({
}
}
},
{
entry: 'electron/wcdbWorker.ts',
vite: {
build: {
outDir: 'dist-electron',
rollupOptions: {
external: [
'better-sqlite3',
'koffi',
'fsevents'
],
output: {
entryFileNames: 'wcdbWorker.js',
inlineDynamicImports: true
}
}
}
}
},
{
entry: 'electron/preload.ts',
onstart(options) {
Submodule
+1
Submodule wx_key added at 40be59dc47.