diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/docs/assets/obsidian-settings-page.png b/docs/assets/obsidian-settings-page.png new file mode 100644 index 0000000..ee0577f Binary files /dev/null and b/docs/assets/obsidian-settings-page.png differ diff --git a/manifest.json b/manifest.json old mode 100755 new mode 100644 index 5788fbe..2c19d1a --- a/manifest.json +++ b/manifest.json @@ -8,4 +8,4 @@ "authorUrl": "https://lestua.eu.org", "isDesktopOnly": true, "fundingUrl": "https://lestua.eu.org/donate/" -} \ No newline at end of file +} diff --git a/src/config.ts b/src/config.ts index 0f8551a..f100736 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ import { ImgbbParms, IMGBB_DEFAULT_PARMS } from './parms/parms-imgbb' import { ImgurParms, IMGUR_DEFAULT_PARMS } from './parms/parms-imgur' import { ImgurlParms, IMGURL_DEFAULT_PARMS } from './parms/parms-imgurl' import { SmmsParms, SMMS_DEFAULT_PARMS } from './parms/parms-smms' +import { ALIST_DEFAULT_PARMS, AlistParms } from './parms/parms-alist' export interface Config { // data from data.json choice: HostingProvider @@ -17,6 +18,7 @@ export interface Config { // data from data.json imgur_parms: ImgurParms catbox_parms: CatboxParms chevereto_parms: CheveretoParms + alist_parms: AlistParms } export enum HostingProvider { // target hosting @@ -27,7 +29,8 @@ export enum HostingProvider { // target hosting Smms = 'SM.MS', ImgURL = 'ImgURL', Imgbb = 'imgbb', - Chevereto = 'chevereto' + Chevereto = 'chevereto', + Alist = 'alist' } export const DEFAULT_SETTINGS: Config = { @@ -39,5 +42,6 @@ export const DEFAULT_SETTINGS: Config = { imgurl_parms: IMGURL_DEFAULT_PARMS, imgbb_parms: IMGBB_DEFAULT_PARMS, catbox_parms: CATBOX_DEFAULT_PARMS, - chevereto_parms: CHEVERETO_DEFAULT_PARMS + chevereto_parms: CHEVERETO_DEFAULT_PARMS, + alist_parms: ALIST_DEFAULT_PARMS } diff --git a/src/fragment/fragment-alist.ts b/src/fragment/fragment-alist.ts new file mode 100644 index 0000000..9e4bcfc --- /dev/null +++ b/src/fragment/fragment-alist.ts @@ -0,0 +1,61 @@ +import { Setting } from 'obsidian' +import Emo from '../main' +import { EmoFragment } from '../base/emo-fragment' +import { HostingProvider } from '../config' +import { t } from '../lang/helpers' + +export class AlistFragment extends EmoFragment { + constructor (el: HTMLElement, plugin: Emo) { + super(HostingProvider.Alist, el, plugin) + } + + display (el: HTMLElement, plugin: Emo): void { + const parms = plugin.config.alist_parms + el.createEl('h3', { text: 'Alist Settings'}) + + new Setting(el) + .setName(t('domain')) + .addText((text) => { + text + .setValue(parms.required.domain) + .onChange(async (value) => { + parms.required.domain = value + await plugin.saveSettings() + }) + }) + + new Setting(el) + .setName('username') + .addText((text) => { + text + .setValue(parms.required.username) + .onChange(async (value) => { + parms.required.username = value + await plugin.saveSettings() + }) + }) + + new Setting(el) + .setName('password') + .addText((text) => { + text + .setValue(parms.required.password) + .onChange(async (value) => { + parms.required.password = value + await plugin.saveSettings() + }) + }) + + new Setting(el) + .setName('uploadPath') + .addText((text) => { + text + .setValue(parms.required.uploadPath) + .onChange(async (value) => { + parms.required.uploadPath = value + await plugin.saveSettings() + }) + }) + + } +} diff --git a/src/main.ts b/src/main.ts old mode 100755 new mode 100644 index 871e862..2bf0ea7 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,7 @@ import { ImgbbUploader } from './uploader/uploader-imgbb' import { ImgurUploader } from './uploader/uploader-imgur' import { CatboxUploader } from './uploader/uploader-catbox' import { CheveretoUploader } from './uploader/uploader-chevereto' +import { AlistUploader } from './uploader/uploader-alist' export default class Emo extends Plugin { config!: Config @@ -83,6 +84,9 @@ export default class Emo extends Plugin { case HostingProvider.Chevereto: uploader = new CheveretoUploader(this.config.chevereto_parms) break + case HostingProvider.Alist: + uploader = new AlistUploader(this.config.alist_parms) + break default: console.log(new Notice(t('broken'), 2000)) return diff --git a/src/parms/parms-alist.ts b/src/parms/parms-alist.ts new file mode 100644 index 0000000..388e18e --- /dev/null +++ b/src/parms/parms-alist.ts @@ -0,0 +1,22 @@ +import { EmoParms } from '../base/emo-parms' + +export interface AlistParms extends EmoParms { + required: Required +} + +interface Required { + domain: string + username: string + password: string + uploadPath: string +} + +export const ALIST_DEFAULT_PARMS: AlistParms = { + required: { + domain: 'https://alist.example.com', + username: '', + password: '', + uploadPath: '上传的相对路径', + } +} + diff --git a/src/settings-tab.ts b/src/settings-tab.ts old mode 100755 new mode 100644 index e69a3a9..ad24137 --- a/src/settings-tab.ts +++ b/src/settings-tab.ts @@ -17,6 +17,7 @@ import { ImgurFragment } from './fragment/fragment-imgur' import { CatboxFragment } from './fragment/fragment-catbox' import { IMGUR_ACCESS_TOKEN_LOCALSTORAGE_KEY } from './base/constants' import { CheveretoFragment } from './fragment/fragment-chevereto' +import { AlistFragment } from './fragment/fragment-alist' export class EmoUploaderSettingTab extends PluginSettingTab { private readonly plugin: Emo @@ -54,6 +55,7 @@ export class EmoUploaderSettingTab extends PluginSettingTab { fragmentList.push(new ImgbbFragment(containerEl, this.plugin)) fragmentList.push(new CatboxFragment(containerEl, this.plugin)) fragmentList.push(new CheveretoFragment(containerEl, this.plugin)) + fragmentList.push(new AlistFragment(containerEl, this.plugin)) // which one will show at the first time fragmentList.forEach(element => { diff --git a/src/uploader/uploader-alist.ts b/src/uploader/uploader-alist.ts new file mode 100644 index 0000000..105f5f6 --- /dev/null +++ b/src/uploader/uploader-alist.ts @@ -0,0 +1,205 @@ +// 第一部分 +import { request, RequestUrlParam } from 'obsidian' +import { EmoFormData } from '../utils/emo-formdata' +import { EmoUploader } from '../base/emo-uploader' +import { CONTENT_TYPE_FORMDATA } from '../base/constants' +import { AlistParms } from '../parms/parms-alist' +import { sha256 } from 'js-sha256' + +export class AlistUploader extends EmoUploader { + parms!: AlistParms + constructor (alistParms: AlistParms) { + super() + this.parms = alistParms + } + + + async upload (file: File): Promise { + // 获取token + const token = await this.getToken() + // 新建文件夹 + const determine = await this.determine(file) + if (determine != 'success') { + await this.mkdirFile(file) + } + // 上传文件 + await this.putFile(file) + // 刷新列表 + await this.refreshDir(file) + // 重命名文件 + let newName = file.name + if (file.name == 'image.png'){ + newName = await this.renameFile(file) + } + + const extension = await this.getFileExtension(file) + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/get`, + method: 'POST', + headers: { + 'Authorization': token , + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: `/${this.parms.required.uploadPath}/${extension}/${newName}` + }) + } + // 发送请求并返回结果 + return await new Promise((resolve, reject) => { + request(req).then(async res => { + const json = JSON.parse(res) + // 赋给markdownText + const markdownText = `![${newName}](${json.data.raw_url})` + resolve(markdownText) + }).catch(err => { + reject(err) + }) + }) + } + + //第二部分 + //获取token + async getToken (): Promise { + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/auth/login/hash`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + username: this.parms.required.username, + password: sha256(this.parms.required.password + '-https://github.com/alist-org/alist') + }) + } + // 发送请求并返回token + return await new Promise((resolve, reject) => { + request(req).then(res => { + const json = JSON.parse(res) + resolve(json.data.token as string) + }).catch(err => { + reject(err) + }) + }) + } + + + //文件类型 + async getFileExtension(file: File): Promise { + const filename = file.name + const match = filename.match(/\.([^.]+)$/) + return match ? match[1] : "" + } + + //判断文件夹是否存在 + async determine (file: File): Promise { + const extension = await this.getFileExtension(file) + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/get`, + method: 'POST', + headers: { + 'Authorization': await this.getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: `/${this.parms.required.uploadPath}/${extension}` + }) + } + return await new Promise((resolve, reject) => { + request(req).then(res => { + const json = JSON.parse(res) + resolve(json.message as string) + }).catch(err => { + reject(err) + }) + }) + } + + async mkdirFile (file: File): Promise { + const extension = await this.getFileExtension(file) + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/mkdir`, + method: 'POST', + headers: { + 'Authorization': await this.getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: `/${this.parms.required.uploadPath}/${extension}`, + }) + } + await request(req) // 发送请求并返回链接 + } + + + + // 上传文件 + async putFile (file: File): Promise { + const extension = await this.getFileExtension(file) + const formData = new EmoFormData() + await formData.add('file', file) + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/form`, + method: 'PUT', + headers: { + 'Authorization': await this.getToken(), + 'Content-Type': CONTENT_TYPE_FORMDATA, + 'File-Path': encodeURIComponent(`/${this.parms.required.uploadPath}/${extension}/${file.name}`), + 'As-Task': 'true' + }, + body: formData.getBody() + } + await request(req) // 发送请求并返回链接 + } + + //第三部分 + // 刷新目录 + async refreshDir (file: File): Promise { + const extension = await this.getFileExtension(file) + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/list`, + method: 'POST', + headers: { + 'Authorization': await this.getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: `/${this.parms.required.uploadPath}/${extension}`, + page: 1, + password: "", + per_page: 0, + refresh: true, + }) + } + await request(req) + + } + + // 重命名文件 + async renameFile (file: File): Promise { + const extension = await this.getFileExtension(file) + // 获取当前时间 + const now = new Date() + // 格式化时间 + const formatTime = (n: number) => n.toString().padStart(2, '0') + // 生成新文件名 + const newName = `${now.getFullYear()}${formatTime(now.getMonth() + 1)}${formatTime(now.getDate())}-${formatTime(now.getHours())}:${formatTime(now.getMinutes())}:${formatTime(now.getSeconds())}.${extension}` + // 构造请求参数 + const req: RequestUrlParam = { + url: `${this.parms.required.domain}/api/fs/rename`, + method: 'POST', + headers: { + 'Authorization': await this.getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: newName, + path: `/${this.parms.required.uploadPath}/${extension}/${file.name}` + }) + } + // 发送请求 + await request(req) + return newName + } + +} +