| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- const {
- getWxApi,
- isCancelError
- } = require('../utils/base-utils.js')
- const {
- formatBytes
- } = require('../utils/binary-utils.js')
- 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 escapeRegExp(text) {
- return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- }
- function getRuntimeInfo() {
- const wxApi = getWxApi()
- try {
- if (typeof wxApi.getDeviceInfo === 'function') return wxApi.getDeviceInfo() || {}
- } catch (error) {}
- try {
- if (typeof wxApi.getSystemInfoSync === 'function') return wxApi.getSystemInfoSync() || {}
- } catch (error) {}
- return {}
- }
- function getRuntimePlatform() {
- const info = getRuntimeInfo()
- return String(info.platform || info.system || '').toLowerCase()
- }
- function isPcRuntime() {
- const platform = getRuntimePlatform()
- return /windows|mac|devtools/.test(platform)
- }
- function getPathFileName(filePath) {
- const text = String(filePath || '').split(/[\\/]/).pop() || ''
- try {
- return decodeURIComponent(text)
- } catch (error) {
- return text
- }
- }
- function getFileName(file, fallback = '未命名文件') {
- const name = file && (file.name || file.fileName)
- if (name) return String(name)
- return getPathFileName(getFilePath(file)) || fallback
- }
- function getFilePath(file) {
- return file && (file.path || file.tempFilePath || file.filePath)
- ? (file.path || file.tempFilePath || file.filePath)
- : ''
- }
- function getFileSize(file) {
- const size = Number(file && file.size)
- return Number.isFinite(size) && size >= 0 ? size : 0
- }
- function normalizeSelectedFiles(result, fallbackName = '未命名文件') {
- const sourceFiles = Array.isArray(result && result.tempFiles)
- ? result.tempFiles
- : []
- const pathFiles = Array.isArray(result && result.tempFilePaths)
- ? result.tempFilePaths.map((filePath) => ({ path: filePath }))
- : []
- const directFile = result && (result.path || result.tempFilePath || result.filePath)
- ? [result]
- : []
- return sourceFiles.concat(pathFiles, directFile)
- .map((file, index) => {
- const path = getFilePath(file)
- return {
- file,
- name: getFileName(file, index === 0 ? fallbackName : `${fallbackName}-${index + 1}`),
- path,
- size: getFileSize(file)
- }
- })
- .filter((file) => !!file.path)
- }
- function getFirstSelectedFile(result, fallbackName = '未命名文件') {
- const files = normalizeSelectedFiles(result, fallbackName)
- const fileInfo = files[0]
- if (!fileInfo) throw new Error('没有选择文件')
- return fileInfo
- }
- 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(`\\.${escapeRegExp(extension)}$`, 'i').test(nameText)
- ))
- if (!matched) throw new Error(message)
- }
- function chooseMessageFile(options = {}) {
- const wxApi = getWxApi()
- const extensions = normalizeExtensions(options.extensions || options.extension || [])
- const type = extensions.length ? 'file' : (options.type || 'all')
- return new Promise((resolve, reject) => {
- if (typeof wxApi.chooseMessageFile !== 'function') {
- reject(new Error('当前微信版本不支持从聊天记录选择文件,请升级微信或将文件转发到文件传输助手后重试'))
- return
- }
- const chooseOptions = {
- count: options.count || 1,
- type,
- 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 || [])
- const type = extensions.length ? 'file' : (options.type || 'all')
- return new Promise((resolve, reject) => {
- if (typeof wxApi.chooseFile !== 'function') {
- reject(new Error(options.unsupportedMessage || '当前微信环境不支持本地文件选择,请将文件发送到文件传输助手后从聊天记录选择'))
- return
- }
- const chooseOptions = {
- count: options.count || 1,
- type,
- success: resolve,
- fail: reject
- }
- if (extensions.length) chooseOptions.extension = extensions
- wxApi.chooseFile(chooseOptions)
- })
- }
- async function chooseFile(source = 'message', options = {}) {
- const normalizedSource = source === 'local' || source === 'auto' ? source : 'message'
- // PC 微信更接近本地文件选择,移动端更稳定的是从聊天记录取文件。
- const preferLocal = normalizedSource === 'local' || (normalizedSource === 'auto' && isPcRuntime())
- const firstChooser = preferLocal ? chooseLocalFile : chooseMessageFile
- const fallbackChooser = preferLocal ? chooseMessageFile : chooseLocalFile
- try {
- return await firstChooser(options)
- } catch (error) {
- if (isCancelError(error) || options.fallback === false) throw error
- if (normalizedSource !== 'auto' && options.fallbackToOtherSource === false) throw error
- try {
- return await fallbackChooser(options)
- } catch (fallbackError) {
- if (isCancelError(fallbackError)) throw fallbackError
- const firstMessage = error && (error.errMsg || error.message || error)
- const fallbackMessage = fallbackError && (fallbackError.errMsg || fallbackError.message || fallbackError)
- throw new Error([
- firstMessage || '文件选择失败',
- fallbackMessage || ''
- ].filter(Boolean).join(';'))
- }
- }
- }
- 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 : (fileInfo.size || String(data || '').length),
- sizeText: formatBytes(bytes ? bytes.length : (fileInfo.size || 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()
- return new Promise((resolve, reject) => {
- if (typeof wxApi.getFileSystemManager !== 'function') {
- reject(new Error('当前微信版本不支持生成文件'))
- return
- }
- const fs = wxApi.getFileSystemManager()
- if (typeof fs.writeFileSync === 'function') {
- try {
- fs.writeFileSync(filePath, data, encoding)
- resolve(filePath)
- } catch (error) {
- reject(error)
- }
- return
- }
- if (typeof fs.writeFile !== 'function') {
- reject(new Error('当前微信版本不支持生成文件'))
- return
- }
- fs.writeFile({
- data,
- encoding,
- filePath,
- fail: reject,
- success: () => resolve(filePath)
- })
- })
- }
- function saveFileToDisk(filePath) {
- const wxApi = getWxApi()
- return new Promise((resolve, reject) => {
- if (typeof wxApi.saveFileToDisk !== 'function') {
- reject(new Error('当前微信环境不支持保存到电脑磁盘'))
- return
- }
- wxApi.saveFileToDisk({
- filePath,
- fail: reject,
- success: resolve
- })
- })
- }
- 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 exportFile(filePath, fileName, options = {}) {
- const wxApi = getWxApi()
- const preferDisk = options.target === 'disk'
- || (options.target !== 'chat' && isPcRuntime() && typeof wxApi.saveFileToDisk === 'function')
- if (preferDisk && typeof wxApi.saveFileToDisk === 'function') {
- try {
- await saveFileToDisk(filePath)
- return {
- method: 'disk'
- }
- } catch (error) {
- if (isCancelError(error) || typeof wxApi.shareFileMessage !== 'function') throw error
- }
- }
- if (typeof wxApi.shareFileMessage === 'function') {
- await shareFileToChat(filePath, fileName)
- return {
- method: 'chat'
- }
- }
- if (typeof wxApi.saveFileToDisk === 'function') {
- await saveFileToDisk(filePath)
- return {
- method: 'disk'
- }
- }
- throw new Error('当前微信环境不支持导出文件,请升级微信后重试')
- }
- async function saveTextFileToChat(fileName, data) {
- const filePath = getUserDataFilePath(fileName)
- await writeTextFile(filePath, data, 'utf8')
- const result = await exportFile(filePath, fileName)
- return {
- filePath,
- ...result
- }
- }
- module.exports = {
- assertFileExtension,
- chooseFile,
- chooseLocalFile,
- chooseMessageFile,
- exportFile,
- formatBytes,
- formatExportStamp,
- getFirstSelectedFile,
- getRuntimePlatform,
- getUserDataFilePath,
- isCancelError,
- isPcRuntime,
- loadSelectedFile,
- normalizeSelectedFiles,
- readFile,
- saveFileToDisk,
- saveTextFileToChat,
- shareFileToChat,
- toUint8Array,
- writeTextFile
- }
|