feat(client): AiScriptプラグインからAPIアクセスできるように

This commit is contained in:
syuilo 2020-07-18 14:28:32 +09:00
parent b9c5e95b85
commit b39850de01
5 changed files with 107 additions and 20 deletions

View File

@ -533,6 +533,8 @@ generateAccessToken: "アクセストークンの発行"
permission: "権限" permission: "権限"
enableAll: "全て有効にする" enableAll: "全て有効にする"
disableAll: "全て無効にする" disableAll: "全て無効にする"
tokenRequested: "アカウントへのアクセス許可"
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を行使できるようになります。"
_theme: _theme:
explore: "テーマを探す" explore: "テーマを探す"

View File

@ -2,6 +2,9 @@
<x-window ref="window" :width="400" :height="450" :no-padding="true" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="false" @ok="ok()" :can-close="false"> <x-window ref="window" :width="400" :height="450" :no-padding="true" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="false" @ok="ok()" :can-close="false">
<template #header>{{ title || $t('generateAccessToken') }}</template> <template #header>{{ title || $t('generateAccessToken') }}</template>
<div class="ugkkpisj"> <div class="ugkkpisj">
<div>
<mk-info warn v-if="information">{{ information }}</mk-info>
</div>
<div> <div>
<mk-input v-model="name">{{ $t('name') }}</mk-input> <mk-input v-model="name">{{ $t('name') }}</mk-input>
</div> </div>
@ -9,7 +12,7 @@
<div style="margin-bottom: 16px;"><b>{{ $t('permission') }}</b></div> <div style="margin-bottom: 16px;"><b>{{ $t('permission') }}</b></div>
<mk-button inline @click="disableAll">{{ $t('disableAll') }}</mk-button> <mk-button inline @click="disableAll">{{ $t('disableAll') }}</mk-button>
<mk-button inline @click="enableAll">{{ $t('enableAll') }}</mk-button> <mk-button inline @click="enableAll">{{ $t('enableAll') }}</mk-button>
<mk-switch v-for="kind in kinds" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</mk-switch> <mk-switch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</mk-switch>
</div> </div>
</div> </div>
</x-window> </x-window>
@ -23,6 +26,7 @@ import MkInput from './ui/input.vue';
import MkTextarea from './ui/textarea.vue'; import MkTextarea from './ui/textarea.vue';
import MkSwitch from './ui/switch.vue'; import MkSwitch from './ui/switch.vue';
import MkButton from './ui/button.vue'; import MkButton from './ui/button.vue';
import MkInfo from './ui/info.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -31,6 +35,7 @@ export default Vue.extend({
MkTextarea, MkTextarea,
MkSwitch, MkSwitch,
MkButton, MkButton,
MkInfo,
}, },
props: { props: {
@ -38,21 +43,42 @@ export default Vue.extend({
type: String, type: String,
required: false, required: false,
default: null default: null
},
information: {
type: String,
required: false,
default: null
},
initialName: {
type: String,
required: false,
default: null
},
initialPermissions: {
type: Array,
required: false,
default: null
} }
}, },
data() { data() {
return { return {
name: null, name: this.initialName,
permissions: {}, permissions: {},
kinds kinds
}; };
}, },
created() { created() {
if (this.initialPermissions) {
for (const kind of this.initialPermissions) {
Vue.set(this.permissions, kind, true);
}
} else {
for (const kind of this.kinds) { for (const kind of this.kinds) {
Vue.set(this.permissions, kind, false); Vue.set(this.permissions, kind, false);
} }
}
}, },
methods: { methods: {

View File

@ -30,7 +30,10 @@
<div>{{ $t('description') }}:</div> <div>{{ $t('description') }}:</div>
<div>{{ selectedPlugin.description }}</div> <div>{{ selectedPlugin.description }}</div>
</div> </div>
<mk-button @click="uninstall()" style="margin-top: 8px;"><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button> <div style="margin-top: 8px;">
<mk-button @click="config()" inline v-if="selectedPlugin.config"><fa :icon="faCog"/> {{ $t('settings') }}</mk-button>
<mk-button @click="uninstall()" inline><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button>
</div>
</template> </template>
</details> </details>
</div> </div>
@ -39,7 +42,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload } from '@fortawesome/free-solid-svg-icons'; import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog } from '@fortawesome/free-solid-svg-icons';
import MkButton from '../../components/ui/button.vue'; import MkButton from '../../components/ui/button.vue';
import MkTextarea from '../../components/ui/textarea.vue'; import MkTextarea from '../../components/ui/textarea.vue';
import MkSelect from '../../components/ui/select.vue'; import MkSelect from '../../components/ui/select.vue';
@ -58,7 +61,7 @@ export default Vue.extend({
return { return {
script: '', script: '',
selectedPluginId: null, selectedPluginId: null,
faPlug, faSave, faTrashAlt, faFolderOpen, faDownload faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog
} }
}, },
@ -70,7 +73,7 @@ export default Vue.extend({
}, },
methods: { methods: {
install() { async install() {
let ast; let ast;
try { try {
ast = parse(this.script); ast = parse(this.script);
@ -82,7 +85,6 @@ export default Vue.extend({
return; return;
} }
const meta = AiScript.collectMetadata(ast); const meta = AiScript.collectMetadata(ast);
console.log(meta);
if (meta == null) { if (meta == null) {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
@ -98,7 +100,7 @@ export default Vue.extend({
}); });
return; return;
} }
const { id, name, version, author, description } = data; const { id, name, version, author, description, permissions, config } = data;
if (id == null || name == null || version == null || author == null) { if (id == null || name == null || version == null || author == null) {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
@ -106,16 +108,40 @@ export default Vue.extend({
}); });
return; return;
} }
const token = permissions == null || permissions.length === 0 ? null : await new Promise(async (res, rej) => {
this.$root.new(await import('../../components/token-generate-window.vue').then(m => m.default), {
title: this.$t('tokenRequested'),
information: this.$t('pluginTokenRequestedDescription'),
initialName: name,
initialPermissions: permissions
}).$on('ok', async ({ name, permissions }) => {
const { token } = await this.$root.api('miauth/gen-token', {
session: null,
name: name,
permission: permissions,
});
res(token);
});
});
this.$store.commit('deviceUser/installPlugin', { this.$store.commit('deviceUser/installPlugin', {
meta: { meta: {
id, name, version, author, description id, name, version, author, description, permissions, config
}, },
ast token,
ast // TODO: astMapMapJSON
}); });
this.$root.dialog({ this.$root.dialog({
type: 'success', type: 'success',
iconOnly: true, autoClose: true iconOnly: true, autoClose: true
}); });
this.$nextTick(() => {
location.reload();
});
}, },
uninstall() { uninstall() {
@ -124,6 +150,29 @@ export default Vue.extend({
type: 'success', type: 'success',
iconOnly: true, autoClose: true iconOnly: true, autoClose: true
}); });
this.$nextTick(() => {
location.reload();
});
},
// TODO: storeactionAiScriptAPI
async config() {
const config = this.selectedPlugin.config;
for (const key in this.selectedPlugin.configData) {
config[key].default = this.selectedPlugin.configData[key];
}
const { canceled, result } = await this.$root.form(this.selectedPlugin.name, config);
if (canceled) return;
this.$store.commit('deviceUser/configPlugin', {
id: this.selectedPluginId,
config: result
});
this.$nextTick(() => {
location.reload();
});
} }
}, },
}); });

View File

@ -1,4 +1,5 @@
import { utils, values } from '@syuilo/aiscript'; import { utils, values } from '@syuilo/aiscript';
import { jsToVal } from '@syuilo/aiscript/built/interpreter/util';
export function createAiScriptEnv(vm, opts) { export function createAiScriptEnv(vm, opts) {
let apiRequests = 0; let apiRequests = 0;
@ -26,7 +27,7 @@ export function createAiScriptEnv(vm, opts) {
if (token) utils.assertString(token); if (token) utils.assertString(token);
apiRequests++; apiRequests++;
if (apiRequests > 16) return values.NULL; if (apiRequests > 16) return values.NULL;
const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : null); const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : (opts.token || null));
return utils.jsToVal(res); return utils.jsToVal(res);
}), }),
'Mk:save': values.FN_NATIVE(([key, value]) => { 'Mk:save': values.FN_NATIVE(([key, value]) => {
@ -42,8 +43,14 @@ export function createAiScriptEnv(vm, opts) {
} }
export function createPluginEnv(vm, opts) { export function createPluginEnv(vm, opts) {
const config = new Map();
for (const key in opts.plugin.config) {
const val = opts.plugin.configData[key] || opts.plugin.config[key].default;
config.set(key, jsToVal(val));
}
return { return {
...createAiScriptEnv(vm, opts), ...createAiScriptEnv(vm, { ...opts, token: opts.plugin.token }),
'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => { 'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler }); vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler });
}), }),
@ -53,5 +60,6 @@ export function createPluginEnv(vm, opts) {
'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => { 'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => {
vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler }); vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler });
}), }),
'Plugin:config': values.OBJ(config),
}; };
} }

View File

@ -587,13 +587,11 @@ export default () => new Vuex.Store({
}, },
//#endregion //#endregion
installPlugin(state, { meta, ast }) { installPlugin(state, { meta, ast, token }) {
state.plugins.push({ state.plugins.push({
id: meta.id, ...meta,
name: meta.name, configData: {},
version: meta.version, token: token,
author: meta.author,
description: meta.description,
ast: ast ast: ast
}); });
}, },
@ -601,6 +599,10 @@ export default () => new Vuex.Store({
uninstallPlugin(state, id) { uninstallPlugin(state, id) {
state.plugins = state.plugins.filter(x => x.id != id); state.plugins = state.plugins.filter(x => x.id != id);
}, },
configPlugin(state, { id, config }) {
state.plugins.find(p => p.id === id).configData = config;
},
} }
}, },