const { getWxApi, isCancelError } = require('./platform-utils') function formatBytes(byteLength) { const length = Number(byteLength) || 0 if (length >= 1024 && length % 1024 === 0) return `${length / 1024} KB` if (length >= 1024) return `${(length / 1024).toFixed(2)} KB` return `${length} bytes` } function formatExportStamp(date = new Date()) { const pad = (value, length = 2) => String(value).padStart(length, '0') return [ date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate()), '-', pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds()) ].join('') } function normalizeExtensions(extensions = []) { return extensions .map((extension) => String(extension || '').trim().replace(/^\./, '').toLowerCase()) .filter(Boolean) } function getFileName(file, fallback = '未命名文件') { return String(file && file.name ? file.name : fallback) } function getFilePath(file) { return file && (file.path || file.tempFilePath) ? (file.path || file.tempFilePath) : '' } function getFirstSelectedFile(result, fallbackName = '未命名文件') { const file = result && Array.isArray(result.tempFiles) ? result.tempFiles[0] : null if (!file) throw new Error('没有选择文件') const filePath = getFilePath(file) if (!filePath) throw new Error('无法读取所选文件路径') return { file, name: getFileName(file, fallbackName), path: filePath } } function assertFileExtension(fileInfo, extensions = [], message = '文件格式不符') { const normalizedExtensions = normalizeExtensions(extensions) if (!normalizedExtensions.length) return const nameText = `${fileInfo && fileInfo.name ? fileInfo.name : ''} ${fileInfo && fileInfo.path ? fileInfo.path : ''}` const matched = normalizedExtensions.some((extension) => ( new RegExp(`\\.${extension}$`, 'i').test(nameText) )) if (!matched) throw new Error(message) } function chooseMessageFile(options = {}) { const wxApi = getWxApi() const extensions = normalizeExtensions(options.extensions || options.extension || []) return new Promise((resolve, reject) => { if (typeof wxApi.chooseMessageFile !== 'function') { reject(new Error('当前微信版本不支持从聊天记录选择文件')) return } const chooseOptions = { count: options.count || 1, type: options.type || 'file', success: resolve, fail: reject } if (extensions.length) chooseOptions.extension = extensions wxApi.chooseMessageFile(chooseOptions) }) } function chooseLocalFile(options = {}) { const wxApi = getWxApi() const extensions = normalizeExtensions(options.extensions || options.extension || []) return new Promise((resolve, reject) => { if (typeof wxApi.chooseFile !== 'function') { reject(new Error(options.unsupportedMessage || '当前微信版本不支持打开本地文件,请从聊天记录选择')) return } const chooseOptions = { count: options.count || 1, type: options.type || 'file', success: resolve, fail: reject } if (extensions.length) chooseOptions.extension = extensions wxApi.chooseFile(chooseOptions) }) } function chooseFile(source = 'message', options = {}) { return source === 'local' ? chooseLocalFile(options) : chooseMessageFile(options) } function readFile(filePath, options = {}) { const wxApi = getWxApi() return new Promise((resolve, reject) => { if (typeof wxApi.getFileSystemManager !== 'function') { reject(new Error('当前微信版本不支持读取文件')) return } const fs = wxApi.getFileSystemManager() const readOptions = { filePath, success: (res) => resolve(res.data), fail: reject } if (options.encoding) readOptions.encoding = options.encoding fs.readFile(readOptions) }) } function toUint8Array(data) { if (data instanceof Uint8Array) return data if (data instanceof ArrayBuffer) return new Uint8Array(data) if (ArrayBuffer.isView(data)) { return new Uint8Array(data.buffer, data.byteOffset, data.byteLength) } return new Uint8Array(data || new ArrayBuffer(0)) } async function loadSelectedFile(source = 'message', options = {}) { const result = await chooseFile(source, options) const fileInfo = getFirstSelectedFile(result, options.fallbackName || '未命名文件') assertFileExtension(fileInfo, options.extensions || options.extension || [], options.extensionMessage || '文件格式不符') const data = await readFile(fileInfo.path, { encoding: options.encoding }) const bytes = options.encoding ? null : toUint8Array(data) return { ...fileInfo, bytes, data, size: bytes ? bytes.length : String(data || '').length, sizeText: formatBytes(bytes ? bytes.length : String(data || '').length), text: options.encoding ? String(data || '') : '' } } function getUserDataFilePath(fileName) { const wxApi = getWxApi() const userDataPath = wxApi.env && wxApi.env.USER_DATA_PATH if (!userDataPath) throw new Error('当前微信版本不支持生成文件') return `${userDataPath}/${fileName}` } function writeTextFile(filePath, data, encoding = 'utf8') { const wxApi = getWxApi() if (typeof wxApi.getFileSystemManager !== 'function') { throw new Error('当前微信版本不支持生成文件') } const fs = wxApi.getFileSystemManager() if (typeof fs.writeFileSync !== 'function') { throw new Error('当前微信版本不支持同步生成文件') } fs.writeFileSync(filePath, data, encoding) } function shareFileToChat(filePath, fileName) { const wxApi = getWxApi() return new Promise((resolve, reject) => { if (typeof wxApi.shareFileMessage !== 'function') { reject(new Error('当前微信版本不支持发送文件到聊天')) return } wxApi.shareFileMessage({ fileName, filePath, success: resolve, fail: reject }) }) } async function saveTextFileToChat(fileName, data) { const filePath = getUserDataFilePath(fileName) writeTextFile(filePath, data, 'utf8') await shareFileToChat(filePath, fileName) return filePath } module.exports = { assertFileExtension, chooseFile, chooseLocalFile, chooseMessageFile, formatBytes, formatExportStamp, getFirstSelectedFile, getUserDataFilePath, isCancelError, loadSelectedFile, readFile, saveTextFileToChat, shareFileToChat, toUint8Array, writeTextFile }