1
0

file.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. const {
  2. getWxApi,
  3. isCancelError
  4. } = require('../utils/platform-utils.js')
  5. const {
  6. formatBytes
  7. } = require('../utils/binary-utils.js')
  8. function formatExportStamp(date = new Date()) {
  9. const pad = (value, length = 2) => String(value).padStart(length, '0')
  10. return [
  11. date.getFullYear(),
  12. pad(date.getMonth() + 1),
  13. pad(date.getDate()),
  14. '-',
  15. pad(date.getHours()),
  16. pad(date.getMinutes()),
  17. pad(date.getSeconds())
  18. ].join('')
  19. }
  20. function normalizeExtensions(extensions = []) {
  21. return extensions
  22. .map((extension) => String(extension || '').trim().replace(/^\./, '').toLowerCase())
  23. .filter(Boolean)
  24. }
  25. function getFileName(file, fallback = '未命名文件') {
  26. return String(file && file.name ? file.name : fallback)
  27. }
  28. function getFilePath(file) {
  29. return file && (file.path || file.tempFilePath) ? (file.path || file.tempFilePath) : ''
  30. }
  31. function getFirstSelectedFile(result, fallbackName = '未命名文件') {
  32. const file = result && Array.isArray(result.tempFiles) ? result.tempFiles[0] : null
  33. if (!file) throw new Error('没有选择文件')
  34. const filePath = getFilePath(file)
  35. if (!filePath) throw new Error('无法读取所选文件路径')
  36. return {
  37. file,
  38. name: getFileName(file, fallbackName),
  39. path: filePath
  40. }
  41. }
  42. function assertFileExtension(fileInfo, extensions = [], message = '文件格式不符') {
  43. const normalizedExtensions = normalizeExtensions(extensions)
  44. if (!normalizedExtensions.length) return
  45. const nameText = `${fileInfo && fileInfo.name ? fileInfo.name : ''} ${fileInfo && fileInfo.path ? fileInfo.path : ''}`
  46. const matched = normalizedExtensions.some((extension) => (
  47. new RegExp(`\\.${extension}$`, 'i').test(nameText)
  48. ))
  49. if (!matched) throw new Error(message)
  50. }
  51. function chooseMessageFile(options = {}) {
  52. const wxApi = getWxApi()
  53. const extensions = normalizeExtensions(options.extensions || options.extension || [])
  54. return new Promise((resolve, reject) => {
  55. if (typeof wxApi.chooseMessageFile !== 'function') {
  56. reject(new Error('当前微信版本不支持从聊天记录选择文件'))
  57. return
  58. }
  59. const chooseOptions = {
  60. count: options.count || 1,
  61. type: options.type || 'file',
  62. success: resolve,
  63. fail: reject
  64. }
  65. if (extensions.length) chooseOptions.extension = extensions
  66. wxApi.chooseMessageFile(chooseOptions)
  67. })
  68. }
  69. function chooseLocalFile(options = {}) {
  70. const wxApi = getWxApi()
  71. const extensions = normalizeExtensions(options.extensions || options.extension || [])
  72. return new Promise((resolve, reject) => {
  73. if (typeof wxApi.chooseFile !== 'function') {
  74. reject(new Error(options.unsupportedMessage || '当前微信版本不支持打开本地文件,请从聊天记录选择'))
  75. return
  76. }
  77. const chooseOptions = {
  78. count: options.count || 1,
  79. type: options.type || 'file',
  80. success: resolve,
  81. fail: reject
  82. }
  83. if (extensions.length) chooseOptions.extension = extensions
  84. wxApi.chooseFile(chooseOptions)
  85. })
  86. }
  87. function chooseFile(source = 'message', options = {}) {
  88. return source === 'local'
  89. ? chooseLocalFile(options)
  90. : chooseMessageFile(options)
  91. }
  92. function readFile(filePath, options = {}) {
  93. const wxApi = getWxApi()
  94. return new Promise((resolve, reject) => {
  95. if (typeof wxApi.getFileSystemManager !== 'function') {
  96. reject(new Error('当前微信版本不支持读取文件'))
  97. return
  98. }
  99. const fs = wxApi.getFileSystemManager()
  100. const readOptions = {
  101. filePath,
  102. success: (res) => resolve(res.data),
  103. fail: reject
  104. }
  105. if (options.encoding) readOptions.encoding = options.encoding
  106. fs.readFile(readOptions)
  107. })
  108. }
  109. function toUint8Array(data) {
  110. if (data instanceof Uint8Array) return data
  111. if (data instanceof ArrayBuffer) return new Uint8Array(data)
  112. if (ArrayBuffer.isView(data)) {
  113. return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
  114. }
  115. return new Uint8Array(data || new ArrayBuffer(0))
  116. }
  117. async function loadSelectedFile(source = 'message', options = {}) {
  118. const result = await chooseFile(source, options)
  119. const fileInfo = getFirstSelectedFile(result, options.fallbackName || '未命名文件')
  120. assertFileExtension(fileInfo, options.extensions || options.extension || [], options.extensionMessage || '文件格式不符')
  121. const data = await readFile(fileInfo.path, {
  122. encoding: options.encoding
  123. })
  124. const bytes = options.encoding ? null : toUint8Array(data)
  125. return {
  126. ...fileInfo,
  127. bytes,
  128. data,
  129. size: bytes ? bytes.length : String(data || '').length,
  130. sizeText: formatBytes(bytes ? bytes.length : String(data || '').length),
  131. text: options.encoding ? String(data || '') : ''
  132. }
  133. }
  134. function getUserDataFilePath(fileName) {
  135. const wxApi = getWxApi()
  136. const userDataPath = wxApi.env && wxApi.env.USER_DATA_PATH
  137. if (!userDataPath) throw new Error('当前微信版本不支持生成文件')
  138. return `${userDataPath}/${fileName}`
  139. }
  140. function writeTextFile(filePath, data, encoding = 'utf8') {
  141. const wxApi = getWxApi()
  142. if (typeof wxApi.getFileSystemManager !== 'function') {
  143. throw new Error('当前微信版本不支持生成文件')
  144. }
  145. const fs = wxApi.getFileSystemManager()
  146. if (typeof fs.writeFileSync !== 'function') {
  147. throw new Error('当前微信版本不支持同步生成文件')
  148. }
  149. fs.writeFileSync(filePath, data, encoding)
  150. }
  151. function shareFileToChat(filePath, fileName) {
  152. const wxApi = getWxApi()
  153. return new Promise((resolve, reject) => {
  154. if (typeof wxApi.shareFileMessage !== 'function') {
  155. reject(new Error('当前微信版本不支持发送文件到聊天'))
  156. return
  157. }
  158. wxApi.shareFileMessage({
  159. fileName,
  160. filePath,
  161. success: resolve,
  162. fail: reject
  163. })
  164. })
  165. }
  166. async function saveTextFileToChat(fileName, data) {
  167. const filePath = getUserDataFilePath(fileName)
  168. writeTextFile(filePath, data, 'utf8')
  169. await shareFileToChat(filePath, fileName)
  170. return filePath
  171. }
  172. module.exports = {
  173. assertFileExtension,
  174. chooseFile,
  175. chooseLocalFile,
  176. chooseMessageFile,
  177. formatBytes,
  178. formatExportStamp,
  179. getFirstSelectedFile,
  180. getUserDataFilePath,
  181. isCancelError,
  182. loadSelectedFile,
  183. readFile,
  184. saveTextFileToChat,
  185. shareFileToChat,
  186. toUint8Array,
  187. writeTextFile
  188. }