From c670c258dc94a44261395c0cc9603fa7947f2b78 Mon Sep 17 00:00:00 2001 From: Fairy-Phy Date: Sat, 11 Nov 2023 02:40:29 +0900 Subject: [PATCH 01/14] add xxhash --- packages/frontend/package.json | 7 ++++--- pnpm-lock.yaml | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index de74922644..5d4aa3fd8a 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -27,9 +27,9 @@ "@vitejs/plugin-vue": "4.4.0", "@vue-macros/reactivity-transform": "0.3.23", "@vue/compiler-sfc": "3.3.7", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.5", "astring": "1.8.6", "autosize": "6.0.1", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.5", "broadcast-channel": "6.0.0", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "buraha": "0.0.1", @@ -59,8 +59,8 @@ "querystring": "0.2.1", "rollup": "4.2.0", "sanitize-html": "2.11.0", - "shiki": "^0.14.5", "sass": "1.69.5", + "shiki": "^0.14.5", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.158.0", @@ -75,7 +75,8 @@ "vanilla-tilt": "1.8.1", "vite": "4.5.0", "vue": "3.3.7", - "vuedraggable": "next" + "vuedraggable": "next", + "xxhash-wasm": "^1.0.2" }, "devDependencies": { "@storybook/addon-actions": "7.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a731705fa2..d6543b12d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -820,6 +820,9 @@ importers: vuedraggable: specifier: next version: 4.1.0(vue@3.3.7) + xxhash-wasm: + specifier: ^1.0.2 + version: 1.0.2 devDependencies: '@storybook/addon-actions': specifier: 7.5.2 @@ -6931,7 +6934,7 @@ packages: hasBin: true peerDependencies: '@swc/core': ^1.2.66 - chokidar: 3.5.3 + chokidar: ^3.5.1 peerDependenciesMeta: chokidar: optional: true @@ -19666,6 +19669,10 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + /xxhash-wasm@1.0.2: + resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} + dev: false + /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: false From f395324e8b4f1cdd5e4bd688a4863c50218a233e Mon Sep 17 00:00:00 2001 From: Fairy-Phy Date: Sat, 11 Nov 2023 02:41:38 +0900 Subject: [PATCH 02/14] add backend api remove all keys in scope --- .../backend/src/core/RegistryApiService.ts | 6 ++- .../backend/src/server/api/EndpointsModule.ts | 4 ++ packages/backend/src/server/api/endpoints.ts | 2 + .../i/registry/remove-all-keys-in-scope.ts | 39 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/i/registry/remove-all-keys-in-scope.ts diff --git a/packages/backend/src/core/RegistryApiService.ts b/packages/backend/src/core/RegistryApiService.ts index d340c5e480..64016726ea 100644 --- a/packages/backend/src/core/RegistryApiService.ts +++ b/packages/backend/src/core/RegistryApiService.ts @@ -131,7 +131,7 @@ export class RegistryApiService { } @bindThis - public async remove(userId: MiUser['id'], domain: string | null, scope: string[], key: string) { + public async remove(userId: MiUser['id'], domain: string | null, scope: string[], key?: string) { const query = this.registryItemsRepository.createQueryBuilder().delete(); if (domain) { query.where('domain = :domain', { domain: domain }); @@ -139,7 +139,9 @@ export class RegistryApiService { query.where('domain IS NULL'); } query.andWhere('userId = :userId', { userId: userId }); - query.andWhere('key = :key', { key: key }); + if (key) { + query.andWhere('key = :key', { key: key }); + } query.andWhere('scope = :scope', { scope: scope }); await query.execute(); diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 23067a9b26..13097317ff 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -230,6 +230,7 @@ import * as ep___i_registry_get from './endpoints/i/registry/get.js'; import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; +import * as ep___i_registry_removeAllKeysInScope from './endpoints/i/registry/remove-all-keys-in-scope.js'; import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js'; import * as ep___i_registry_set from './endpoints/i/registry/set.js'; import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; @@ -588,6 +589,7 @@ const $i_registry_get: Provider = { provide: 'ep:i/registry/get', useClass: ep__ const $i_registry_keysWithType: Provider = { provide: 'ep:i/registry/keys-with-type', useClass: ep___i_registry_keysWithType.default }; const $i_registry_keys: Provider = { provide: 'ep:i/registry/keys', useClass: ep___i_registry_keys.default }; const $i_registry_remove: Provider = { provide: 'ep:i/registry/remove', useClass: ep___i_registry_remove.default }; +const $i_registry_removeAllKeysInScope: Provider = { provide: 'ep:i/registry/remove-all-keys-in-scope', useClass: ep___i_registry_removeAllKeysInScope.default }; const $i_registry_scopesWithDomain: Provider = { provide: 'ep:i/registry/scopes-with-domain', useClass: ep___i_registry_scopesWithDomain.default }; const $i_registry_set: Provider = { provide: 'ep:i/registry/set', useClass: ep___i_registry_set.default }; const $i_revokeToken: Provider = { provide: 'ep:i/revoke-token', useClass: ep___i_revokeToken.default }; @@ -950,6 +952,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_registry_keysWithType, $i_registry_keys, $i_registry_remove, + $i_registry_removeAllKeysInScope, $i_registry_scopesWithDomain, $i_registry_set, $i_revokeToken, @@ -1306,6 +1309,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_registry_keysWithType, $i_registry_keys, $i_registry_remove, + $i_registry_removeAllKeysInScope, $i_registry_scopesWithDomain, $i_registry_set, $i_revokeToken, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index af798fd166..3301e0020d 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -230,6 +230,7 @@ import * as ep___i_registry_get from './endpoints/i/registry/get.js'; import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; +import * as ep___i_registry_removeAllKeysInScope from './endpoints/i/registry/remove-all-keys-in-scope.js'; import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js'; import * as ep___i_registry_set from './endpoints/i/registry/set.js'; import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; @@ -586,6 +587,7 @@ const eps = [ ['i/registry/keys-with-type', ep___i_registry_keysWithType], ['i/registry/keys', ep___i_registry_keys], ['i/registry/remove', ep___i_registry_remove], + ['i/registry/remove-all-keys-in-scope', ep___i_registry_removeAllKeysInScope], ['i/registry/scopes-with-domain', ep___i_registry_scopesWithDomain], ['i/registry/set', ep___i_registry_set], ['i/revoke-token', ep___i_revokeToken], diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove-all-keys-in-scope.ts b/packages/backend/src/server/api/endpoints/i/registry/remove-all-keys-in-scope.ts new file mode 100644 index 0000000000..59862bf659 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/remove-all-keys-in-scope.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { RegistryApiService } from '@/core/RegistryApiService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + requireCredential: true, + + errors: {}, +} as const; + +export const paramDef = { + type: 'object', + properties: { + scope: { type: 'array', default: [], items: { + type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + } }, + domain: { type: 'string', nullable: true }, + }, + required: ['scope'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private registryApiService: RegistryApiService, + ) { + super(meta, paramDef, async (ps, me, accessToken) => { + await this.registryApiService.remove(me.id, accessToken != null ? accessToken.id : (ps.domain ?? null), ps.scope); + }); + } +} From 8eb5f4957c12eada279b24ee31b70ecff8b31866 Mon Sep 17 00:00:00 2001 From: Fairy-Phy Date: Sat, 11 Nov 2023 02:42:33 +0900 Subject: [PATCH 03/14] add sync plugin and storage sync --- packages/frontend/src/boot/main-boot.ts | 15 +++- .../components/MkPluginSelectSaveWindow.vue | 76 +++++++++++++++++++ packages/frontend/src/pages/flash/flash.vue | 5 +- packages/frontend/src/pages/scratchpad.vue | 4 +- .../frontend/src/pages/settings/plugin.vue | 40 ++++++++-- packages/frontend/src/plugin.ts | 19 ++++- packages/frontend/src/scripts/aiscript/api.ts | 23 ++++-- .../frontend/src/scripts/aiscript/storage.ts | 35 +++++++++ .../frontend/src/scripts/install-plugin.ts | 73 ++++++++++++++++-- packages/frontend/src/store.ts | 1 + .../frontend/src/widgets/WidgetAiscript.vue | 4 +- .../src/widgets/WidgetAiscriptApp.vue | 4 +- .../frontend/src/widgets/WidgetButton.vue | 4 +- 13 files changed, 273 insertions(+), 30 deletions(-) create mode 100644 packages/frontend/src/components/MkPluginSelectSaveWindow.vue create mode 100644 packages/frontend/src/scripts/aiscript/storage.ts diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 887740f4a5..7bccd00a24 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -19,6 +19,8 @@ import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js import { mainRouter } from '@/router.js'; import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; +import { getPluginList } from '@/plugin.js'; +import { unisonReload } from '@/scripts/unison-reload.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -57,7 +59,11 @@ export async function mainBoot() { } }); - for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { + let plugins = ColdDeviceStorage.get('plugins').filter(p => p.active); + const accountPlugins = Object.values(await getPluginList()).filter(p => p.active); + plugins.push(...accountPlugins); + + for (const plugin of plugins) { import('@/plugin.js').then(async ({ install }) => { // Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740 await new Promise(r => setTimeout(r, 0)); @@ -274,6 +280,13 @@ export async function mainBoot() { main.on('myTokenRegenerated', () => { signout(); }); + + // プラグインに変更が入ったら自動でリロードする + stream.useChannel('main').on('registryUpdated', ({ scope, key }: { scope: string[], key: string, value: any }) => { + if (scope[0] === 'client' && key === 'plugins') { + unisonReload(); + } + }); } // shortcut diff --git a/packages/frontend/src/components/MkPluginSelectSaveWindow.vue b/packages/frontend/src/components/MkPluginSelectSaveWindow.vue new file mode 100644 index 0000000000..5910798182 --- /dev/null +++ b/packages/frontend/src/components/MkPluginSelectSaveWindow.vue @@ -0,0 +1,76 @@ + + + + + + + diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index ebf117ffbf..fa4050915b 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -145,7 +145,10 @@ async function run() { aiscript = new Interpreter({ ...createAiScriptEnv({ - storageKey: 'flash:' + flash.id, + scriptData: { + type: 'flash', + id: flash.id, + }, }), ...registerAsUiLib(components, (_root) => { root.value = _root.value; diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index f8d3187bd4..21f63ef1ca 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -79,8 +79,10 @@ async function run() { logs.value = []; aiscript = new Interpreter(({ ...createAiScriptEnv({ - storageKey: 'widget', token: $i?.token, + scriptData: { + type: 'widget', + }, }), ...registerAsUiLib(components, (_root) => { root.value = _root.value; diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 5ebd74ef7a..f64c554561 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ plugin.name }}v{{ plugin.version }} + {{ plugin.name }}{{ '同期' }}v{{ plugin.version }} {{ i18n.ts.makeActive }}
@@ -74,11 +74,21 @@ import { ColdDeviceStorage } from '@/store.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { getPluginList } from '@/plugin.js'; -const plugins = ref(ColdDeviceStorage.get('plugins')); +let plugins = Object.values(await getPluginList()); +plugins.push(...ColdDeviceStorage.get('plugins')); +plugins = ref(plugins); async function uninstall(plugin) { - ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); + if (plugin.fromAccount) { + let plugins = await getPluginList(); + delete plugins[plugin.id]; + await os.api('i/registry/remove-all-keys-in-scope', { scope: ['client', 'aiscript', 'plugins', plugin.id] }); + await os.api('i/registry/set', { scope: ['client'], key: 'plugins', value: plugins }); + } else { + ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); + } await os.apiWithDialog('i/revoke-token', { token: plugin.token, }); @@ -102,9 +112,15 @@ async function config(plugin) { const { canceled, result } = await os.form(plugin.name, config); if (canceled) return; - const coldPlugins = ColdDeviceStorage.get('plugins'); - coldPlugins.find(p => p.id === plugin.id)!.configData = result; - ColdDeviceStorage.set('plugins', coldPlugins); + if (plugin.fromAccount) { + let plugins = await getPluginList(); + plugins[plugin.id].configData = result; + await os.api('i/registry/set', { scope: ['client'], key: 'plugins', value: plugins }); + } else { + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.configData = result; + ColdDeviceStorage.set('plugins', coldPlugins); + } nextTick(() => { location.reload(); @@ -130,3 +146,15 @@ definePageMetadata({ icon: 'ti ti-plug', }); + + diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index e24f646a35..d780061b37 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -5,19 +5,30 @@ import { Interpreter, Parser, utils, values } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api.js'; -import { inputText } from '@/os.js'; +import { inputText, api } from '@/os.js'; import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js'; +import { $i } from './account.js'; const parser = new Parser(); const pluginContexts = new Map(); +export async function getPluginList(): Promise> { + if ($i == null) return {}; + + try { + return await api('i/registry/get', { scope: ['client'], key: 'plugins' }); + } catch (err) { + if (err.code === 'NO_SUCH_KEY') return {}; + throw err; + } +} + export async function install(plugin: Plugin): Promise { // 後方互換性のため if (plugin.src == null) return; const aiscript = new Interpreter(createPluginEnv({ plugin: plugin, - storageKey: 'plugins:' + plugin.id, }), { in: (q): Promise => { return new Promise(ok => { @@ -51,14 +62,14 @@ export async function install(plugin: Plugin): Promise { console.info('Plugin installed:', plugin.name, 'v' + plugin.version); } -function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record { +function createPluginEnv(opts: { plugin: Plugin; }): Record { const config = new Map(); for (const [k, v] of Object.entries(opts.plugin.config ?? {})) { config.set(k, utils.jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default)); } return { - ...createAiScriptEnv({ ...opts, token: opts.plugin.token }), + ...createAiScriptEnv({ token: opts.plugin.token, scriptData: { type: 'plugins', id: opts.plugin.id, fromAccount: opts.plugin.fromAccount } }), //#region Deprecated 'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => { utils.assertString(title); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index fb7ab924b7..4bd9c197cb 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -6,12 +6,12 @@ import { utils, values } from '@syuilo/aiscript'; import * as os from '@/os.js'; import { $i } from '@/account.js'; -import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; import { url, lang } from '@/config.js'; import { nyaize } from '@/scripts/nyaize.js'; +import { ScriptData, loadScriptStorage, saveScriptStorage } from './storage.js'; -export function createAiScriptEnv(opts) { +export function createAiScriptEnv(opts: { token: string; scriptData: ScriptData; }) { return { USER_ID: $i ? values.STR($i.id) : values.NULL, USER_NAME: $i ? values.STR($i.name) : values.NULL, @@ -60,14 +60,23 @@ export function createAiScriptEnv(opts) { return values.ERROR('request_failed', utils.jsToVal(err)); }); }), - 'Mk:save': values.FN_NATIVE(([key, value]) => { + 'Mk:save': values.FN_NATIVE(async ([key, value, toAccount]) => { utils.assertString(key); - miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value))); - return values.NULL; + const saveToAccount = toAccount ? toAccount.value : false; + return saveScriptStorage(saveToAccount, opts.scriptData, key.value, utils.valToJs(value)).then(() => { + return values.NULL; + }, err => { + return values.ERROR('request_failed', utils.jsToVal(err)); + }); }), - 'Mk:load': values.FN_NATIVE(([key]) => { + 'Mk:load': values.FN_NATIVE(async ([key, toAccount]) => { utils.assertString(key); - return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`))); + const loadToAccount = toAccount ? toAccount.value : false; + return loadScriptStorage(loadToAccount, opts.scriptData, key.value).then(res => { + return utils.jsToVal(res); + }, err => { + return values.ERROR('request_failed', utils.jsToVal(err)); + }); }), 'Mk:url': values.FN_NATIVE(() => { return values.STR(window.location.href); diff --git a/packages/frontend/src/scripts/aiscript/storage.ts b/packages/frontend/src/scripts/aiscript/storage.ts new file mode 100644 index 0000000000..cc98bf9b32 --- /dev/null +++ b/packages/frontend/src/scripts/aiscript/storage.ts @@ -0,0 +1,35 @@ +import { api } from '@/os.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { $i } from '@/account.js'; + +type ScriptType = 'widget' | 'plugins' | 'flash'; +export type ScriptData = { type: ScriptType; id?: string; fromAccount?: boolean }; + +export async function loadScriptStorage(toAccount: boolean, scriptData: ScriptData, key: string) { + let value: string | null; + if ($i && toAccount && (scriptData.type !== 'plugins' || (scriptData.type === 'plugins' && scriptData.fromAccount))) { + if (scriptData.type === 'widget') { + value = await api('i/registry/get', { scope: ['client', 'aiscript', scriptData.type], key: key }); + } else { + value = await api('i/registry/get', { scope: ['client', 'aiscript', scriptData.type, scriptData.id!], key: key }); + } + } else { + value = miLocalStorage.getItem(`aiscript:${scriptData.type}:${key}`); + } + + if (value === null) return null; + return JSON.parse(value); +} + +export async function saveScriptStorage(toAccount: boolean, scriptData: ScriptData, key: string, value: any) { + const jsonValue = JSON.stringify(value); + if ($i && toAccount && (scriptData.type !== 'plugins' || (scriptData.type === 'plugins' && scriptData.fromAccount))) { + if (scriptData.type === 'widget') { + await api('i/registry/set', { scope: ['client', 'aiscript', scriptData.type], key: key, value: jsonValue }); + } else { + await api('i/registry/set', { scope: ['client', 'aiscript', scriptData.type, scriptData.id!], key: key, value: jsonValue }); + } + } else { + miLocalStorage.setItem(`aiscript:${scriptData.type}:${key}`, jsonValue); + } +} diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts index 1310a0dc73..f9eb66ac71 100644 --- a/packages/frontend/src/scripts/install-plugin.ts +++ b/packages/frontend/src/scripts/install-plugin.ts @@ -6,11 +6,13 @@ import { defineAsyncComponent } from 'vue'; import { compareVersions } from 'compare-versions'; import { v4 as uuid } from 'uuid'; +import xxhash from 'xxhash-wasm'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; import type { Plugin } from '@/store.js'; import { ColdDeviceStorage } from '@/store.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +import { getPluginList } from '@/plugin.js'; export type AiScriptPluginMeta = { name: string; @@ -23,6 +25,11 @@ export type AiScriptPluginMeta = { const parser = new Parser(); +async function toHash(name: string, author: string) { + const { h32ToString } = await xxhash(); + return h32ToString(author + name); +} + export function savePlugin({ id, meta, src, token }: { id: string; meta: AiScriptPluginMeta; @@ -36,9 +43,37 @@ export function savePlugin({ id, meta, src, token }: { configData: {}, token: token, src: src, + fromAccount: false, } as Plugin)); } +async function savePluginToAccount(pluginOnlyOverride: boolean, { hash, meta, src, token }: { + hash: string; + meta: AiScriptPluginMeta; + src: string; + token: string; +}) { + let plugins = await getPluginList(); + // pluginOnlyOverrideがtrueになっているということはすでに重複していることが確定している + const configData = pluginOnlyOverride ? plugins[hash].configData : {}; + const pluginToken = pluginOnlyOverride ? plugins[hash].token : token; + plugins[hash] = { + ...meta, + id: hash, + active: true, + configData, + token: pluginToken, + src: src, + fromAccount: true, + } as Plugin; + + if (!pluginOnlyOverride) { + await os.api('i/registry/remove-all-keys-in-scope', { scope: ['client', 'aiscript', 'plugins', hash] }); + } + + await os.api('i/registry/set', { scope: ['client'], key: 'plugins', value: plugins }); +} + export function isSupportedAiScriptVersion(version: string): boolean { try { return (compareVersions(version, '0.12.0') >= 0); @@ -101,7 +136,20 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { realMeta = meta; } - const token = realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => { + const plugins = Object.keys(await getPluginList()); + const pluginHash = await toHash(realMeta.name, realMeta.author); + + const { isLocal, pluginOnlyOverride } = (await new Promise((res, rej) => { + os.popup(defineAsyncComponent(() => import('@/components/MkPluginSelectSaveWindow.vue')), { + isExistsFromAccount: plugins.some(v => v === pluginHash) + }, { + done: result => { + res(result); + }, + }, 'closed'); + })); + + const token = realMeta.permissions == null || realMeta.permissions.length === 0 || pluginOnlyOverride ? null : await new Promise((res, rej) => { os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), { title: i18n.ts.tokenRequested, information: i18n.ts.pluginTokenRequestedDescription, @@ -120,10 +168,21 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { }, 'closed'); }); - savePlugin({ - id: uuid(), - meta: realMeta, - token, - src: code, - }); + if (isLocal) { + + savePlugin({ + id: uuid(), + meta: realMeta, + token, + src: code, + }); + } + else { + await savePluginToAccount(pluginOnlyOverride, { + hash: pluginHash, + meta: realMeta, + token, + src: code, + }); + } } diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 6d95ddba35..eb6f82cb95 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -426,6 +426,7 @@ export type Plugin = { author?: string; description?: string; permissions?: string[]; + fromAccount: boolean; }; interface Watcher { diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index 1b8c8ad9bc..df3f163c10 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -66,8 +66,10 @@ const logs = ref<{ const run = async () => { logs.value = []; const aiscript = new Interpreter(createAiScriptEnv({ - storageKey: 'widget', token: $i?.token, + scriptData: { + type: 'widget', + }, }), { in: (q) => { return new Promise(ok => { diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index 53b6020ffc..b99ab5a15c 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -57,8 +57,10 @@ const components: Ref[] = $ref([]); async function run() { const aiscript = new Interpreter({ ...createAiScriptEnv({ - storageKey: 'widget', token: $i?.token, + scriptData: { + type: 'widget', + }, }), ...registerAsUiLib(components, (_root) => { root.value = _root.value; diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue index a7bdd4c49c..387af44e69 100644 --- a/packages/frontend/src/widgets/WidgetButton.vue +++ b/packages/frontend/src/widgets/WidgetButton.vue @@ -53,8 +53,10 @@ const parser = new Parser(); const run = async () => { const aiscript = new Interpreter(createAiScriptEnv({ - storageKey: 'widget', token: $i?.token, + scriptData: { + type: 'widget', + }, }), { in: (q) => { return new Promise(ok => { From c9edfc61240ea74d223444a37a5daa1da4af8206 Mon Sep 17 00:00:00 2001 From: Fairy-Phy Date: Sat, 11 Nov 2023 02:53:51 +0900 Subject: [PATCH 04/14] add text flavor --- locales/index.d.ts | 6 ++++++ locales/ja-JP.yml | 6 ++++++ .../src/components/MkPluginSelectSaveWindow.vue | 15 +++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index fc6653b05b..5a2f3f77b2 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1161,6 +1161,12 @@ export interface Locale { "signupPendingError": string; "cwNotationRequired": string; "doReaction": string; + "pluginSyncSettings": string; + "pluginSyncSettingsInfo": string; + "duplicateSyncedPlugin": string; + "overrideSourceCodeOnly": string; + "syncSetting": string; + "syncing": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 67a57f994c..5b070eca3e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1158,6 +1158,12 @@ useGroupedNotifications: "通知をグルーピングして表示する" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" +pluginSyncSettings: "プラグインの同期設定" +pluginSyncSettingsInfo: "このプラグインをローカルのみにインストールするか他端末と同期するかを選択できます。" +duplicateSyncedPlugin: "このプラグインは同期されているプラグインと重複しています。" +overrideSourceCodeOnly: "コードのみを上書きする" +syncSetting: "同期設定" +syncing: "他端末と同期" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/src/components/MkPluginSelectSaveWindow.vue b/packages/frontend/src/components/MkPluginSelectSaveWindow.vue index 5910798182..77e0c3ddb6 100644 --- a/packages/frontend/src/components/MkPluginSelectSaveWindow.vue +++ b/packages/frontend/src/components/MkPluginSelectSaveWindow.vue @@ -15,22 +15,22 @@ SPDX-License-Identifier: AGPL-3.0-only @closed="$emit('closed')" @ok="ok()" > - +
- {{ 'このプラグインをローカルのみにインストールするか他端末と同期するかを選択できます。' }} + {{ i18n.ts.pluginSyncSettingsInfo }}
- {{ 'このプラグインは同期されているプラグインと重複しています。' }} - {{ 'コードのみを上書きする' }} + {{ i18n.ts.duplicateSyncedPlugin }} + {{ i18n.ts.overrideSourceCodeOnly }}
- - - + + +
@@ -40,7 +40,6 @@ SPDX-License-Identifier: AGPL-3.0-only