Skip to content

Commit

Permalink
Add support for Qiniu Kodo
Browse files Browse the repository at this point in the history
  • Loading branch information
addozhang committed Jan 5, 2025
1 parent b8dd6c5 commit adb46cb
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 10 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Obsidian Image Upload Toolkit

This plugin cloud upload all local images embedded in markdown to specified remote image store
(support [imgur](https://imgur.com),[AliYun OSS](https://www.alibabacloud.com/product/object-storage-service) and [Imagekit](https://imagekit.io), currently) and export markdown with image urls to clipboard directly.
(support [imgur](https://imgur.com),[AliYun OSS](https://www.alibabacloud.com/product/object-storage-service),
[Imagekit](https://imagekit.io),
[TencentCloud COS](https://cloud.tencent.com/product/cos)
and [Qiniu Kodo](https://www.qiniu.com/products/kodo), currently) and export markdown with image urls to clipboard directly.
The origin markdown in vault is still using local images.

It will be help for publishing to the static site such [GitHub pages](https://pages.github.com).
Expand Down Expand Up @@ -30,6 +33,7 @@ and copy markdown with replaced image syntax to clipboard with notification.
- [x] ImageKit
- [x] Amazon S3
- [x] TencentCloud COS
- [x] Qiniu Kodo
- [ ] more...
- [x] setting for replacing images embedded in origin markdown directly

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"authorUrl": "https://atbug.com",
"isDesktopOnly": true,
"minAppVersion": "0.11.0",
"version": "0.7.0"
"version": "0.8.0"
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-image-upload-toolkit",
"version": "0.7.0",
"version": "0.8.0",
"description": "",
"author": "addozhang",
"main": "main.js",
Expand All @@ -21,6 +21,7 @@
"aws-sdk": "^2.1664.0",
"cos-nodejs-sdk-v5": "^2.14.6",
"imagekit": "^5.0.0",
"proxy-agent": "^5.0.0"
"proxy-agent": "^5.0.0",
"qiniu": "^7.14.0"
}
}
5 changes: 5 additions & 0 deletions src/imageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export default class ImageStore {
"TencentCloud COS"
)

static readonly QINIU_KUDO = new ImageStore(
"QINIU_KUDO",
"Qiniu KuDo"
)

private constructor(readonly id: string, readonly description: string) {
ImageStore.values.push(this)
}
Expand Down
11 changes: 10 additions & 1 deletion src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {OssSetting} from "./uploader/oss/ossUploader";
import {ImagekitSetting} from "./uploader/imagekit/imagekitUploader";
import {AwsS3Setting} from "./uploader/s3/awsS3Uploader";
import {CosSetting} from "./uploader/cos/cosUploader";
import {KodoSetting} from "./uploader/qiniu/kodoUploader";

export interface PublishSettings {
imageAltText: boolean;
Expand All @@ -27,6 +28,7 @@ export interface PublishSettings {
imagekitSetting: ImagekitSetting;
awsS3Setting: AwsS3Setting;
cosSetting: CosSetting;
kodoSetting: KodoSetting
}

const DEFAULT_SETTINGS: PublishSettings = {
Expand Down Expand Up @@ -67,7 +69,14 @@ const DEFAULT_SETTINGS: PublishSettings = {
secretKey: "",
path: "",
customDomainName: "",
}
},
kodoSetting: {
accessKey: "",
secretKey: "",
bucket: "",
customDomainName: "",
path: ""
},
};
export default class ObsidianPublish extends Plugin {
settings: PublishSettings;
Expand Down
53 changes: 50 additions & 3 deletions src/ui/publishSettingTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ export default class PublishSettingTab extends PluginSettingTab {
await this.drawImageStoreSettings(this.imageStoreDiv);
});
});
this.drawImageStoreSettings(this.imageStoreDiv).then(() => {
}).finally(() => {
})
this.drawImageStoreSettings(this.imageStoreDiv);
}

async hide(): Promise<any> {
Expand All @@ -109,6 +107,9 @@ export default class PublishSettingTab extends PluginSettingTab {
case ImageStore.TENCENTCLOUD_COS.id:
this.drawTencentCloudCosSetting(parentEL);
break;
case ImageStore.QINIU_KUDO.id:
this.drawQiniuSetting(parentEL);
break
default:
throw new Error(
"Should not reach here!"
Expand Down Expand Up @@ -359,4 +360,50 @@ export default class PublishSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.cosSetting.customDomainName)
.onChange(value => this.plugin.settings.cosSetting.customDomainName = value))
}

private drawQiniuSetting(parentEL: HTMLDivElement) {
new Setting(parentEL)
.setName("Access Key")
.setDesc("The access key of Qiniu.")
.addText(text =>
text
.setPlaceholder("Enter access key")
.setValue(this.plugin.settings.kodoSetting.accessKey)
.onChange(value => this.plugin.settings.kodoSetting.accessKey = value))
new Setting(parentEL)
.setName("Secret Key")
.setDesc("The secret key of Qiniu.")
.addText(text =>
text
.setPlaceholder("Enter secret key")
.setValue(this.plugin.settings.kodoSetting.secretKey)
.onChange(value => this.plugin.settings.kodoSetting.secretKey = value))
new Setting(parentEL)
.setName("Bucket Name")
.setDesc("The name of bucket to store images.")
.addText(text =>
text
.setPlaceholder("Enter bucket name")
.setValue(this.plugin.settings.kodoSetting.bucket)
.onChange(value => this.plugin.settings.kodoSetting.bucket = value))

// new Setting(parentEL)
// .setName("Target Path")
// .setDesc("The path to store image.\nSupport {year} {mon} {day} {random} {filename} vars. For example, /{year}/{mon}/{day}/{filename} with uploading pic.jpg, it will store as /2023/06/08/pic.jpg.")
// .addText(text =>
// text
// .setPlaceholder("Enter path")
// .setValue(this.plugin.settings.kodoSetting.path)
// .onChange(value => this.plugin.settings.kodoSetting.path = value))

//custom domain
new Setting(parentEL)
.setName("Custom Domain Name")
.setDesc("If the custom domain name is example.com, you can use https://example.com/pic.jpg to access pic.img.")
.addText(text =>
text
.setPlaceholder("Enter path")
.setValue(this.plugin.settings.kodoSetting.customDomainName)
.onChange(value => this.plugin.settings.kodoSetting.customDomainName = value))
}
}
5 changes: 3 additions & 2 deletions src/uploader/imageTagProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ export default class ImageTagProcessor {
uploader.upload(new File([buf], image.name), basePath + '/' + image.path).then(imgUrl => {
image.url = imgUrl;
resolve(image)
}).catch(e => new Notice(`Upload ${image.path} failed, remote server returned an error: ${e.message}`, 10000))
}).catch(e => {
new Notice(`Upload ${image.path} failed, remote server returned an error: ${e.error || e.message || e}`, 10000)
})
}));
}

return promises.length >= 0 && Promise.all(promises).then(images => {
let altText;
for (const image of images) {
altText = this.settings.imageAltText ? path.parse(image.name)?.name?.replaceAll("-", " ")?.replaceAll("_", " ") : '';
// console.log(`replacing ${image.source} with ![${altText}](${image.url})`);
value = value.replaceAll(image.source, `![${altText}](${image.url})`);
}
if (this.settings.replaceOriginalDoc) {
Expand Down
3 changes: 3 additions & 0 deletions src/uploader/imageUploaderBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import OssUploader from "./oss/ossUploader";
import ImagekitUploader from "./imagekit/imagekitUploader";
import AwsS3Uploader from "./s3/awsS3Uploader";
import CosUploader from "./cos/cosUploader";
import KodoUploader from "./qiniu/kodoUploader";

export default function buildUploader(settings: PublishSettings): ImageUploader {
switch (settings.imageStore) {
Expand All @@ -19,6 +20,8 @@ export default function buildUploader(settings: PublishSettings): ImageUploader
return new AwsS3Uploader(settings.awsS3Setting);
case ImageStore.TENCENTCLOUD_COS.id:
return new CosUploader(settings.cosSetting);
case ImageStore.QINIU_KUDO.id:
return new KodoUploader(settings.kodoSetting);
//todo more cases
default:
throw new Error('should not reach here!')
Expand Down
4 changes: 4 additions & 0 deletions src/uploader/oss/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ export const AliYunRegionList: Record<string, string> = {
"oss-eu-central-1": "Germany (Frankfurt) *",
"oss-eu-west-1": "UK (London)",
"oss-me-east-1": "UAE (Dubai) *"
}

export const QiniuZoneList: Record<string, string> = {

}
64 changes: 64 additions & 0 deletions src/uploader/qiniu/kodoUploader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import ImageUploader from "../imageUploader";
import qiniu from "qiniu";
import {UploaderUtils} from "../uploaderUtils";

export default class KodoUploader implements ImageUploader {

private uploadToken: string;
private tokenExpireTime: number;
private setting: KodoSetting;

constructor(setting: KodoSetting) {
this.setting = setting;
}

async upload(image: File, path: string): Promise<string> {
//check custom domain name
if (!this.setting.customDomainName || this.setting.customDomainName.trim() === "") {
throw new Error("Custom domain name is required for Qiniu Kodo.")
}
this.updateToken();
const config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z0;
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();
let key = UploaderUtils.generateName(this.setting.path, image.name.replaceAll(' ', '_')); //replace space with _ in file name
return formUploader
.putFile(this.uploadToken, key, path, putExtra)
.then(({data, resp}) => {
if (resp.statusCode === 200) {
return this.setting.customDomainName + '/' + data.key;
} else {
throw data;
}
})
.catch((err) => {
throw err
});
}

updateToken(): void {
if (this.tokenExpireTime && this.tokenExpireTime > Date.now()) {
return;
}
const mac = new qiniu.auth.digest.Mac(this.setting.accessKey, this.setting.secretKey);
const expires = 3600;
this.tokenExpireTime = Date.now() + expires * 1000;
const options = {
scope: this.setting.bucket,
expires: expires,
returnBody:
'{"key":"$(key)","hash":"$(etag)","bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}',
};
const putPolicy = new qiniu.rs.PutPolicy(options);
this.uploadToken = putPolicy.uploadToken(mac);
}
}

export interface KodoSetting {
accessKey: string;
secretKey: string;
bucket: string;
customDomainName: string;
path: string;
}

0 comments on commit adb46cb

Please sign in to comment.