1
0
forked from mirror/misskey
This commit is contained in:
syuilo 2020-04-13 03:23:23 +09:00
parent 36b9a0d42f
commit 11cc9cbc7c
28 changed files with 195 additions and 63 deletions

View File

@ -481,6 +481,8 @@ descendingOrder: "降順"
scratchpad: "スクラッチパッド" scratchpad: "スクラッチパッド"
scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。" scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
output: "出力" output: "出力"
script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にする"
_theme: _theme:
explore: "テーマを探す" explore: "テーマを探す"
@ -813,6 +815,9 @@ _pages:
message: "押したときに表示するメッセージ" message: "押したときに表示するメッセージ"
variable: "送信する変数" variable: "送信する変数"
no-variable: "なし" no-variable: "なし"
callAiScript: "AiScript呼び出し"
_callAiScript:
functionName: "関数名"
radioButton: "選択肢" radioButton: "選択肢"
_radioButton: _radioButton:
@ -975,6 +980,7 @@ _pages:
_splitStrByLine: _splitStrByLine:
arg1: "テキスト" arg1: "テキスト"
ref: "変数" ref: "変数"
aiScriptVar: "AiScript変数"
fn: "関数" fn: "関数"
_fn: _fn:
slots: "スロット" slots: "スロット"

View File

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class pageAiScript1586708940386 implements MigrationInterface {
name = 'pageAiScript1586708940386'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "page" ADD "script" character varying(16384) NOT NULL DEFAULT ''`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "script"`, undefined);
}
}

View File

@ -42,7 +42,7 @@
"@koa/cors": "3.0.0", "@koa/cors": "3.0.0",
"@koa/multer": "2.0.2", "@koa/multer": "2.0.2",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@syuilo/aiscript": "0.1.2", "@syuilo/aiscript": "0.1.4",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.1", "@types/bull": "3.12.1",
"@types/cbor": "5.0.0", "@types/cbor": "5.0.0",

View File

@ -28,7 +28,7 @@ export default Vue.extend({
text: this.script.interpolate(this.value.content) text: this.script.interpolate(this.value.content)
}); });
} else if (this.value.action === 'resetRandom') { } else if (this.value.action === 'resetRandom') {
this.script.aiScript.updateRandomSeed(Math.random()); this.script.aoiScript.updateRandomSeed(Math.random());
this.script.eval(); this.script.eval();
} else if (this.value.action === 'pushEvent') { } else if (this.value.action === 'pushEvent') {
this.$root.api('page-push', { this.$root.api('page-push', {
@ -43,6 +43,8 @@ export default Vue.extend({
type: 'success', type: 'success',
text: this.script.interpolate(this.value.message) text: this.script.interpolate(this.value.message)
}); });
} else if (this.value.action === 'callAiScript') {
this.script.callAiScript(this.value.fn);
} }
} }
} }

View File

@ -27,7 +27,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
}, },

View File

@ -27,7 +27,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
} }

View File

@ -28,7 +28,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
} }

View File

@ -27,7 +27,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
} }

View File

@ -27,7 +27,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
} }

View File

@ -27,7 +27,7 @@ export default Vue.extend({
}, },
watch: { watch: {
v() { v() {
this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval(); this.script.eval();
} }
} }

View File

@ -6,30 +6,57 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../i18n'; import { AiScript, parse, values } from '@syuilo/aiscript';
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
import { faHeart } from '@fortawesome/free-regular-svg-icons'; import { faHeart } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../i18n';
import XBlock from './page.block.vue'; import XBlock from './page.block.vue';
import { ASEvaluator } from '../../scripts/aoiscript/evaluator'; import { ASEvaluator } from '../../scripts/aoiscript/evaluator';
import { collectPageVars } from '../../scripts/collect-page-vars'; import { collectPageVars } from '../../scripts/collect-page-vars';
import { url } from '../../config'; import { url } from '../../config';
class Script { class Script {
public aiScript: ASEvaluator; public aoiScript: ASEvaluator;
private onError: any; private onError: any;
public vars: Record<string, any>; public vars: Record<string, any>;
public page: Record<string, any>; public page: Record<string, any>;
constructor(page, aiScript, onError) { constructor(page, aoiScript, onError, cb) {
this.page = page; this.page = page;
this.aiScript = aiScript; this.aoiScript = aoiScript;
this.onError = onError; this.onError = onError;
this.eval();
if (this.page.script) {
let ast;
try {
ast = parse(this.page.script);
} catch (e) {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: 'Syntax error :('
});*/
return;
}
this.aoiScript.aiscript.exec(ast).then(() => {
this.eval();
cb();
}).catch(e => {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: e
});*/
});
} else {
this.eval();
cb();
}
} }
public eval() { public eval() {
try { try {
this.vars = this.aiScript.evaluateVars(); this.vars = this.aoiScript.evaluateVars();
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} }
@ -42,6 +69,10 @@ class Script {
return v == null ? 'NULL' : v.toString(); return v == null ? 'NULL' : v.toString();
}); });
} }
public callAiScript(fn: string) {
this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []);
}
} }
export default Vue.extend({ export default Vue.extend({
@ -67,14 +98,21 @@ export default Vue.extend({
created() { created() {
const pageVars = this.getPageVars(); const pageVars = this.getPageVars();
this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, {
const s = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, {
randomSeed: Math.random(), randomSeed: Math.random(),
visitor: this.$store.state.i, visitor: this.$store.state.i,
page: this.page, page: this.page,
url: url url: url
}), e => { }), e => {
console.dir(e); console.dir(e);
}, () => {
this.script = s;
}); });
s.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => {
s.eval();
};
}, },
methods: { methods: {

View File

@ -10,6 +10,7 @@
<option value="dialog">{{ $t('_pages.blocks._button._action.dialog') }}</option> <option value="dialog">{{ $t('_pages.blocks._button._action.dialog') }}</option>
<option value="resetRandom">{{ $t('_pages.blocks._button._action.resetRandom') }}</option> <option value="resetRandom">{{ $t('_pages.blocks._button._action.resetRandom') }}</option>
<option value="pushEvent">{{ $t('_pages.blocks._button._action.pushEvent') }}</option> <option value="pushEvent">{{ $t('_pages.blocks._button._action.pushEvent') }}</option>
<option value="callAiScript">{{ $t('_pages.blocks._button._action.callAiScript') }}</option>
</mk-select> </mk-select>
<template v-if="value.action === 'dialog'"> <template v-if="value.action === 'dialog'">
<mk-input v-model="value.content"><span>{{ $t('_pages.blocks._button._action._dialog.content') }}</span></mk-input> <mk-input v-model="value.content"><span>{{ $t('_pages.blocks._button._action._dialog.content') }}</span></mk-input>
@ -20,15 +21,18 @@
<mk-select v-model="value.var"> <mk-select v-model="value.var">
<template #label>{{ $t('_pages.blocks._button._action._pushEvent.variable') }}</template> <template #label>{{ $t('_pages.blocks._button._action._pushEvent.variable') }}</template>
<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option> <option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
<option v-for="v in aiScript.getVarsByType()" :value="v.name">{{ v.name }}</option> <option v-for="v in aoiScript.getVarsByType()" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.pageVariables')"> <optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aiScript.getPageVarsByType()" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getPageVarsByType()" :value="v">{{ v }}</option>
</optgroup> </optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')"> <optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aiScript.getEnvVarsByType()" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getEnvVarsByType()" :value="v">{{ v }}</option>
</optgroup> </optgroup>
</mk-select> </mk-select>
</template> </template>
<template v-else-if="value.action === 'callAiScript'">
<mk-input v-model="value.fn"><span>{{ $t('_pages.blocks._button._action._callAiScript.functionName') }}</span></mk-input>
</template>
</section> </section>
</x-container> </x-container>
</template> </template>
@ -53,7 +57,7 @@ export default Vue.extend({
value: { value: {
required: true required: true
}, },
aiScript: { aoiScript: {
required: true, required: true,
}, },
}, },
@ -72,6 +76,7 @@ export default Vue.extend({
if (this.value.message == null) Vue.set(this.value, 'message', null); if (this.value.message == null) Vue.set(this.value, 'message', null);
if (this.value.primary == null) Vue.set(this.value, 'primary', false); if (this.value.primary == null) Vue.set(this.value, 'primary', false);
if (this.value.var == null) Vue.set(this.value, 'var', null); if (this.value.var == null) Vue.set(this.value, 'var', null);
if (this.value.fn == null) Vue.set(this.value, 'fn', null);
}, },
}); });
</script> </script>

View File

@ -10,16 +10,16 @@
<section class="romcojzs"> <section class="romcojzs">
<mk-select v-model="value.var"> <mk-select v-model="value.var">
<template #label>{{ $t('_pages.blocks._if.variable') }}</template> <template #label>{{ $t('_pages.blocks._if.variable') }}</template>
<option v-for="v in aiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option> <option v-for="v in aoiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.pageVariables')"> <optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup> </optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')"> <optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup> </optgroup>
</mk-select> </mk-select>
<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/> <x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
</section> </section>
</x-container> </x-container>
</template> </template>
@ -45,7 +45,7 @@ export default Vue.extend({
value: { value: {
required: true required: true
}, },
aiScript: { aoiScript: {
required: true, required: true,
}, },
}, },

View File

@ -11,7 +11,7 @@
</template> </template>
<section class="ilrvjyvi"> <section class="ilrvjyvi">
<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/> <x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
</section> </section>
</x-container> </x-container>
</template> </template>
@ -37,7 +37,7 @@ export default Vue.extend({
value: { value: {
required: true required: true
}, },
aiScript: { aoiScript: {
required: true, required: true,
}, },
}, },

View File

@ -2,7 +2,7 @@
<x-container @remove="() => $emit('remove')" :draggable="true"> <x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faAlignLeft"/> {{ $t('_pages.blocks.text') }}</template> <template #header><fa :icon="faAlignLeft"/> {{ $t('_pages.blocks.text') }}</template>
<section class="ihymsbbe"> <section class="vckmsadr">
<textarea v-model="value.text"></textarea> <textarea v-model="value.text"></textarea>
</section> </section>
</x-container> </x-container>
@ -40,7 +40,7 @@ export default Vue.extend({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.ihymsbbe { .vckmsadr {
> textarea { > textarea {
display: block; display: block;
-webkit-appearance: none; -webkit-appearance: none;
@ -55,6 +55,7 @@ export default Vue.extend({
background: transparent; background: transparent;
color: var(--fg); color: var(--fg);
font-size: 14px; font-size: 14px;
box-sizing: border-box;
} }
} }
</style> </style>

View File

@ -55,6 +55,7 @@ export default Vue.extend({
background: transparent; background: transparent;
color: var(--fg); color: var(--fg);
font-size: 14px; font-size: 14px;
box-sizing: border-box;
} }
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5"> <x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :ai-script="aiScript"/> <component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :aoi-script="aoiScript"/>
</x-draggable> </x-draggable>
</template> </template>
@ -31,7 +31,7 @@ export default Vue.extend({
type: Array, type: Array,
required: true required: true
}, },
aiScript: { aoiScript: {
required: true, required: true,
}, },
}, },

View File

@ -2,7 +2,7 @@
<x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable"> <x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable">
<template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template> <template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
<template #func> <template #func>
<button @click="changeType()"> <button @click="changeType()" class="_button">
<fa :icon="faPencilAlt"/> <fa :icon="faPencilAlt"/>
</button> </button>
</template> </template>
@ -24,30 +24,33 @@
</section> </section>
<section v-else-if="value.type === 'ref'" class="hpdwcrvs"> <section v-else-if="value.type === 'ref'" class="hpdwcrvs">
<select v-model="value.value"> <select v-model="value.value">
<option v-for="v in aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option> <option v-for="v in aoiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.argVariables')"> <optgroup :label="$t('_pages.script.argVariables')">
<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option> <option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
</optgroup> </optgroup>
<optgroup :label="$t('_pages.script.pageVariables')"> <optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup> </optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')"> <optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> <option v-for="v in aoiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup> </optgroup>
</select> </select>
</section> </section>
<section v-else-if="value.type === 'aiScriptVar'" class="tbwccoaw">
<input v-model="value.value"/>
</section>
<section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;"> <section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
<mk-textarea v-model="slots"> <mk-textarea v-model="slots">
<span>{{ $t('_pages.script.blocks._fn.slots') }}</span> <span>{{ $t('_pages.script.blocks._fn.slots') }}</span>
<template #desc>{{ $t('_pages.script.blocks._fn.slots-info') }}</template> <template #desc>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
</mk-textarea> </mk-textarea>
<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :ai-script="aiScript" :fn-slots="value.value.slots" :name="name"/> <x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :aoi-script="aoiScript" :fn-slots="value.value.slots" :name="name"/>
</section> </section>
<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;"> <section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :ai-script="aiScript" :name="name" :key="i"/> <x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aoiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :aoi-script="aoiScript" :name="name" :key="i"/>
</section> </section>
<section v-else class="" style="padding:16px;"> <section v-else class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :ai-script="aiScript" :name="name" :fn-slots="fnSlots" :key="i"/> <x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :aoi-script="aoiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
</section> </section>
</x-container> </x-container>
</template> </template>
@ -85,7 +88,7 @@ export default Vue.extend({
required: false, required: false,
default: false default: false
}, },
aiScript: { aoiScript: {
required: true, required: true,
}, },
name: { name: {
@ -153,7 +156,7 @@ export default Vue.extend({
if (this.value.type && this.value.type.startsWith('fn:')) { if (this.value.type && this.value.type.startsWith('fn:')) {
const fnName = this.value.type.split(':')[1]; const fnName = this.value.type.split(':')[1];
const fn = this.aiScript.getVarByName(fnName); const fn = this.aoiScript.getVarByName(fnName);
const empties = []; const empties = [];
for (let i = 0; i < fn.value.slots.length; i++) { for (let i = 0; i < fn.value.slots.length; i++) {
@ -199,9 +202,9 @@ export default Vue.extend({
deep: true deep: true
}); });
this.$watch('aiScript.variables', () => { this.$watch('aoiScript.variables', () => {
if (this.type != null && this.value) { if (this.type != null && this.value) {
this.error = this.aiScript.typeCheck(this.value); this.error = this.aoiScript.typeCheck(this.value);
} }
}, { }, {
deep: true deep: true
@ -223,7 +226,7 @@ export default Vue.extend({
}, },
_getExpectedType(slot: number) { _getExpectedType(slot: number) {
return this.aiScript.getExpectedType(this.value, slot); return this.aoiScript.getExpectedType(this.value, slot);
} }
} }
}); });
@ -258,6 +261,7 @@ export default Vue.extend({
font-size: 16px; font-size: 16px;
background: transparent; background: transparent;
color: var(--fg); color: var(--fg);
box-sizing: border-box;
} }
> textarea { > textarea {

View File

@ -46,7 +46,7 @@
</div> </div>
</template> </template>
<x-blocks class="content" v-model="content" :ai-script="aiScript"/> <x-blocks class="content" v-model="content" :aoi-script="aoiScript"/>
<mk-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></mk-button> <mk-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></mk-button>
</section> </section>
@ -62,7 +62,7 @@
@input="v => updateVariable(v)" @input="v => updateVariable(v)"
@remove="() => removeVariable(variable)" @remove="() => removeVariable(variable)"
:key="variable.name" :key="variable.name"
:ai-script="aiScript" :aoi-script="aoiScript"
:name="variable.name" :name="variable.name"
:title="variable.name" :title="variable.name"
:draggable="true" :draggable="true"
@ -73,11 +73,10 @@
</div> </div>
</mk-container> </mk-container>
<mk-container :body-togglable="true" :expanded="false"> <mk-container :body-togglable="true" :expanded="true">
<template #header><fa :icon="faCode"/> {{ $t('_pages.inspector') }}</template> <template #header><fa :icon="faCode"/> {{ $t('script') }}</template>
<div style="padding:0 32px 32px 32px;"> <div>
<mk-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('_pages.content') }}</mk-textarea> <prism-editor v-model="script" :line-numbers="false" language="js"/>
<mk-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('_pages.variables') }}</mk-textarea>
</div> </div>
</mk-container> </mk-container>
</div> </div>
@ -86,6 +85,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as XDraggable from 'vuedraggable'; import * as XDraggable from 'vuedraggable';
import "prismjs";
import "prismjs/themes/prism.css";
import PrismEditor from 'vue-prism-editor';
import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons'; import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@ -108,7 +110,7 @@ export default Vue.extend({
i18n, i18n,
components: { components: {
XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput, PrismEditor
}, },
props: { props: {
@ -143,7 +145,8 @@ export default Vue.extend({
alignCenter: false, alignCenter: false,
hideTitleWhenPinned: false, hideTitleWhenPinned: false,
variables: [], variables: [],
aiScript: null, aoiScript: null,
script: '',
showOptions: false, showOptions: false,
url, url,
faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
@ -163,14 +166,14 @@ export default Vue.extend({
}, },
async created() { async created() {
this.aiScript = new ASTypeChecker(); this.aoiScript = new ASTypeChecker();
this.$watch('variables', () => { this.$watch('variables', () => {
this.aiScript.variables = this.variables; this.aoiScript.variables = this.variables;
}, { deep: true }); }, { deep: true });
this.$watch('content', () => { this.$watch('content', () => {
this.aiScript.pageVars = collectPageVars(this.content); this.aoiScript.pageVars = collectPageVars(this.content);
}, { deep: true }); }, { deep: true });
if (this.initPageId) { if (this.initPageId) {
@ -193,6 +196,7 @@ export default Vue.extend({
this.currentName = this.page.name; this.currentName = this.page.name;
this.summary = this.page.summary; this.summary = this.page.summary;
this.font = this.page.font; this.font = this.page.font;
this.script = this.page.script;
this.hideTitleWhenPinned = this.page.hideTitleWhenPinned; this.hideTitleWhenPinned = this.page.hideTitleWhenPinned;
this.alignCenter = this.page.alignCenter; this.alignCenter = this.page.alignCenter;
this.content = this.page.content; this.content = this.page.content;
@ -223,6 +227,7 @@ export default Vue.extend({
name: this.name.trim(), name: this.name.trim(),
summary: this.summary, summary: this.summary,
font: this.font, font: this.font,
script: this.script,
hideTitleWhenPinned: this.hideTitleWhenPinned, hideTitleWhenPinned: this.hideTitleWhenPinned,
alignCenter: this.alignCenter, alignCenter: this.alignCenter,
content: this.content, content: this.content,
@ -317,7 +322,7 @@ export default Vue.extend({
name = name.trim(); name = name.trim();
if (this.aiScript.isUsedName(name)) { if (this.aoiScript.isUsedName(name)) {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
text: this.$t('_pages.variableNameIsAlreadyUsed') text: this.$t('_pages.variableNameIsAlreadyUsed')
@ -382,7 +387,7 @@ export default Vue.extend({
} else { } else {
list.push({ list.push({
category: block.category, category: block.category,
label: this.$t(`script.categories.${block.category}`), label: this.$t(`_pages.script.categories.${block.category}`),
items: [{ items: [{
value: block.type, value: block.type,
text: this.$t(`_pages.script.blocks.${block.type}`) text: this.$t(`_pages.script.blocks.${block.type}`)
@ -394,7 +399,7 @@ export default Vue.extend({
const userFns = this.variables.filter(x => x.type === 'fn'); const userFns = this.variables.filter(x => x.type === 'fn');
if (userFns.length > 0) { if (userFns.length > 0) {
list.unshift({ list.unshift({
label: this.$t(`script.categories.fn`), label: this.$t(`_pages.script.categories.fn`),
items: userFns.map(v => ({ items: userFns.map(v => ({
value: 'fn:' + v.name, value: 'fn:' + v.name,
text: v.name text: v.name

View File

@ -23,9 +23,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons';
import "prismjs"; import "prismjs";
import "prismjs/themes/prism.css"; import "prismjs/themes/prism.css";
import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons';
import PrismEditor from 'vue-prism-editor'; import PrismEditor from 'vue-prism-editor';
import { AiScript, parse, utils, values } from '@syuilo/aiscript'; import { AiScript, parse, utils, values } from '@syuilo/aiscript';
import i18n from '../i18n'; import i18n from '../i18n';

View File

@ -2,6 +2,8 @@ import autobind from 'autobind-decorator';
import * as seedrandom from 'seedrandom'; import * as seedrandom from 'seedrandom';
import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.';
import { version } from '../../config'; import { version } from '../../config';
import { AiScript, utils, parse, values } from '@syuilo/aiscript';
import { createAiScriptEnv } from '../create-aiscript-env';
type Fn = { type Fn = {
slots: string[]; slots: string[];
@ -15,15 +17,41 @@ export class ASEvaluator {
private variables: Variable[]; private variables: Variable[];
private pageVars: PageVar[]; private pageVars: PageVar[];
private envVars: Record<keyof typeof envVarsDef, any>; private envVars: Record<keyof typeof envVarsDef, any>;
public aiscript: AiScript;
private pageVarUpdatedCallback;
private opts: { private opts: {
randomSeed: string; visitor?: any; page?: any; url?: string; randomSeed: string; visitor?: any; page?: any; url?: string;
}; };
constructor(variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) { constructor(vm: any, variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) {
this.variables = variables; this.variables = variables;
this.pageVars = pageVars; this.pageVars = pageVars;
this.opts = opts; this.opts = opts;
this.aiscript = new AiScript({ ...createAiScriptEnv(vm, {
storageKey: 'pages:' + opts.page.id
}), ...{
'MkPages:updated': values.FN_NATIVE(([callback]) => {
this.pageVarUpdatedCallback = callback;
})
}}, {
in: (q) => {
return new Promise(ok => {
vm.$root.dialog({
title: q,
input: {}
}).then(({ canceled, result: a }) => {
ok(a);
});
});
},
out: (value) => {
console.log(value);
},
log: (type, params) => {
},
maxStep: 16384
});
const date = new Date(); const date = new Date();
@ -50,6 +78,9 @@ export class ASEvaluator {
const pageVar = this.pageVars.find(v => v.name === name); const pageVar = this.pageVars.find(v => v.name === name);
if (pageVar !== undefined) { if (pageVar !== undefined) {
pageVar.value = value; pageVar.value = value;
if (this.pageVarUpdatedCallback) {
this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
}
} else { } else {
throw new AoiScriptError(`No such page var '${name}'`); throw new AoiScriptError(`No such page var '${name}'`);
} }
@ -110,6 +141,10 @@ export class ASEvaluator {
return scope.getState(block.value); return scope.getState(block.value);
} }
if (block.type === 'aiScriptVar') {
return utils.valToJs(this.aiscript.scope.get(block.value));
}
if (isFnBlock(block)) { // ユーザー関数定義 if (isFnBlock(block)) { // ユーザー関数定義
return { return {
slots: block.value.slots.map(x => x.name), slots: block.value.slots.map(x => x.name),

View File

@ -95,6 +95,7 @@ export const literalDefs: Record<string, { out: any; category: string; icon: any
textList: { out: 'stringArray', category: 'value', icon: faList, }, textList: { out: 'stringArray', category: 'value', icon: faList, },
number: { out: 'number', category: 'value', icon: faSortNumericUp, }, number: { out: 'number', category: 'value', icon: faSortNumericUp, },
ref: { out: null, category: 'value', icon: faMagic, }, ref: { out: null, category: 'value', icon: faMagic, },
aiScriptVar: { out: null, category: 'value', icon: faMagic, },
fn: { out: 'function', category: 'value', icon: faSquareRootAlt, }, fn: { out: 'function', category: 'value', icon: faSquareRootAlt, },
}; };

View File

@ -1,6 +1,7 @@
import { utils, values } from '@syuilo/aiscript'; import { utils, values } from '@syuilo/aiscript';
export function createAiScriptEnv(vm, opts) { export function createAiScriptEnv(vm, opts) {
let apiRequests = 0;
return { return {
USER_ID: values.STR(vm.$store.state.i.id), USER_ID: values.STR(vm.$store.state.i.id),
USER_USERNAME: values.STR(vm.$store.state.i.username), USER_USERNAME: values.STR(vm.$store.state.i.username),
@ -21,6 +22,8 @@ export function createAiScriptEnv(vm, opts) {
return confirm.canceled ? values.FALSE : values.TRUE return confirm.canceled ? values.FALSE : values.TRUE
}), }),
'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => {
apiRequests++;
if (apiRequests > 16) return values.NULL;
const res = await vm.$root.api(ep.value, utils.valToJs(param), token || null); const res = await vm.$root.api(ep.value, utils.valToJs(param), token || null);
return utils.jsToVal(res); return utils.jsToVal(res);
}), }),

View File

@ -85,6 +85,12 @@ export class Page {
}) })
public variables: Record<string, any>[]; public variables: Record<string, any>[];
@Column('varchar', {
length: 16384,
default: ''
})
public script: string;
/** /**
* public ... * public ...
* followers ... * followers ...

View File

@ -74,6 +74,7 @@ export class PageRepository extends Repository<Page> {
hideTitleWhenPinned: page.hideTitleWhenPinned, hideTitleWhenPinned: page.hideTitleWhenPinned,
alignCenter: page.alignCenter, alignCenter: page.alignCenter,
font: page.font, font: page.font,
script: page.script,
eyeCatchingImageId: page.eyeCatchingImageId, eyeCatchingImageId: page.eyeCatchingImageId,
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),

View File

@ -44,6 +44,10 @@ export const meta = {
validator: $.arr($.obj()) validator: $.arr($.obj())
}, },
script: {
validator: $.str,
},
eyeCatchingImageId: { eyeCatchingImageId: {
validator: $.optional.nullable.type(ID), validator: $.optional.nullable.type(ID),
}, },
@ -115,6 +119,7 @@ export default define(meta, async (ps, user) => {
summary: ps.summary, summary: ps.summary,
content: ps.content, content: ps.content,
variables: ps.variables, variables: ps.variables,
script: ps.script,
eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
userId: user.id, userId: user.id,
visibility: 'public', visibility: 'public',

View File

@ -51,6 +51,10 @@ export const meta = {
validator: $.arr($.obj()) validator: $.arr($.obj())
}, },
script: {
validator: $.str,
},
eyeCatchingImageId: { eyeCatchingImageId: {
validator: $.optional.nullable.type(ID), validator: $.optional.nullable.type(ID),
}, },
@ -132,6 +136,7 @@ export default define(meta, async (ps, user) => {
summary: ps.name === undefined ? page.summary : ps.summary, summary: ps.name === undefined ? page.summary : ps.summary,
content: ps.content, content: ps.content,
variables: ps.variables, variables: ps.variables,
script: ps.script,
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
font: ps.font === undefined ? page.font : ps.font, font: ps.font === undefined ? page.font : ps.font,

View File

@ -144,10 +144,10 @@
dependencies: dependencies:
type-detect "4.0.8" type-detect "4.0.8"
"@syuilo/aiscript@0.1.2": "@syuilo/aiscript@0.1.4":
version "0.1.2" version "0.1.4"
resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.2.tgz#65c42793c38707d862b3a64f5edc845789372ade" resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.4.tgz#ff027552f32990ae3e29145ce6efe0a7a516b442"
integrity sha512-W0G/JuVkD9jARPhKFaaHp+59Iv+2LapQ2zKjM08hoB/6hEzHjis0uRbw07TXyughQb17iU452rp1gJEUkXV3Mg== integrity sha512-SMDlBInsGTL3DOe0U394X7na0N6ryYg0RGQPPtCVhXkJpVDZiaqUe5vDO+DkRyuRlkmBbN82LWToou19j/Uv8g==
dependencies: dependencies:
autobind-decorator "2.4.0" autobind-decorator "2.4.0"
chalk "4.0.0" chalk "4.0.0"