From 3cb223a49b067b255cfd2b03bd86dc984bede262 Mon Sep 17 00:00:00 2001 From: terwer Date: Tue, 26 Mar 2024 21:19:08 +0800 Subject: [PATCH] feat: support qiniu --- .../uploader/{aliyun.ts => aliyun/index.ts} | 35 +-- .../{aliyun-node.ts => aliyun/node.ts} | 17 +- .../uploader/{aliyun-web.ts => aliyun/web.ts} | 17 +- .../src/plugins/uploader/index.ts | 6 +- .../src/plugins/uploader/qiniu/digest.ts | 22 ++ .../src/plugins/uploader/qiniu/index.ts | 210 ++++++++++++++++++ .../src/plugins/uploader/qiniu/rs.ts | 94 ++++++++ .../src/plugins/uploader/qiniu/util.ts | 42 ++++ libs/Universal-PicGo-Core/vite.config.ts | 8 +- libs/zhi-siyuan-picgo/src/lib/picgoHelper.ts | 2 +- 10 files changed, 422 insertions(+), 31 deletions(-) rename libs/Universal-PicGo-Core/src/plugins/uploader/{aliyun.ts => aliyun/index.ts} (81%) rename libs/Universal-PicGo-Core/src/plugins/uploader/{aliyun-node.ts => aliyun/node.ts} (81%) rename libs/Universal-PicGo-Core/src/plugins/uploader/{aliyun-web.ts => aliyun/web.ts} (86%) create mode 100644 libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/digest.ts create mode 100644 libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/index.ts create mode 100644 libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/rs.ts create mode 100644 libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/util.ts diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/index.ts similarity index 81% rename from libs/Universal-PicGo-Core/src/plugins/uploader/aliyun.ts rename to libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/index.ts index c94796d..a42488d 100644 --- a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun.ts +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/index.ts @@ -1,8 +1,17 @@ -import { IAliyunConfig, IPicGo, IPluginConfig } from "../../types" -import { ILocalesKey } from "../../i18n/zh-CN" +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +import { IAliyunConfig, IPicGo, IPluginConfig } from "../../../types" +import { ILocalesKey } from "../../../i18n/zh-CN" import { hasNodeEnv } from "universal-picgo-store" -import { handleNode } from "./aliyun-node" -import { handleWeb } from "./aliyun-web" +import { handleNode } from "./node" +import { handleWeb } from "./web" const handle = async (ctx: IPicGo): Promise => { if (hasNodeEnv) { @@ -21,7 +30,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_ACCESSKEYID") }, default: userConfig.accessKeyId || "", - required: true + required: true, }, { name: "accessKeySecret", @@ -30,7 +39,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_ACCESSKEYSECRET") }, default: userConfig.accessKeySecret || "", - required: true + required: true, }, { name: "bucket", @@ -39,7 +48,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_BUCKET") }, default: userConfig.bucket || "", - required: true + required: true, }, { name: "area", @@ -54,7 +63,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { get message() { return ctx.i18n.translate("PICBED_ALICLOUD_MESSAGE_AREA") }, - required: true + required: true, }, { name: "path", @@ -69,7 +78,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_MESSAGE_PATH") }, default: userConfig.path || "", - required: false + required: false, }, { name: "customUrl", @@ -84,7 +93,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_MESSAGE_CUSTOMURL") }, default: userConfig.customUrl || "", - required: false + required: false, }, { name: "options", @@ -99,8 +108,8 @@ const config = (ctx: IPicGo): IPluginConfig[] => { return ctx.i18n.translate("PICBED_ALICLOUD_MESSAGE_OPTIONS") }, default: userConfig.options || "", - required: false - } + required: false, + }, ] return config } @@ -111,6 +120,6 @@ export default function register(ctx: IPicGo): void { return ctx.i18n.translate("PICBED_ALICLOUD") }, handle, - config + config, }) } diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-node.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/node.ts similarity index 81% rename from libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-node.ts rename to libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/node.ts index be99c11..71cb1ea 100644 --- a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-node.ts +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/node.ts @@ -1,9 +1,18 @@ +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + // noinspection ES6PreferShortImport -import { IAliyunConfig, IPicGo } from "../../types" -import { IBuildInEvent } from "../../utils/enums" -import { ILocalesKey } from "../../i18n/zh-CN" -import { base64ToBuffer } from "../../utils/common" +import { IAliyunConfig, IPicGo } from "../../../types" +import { IBuildInEvent } from "../../../utils/enums" +import { ILocalesKey } from "../../../i18n/zh-CN" +import { base64ToBuffer } from "../../../utils/common" import OSS from "ali-oss" const handleNode = async (ctx: IPicGo): Promise => { diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-web.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/web.ts similarity index 86% rename from libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-web.ts rename to libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/web.ts index 3d6b75c..965c687 100644 --- a/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-web.ts +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/aliyun/web.ts @@ -1,10 +1,19 @@ -import { IAliyunConfig, IPicGo } from "../../types" +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +import { IAliyunConfig, IPicGo } from "../../../types" import crypto from "crypto" import mime from "mime-types" -import { IBuildInEvent } from "../../utils/enums" -import { ILocalesKey } from "../../i18n/zh-CN" +import { IBuildInEvent } from "../../../utils/enums" +import { ILocalesKey } from "../../../i18n/zh-CN" import { AxiosRequestConfig } from "axios" -import { base64ToBuffer, safeParse } from "../../utils/common" +import { base64ToBuffer, safeParse } from "../../../utils/common" // generate OSS signature const generateSignature = (options: IAliyunConfig, fileName: string): string => { diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/index.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/index.ts index c37395d..f3594a0 100644 --- a/libs/Universal-PicGo-Core/src/plugins/uploader/index.ts +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/index.ts @@ -8,19 +8,21 @@ */ import { IPicGo, IPicGoPlugin } from "../../types" -import SMMSUploader from "./smms" import githubUploader from "./github" import gitlabUploader from "./gitlab" import aliYunUploader from "./aliyun" +import qiniuUploader from "./qiniu" +import SMMSUploader from "./smms" import imgurUploader from "./imgur" const buildInUploaders: IPicGoPlugin = () => { return { register(ctx: IPicGo) { - SMMSUploader(ctx) githubUploader(ctx) gitlabUploader(ctx) aliYunUploader(ctx) + qiniuUploader(ctx) + SMMSUploader(ctx) imgurUploader(ctx) }, } diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/digest.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/digest.ts new file mode 100644 index 0000000..58353a0 --- /dev/null +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/digest.ts @@ -0,0 +1,22 @@ +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +class Mac { + accessKey: string + secretKey: string + options: any + + constructor(accessKey: string, secretKey: string, options?: Partial) { + this.accessKey = accessKey + this.secretKey = secretKey + this.options = { ...options } + } +} + +export { Mac } diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/index.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/index.ts new file mode 100644 index 0000000..05e3ca1 --- /dev/null +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/index.ts @@ -0,0 +1,210 @@ +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +import { ILocalesKey } from "../../../i18n/zh-CN" +import { IPicGo, IPluginConfig, IQiniuConfig } from "../../../types" +import { IBuildInEvent } from "../../../utils/enums" +import { bufferToBase64, safeParse } from "../../../utils/common" +import mime from "mime-types" +import { AxiosRequestConfig } from "axios" +import { Mac } from "./digest" +import { PutPolicy } from "./rs" + +function postOptions(options: IQiniuConfig, fileName: string, token: string, imgBase64: string): AxiosRequestConfig { + const area = selectArea(options.area || "z0") + const path = options.path || "" + const base64FileName = Buffer.from(path + fileName, "utf-8") + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + return { + method: "POST", + url: `http://upload${area}.qiniup.com/putb64/-1/key/${base64FileName}`, + headers: { + Authorization: `UpToken ${token}`, + "Content-Type": mime.lookup(fileName) || "application/octet-stream", + }, + data: imgBase64, + proxy: false, + } +} + +function selectArea(area: string): string { + return area === "z0" ? "" : "-" + area +} + +function getToken(qiniuOptions: any): string { + const accessKey = qiniuOptions.accessKey + const secretKey = qiniuOptions.secretKey + const mac = new Mac(accessKey, secretKey) + const options = { + scope: qiniuOptions.bucket, + } + const putPolicy = new PutPolicy(options) + return putPolicy.uploadToken(mac) +} + +const handle = async (ctx: IPicGo): Promise => { + const qiniuOptions = ctx.getConfig("picBed.qiniu") + if (!qiniuOptions) { + throw new Error("Can't find qiniu config") + } + + const imgList = ctx.output + for (const img of imgList) { + if (img.fileName && img.buffer) { + try { + let base64Image = img.base64Image + if (!base64Image && img.buffer) { + base64Image = bufferToBase64(img.buffer) + } + if (!base64Image) { + ctx.log.error("Can not find image base64") + throw new Error("Can not find image base64") + } + const options = postOptions(qiniuOptions, img.fileName, getToken(qiniuOptions), base64Image) + const res: any = await ctx.request(options) + const body = safeParse(res) + + if (body?.key) { + delete img.base64Image + delete img.buffer + const baseUrl = qiniuOptions.url + const options = qiniuOptions.options + img.imgUrl = `${baseUrl}/${body.key as string}${options}` + } else { + ctx.emit(IBuildInEvent.NOTIFICATION, { + title: ctx.i18n.translate("UPLOAD_FAILED"), + body: body.msg, + }) + ctx.log.error("qiniu error", body) + throw new Error("Upload failed") + } + } catch (e: any) { + if (e.message !== "Upload failed") { + // err.response maybe undefined + if (e.error) { + const errMsg = e.error + ctx.emit(IBuildInEvent.NOTIFICATION, { + title: ctx.i18n.translate("UPLOAD_FAILED"), + body: errMsg, + }) + throw errMsg + } + } + throw e + } + } + } + return ctx +} + +const config = (ctx: IPicGo): IPluginConfig[] => { + const userConfig = ctx.getConfig("picBed.qiniu") || {} + const config: IPluginConfig[] = [ + { + name: "accessKey", + type: "input", + get alias() { + return ctx.i18n.translate("PICBED_QINIU_ACCESSKEY") + }, + default: userConfig.accessKey || "", + required: true, + }, + { + name: "secretKey", + type: "password", + get alias() { + return ctx.i18n.translate("PICBED_QINIU_SECRETKEY") + }, + default: userConfig.secretKey || "", + required: true, + }, + { + name: "bucket", + type: "input", + get alias() { + return ctx.i18n.translate("PICBED_QINIU_BUCKET") + }, + default: userConfig.bucket || "", + required: true, + }, + { + name: "url", + type: "input", + get prefix() { + return ctx.i18n.translate("PICBED_QINIU_URL") + }, + get alias() { + return ctx.i18n.translate("PICBED_QINIU_URL") + }, + get message() { + return ctx.i18n.translate("PICBED_QINIU_MESSAGE_URL") + }, + default: userConfig.url || "", + required: true, + }, + { + name: "area", + type: "input", + get prefix() { + return ctx.i18n.translate("PICBED_QINIU_AREA") + }, + get alias() { + return ctx.i18n.translate("PICBED_QINIU_AREA") + }, + get message() { + return ctx.i18n.translate("PICBED_QINIU_MESSAGE_AREA") + }, + default: userConfig.area || "", + required: true, + }, + { + name: "options", + type: "input", + get prefix() { + return ctx.i18n.translate("PICBED_QINIU_OPTIONS") + }, + get alias() { + return ctx.i18n.translate("PICBED_QINIU_OPTIONS") + }, + get message() { + return ctx.i18n.translate("PICBED_QINIU_MESSAGE_OPTIONS") + }, + default: userConfig.options || "", + required: false, + }, + { + name: "path", + type: "input", + get prefix() { + return ctx.i18n.translate("PICBED_QINIU_PATH") + }, + get alias() { + return ctx.i18n.translate("PICBED_QINIU_PATH") + }, + get message() { + return ctx.i18n.translate("PICBED_QINIU_MESSAGE_PATH") + }, + default: userConfig.path || "", + required: false, + }, + ] + return config +} + +export default function register(ctx: IPicGo): void { + ctx.helper.uploader.register("qiniu", { + get name() { + return ctx.i18n.translate("PICBED_QINIU") + }, + handle, + config, + }) +} diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/rs.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/rs.ts new file mode 100644 index 0000000..7ba2149 --- /dev/null +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/rs.ts @@ -0,0 +1,94 @@ +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +import { Mac } from "./digest" +import { util } from "./util" + +// 用于与旧 SDK 版本兼容 +function _putPolicyBuildInKeys(): string[] { + return [ + "scope", + "isPrefixalScope", + "insertOnly", + "saveKey", + "forceSaveKey", + "endUser", + "returnUrl", + "returnBody", + "callbackUrl", + "callbackHost", + "callbackBody", + "callbackBodyType", + "callbackFetchKey", + "persistentOps", + "persistentNotifyUrl", + "persistentPipeline", + "fsizeLimit", + "fsizeMin", + "detectMime", + "mimeLimit", + "deleteAfterDays", + "fileType", + ] +} + +/** + * 上传策略 + * @link https://developer.qiniu.com/kodo/manual/1206/put-policy + */ +class PutPolicy { + private readonly expires: number + + constructor(options: any) { + if (typeof options !== "object") { + throw new Error("invalid putpolicy options") + } + + const that = this as any + Object.keys(options).forEach((k) => { + if (k === "expires") { + return + } + that[k] = options[k] + }) + + this.expires = options.expires || 3600 + _putPolicyBuildInKeys().forEach((k) => { + if ((this as any)[k] === undefined) { + that[k] = that[k] || null + } + }) + } + + getFlags(): any { + const that = this as any + const flags: any = {} + + Object.keys(this).forEach((k) => { + if (k === "expires" || that[k] === null) { + return + } + flags[k] = that[k] + }) + + flags.deadline = this.expires + Math.floor(Date.now() / 1000) + + return flags + } + + uploadToken(mac: Mac): string { + const flags = this.getFlags() + const encodedFlags = util.urlsafeBase64Encode(JSON.stringify(flags)) + const encoded = util.hmacSha1(encodedFlags, mac.secretKey) + const encodedSign = util.base64ToUrlSafe(encoded) + return [mac.accessKey, encodedSign, encodedFlags].join(":") + } +} + +export { PutPolicy } diff --git a/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/util.ts b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/util.ts new file mode 100644 index 0000000..5e9904b --- /dev/null +++ b/libs/Universal-PicGo-Core/src/plugins/uploader/qiniu/util.ts @@ -0,0 +1,42 @@ +/* + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * Copyright (C) 2024 Terwer, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +import { Buffer } from "../../../utils/nodePolyfill" +import crypto from "crypto" + +const base64ToUrlSafe = function (v: string) { + return v.replace(/\//g, "_").replace(/\+/g, "-") +} + +const urlSafeToBase64 = function (v: string) { + return v.replace(/_/g, "/").replace(/-/g, "+") +} + +// UrlSafe Base64 Decode +const urlsafeBase64Encode = function (jsonFlags: string) { + const encoded = Buffer.from(jsonFlags).toString("base64") + return base64ToUrlSafe(encoded) +} + +// UrlSafe Base64 Decode +const urlSafeBase64Decode = function (fromStr: string) { + return Buffer.from(urlSafeToBase64(fromStr), "base64").toString() +} + +// Hmac-sha1 Crypt +const hmacSha1 = (encodedFlags: string, secretKey: string) => { + // return value already encoded with base64 + const hmac = crypto.createHmac("sha1", secretKey) + hmac.update(encodedFlags) + return hmac.digest("base64") +} + +const util = { urlsafeBase64Encode, urlSafeBase64Decode, base64ToUrlSafe, urlSafeToBase64, hmacSha1 } + +export { util } diff --git a/libs/Universal-PicGo-Core/vite.config.ts b/libs/Universal-PicGo-Core/vite.config.ts index 28c77d7..86060dd 100644 --- a/libs/Universal-PicGo-Core/vite.config.ts +++ b/libs/Universal-PicGo-Core/vite.config.ts @@ -13,10 +13,6 @@ import fs from "fs" const packageJson = fs.readFileSync("./package.json").toString() const pkg = JSON.parse(packageJson) || {} -const getAppBase = (): string => { - return "/plugins/siyuan-plugin-picgo/" -} - const getDefineEnv = (isDevMode: boolean) => { const mode = process.env.NODE_ENV const isTest = mode === "test" @@ -25,8 +21,7 @@ const getDefineEnv = (isDevMode: boolean) => { const defaultEnv = { DEV_MODE: `${isDevMode || isTest}`, - APP_BASE: `${appBase}`, - NODE_ENV: "development", + NODE_ENV: isDevMode ? "development" : "production", PICGO_VERSION: pkg.version, } const env = loadEnv(mode, process.cwd()) @@ -54,7 +49,6 @@ const isWatch = args.watch || args.w || false const isDev = isServe || isWatch const devDistDir = "./dist" const distDir = isWatch ? devDistDir : "./dist" -const appBase = getAppBase() console.log("isWatch=>", isWatch) console.log("distDir=>", distDir) diff --git a/libs/zhi-siyuan-picgo/src/lib/picgoHelper.ts b/libs/zhi-siyuan-picgo/src/lib/picgoHelper.ts index cebe7fb..519d362 100644 --- a/libs/zhi-siyuan-picgo/src/lib/picgoHelper.ts +++ b/libs/zhi-siyuan-picgo/src/lib/picgoHelper.ts @@ -111,7 +111,7 @@ class PicgoHelper { } }) .sort((a: any) => { - if (a.type === "smms") { + if (a.type === "github") { return -1 } return 0