Sfoglia il codice sorgente

添加 windows 聚焦复制脚步 工具

(cherry picked from commit e94d837233067d3b8afcd44a8550401612fdbfc0)
ph2 5 anni fa
parent
commit
8c8bb249c5
5 ha cambiato i file con 385 aggiunte e 0 eliminazioni
  1. 6 0
      .idea/jsLibraryMappings.xml
  2. 21 0
      package.json
  3. 283 0
      src/windows-spotlight/index.js
  4. 9 0
      src/windows-spotlight/package.json
  5. 66 0
      yarn.lock

+ 6 - 0
.idea/jsLibraryMappings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptLibraryMappings">
+    <includedPredefinedLibrary name="Node.js Core" />
+  </component>
+</project>

+ 21 - 0
package.json

@@ -0,0 +1,21 @@
+{
+  "name": "experiment-node",
+  "version": "1.0.0",
+  "main": "src/c_log:in_2.js",
+  "dependencies": {
+    "commander": "^2.20.0",
+    "file-type": "^11.1.0",
+    "filesize-parser": "^1.5.0",
+    "iconv-lite": "^0.4.24",
+    "js-base64": "^2.4.9",
+    "read-chunk": "^3.2.0"
+  },
+  "devDependencies": {},
+  "scripts": {
+    "script-name": "babel-node script.js",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "description": ""
+}

+ 283 - 0
src/windows-spotlight/index.js

@@ -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)

+ 9 - 0
src/windows-spotlight/package.json

@@ -0,0 +1,9 @@
+{
+  "name": "windows-spotlight",
+  "version": "0.0.1",
+  "main": "./index.js",
+  "dependencies": {
+    "file-type": "^11.1.0",
+    "read-chunk": "^3.2.0"
+  }
+}

+ 66 - 0
yarn.lock

@@ -0,0 +1,66 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+commander@^2.20.0:
+  version "2.20.0"
+  resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+  integrity sha1-1YuytcHuj4ew00ACfp6U4iLFpCI=
+
+file-type@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.npm.taobao.org/file-type/download/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8"
+  integrity sha1-k3gPP+2YtZl1XYRrmaFheirQY7g=
+
+filesize-parser@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.npm.taobao.org/filesize-parser/download/filesize-parser-1.5.0.tgz#97ad66d5b0d7154b2e8b1b4e83f526aed33c62f3"
+  integrity sha1-l61m1bDXFUsuixtOg/UmrtM8YvM=
+
+iconv-lite@^0.4.24:
+  version "0.4.24"
+  resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+  integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
+js-base64@^2.4.9:
+  version "2.4.9"
+  resolved "http://registry.npm.taobao.org/js-base64/download/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03"
+
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+  integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+
+p-try@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=
+
+pify@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.npm.taobao.org/pify/download/pify-4.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpify%2Fdownload%2Fpify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
+  integrity sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=
+
+read-chunk@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.npm.taobao.org/read-chunk/download/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca"
+  integrity sha1-KYSv54ypv7vbdLGTh7+ehiicFso=
+  dependencies:
+    pify "^4.0.1"
+    with-open-file "^0.1.6"
+
+"safer-buffer@>= 2.1.2 < 3":
+  version "2.1.2"
+  resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafer-buffer%2Fdownload%2Fsafer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=
+
+with-open-file@^0.1.6:
+  version "0.1.6"
+  resolved "https://registry.npm.taobao.org/with-open-file/download/with-open-file-0.1.6.tgz#0bc178ecab75f6baac8ae11c85e07445d690ea50"
+  integrity sha1-C8F47Kt19rqsiuEcheB0RdaQ6lA=
+  dependencies:
+    p-finally "^1.0.0"
+    p-try "^2.1.0"
+    pify "^4.0.1"