Files
WeChatDataAnalysis/frontend/composables/useApi.js
T
2977094657 e86cfb42e5 improvement(sns-ui): 优化朋友圈导出面板与保存体验
- 新增 HTML/JSON/TXT 导出格式切换
- 支持选择导出目录并自动保存导出包
- 增加总进度和当前联系人进度展示
2026-04-20 15:24:26 +08:00

673 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { reportServerError } from '~/lib/server-error-logging'
// API请求组合式函数
export const useApi = () => {
const baseURL = useApiBase()
// 基础请求函数
const request = async (url, options = {}) => {
try {
const response = await $fetch(url, {
baseURL,
...options,
async onResponseError({ response }) {
if (response.status === 400) {
throw new Error(response._data?.detail || '请求参数错误')
} else if (response.status >= 500) {
await reportServerError({
status: response.status,
method: options?.method || 'GET',
requestUrl: url,
message: '服务器错误,请稍后重试',
backendDetail: response._data?.detail || '',
source: 'useApi',
apiBase: baseURL,
})
throw new Error('服务器错误,请稍后重试')
}
}
})
return response
} catch (error) {
console.error('API请求错误:', error)
throw error
}
}
// 微信检测API
const detectWechat = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.data_root_path) {
query.set('data_root_path', params.data_root_path)
}
const url = '/wechat-detection' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 检测当前登录账号API
const detectCurrentAccount = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.data_root_path) {
query.set('data_root_path', params.data_root_path)
}
const url = '/current-account' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 数据库解密API
const decryptDatabase = async (data) => {
return await request('/decrypt', {
method: 'POST',
body: data
})
}
// 导入预览API
const importDecryptedPreview = async (data) => {
return await request('/import_decrypted/preview', {
method: 'POST',
body: data
})
}
// 导入已解密目录API
const importDecrypted = async (data) => {
return await request('/import_decrypted', {
method: 'POST',
body: data
})
}
// 健康检查API
const healthCheck = async () => {
return await request('/health')
}
const listChatAccounts = async () => {
return await request('/chat/accounts')
}
const getChatAccountInfo = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/chat/account_info' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const deleteChatAccount = async (params = {}) => {
const account = String(params?.account || '').trim()
if (!account) throw new Error('Missing account')
const query = new URLSearchParams()
query.set('account', account)
const url = '/chat/account' + (query.toString() ? `?${query.toString()}` : '')
return await request(url, { method: 'DELETE' })
}
const listChatSessions = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.include_hidden != null) query.set('include_hidden', String(!!params.include_hidden))
if (params && params.include_official != null) query.set('include_official', String(!!params.include_official))
if (params && params.source) query.set('source', params.source)
const url = '/chat/sessions' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const listChatMessages = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.offset != null) query.set('offset', String(params.offset))
if (params && params.order) query.set('order', params.order)
if (params && params.render_types) query.set('render_types', params.render_types)
if (params && params.source) query.set('source', params.source)
const url = '/chat/messages' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const getChatMessageRaw = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.message_id) query.set('message_id', params.message_id)
const url = '/chat/messages/raw' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const editChatMessage = async (payload = {}) => {
return await request('/chat/messages/edit', {
method: 'POST',
body: payload
})
}
const repairChatMessageSender = async (payload = {}) => {
return await request('/chat/messages/repair_sender', {
method: 'POST',
body: payload
})
}
// Flip message direction in the WeChat client by swapping packed_info_data (unsafe, but undoable via reset).
const flipChatMessageDirection = async (payload = {}) => {
return await request('/chat/messages/flip_direction', {
method: 'POST',
body: payload
})
}
const listChatEditedSessions = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/chat/edits/sessions' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const listChatEditedMessages = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
const url = '/chat/edits/messages' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const getChatEditStatus = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.message_id) query.set('message_id', params.message_id)
const url = '/chat/edits/message_status' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const resetChatEditedMessage = async (payload = {}) => {
return await request('/chat/edits/reset_message', {
method: 'POST',
body: payload
})
}
const resetChatEditedSession = async (payload = {}) => {
return await request('/chat/edits/reset_session', {
method: 'POST',
body: payload
})
}
const getChatRealtimeStatus = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/chat/realtime/status' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const syncChatRealtimeMessages = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.max_scan != null) query.set('max_scan', String(params.max_scan))
if (params && params.backfill_limit != null) query.set('backfill_limit', String(params.backfill_limit))
const url = '/chat/realtime/sync' + (query.toString() ? `?${query.toString()}` : '')
return await request(url, { method: 'POST' })
}
const syncChatRealtimeAll = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.max_scan != null) query.set('max_scan', String(params.max_scan))
if (params && params.priority_username) query.set('priority_username', params.priority_username)
if (params && params.priority_max_scan != null) query.set('priority_max_scan', String(params.priority_max_scan))
if (params && params.include_hidden != null) query.set('include_hidden', String(!!params.include_hidden))
if (params && params.include_official != null) query.set('include_official', String(!!params.include_official))
if (params && params.only_official != null) query.set('only_official', String(!!params.only_official))
if (params && params.backfill_limit != null) query.set('backfill_limit', String(params.backfill_limit))
const url = '/chat/realtime/sync_all' + (query.toString() ? `?${query.toString()}` : '')
return await request(url, { method: 'POST' })
}
const searchChatMessages = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.q) query.set('q', params.q)
if (params && params.username) query.set('username', params.username)
if (params && params.sender) query.set('sender', params.sender)
if (params && params.session_type) query.set('session_type', params.session_type)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.offset != null) query.set('offset', String(params.offset))
if (params && params.start_time != null) query.set('start_time', String(params.start_time))
if (params && params.end_time != null) query.set('end_time', String(params.end_time))
if (params && params.render_types) query.set('render_types', params.render_types)
if (params && params.include_hidden != null) query.set('include_hidden', String(!!params.include_hidden))
if (params && params.include_official != null) query.set('include_official', String(!!params.include_official))
if (params && params.session_limit != null) query.set('session_limit', String(params.session_limit))
if (params && params.per_chat_scan != null) query.set('per_chat_scan', String(params.per_chat_scan))
if (params && params.scan_limit != null) query.set('scan_limit', String(params.scan_limit))
const url = '/chat/search' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const listChatSearchSenders = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.session_type) query.set('session_type', params.session_type)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.q) query.set('q', params.q)
if (params && params.message_q) query.set('message_q', params.message_q)
if (params && params.start_time != null) query.set('start_time', String(params.start_time))
if (params && params.end_time != null) query.set('end_time', String(params.end_time))
if (params && params.render_types) query.set('render_types', params.render_types)
if (params && params.include_hidden != null) query.set('include_hidden', String(!!params.include_hidden))
if (params && params.include_official != null) query.set('include_official', String(!!params.include_official))
const url = '/chat/search-index/senders' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const getChatSearchIndexStatus = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/chat/search-index/status' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const buildChatSearchIndex = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.rebuild != null) query.set('rebuild', String(!!params.rebuild))
const url = '/chat/search-index/build' + (query.toString() ? `?${query.toString()}` : '')
return await request(url, { method: 'POST' })
}
const getChatMessagesAround = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.anchor_id) query.set('anchor_id', params.anchor_id)
if (params && params.before != null) query.set('before', String(params.before))
if (params && params.after != null) query.set('after', String(params.after))
const url = '/chat/messages/around' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 聊天记录日历热力图:某月每日消息数
const getChatMessageDailyCounts = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.year != null) query.set('year', String(params.year))
if (params && params.month != null) query.set('month', String(params.month))
const url = '/chat/messages/daily_counts' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 聊天记录定位锚点:某日第一条 / 会话最早一条
const getChatMessageAnchor = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.kind) query.set('kind', String(params.kind))
if (params && params.date) query.set('date', String(params.date))
const url = '/chat/messages/anchor' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 解析嵌套合并转发聊天记录(通过 server_id)
const resolveNestedChatHistory = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.server_id != null) query.set('server_id', String(params.server_id))
const url = '/chat/chat_history/resolve' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 解析卡片/小程序等 App 消息(通过 server_id
const resolveAppMsg = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.server_id != null) query.set('server_id', String(params.server_id))
const url = '/chat/appmsg/resolve' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 朋友圈时间线
const listSnsTimeline = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.offset != null) query.set('offset', String(params.offset))
if (params && params.usernames && Array.isArray(params.usernames) && params.usernames.length > 0) {
query.set('usernames', params.usernames.join(','))
} else if (params && params.usernames && typeof params.usernames === 'string') {
query.set('usernames', params.usernames)
}
if (params && params.keyword) query.set('keyword', params.keyword)
const url = '/sns/timeline' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 朋友圈联系人列表(按发圈数统计)
const listSnsUsers = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.keyword) query.set('keyword', String(params.keyword))
if (params && params.limit != null) query.set('limit', String(params.limit))
const url = '/sns/users' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const openChatMediaFolder = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.kind) query.set('kind', params.kind)
if (params && params.md5) query.set('md5', params.md5)
if (params && params.file_id) query.set('file_id', params.file_id)
if (params && params.server_id != null) query.set('server_id', String(params.server_id))
const url = '/chat/media/open_folder' + (query.toString() ? `?${query.toString()}` : '')
return await request(url, { method: 'POST' })
}
const downloadChatEmoji = async (data = {}) => {
return await request('/chat/media/emoji/download', {
method: 'POST',
body: {
account: data.account || null,
md5: data.md5 || '',
emoji_url: data.emoji_url || '',
force: !!data.force
}
})
}
// 保存图片解密密钥
const saveMediaKeys = async (params = {}) => {
return await request('/media/keys', {
method: 'POST',
body: {
account: params.account || null,
xor_key: params.xor_key || '',
aes_key: params.aes_key || null
}
})
}
// 获取已保存的密钥(数据库密钥 + 图片密钥)
const getSavedKeys = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/keys' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 批量解密所有图片
const decryptAllMedia = async (params = {}) => {
return await request('/media/decrypt_all', {
method: 'POST',
body: {
account: params.account || null,
xor_key: params.xor_key || null,
aes_key: params.aes_key || null
}
})
}
// 聊天记录导出(离线zip
const createChatExport = async (data = {}) => {
return await request('/chat/exports', {
method: 'POST',
body: {
account: data.account || null,
scope: data.scope || 'selected',
usernames: Array.isArray(data.usernames) ? data.usernames : [],
format: data.format || 'json',
start_time: data.start_time != null ? Number(data.start_time) : null,
end_time: data.end_time != null ? Number(data.end_time) : null,
include_hidden: !!data.include_hidden,
include_official: !!data.include_official,
message_types: Array.isArray(data.message_types) ? data.message_types : [],
include_media: data.include_media == null ? true : !!data.include_media,
media_kinds: Array.isArray(data.media_kinds) ? data.media_kinds : ['image', 'emoji', 'video', 'video_thumb', 'voice', 'file'],
output_dir: data.output_dir == null ? null : String(data.output_dir || '').trim(),
allow_process_key_extract: !!data.allow_process_key_extract,
download_remote_media: !!data.download_remote_media,
html_page_size: data.html_page_size != null ? Number(data.html_page_size) : 1000,
privacy_mode: !!data.privacy_mode,
file_name: data.file_name || null
}
})
}
const getChatExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/chat/exports/${encodeURIComponent(String(exportId))}`)
}
const listChatExports = async () => {
return await request('/chat/exports')
}
const cancelChatExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/chat/exports/${encodeURIComponent(String(exportId))}`, { method: 'DELETE' })
}
// 朋友圈导出(离线 ZIP,支持 HTML / JSON / TXT
const createSnsExport = async (data = {}) => {
return await request('/sns/exports', {
method: 'POST',
body: {
account: data.account || null,
scope: data.scope || 'selected',
usernames: Array.isArray(data.usernames) ? data.usernames : [],
format: data.format || 'html',
use_cache: data.use_cache == null ? true : !!data.use_cache,
output_dir: data.output_dir == null ? null : String(data.output_dir || '').trim(),
file_name: data.file_name || null
}
})
}
const getSnsExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/sns/exports/${encodeURIComponent(String(exportId))}`)
}
const cancelSnsExport = async (exportId) => {
if (!exportId) throw new Error('Missing exportId')
return await request(`/sns/exports/${encodeURIComponent(String(exportId))}`, { method: 'DELETE' })
}
// 联系人
const listChatContacts = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.keyword) query.set('keyword', params.keyword)
if (params && params.include_friends != null) query.set('include_friends', String(!!params.include_friends))
if (params && params.include_groups != null) query.set('include_groups', String(!!params.include_groups))
if (params && params.include_officials != null) query.set('include_officials', String(!!params.include_officials))
const url = '/chat/contacts' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const exportChatContacts = async (payload = {}) => {
return await request('/chat/contacts/export', {
method: 'POST',
body: {
account: payload.account || null,
output_dir: payload.output_dir || '',
format: payload.format || 'json',
include_avatar_link: payload.include_avatar_link == null ? true : !!payload.include_avatar_link,
keyword: payload.keyword || null,
contact_types: {
friends: payload?.contact_types?.friends == null ? true : !!payload.contact_types.friends,
groups: payload?.contact_types?.groups == null ? true : !!payload.contact_types.groups,
officials: payload?.contact_types?.officials == null ? true : !!payload.contact_types.officials,
}
}
})
}
// WeChat Wrapped(年度总结)
const getWrappedAnnual = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.year != null) query.set('year', String(params.year))
if (params && params.account) query.set('account', String(params.account))
if (params && params.refresh != null) query.set('refresh', String(!!params.refresh))
const url = '/wrapped/annual' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// WeChat Wrapped(年度总结)- 目录/元信息(轻量,用于按页懒加载)
const getWrappedAnnualMeta = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.year != null) query.set('year', String(params.year))
if (params && params.account) query.set('account', String(params.account))
if (params && params.refresh != null) query.set('refresh', String(!!params.refresh))
const url = '/wrapped/annual/meta' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// WeChat Wrapped(年度总结)- 单张卡片(按页加载)
const getWrappedAnnualCard = async (cardId, params = {}) => {
if (cardId == null) throw new Error('Missing cardId')
const query = new URLSearchParams()
if (params && params.year != null) query.set('year', String(params.year))
if (params && params.account) query.set('account', String(params.account))
if (params && params.refresh != null) query.set('refresh', String(!!params.refresh))
const safeId = encodeURIComponent(String(cardId))
const url = `/wrapped/annual/cards/${safeId}` + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 获取微信进程状态
const getWxStatus = async () => {
return await request('/wechat/status')
}
// 获取数据库密钥
const getKeys = async () => {
return await request('/get_keys')
}
// 获取图片密钥
const getImageKey = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.db_storage_path) query.set('db_storage_path', params.db_storage_path)
if (params && params.wxid_dir) query.set('wxid_dir', params.wxid_dir)
const url = '/get_image_key' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 枚举服务号信息
const listBizAccounts = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
const url = '/biz/list' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 获取普通服务号消息
const listBizMessages = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.username) query.set('username', params.username)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.offset != null) query.set('offset', String(params.offset))
const url = '/biz/messages' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
// 获取微信支付记录
const listBizPayRecords = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
if (params && params.limit != null) query.set('limit', String(params.limit))
if (params && params.offset != null) query.set('offset', String(params.offset))
const url = '/biz/pay_records' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
const getBizProxyImageUrl = (url) => {
if (!url) return ''
if (url.startsWith('data:')) return url // 如果已经是 base64,不处理
const query = new URLSearchParams()
query.set('url', url)
const base = baseURL ? baseURL.replace(/\/$/, '') : ''
return `${base}/biz/proxy_image?${query.toString()}`
}
const pickSystemDirectory = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.title) query.set('title', params.title)
if (params && params.initial_dir) query.set('initial_dir', params.initial_dir)
const url = '/system/pick_directory' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
return {
pickSystemDirectory,
detectWechat,
detectCurrentAccount,
decryptDatabase,
importDecryptedPreview,
importDecrypted,
healthCheck,
listChatAccounts,
getChatAccountInfo,
deleteChatAccount,
listChatSessions,
listChatMessages,
getChatMessageRaw,
editChatMessage,
repairChatMessageSender,
flipChatMessageDirection,
listChatEditedSessions,
listChatEditedMessages,
getChatEditStatus,
resetChatEditedMessage,
resetChatEditedSession,
getChatRealtimeStatus,
syncChatRealtimeMessages,
syncChatRealtimeAll,
searchChatMessages,
getChatSearchIndexStatus,
buildChatSearchIndex,
listChatSearchSenders,
getChatMessagesAround,
getChatMessageDailyCounts,
getChatMessageAnchor,
resolveNestedChatHistory,
resolveAppMsg,
listSnsTimeline,
listSnsUsers,
openChatMediaFolder,
downloadChatEmoji,
saveMediaKeys,
getSavedKeys,
decryptAllMedia,
createChatExport,
getChatExport,
listChatExports,
cancelChatExport,
createSnsExport,
getSnsExport,
cancelSnsExport,
listChatContacts,
exportChatContacts,
getWrappedAnnual,
getWrappedAnnualMeta,
getWrappedAnnualCard,
getKeys,
getImageKey,
getWxStatus,
listBizAccounts,
listBizMessages,
listBizPayRecords,
getBizProxyImageUrl,
}
}