|
@@ -0,0 +1,283 @@
|
|
|
|
+#!/usr/bin/env node
|
|
|
|
+
|
|
|
|
+// Spotlight
|
|
|
|
+// 保存 windows 聚焦 图片
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
|
+/* Requirements */
|
|
|
|
+
|
|
|
|
+const fs = require('fs')
|
|
|
|
+
|
|
|
|
+const path = require('path')
|
|
|
|
+const util = require('util')
|
|
|
|
+const child_process = require('child_process')
|
|
|
|
+const exec = util.promisify(child_process.exec)
|
|
|
|
+
|
|
|
|
+const readChunk = require('read-chunk')
|
|
|
|
+const fileType = require('file-type')
|
|
|
|
+const iconv = require('iconv-lite')
|
|
|
|
+const program = require('commander')
|
|
|
|
+const filesizeParser = require('filesize-parser');
|
|
|
|
+
|
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
|
+
|
|
|
|
+const fsp = fs.promises
|
|
|
|
+
|
|
|
|
+const encoding = 'cp936'
|
|
|
|
+const binaryEncoding = 'binary'
|
|
|
|
+const THRESHOLD_SIZE = '200KB'
|
|
|
|
+
|
|
|
|
+// 文件大小阈值
|
|
|
|
+let minSizeLimit = 0
|
|
|
|
+let outputDir = ''
|
|
|
|
+let isDebug = false
|
|
|
|
+
|
|
|
|
+// -----------------------------------------------------------------------------
|
|
|
|
+
|
|
|
|
+// 读取命令行参数
|
|
|
|
+function parseCommandLineArg() {
|
|
|
|
+ program.version('0.0.1')
|
|
|
|
+
|
|
|
|
+ program
|
|
|
|
+ .option('-o, --outputDir <path>', 'set output directory. defaults to %My Pictures%')
|
|
|
|
+ .option('-s, --min-size <value>', 'file min size.', parseSize, THRESHOLD_SIZE)
|
|
|
|
+ .option('-d, --debug', 'output extra debugging')
|
|
|
|
+
|
|
|
|
+ program.parse(process.argv)
|
|
|
|
+ // console.log(program.opts())
|
|
|
|
+
|
|
|
|
+ minSizeLimit = filesizeParser(program.minSize)
|
|
|
|
+ outputDir = program.outputDir
|
|
|
|
+ isDebug = program.debug
|
|
|
|
+
|
|
|
|
+ function parseSize(value, previous) {
|
|
|
|
+ let size
|
|
|
|
+ try {
|
|
|
|
+ filesizeParser(value)
|
|
|
|
+ size = value
|
|
|
|
+ } catch (e) {
|
|
|
|
+ size = previous
|
|
|
|
+ }
|
|
|
|
+ return size
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 确定输出目录
|
|
|
|
+async function determineOutputDir() {
|
|
|
|
+ let dir
|
|
|
|
+ if (outputDir) {
|
|
|
|
+ dir = path.resolve(outputDir)
|
|
|
|
+ try {
|
|
|
|
+ await determineDirectory(dir)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error(e.message)
|
|
|
|
+ dir = ''
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!dir) {
|
|
|
|
+ dir = path.resolve(await getDefaultMyPicturesDir(), 'spotlight')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ outputDir = dir
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取 Windows 聚焦 目录
|
|
|
|
+function getSpotlightDirPath() {
|
|
|
|
+ let appDataDir = 'Packages\\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\\LocalState\\Assets'
|
|
|
|
+ return path.resolve(process.env.localappdata, appDataDir)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取用户电脑 我的 图片 文件夹位置
|
|
|
|
+async function getDefaultMyPicturesDir() {
|
|
|
|
+ const valName = 'My Pictures'
|
|
|
|
+ const keyName = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
|
|
|
|
+ let command = `reg query "${keyName}" /v "${valName}"`
|
|
|
|
+
|
|
|
|
+ let result = null
|
|
|
|
+ try {
|
|
|
|
+ const {stdout} = await exec(command, {encoding: binaryEncoding})
|
|
|
|
+ const outputStr = iconvDecode(stdout).trim()
|
|
|
|
+
|
|
|
|
+ const lines = outputStr.split(/[\r?\n]/)
|
|
|
|
+ for (let line of lines) {
|
|
|
|
+ line = line.trim()
|
|
|
|
+ if (!line.startsWith(valName)) {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ const str = line.slice(valName.length).trim()
|
|
|
|
+ const regResult = /(.+)\s+(.+)/.exec(str)
|
|
|
|
+ if (regResult) {
|
|
|
|
+ result = regResult[2]
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ throw {
|
|
|
|
+ ...error,
|
|
|
|
+ stderr: iconvDecode(error.stderr)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ await determineDirectory(result)
|
|
|
|
+ return result
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * checkFileType
|
|
|
|
+ * @param filePath
|
|
|
|
+ * @returns {Promise<(fileType.FileTypeResult&{ext: string, mime: string, isImage: boolean})|*>}
|
|
|
|
+ */
|
|
|
|
+async function checkFileType(filePath) {
|
|
|
|
+ const buffer = await readChunk(filePath, 0, fileType.minimumBytes)
|
|
|
|
+
|
|
|
|
+ //=> {ext: 'png', mime: 'image/png'}
|
|
|
|
+ let res = fileType(buffer)
|
|
|
|
+ return {
|
|
|
|
+ ...res,
|
|
|
|
+ isImage: res.mime.startsWith('image/')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 确认文件夹可访问
|
|
|
|
+ * @param {PathLike} dir
|
|
|
|
+ * @param {boolean} autoCreate
|
|
|
|
+ * @return {Promise<void>}
|
|
|
|
+ */
|
|
|
|
+async function determineDirectory(dir, autoCreate = false) {
|
|
|
|
+ let err = null
|
|
|
|
+ try {
|
|
|
|
+ await fsp.access(dir, fs.constants.F_OK | fs.constants.W_OK)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ err = e
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (err && autoCreate) {
|
|
|
|
+ try {
|
|
|
|
+ await fsp.mkdir(dir, {recursive: true})
|
|
|
|
+ err = null
|
|
|
|
+ } catch (e) {
|
|
|
|
+ err = e
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (err) {
|
|
|
|
+ throw err
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function iconvDecode(str = '') {
|
|
|
|
+ return iconv.decode(Buffer.from(str, binaryEncoding), encoding)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+async function findAndCopy() {
|
|
|
|
+ let targetDir = getSpotlightDirPath()
|
|
|
|
+
|
|
|
|
+ let filesNames = await fsp.readdir(targetDir)
|
|
|
|
+
|
|
|
|
+ let list = []
|
|
|
|
+
|
|
|
|
+ for (const filesName of filesNames) {
|
|
|
|
+ let filePath = path.join(targetDir, filesName)
|
|
|
|
+ let stat = await fsp.stat(filePath)
|
|
|
|
+
|
|
|
|
+ if (stat.isFile() && stat.size >= minSizeLimit) {
|
|
|
|
+ list.push({
|
|
|
|
+ path: filePath,
|
|
|
|
+ name: filesName,
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @typedef imageFiles~item
|
|
|
|
+ * @property {string} path - eg: /path/to/8cf892
|
|
|
|
+ * @property {string} name - eg: 8cf892
|
|
|
|
+ * @property {string} ext - eg: png
|
|
|
|
+ * @property {string} mime - eg: image/png
|
|
|
|
+ * @property {boolean} isImage
|
|
|
|
+ * @property {string} outputName - eg: 8cf892.png
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * imageFiles
|
|
|
|
+ * @type {imageFiles~item[]}
|
|
|
|
+ */
|
|
|
|
+ const imageFiles = []
|
|
|
|
+
|
|
|
|
+ // checkFileType
|
|
|
|
+ for (const item of list) {
|
|
|
|
+ let result = await checkFileType(item.path)
|
|
|
|
+ if (result.isImage) {
|
|
|
|
+ imageFiles.push({
|
|
|
|
+ ...item,
|
|
|
|
+ ...result,
|
|
|
|
+ outputName: `${item.name}.${result.ext}`,
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ await determineDirectory(outputDir, true)
|
|
|
|
+
|
|
|
|
+ // copy file
|
|
|
|
+ const copyResult = {
|
|
|
|
+ list: [],
|
|
|
|
+ successes: [],
|
|
|
|
+ errors: [],
|
|
|
|
+ ignores: [],
|
|
|
|
+ }
|
|
|
|
+ for (const img of imageFiles) {
|
|
|
|
+ let dest = path.resolve(outputDir, img.outputName)
|
|
|
|
+ let result = {img, error: null}
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ await fsp.copyFile(img.path, dest, fs.constants.COPYFILE_EXCL)
|
|
|
|
+ copyResult.successes.push(result)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ if (e.code === 'EEXIST') {
|
|
|
|
+ copyResult.ignores.push(result)
|
|
|
|
+ } else {
|
|
|
|
+ result.error = e
|
|
|
|
+ copyResult.errors.push(result)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ copyResult.list.push(result)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return copyResult
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function start() {
|
|
|
|
+ parseCommandLineArg()
|
|
|
|
+ await determineOutputDir()
|
|
|
|
+
|
|
|
|
+ let result
|
|
|
|
+ try {
|
|
|
|
+ result = await findAndCopy()
|
|
|
|
+ } catch (err) {
|
|
|
|
+ console.warn(err)
|
|
|
|
+ process.exit(1)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let msg = `
|
|
|
|
+输出目录: ${outputDir}
|
|
|
|
+
|
|
|
|
+找到图片数: ${result.list.length}
|
|
|
|
+复制成功: ${result.successes.length}
|
|
|
|
+已存在: ${result.ignores.length}
|
|
|
|
+复制失败: ${result.errors.length}
|
|
|
|
+ `
|
|
|
|
+
|
|
|
|
+ result.errors.forEach(err => {
|
|
|
|
+ let errMsg = `${err.img.outputName}\n${isDebug ? err.error : err.error.code}\n`
|
|
|
|
+ console.warn(errMsg)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ console.log(msg)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+start()
|
|
|
|
+ .catch(console.error)
|