+ The script will be executed with the image file path as its only argument and it should output any valid value for the src
attribute of a HTMLImageElement.
+
+
+ Save
+ Set as default
+
+
@@ -62,6 +75,7 @@
import { shell } from 'electron'
import services, { isValidService } from './services.js'
import legalNoticesCheckbox from './legalNoticesCheckbox'
+import { isFileExecutable } from '../../util/fileSystem'
export default {
components: {
@@ -76,6 +90,7 @@ export default {
repo: '',
branch: ''
},
+ cliScript: '',
uploadServices: services,
legalNoticesErrorStates: {
smms: false,
@@ -99,8 +114,19 @@ export default {
return this.$store.state.preferences.githubToken
}
},
+ prefCliScript: {
+ get: function () {
+ return this.$store.state.preferences.cliScript
+ }
+ },
githubDisable () {
return !this.githubToken || !this.github.owner || !this.github.repo
+ },
+ cliScriptDisable () {
+ if (!this.cliScript) {
+ return true
+ }
+ return !isFileExecutable(this.cliScript)
}
},
watch: {
@@ -114,6 +140,7 @@ export default {
this.$nextTick(() => {
this.github = this.imageBed.github
this.githubToken = this.prefGithubToken
+ this.cliScript = this.prefCliScript
if (services.hasOwnProperty(this.currentUploader)) {
services[this.currentUploader].agreedToLegalNotices = true
@@ -143,11 +170,22 @@ export default {
value: this.githubToken
})
}
- if (withNotice) {
+ if (type === 'cliScript') {
+ this.$store.dispatch('SET_USER_DATA', {
+ type: 'cliScript',
+ value: this.cliScript
+ })
+ }
+ if (withNotice && type === 'github') {
new Notification('Save Image Uploader', {
body: 'The Github configration has been saved.'
})
}
+ if (withNotice && type === 'cliScript') {
+ new Notification('Save Image Uploader', {
+ body: 'The command line script configuration has been saved'
+ })
+ }
},
setCurrentUploader (value) {
const service = services[value]
@@ -162,10 +200,12 @@ export default {
return
}
// Save the setting before set it as default uploader.
- if (value === 'github') {
+ if (value === 'github' || value === 'cliScript') {
this.save(value, false)
}
- this.legalNoticesErrorStates[value] = false
+ if (this.legalNoticesErrorStates[value] !== undefined) {
+ this.legalNoticesErrorStates[value] = false
+ }
const type = 'currentUploader'
this.$store.dispatch('SET_USER_DATA', { type, value })
diff --git a/src/renderer/prefComponents/imageUploader/services.js b/src/renderer/prefComponents/imageUploader/services.js
index a733cc28..9b824728 100644
--- a/src/renderer/prefComponents/imageUploader/services.js
+++ b/src/renderer/prefComponents/imageUploader/services.js
@@ -35,6 +35,14 @@ const services = {
// Currently a non-persistent value
agreedToLegalNotices: false
+ },
+
+ cliScript: {
+ name: 'command line script',
+ isGdprCompliant: true,
+ privacyUrl: '',
+ tosUrl: '',
+ agreedToLegalNotices: true
}
}
diff --git a/src/renderer/store/preferences.js b/src/renderer/store/preferences.js
index 25259719..fb5aeb41 100644
--- a/src/renderer/store/preferences.js
+++ b/src/renderer/store/preferences.js
@@ -94,7 +94,8 @@ const state = {
repo: '',
branch: ''
}
- }
+ },
+ cliScript: ''
}
const getters = {}
diff --git a/src/renderer/util/fileSystem.js b/src/renderer/util/fileSystem.js
index ef36a84b..18d5ae26 100644
--- a/src/renderer/util/fileSystem.js
+++ b/src/renderer/util/fileSystem.js
@@ -6,6 +6,9 @@ import dayjs from 'dayjs'
import { Octokit } from '@octokit/rest'
import { ensureDirSync } from 'common/filesystem'
import { isImageFile } from 'common/filesystem/paths'
+import cp from 'child_process'
+import { tmpdir } from 'os'
+import { unlink, writeFileSync, statSync, constants } from 'fs'
import { isWindows, dataURItoBlob } from './index'
import axios from '../axios'
@@ -104,6 +107,7 @@ export const uploadImage = async (pathname, image, preferences) => {
const { currentUploader } = preferences
const { owner, repo, branch } = preferences.imageBed.github
const token = preferences.githubToken
+ const cliScript = preferences.cliScript
const isPath = typeof image === 'string'
const MAX_SIZE = 5 * 1024 * 1024
let re
@@ -164,6 +168,25 @@ export const uploadImage = async (pathname, image, preferences) => {
})
}
+ const uploadByCliScript = (filepath, name = null) => {
+ let isPath = true
+ if (typeof filepath !== 'string') {
+ isPath = false
+ const data = new Uint8Array(filepath)
+ filepath = path.join(tmpdir(), name || +new Date())
+ writeFileSync(filepath, data)
+ }
+ cp.execFile(cliScript, [filepath], (err, data) => {
+ if (!isPath) {
+ unlink(filepath)
+ }
+ if (err) {
+ return rj(err)
+ }
+ re(data.trim())
+ })
+ }
+
const notification = () => {
rj('Cannot upload more than 5M image, the image will be copied to the image folder')
}
@@ -177,6 +200,10 @@ export const uploadImage = async (pathname, image, preferences) => {
if (size > MAX_SIZE) {
notification()
} else {
+ if (currentUploader === 'cliScript') {
+ uploadByCliScript(imagePath)
+ return promise
+ }
const imageFile = await fs.readFile(imagePath)
const blobFile = new Blob([imageFile])
if (currentUploader === 'smms') {
@@ -196,17 +223,34 @@ export const uploadImage = async (pathname, image, preferences) => {
} else {
const reader = new FileReader()
reader.onload = async () => {
- const blobFile = dataURItoBlob(reader.result, image.name)
- if (currentUploader === 'smms') {
- uploadToSMMS(blobFile)
- } else {
- uploadByGithub(reader.result, image.name)
+ switch (currentUploader) {
+ case 'cliScript':
+ uploadByCliScript(reader.result, image.name)
+ break
+
+ case 'smms':
+ uploadToSMMS(dataURItoBlob(reader.result, image.name))
+ break
+
+ default:
+ uploadByGithub(reader.result, image.name)
}
}
- reader.readAsDataURL(image)
+ const readerFunction = currentUploader === 'cliScript' ? 'readAsArrayBuffer' : 'readAsDataURL'
+ reader[readerFunction](image)
}
}
return promise
}
+
+export const isFileExecutable = (filepath) => {
+ try {
+ const stat = statSync(filepath)
+ return stat.isFile() && (stat.mode & (constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH)) !== 0
+ } catch (err) {
+ // err ignored
+ return false
+ }
+}