PicGo图床插件

153 阅读3分钟

PicGo 是一个用于快速上传图片并获取图片预览链接的工具,可以作为 Typora 在编辑 MarkDown 文档时插件处理图片保存的问题。

官方文档地址:picgo.github.io/PicGo-Core-…

PicGo 上传流程

core-lifecycle

可供开发的部分包括:两个模块(Transformer、Uploader)、三个生命周期函数(beforeTransformPlugins、beforeUploadPlugins、afterUploadPlugins)

module.exports = (ctx) => {
  const register = () => {
    // 注册钩子函数
    ctx.helper.uploader.register('wlyd', { handle: handleUploader, config })
    ctx.helper.beforeTransformPlugins.register('wlyd', handleBeforeTransform)
  }

  return {
    register,         // 注册函数
    uploader: 'wlyd'  // 声明注册的uploader的id
  }
}

注册模块(Transformer、Uploader):handle 执行函数;config 界面配置

ctx.helper.uploader.register('wlyd', { handle, config })

执行函数

const handle = (ctx) => {
    // todo...
}

ctx 常用属性如下

  • output:Array 上传文件列表
  • getConfig、setConfig、saveConfig、unsetConfig、removeConfig 操作配置文件
  • Request.request 发起请求(v1.5.0后废弃,可以使用 request 方法)

界面配置

const config = (ctx) => {
    // todo...
    return [...]
}

配置对象

    {
      name: 'upload_guid',  // 字段名称,读取时使用
      type: 'input',        // 展示类型
      default: '',          // 默认值
      required: true,       // 是否必须
      alias: '上传GUID'     // 字段别名,界面展示使用
      choices: [            // 下拉框选项
          { name: '', value: '' }
      ]
    }

type值

  • input:输入框

  • password

  • list:下拉框,必须包含 choices 属性

  • checkbox:多选下拉框,必须包含 choices 属性

  • confirm:开关

注意事项

上传完成后必要操作:

delete ctx.output[index].base64Image
delete ctx.output[index].buffer
// 更新图片链接,否则无法正常预览
ctx.output[index].imgUrl = link

完整demo

const fs = require('fs')
const path = require('path')

/** 获取上传后图片的预览链接 */
const getPreviewLink = () => {
  return ''
}

/** 获取上传链接 */
const getUploadLink = async () => {
  return ''
}
/** 获取上传配置 */
const getUploadConfig = (uploadLink, fileName, file) => {
  return {
    method: 'POST',
    url: uploadLink,
    headers: {
      contentType: 'multipart/form-data'
    },
    formData: {/** 上传参数 */}
  }
}

const getImageBuffer = (image) => {
  let buffer = image.buffer
  if (!buffer && image.base64Image) {
    buffer = Buffer.from(image.base64Image, 'base64')
  }

  return new Uint8Array(buffer)
}

const getFilePath = (fileName) => {
  return path.join(__dirname, fileName)
}

/** 获取上传文件:先保存本地目录,然后再读取,最后删除本地文件 */
const getFile = async (image) => {
  const filePath = getFilePath(image.fileName)
  await fs.writeFileSync(filePath, getImageBuffer(image))
  const stream = fs.createReadStream(filePath)
  setTimeout(() => fs.unlink(getFilePath(image.fileName), () => {}), 100)

  return stream
}

/** 判断是否上传成功 */
const isUploadSuccess = (data) => {
  return true
}

/** 上传文件 */
const uploadFile = (ctx, config) => {
  return async image => {
    try {
      const uploadLink = getUploadLink()
      const file = await getFile(image)

      const uploadConfig = getUploadConfig(uploadLink, image.fileName, file)
      return await ctx.Request.request(uploadConfig)
    } catch (error) {
      ctx.emit('notification', { title: '上传失败', error })
      throw new Error(JSON.stringify(error))
    }
  }
}

/** 图片重命名方式 */
const UPDATE_FILE_NAME_ACTION = {
  time: (file) => {
    const date = new Date()
    const hour = `${date.getHours()}`.padStart(2, '0')
    const minute = `${date.getMinutes()}`.padStart(2, '0')
    const second = `${date.getSeconds()}`.padStart(2, '0')

    return `${hour}${minute}${second}${date.getMilliseconds()}${file.extname}`
  },
  guid: (file) => {
    let d = new Date().getTime()
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
      const r = (d + Math.random() * 16) % 16 | 0
      d = Math.floor(d / 16)
      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
    }) + file.extname
  }
}

/** 图片重命名 */
const updateFileName = (type, file) => {
  return type in UPDATE_FILE_NAME_ACTION
    ? UPDATE_FILE_NAME_ACTION[type](file)
    : file.fileName
}

/** 主函数 */
const handle = async (ctx) => {
  // 获取 data.json 配置
  const config = ctx.getConfig('picBed.wlyd')
  const upload = uploadFile(ctx, config)
  
  for (const key in ctx.output) {
    const image = ctx.output[key]
    image.fileName = updateFileName(config.rename_type, image)

    const body = await upload(image)

    if (isUploadSuccess(body)) {
      delete ctx.output[key].base64Image
      delete ctx.output[key].buffer
      // 更新图片链接,否则 typora 无法正常预览
      ctx.output[key].imgUrl = getPreviewLink(image.fileName, config.preview_guid)
    }
  }
}

/** 界面配置 */
const config = (ctx) => {
  return [
    {
      name: 'upload_guid',
      type: 'input',
      default: '',
      required: true,
      alias: '上传GUID'
    },
    {
      name: 'rename_type',
      type: 'list',
      default: 'time',
      alias: '文件重命名',
      choices: [
        { name: '关闭', value: 'close' },
        { name: '时间戳', value: 'time' },
        { name: 'GUID', value: 'guid' }
      ]
    }
  ]
}

module.exports = (ctx) => {
  const register = () => {
    ctx.helper.uploader.register('wlyd', { handle, config })
  }

  return { uploader: 'wlyd', register }
}

原文链接: # PicGo图床插件