Skip to content

Commit

Permalink
add support for AWS S3
Browse files Browse the repository at this point in the history
Signed-off-by: Addo.Zhang <[email protected]>
  • Loading branch information
addozhang committed Jul 27, 2024
1 parent e285eef commit 6616f79
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 46 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ and copy markdown with replaced image syntax to clipboard with notification.
- [ ] support uploading images to more storages
- [x] Aliyun Oss
- [x] ImageKit
- [ ] Amazon S3
- [x] Amazon S3
- [ ]
- [x] setting for replacing images embedded in origin markdown directly

## Contributing
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.5.2"
"version": "0.6.0"
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-image-upload-toolkit",
"version": "0.5.2",
"version": "0.6.0",
"description": "",
"author": "addozhang",
"main": "main.js",
Expand All @@ -17,6 +17,7 @@
},
"dependencies": {
"ali-oss": "^6.17.1",
"aws-sdk": "^2.1664.0",
"imagekit": "^5.0.0",
"proxy-agent": "^5.0.0"
}
Expand Down
5 changes: 5 additions & 0 deletions src/imageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export default class ImageStore {
"Imagekit"
);

static readonly AWS_S3 = new ImageStore(
"AWS_S3",
"AWS S3"
)

private constructor(readonly id: string, readonly description: string) {
ImageStore.values.push(this)
}
Expand Down
10 changes: 10 additions & 0 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import buildUploader from "./uploader/imageUploaderBuilder";
import PublishSettingTab from "./ui/publishSettingTab";
import {OssSetting} from "./uploader/oss/ossUploader";
import {ImagekitSetting} from "./uploader/imagekit/imagekitUploader";
import {AwsS3Setting} from "./uploader/s3/awsS3Uploader";

export interface PublishSettings {
imageAltText: boolean;
Expand All @@ -23,6 +24,7 @@ export interface PublishSettings {
imgurAnonymousSetting: ImgurAnonymousSetting;
ossSetting: OssSetting;
imagekitSetting: ImagekitSetting;
awsS3Setting: AwsS3Setting;
}

const DEFAULT_SETTINGS: PublishSettings = {
Expand All @@ -47,6 +49,14 @@ const DEFAULT_SETTINGS: PublishSettings = {
privateKey: "",
publicKey: "",
folder: "",
},
awsS3Setting: {
accessKeyId: "",
secretAccessKey: "",
region: "",
bucketName: "",
path: "",
customDomainName: "",
}
};
export default class ObsidianPublish extends Plugin {
Expand Down
61 changes: 59 additions & 2 deletions src/ui/publishSettingTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ export default class PublishSettingTab extends PluginSettingTab {
this.drawOSSSetting(partentEL);
break;
case ImageStore.ImageKit.id:
this.drawImageKitSetting(partentEL)
break
this.drawImageKitSetting(partentEL);
break;
case ImageStore.AWS_S3.id:
this.drawAwsS3Setting(partentEL);
break;
default:
throw new Error(
"Should not reach here!"
Expand Down Expand Up @@ -241,4 +244,58 @@ export default class PublishSettingTab extends PluginSettingTab {
fragment.append(a);
return fragment;
}

private drawAwsS3Setting(parentEL: HTMLDivElement) {
// Add AWS S3 configuration section
new Setting(parentEL)
.setName('AWS S3 Access Key ID')
.setDesc('Your AWS S3 access key ID')
.addText(text => text
.setPlaceholder('Enter your access key ID')
.setValue(this.plugin.settings.awsS3Setting?.accessKeyId || '')
.onChange(value => this.plugin.settings.awsS3Setting.accessKeyId = value
));

new Setting(parentEL)
.setName('AWS S3 Secret Access Key')
.setDesc('Your AWS S3 secret access key')
.addText(text => text
.setPlaceholder('Enter your secret access key')
.setValue(this.plugin.settings.awsS3Setting?.secretAccessKey || '')
.onChange(value => this.plugin.settings.awsS3Setting.secretAccessKey = value));

new Setting(parentEL)
.setName('AWS S3 Region')
.setDesc('Your AWS S3 region')
.addText(text => text
.setPlaceholder('Enter your region')
.setValue(this.plugin.settings.awsS3Setting?.region || '')
.onChange(value => this.plugin.settings.awsS3Setting.region = value));

new Setting(parentEL)
.setName('AWS S3 Bucket Name')
.setDesc('Your AWS S3 bucket name')
.addText(text => text
.setPlaceholder('Enter your bucket name')
.setValue(this.plugin.settings.awsS3Setting?.bucketName || '')
.onChange(value => this.plugin.settings.awsS3Setting.bucketName = 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.ossSetting.path)
.onChange(value => this.plugin.settings.awsS3Setting.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.ossSetting.customDomainName)
.onChange(value => this.plugin.settings.awsS3Setting.customDomainName = value))
}
}
3 changes: 3 additions & 0 deletions src/uploader/imageUploaderBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ImageStore from "../imageStore";
import ImgurAnonymousUploader from "./imgur/imgurAnonymousUploader";
import OssUploader from "./oss/ossUploader";
import ImagekitUploader from "./imagekit/imagekitUploader";
import AwsS3Uploader from "./s3/awsS3Uploader";

export default function buildUploader(settings: PublishSettings): ImageUploader {
switch (settings.imageStore) {
Expand All @@ -13,6 +14,8 @@ export default function buildUploader(settings: PublishSettings): ImageUploader
return new OssUploader(settings.ossSetting);
case ImageStore.ImageKit.id:
return new ImagekitUploader(settings.imagekitSetting);
case ImageStore.AWS_S3.id:
return new AwsS3Uploader(settings.awsS3Setting);
//todo more cases
default:
throw new Error('should not reach here!')
Expand Down
44 changes: 3 additions & 41 deletions src/uploader/oss/ossUploader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ImageUploader from "../imageUploader";
import {UploaderUtils} from "../uploaderUtils";
import OSS from "ali-oss"

export default class OssUploader implements ImageUploader {
Expand All @@ -21,47 +22,8 @@ export default class OssUploader implements ImageUploader {
}

async upload(image: File, path: string): Promise<string> {
const result = this.client.put(this.generateName(image.name), path);
return this.customizeDomainName((await result).url);
}

private generateName(imageName: string): string {
const date = new Date();
const year = date.getFullYear().toString();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const random = this.generateRandomString(20);

return this.pathTmpl != undefined && this.pathTmpl.trim().length > 0 ? this.pathTmpl
.replace('{year}', year)
.replace('{mon}', month)
.replace('{day}', day)
.replace('{random}', random)
.replace('{filename}', imageName)
: imageName
;
}

private generateRandomString(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';

for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}

return result;
}

private customizeDomainName(url) {
const regex = /https?:\/\/([^/]+)/;
if (this.customDomainName && this.customDomainName.trim() !== "") {
return url.replace(regex, (match, domain) => {
return match.replace(domain, this.customDomainName);
})
}
return url;
const result = this.client.put(UploaderUtils.generateName(this.pathTmpl, image.name), path);
return UploaderUtils.customizeDomainName((await result).url, this.customDomainName);
}

}
Expand Down
61 changes: 61 additions & 0 deletions src/uploader/s3/awsS3Uploader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import ImageUploader from "../imageUploader";
import AWS from 'aws-sdk';
import {UploaderUtils} from "../uploaderUtils";

export default class AwsS3Uploader implements ImageUploader {
private readonly s3!: AWS.S3;
private readonly bucket!: string;
private pathTmpl: string;
private customDomainName: string;


constructor(setting: AwsS3Setting) {
this.s3 = new AWS.S3({
accessKeyId: setting.accessKeyId,
secretAccessKey: setting.secretAccessKey,
region: setting.region
});
this.bucket = setting.bucketName;
this.pathTmpl = setting.path;
this.customDomainName = setting.customDomainName;
}

async upload(image: File, fullPath: string): Promise<string> {
const arrayBuffer = await this.readFileAsArrayBuffer(image);
const uint8Array = new Uint8Array(arrayBuffer);
var path = UploaderUtils.generateName(this.pathTmpl, image.name);
path = path.replace(/^\/+/, ''); // remove the /
const params = {
Bucket: this.bucket,
Key: path,
Body: uint8Array,
};
return new Promise((resolve, reject) => {
this.s3.upload(params, (err, data) => {
if (err) {
console.log(err)
reject(err);
} else {
resolve(UploaderUtils.customizeDomainName(data.Location, this.customDomainName));
}
});
});
}

private readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
}
export interface AwsS3Setting {
accessKeyId: string;
secretAccessKey: string;
region: string;
bucketName: string;
path: string;
customDomainName: string;
}
40 changes: 40 additions & 0 deletions src/uploader/uploaderUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export class UploaderUtils {
static generateName(pathTmpl,imageName: string): string {
const date = new Date();
const year = date.getFullYear().toString();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const random = this.generateRandomString(20);

return pathTmpl != undefined && pathTmpl.trim().length > 0 ? pathTmpl
.replace('{year}', year)
.replace('{mon}', month)
.replace('{day}', day)
.replace('{random}', random)
.replace('{filename}', imageName)
: imageName
;
}

private static generateRandomString(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';

for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}

return result;
}

static customizeDomainName(url, customDomainName) {
const regex = /https?:\/\/([^/]+)/;
if (customDomainName && customDomainName.trim() !== "") {
return url.replace(regex, (match, domain) => {
return match.replace(domain, customDomainName);
})
}
return url;
}
}

0 comments on commit 6616f79

Please sign in to comment.