From b527e0e7850fcef26b4eb6537c7de8b0c8d894aa Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sat, 19 Jun 2021 19:57:12 +0800 Subject: [PATCH 01/21] fix(adventure): optimize ending output --- packages/plugin-adventure/src/phase.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index 64f7f7e668..e07356f463 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -415,14 +415,15 @@ export namespace Phase { ].join('\n') } - const titles = storyMap[reversedLineMap[name]] + const prefix = reversedLineMap[name] + const titles = storyMap[prefix] if (!titles) return options['pass'] ? next().then(() => '') : `你尚未解锁剧情「${name}」。` const output = titles.map((name) => { const id = reversedEndingMap[name] - return `${id}. ${name}×${endings[id]}${badEndings.has(id) ? `(BE)` : ''}` + return `${id.slice(prefix.length + 1)}. ${name}×${endings[id]}${badEndings.has(id) ? `(BE)` : ''}` }).sort() - const [title, count] = lines[reversedLineMap[name]] - output.unshift(`${session.username},你已达成${title}剧情线的 ${titles.length}/${count} 个结局:`) + const [title, count] = lines[prefix] + output.unshift(`${session.username},你已达成${title}的 ${titles.length}/${count} 个结局:`) return output.join('\n') } From af73653e4f5b62d649562035a1a2b4ec9bcaa038 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 21 Jun 2021 19:25:22 +0800 Subject: [PATCH 02/21] fix(adventure): use timer for phase lock --- packages/plugin-adventure/src/luck.ts | 2 +- packages/plugin-adventure/src/phase.ts | 44 +++++++++++++++----------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/plugin-adventure/src/luck.ts b/packages/plugin-adventure/src/luck.ts index 8cf63382da..9994244b65 100644 --- a/packages/plugin-adventure/src/luck.ts +++ b/packages/plugin-adventure/src/luck.ts @@ -77,7 +77,7 @@ namespace Luck { .usage(showLotteryUsage) .action(async ({ session, options }) => { const { user } = session - if (Phase.metaMap[session.userId]) return '当前处于剧情模式中,无法抽卡。' + if (Phase.userSessionMap[session.user.id]) return '当前处于剧情模式中,无法抽卡。' if (user.progress) return '检测到你有未完成的剧情,请尝试输入“继续当前剧情”。' if (checkTimer('lottery', user, minInterval * 60 * 1000)) { diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index e07356f463..a6954c041f 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -38,8 +38,8 @@ export namespace Phase { export const phaseMap: Record> = { '': mainPhase } export const salePlots: Record> = {} - export const metaMap: Record> = {} - export const groupStates: Record = {} + export const userSessionMap: Record, NodeJS.Timer]> = {} + export const channelUserMap: Record = {} export const activeUsers = new Set() export function getBadEndingCount(user: Pick) { @@ -47,15 +47,15 @@ export namespace Phase { } export function checkStates(session: Session<'id'>, active = false) { - // check group state - const groupState = groupStates[session.groupId] - if (session.subtype === 'group' && groupState && groupState !== session.userId) { + // check channel state + const userState = channelUserMap[session.cid] + if (session.subtype === 'group' && userState && userState[0] !== session.user.id) { return '同一个群内只能同时进行一处剧情,请尝试私聊或稍后重试。' } // check user state - const _meta = metaMap[session.userId] - if (_meta && !(active && _meta.channelId === session.channelId && activeUsers.has(session.user.id))) { + const sessionState = userSessionMap[session.user.id] + if (sessionState && !(active && sessionState[0].cid === session.cid && activeUsers.has(session.user.id))) { return '同一用户只能同时进行一处剧情。' } } @@ -344,20 +344,28 @@ export namespace Phase { logger.debug('%s phase finish', session.userId) } - export async function start(session: Session) { - metaMap[session.userId] = session - if (session.subtype === 'group') { - groupStates[session.groupId] = session.userId + function setState(map: Record, key: string, value: V) { + const current = map[key] + if (current) clearTimeout(current[1]) + const timer = setTimeout(() => this.map.delete(key), Time.hour) + const entry = map[key] = [value, timer] + return () => { + if (map[key] !== entry) return + clearTimeout(entry[1]) + delete map[key] } + } + + export async function start(session: Session) { + const disposeUser = setState(userSessionMap, session.user.id, session) + const disposeChannel = setState(channelUserMap, session.cid, session.user.id) try { await plot(session) } catch (error) { new Logger('cosmos').warn(error) } - delete metaMap[session.userId] - if (session.subtype === 'group') { - delete groupStates[session.groupId] - } + disposeUser() + disposeChannel() } function findEndings(names: string[]) { @@ -482,7 +490,7 @@ export namespace Phase { ctx.command('adv/skip [-- command:text]', '跳过剧情') .shortcut('跳过剧情') .shortcut('跳过当前剧情') - .userFields(['phases', 'progress']) + .userFields(['phases', 'progress', 'id']) .useRest() .usage('这个指令用于跳过剧情的主体部分,并不会略去结算文字。当进入下一段剧情时需要再次跳过。未读过的剧情无法跳过。') .action(async ({ session, next, options }) => { @@ -493,7 +501,7 @@ export namespace Phase { return } if (session._skipAll) return - if (!(session = metaMap[session.userId])) return + if (!(session = userSessionMap[session.user.id]?.[0])) return if (session._skipCurrent || !session._canSkip) return session.cancelQueued() session._skipCurrent = true @@ -558,7 +566,7 @@ export namespace Phase { return start(session) } else { if (!item || session._skipAll) return - const next = !metaMap[session.userId] && getValue(mainPhase.items[item], user) + const next = !userSessionMap[session.user.id] && getValue(mainPhase.items[item], user) if (next) { return `物品“${item}”当前不可用,请尝试输入“继续当前剧情”。` } else { From 90d9dc864c522969b26de2000e062c295323ea0f Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 21 Jun 2021 20:55:03 +0800 Subject: [PATCH 03/21] feat(onebot): update to latest go-cqhttp api --- packages/adapter-onebot/src/bot.ts | 24 ++++++++++++++++++++---- packages/adapter-onebot/src/types.ts | 14 +++++++++++++- packages/adapter-onebot/src/utils.ts | 4 ++++ packages/koishi-core/src/adapter.ts | 1 + packages/koishi-core/src/session.ts | 1 + 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/adapter-onebot/src/bot.ts b/packages/adapter-onebot/src/bot.ts index d0e3b2fe83..2b78ef28e6 100644 --- a/packages/adapter-onebot/src/bot.ts +++ b/packages/adapter-onebot/src/bot.ts @@ -187,6 +187,10 @@ export class CQBot extends Bot { await this.$setGroupAddRequest(messageId, 'add', approve, comment) } + async deleteFriend(userId: string) { + await this.$deleteFriend(userId) + } + async getStatus() { if (this.status !== Bot.Status.GOOD) return this.status try { @@ -198,9 +202,16 @@ export class CQBot extends Bot { } } -function define(name: string, ...params: string[]) { +const asyncPrefixes = ['$set', '$send', '$delete', '$create', '$upload'] + +function prepareMethod(name: string) { const prop = '$' + camelCase(name.replace(/^[_.]/, '')) - const isAsync = prop.startsWith('$set') || prop.startsWith('$send') || prop.startsWith('$delete') + const isAsync = asyncPrefixes.some(prefix => prop.startsWith(prefix)) + return [prop, isAsync] as const +} + +function define(name: string, ...params: string[]) { + const [prop, isAsync] = prepareMethod(name) CQBot.prototype[prop] = async function (this: CQBot, ...args: any[]) { const data = await this.get(name, Object.fromEntries(params.map((name, index) => [name, args[index]]))) if (!isAsync) return data @@ -212,8 +223,7 @@ function define(name: string, ...params: string[]) { function defineExtract(name: string, key: string, ...params: string[]) { key = camelCase(key) - const prop = '$' + camelCase(name.replace(/^[_.]/, '')) - const isAsync = prop.startsWith('$set') || prop.startsWith('$send') || prop.startsWith('$delete') + const [prop, isAsync] = prepareMethod(name) CQBot.prototype[prop] = async function (this: CQBot, ...args: any[]) { const data = await this.get(name, Object.fromEntries(params.map((name, index) => [name, args[index]]))) return data[key] @@ -238,6 +248,8 @@ defineExtract('.get_word_slices', 'slices', 'content') define('get_group_msg_history', 'group_id', 'message_seq') define('set_friend_add_request', 'flag', 'approve', 'remark') define('set_group_add_request', 'flag', 'sub_type', 'approve', 'reason') +defineExtract('_get_model_show', 'variants', 'model') +define('_set_model_show', 'model', 'model_show') define('set_group_kick', 'group_id', 'user_id', 'reject_add_request') define('set_group_ban', 'group_id', 'user_id', 'duration') @@ -266,10 +278,14 @@ define('get_group_file_system_info', 'group_id') define('get_group_root_files', 'group_id') define('get_group_files_by_folder', 'group_id', 'folder_id') define('upload_group_file', 'group_id', 'file', 'name', 'folder') +define('create_group_file_folder', 'group_id', 'folder_id', 'name') +define('delete_group_folder', 'group_id', 'folder_id') +define('delete_group_file', 'group_id', 'folder_id', 'file_id', 'busid') defineExtract('get_group_file_url', 'url', 'group_id', 'file_id', 'busid') defineExtract('download_file', 'file', 'url', 'headers', 'thread_count') defineExtract('get_online_clients', 'clients', 'no_cache') defineExtract('check_url_safely', 'level', 'url') +define('delete_friend', 'user_id') defineExtract('get_cookies', 'cookies', 'domain') defineExtract('get_csrf_token', 'token') diff --git a/packages/adapter-onebot/src/types.ts b/packages/adapter-onebot/src/types.ts index fb0bbae426..4c023c25a8 100644 --- a/packages/adapter-onebot/src/types.ts +++ b/packages/adapter-onebot/src/types.ts @@ -270,6 +270,11 @@ export interface Device { deviceKind: string } +export interface ModelVariant { + modelShow: string + needPay: boolean +} + export enum SafetyLevel { safe, unknown, danger } type id = string | number @@ -295,6 +300,8 @@ export interface API { $getWordSlices(content: string): Promise $ocrImage(image: string): Promise $getGroupMsgHistory(groupId: id, messageSeq: id): Promise + $deleteFriend(userId: id): Promise + $deleteFriendAsync(userId: id): Promise $setFriendAddRequest(flag: string, approve: boolean, remark?: string): Promise $setFriendAddRequestAsync(flag: string, approve: boolean, remark?: string): Promise $setGroupAddRequest(flag: string, subType: 'add' | 'invite', approve: boolean, reason?: string): Promise @@ -338,10 +345,15 @@ export interface API { $getGroupRootFiles(groupId: id): Promise $getGroupFilesByFolder(groupId: id, folderId: string): Promise $getGroupFileUrl(groupId: id, fileId: string, busid: number): Promise - $uploadGroupFile(groupId: id, file: string, name: string, folder?: string): Promise $downloadFile(url: string, headers?: string | string[], threadCount?: number): Promise + $uploadGroupFile(groupId: id, file: string, name: string, folder?: string): Promise + $createGroupFileFolder(groupId: id, folderId: string, name: string): Promise + $deleteGroupFolder(groupId: id, folderId: string): Promise + $deleteGroupFile(groupId: id, folderId: string, fileId: string, busid: number): Promise $getOnlineClients(noCache?: boolean): Promise $checkUrlSafely(url: string): Promise + $getModelShow(model: string): Promise + $setModelShow(model: string, modelShow: string): Promise $getCookies(domain?: string): Promise $getCsrfToken(): Promise diff --git a/packages/adapter-onebot/src/utils.ts b/packages/adapter-onebot/src/utils.ts index 35a40edd09..70f233366c 100644 --- a/packages/adapter-onebot/src/utils.ts +++ b/packages/adapter-onebot/src/utils.ts @@ -124,6 +124,10 @@ export function createSession(adapter: Adapter, data: any) { session.type = session.userId === session.selfId ? 'group-added' : 'group-member-added' session.subtype = session.userId === session.operatorId ? 'active' : 'passive' break + case 'group_card': + session.type = 'group-member' + session.subtype = 'nickname' + break case 'notify': session.type = 'notice' session.subtype = paramCase(data.sub_type) diff --git a/packages/koishi-core/src/adapter.ts b/packages/koishi-core/src/adapter.ts index bfb1ea35ba..7a98494026 100644 --- a/packages/koishi-core/src/adapter.ts +++ b/packages/koishi-core/src/adapter.ts @@ -193,6 +193,7 @@ export interface Bot

extends BotOptions, UserBase { getSelf(): Promise getUser(userId: string): Promise getFriendList(): Promise + deleteFriend(userId: string): Promise // group getGroup(groupId: string): Promise diff --git a/packages/koishi-core/src/session.ts b/packages/koishi-core/src/session.ts index 8572cafda4..fc894c6322 100644 --- a/packages/koishi-core/src/session.ts +++ b/packages/koishi-core/src/session.ts @@ -41,6 +41,7 @@ export namespace Session { 'group-member': { 'role': {} 'ban': {} + 'nickname': {} } 'notice': { 'poke': {} From 0426a86ffde2baf2f28ff1f5449d3653211abb5b Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 22 Jun 2021 02:07:37 +0800 Subject: [PATCH 04/21] fix(utils): use empty string for reference error in interpolation --- packages/koishi-utils/src/string.ts | 11 ++++++++--- packages/koishi-utils/tests/string.spec.ts | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/koishi-utils/src/string.ts b/packages/koishi-utils/src/string.ts index 5db4fbe04b..391a43c2c4 100644 --- a/packages/koishi-utils/src/string.ts +++ b/packages/koishi-utils/src/string.ts @@ -31,12 +31,17 @@ export function capitalize(source: string) { // eslint-disable-next-line no-new-func export const interpolate = new Function('template', 'context', ` -with (context) { return template.replace(/\\{\\{[\\s\\S]+?\\}\\}/g, (sub) => { const expr = sub.substring(2, sub.length - 2) - return eval(expr) + try { + with (context) { + return eval(expr) + } + } catch { + return '' + } }) -}`) as ((template: string, context: object) => string) +`) as ((template: string, context: object) => string) export function escapeRegExp(source: string) { return source diff --git a/packages/koishi-utils/tests/string.spec.ts b/packages/koishi-utils/tests/string.spec.ts index 4877e83d01..92df93f27b 100644 --- a/packages/koishi-utils/tests/string.spec.ts +++ b/packages/koishi-utils/tests/string.spec.ts @@ -31,6 +31,7 @@ describe('String Manipulations', () => { it('interpolate', () => { expect(interpolate('foo{{ bar }}foo', { bar: 'baz' })).to.equal('foobazfoo') + expect(interpolate('foo{{ bar }}foo', {})).to.equal('foofoo') }) it('escape regexp', () => { From 83cfa2a16a1061639395a25c3ec7f67654402650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=BA=E6=99=BA=E7=9A=84=E5=B0=8F=E9=B1=BC=E5=90=9B?= <44761872+Dragon-Fish@users.noreply.github.com> Date: Tue, 22 Jun 2021 02:20:59 +0800 Subject: [PATCH 05/21] feat(discord): add $setGroupName, $setGroupCard (#283) Co-authored-by: Shigma <1700011071@pku.edu.cn> --- packages/adapter-discord/src/bot.ts | 46 ++++++++++++++++----------- packages/adapter-discord/src/types.ts | 34 ++++++++++++++++++-- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/packages/adapter-discord/src/bot.ts b/packages/adapter-discord/src/bot.ts index e1cb6a2c67..09051eba7a 100644 --- a/packages/adapter-discord/src/bot.ts +++ b/packages/adapter-discord/src/bot.ts @@ -161,7 +161,7 @@ export class DiscordBot extends Bot<'discord'> { return session.messageId } - async deleteMessage(channelId: string, messageId: string) { + deleteMessage(channelId: string, messageId: string) { return this.request('DELETE', `/channels/${channelId}/messages/${messageId}`) } @@ -176,7 +176,7 @@ export class DiscordBot extends Bot<'discord'> { }) } - async $getMessage(channelId: string, messageId: string) { + $getMessage(channelId: string, messageId: string) { return this.request('GET', `/channels/${channelId}/messages/${messageId}`) } @@ -226,7 +226,7 @@ export class DiscordBot extends Bot<'discord'> { return await this.sendFullMessage(`/webhooks/${id}/${token}?wait=${wait}`, data.content, data) } - async $getGuildMember(guildId: string, userId: string) { + $getGuildMember(guildId: string, userId: string) { return this.request('GET', `/guilds/${guildId}/members/${userId}`) } @@ -238,15 +238,15 @@ export class DiscordBot extends Bot<'discord'> { } } - async $getGuildRoles(guildId: string) { + $getGuildRoles(guildId: string) { return this.request('GET', `/guilds/${guildId}/roles`) } - async $getChannel(channelId: string) { + $getChannel(channelId: string) { return this.request('GET', `/channels/${channelId}`) } - async $listGuildMembers(guildId: string, limit?: number, after?: string) { + $listGuildMembers(guildId: string, limit?: number, after?: string) { return this.request('GET', `/guilds/${guildId}/members?limit=${limit || 1000}&after=${after || '0'}`) } @@ -265,38 +265,46 @@ export class DiscordBot extends Bot<'discord'> { return members.filter(v => v.roles.includes(roleId)) } - async $modifyGuildMember(guildId: string, userId: string, data: Partial) { + $modifyGuildMember(guildId: string, userId: string, data: Partial) { return this.request('PATCH', `/guilds/${guildId}/members/${userId}`, data) } - async $addGuildMemberRole(guildId: string, userId: string, roleId: string) { + $setGroupCard(guildId: string, userId: string, nick: string) { + return this.$modifyGuildMember(guildId, userId, { nick }) + } + + $addGuildMemberRole(guildId: string, userId: string, roleId: string) { return this.request('PUT', `/guilds/${guildId}/members/${userId}/roles/${roleId}`) } - async $removeGuildMemberRole(guildId: string, userId: string, roleId: string) { + $removeGuildMemberRole(guildId: string, userId: string, roleId: string) { return this.request('DELETE', `/guilds/${guildId}/members/${userId}/roles/${roleId}`) } - async $createGuildRole(guildId: string, data: DC.GuildRoleBody) { + $createGuildRole(guildId: string, data: DC.GuildRoleBody) { return this.request('POST', `/guilds/${guildId}/roles`, data) } - async $modifyGuildRole(guildId: string, roleId: string, data: Partial) { + $modifyGuildRole(guildId: string, roleId: string, data: Partial) { return this.request('PATCH', `/guilds/${guildId}/roles/${roleId}`, data) } - async $modifyGuild(guildId: string, data: DC.GuildBody) { + $modifyGuild(guildId: string, data: DC.GuildModify) { return this.request('PATCH', `/guilds/${guildId}`, data) } - async $createWebhook(channelId: string, data: { + $setGroupName(guildId: string, name: string) { + return this.$modifyGuild(guildId, { name }) + } + + $createWebhook(channelId: string, data: { name: string; avatar?: string }) { return this.request('POST', `/channels/${channelId}/webhooks`, data) } - async $modifyWebhook(webhookId: string, data: { + $modifyWebhook(webhookId: string, data: { name?: string; avatar?: string channel_id?: string @@ -304,19 +312,19 @@ export class DiscordBot extends Bot<'discord'> { return this.request('PATCH', `/webhooks/${webhookId}`, data) } - async $getChannelWebhooks(channelId: string) { + $getChannelWebhooks(channelId: string) { return this.request('GET', `/channels/${channelId}/webhooks`) } - async $getGuildWebhooks(guildId: string) { + $getGuildWebhooks(guildId: string) { return this.request('GET', `/guilds/${guildId}/webhooks`) } - async $modifyChannel(channelId, data: Partial) { + $modifyChannel(channelId: string, data: DC.ModifyChannel) { return this.request('PATCH', `/channels/${channelId}`, data) } - async $getGuild(guildId: string) { + $getGuild(guildId: string) { return this.request('GET', `/guilds/${guildId}`) } @@ -334,7 +342,7 @@ export class DiscordBot extends Bot<'discord'> { return data.map(v => adaptGroup(v)) } - async $getGuildChannels(guildId: string) { + $getGuildChannels(guildId: string) { return this.request('GET', `/guilds/${guildId}/channels`) } diff --git a/packages/adapter-discord/src/types.ts b/packages/adapter-discord/src/types.ts index 3dd6b50703..6688b67ee1 100644 --- a/packages/adapter-discord/src/types.ts +++ b/packages/adapter-discord/src/types.ts @@ -81,7 +81,19 @@ export interface Channel { last_pin_timestamp?: ISO8601; } -export interface ModifyGuild extends Pick {} +type ChannelModifyProps = + | 'name' + | 'type' + | 'position' + | 'topic' + | 'nsfw' + | 'rate_limit_per_user' + | 'bitrate' + | 'user_limit' + | 'permission_overwrites' + | 'parent_id' + +export type ModifyChannel = Partial> /** https://discord.com/developers/docs/resources/guild#guild-object-guild-structure */ export interface Guild { @@ -133,8 +145,24 @@ export interface Guild { welcome_screen?: any; } -export interface GuildBody extends Pick { -} +type GuildModifyProps = + | 'name' + | 'region' + | 'verification_level' + | 'default_message_notifications' + | 'explicit_content_filter' + | 'afk_channel_id' + | 'afk_timeout' + | 'icon' + | 'owner_id' + | 'splash' + | 'banner' + | 'system_channel_id' + | 'rules_channel_id' + | 'public_updates_channel_id' + | 'preferred_locale' + +export type GuildModify = Partial> /** https://discord.com/developers/docs/resources/user#user-object-user-structure */ export interface User { From 5f34a0e7efa76057da495979cb41b5bd4238bc91 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 22 Jun 2021 20:04:55 +0800 Subject: [PATCH 06/21] fix(chat): fix debug output error --- packages/plugin-chat/src/receiver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-chat/src/receiver.ts b/packages/plugin-chat/src/receiver.ts index 191328fda2..e628fc5631 100644 --- a/packages/plugin-chat/src/receiver.ts +++ b/packages/plugin-chat/src/receiver.ts @@ -128,7 +128,7 @@ export default function apply(ctx: Context, config: ReceiverConfig = {}) { } else if (session.subtype === 'group') { const id = `${session.platform}:${code.data.id}` if (code.data.name) { - userMap[session.uid] = [Promise.resolve(code.data.name), timestamp] + userMap[id] = [Promise.resolve(code.data.name), timestamp] } else if (!userMap[id] || timestamp - userMap[id][1] >= refreshUserName) { userMap[id] = [getUserName(session.bot, session.groupId, code.data.id), timestamp] } From 782d33ff69cdb36fbff61f62418844fd87ce9386 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 22 Jun 2021 22:59:38 +0800 Subject: [PATCH 07/21] feat(test-utils): do not use cache for tests --- packages/koishi-core/tests/runtime.spec.ts | 2 -- packages/koishi-test-utils/src/app.ts | 8 +++++++- packages/plugin-github/tests/index.spec.ts | 2 -- packages/plugin-teach/tests/environment.ts | 1 - 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/koishi-core/tests/runtime.spec.ts b/packages/koishi-core/tests/runtime.spec.ts index caa1111e5d..84d1617828 100644 --- a/packages/koishi-core/tests/runtime.spec.ts +++ b/packages/koishi-core/tests/runtime.spec.ts @@ -4,8 +4,6 @@ import { install } from '@sinonjs/fake-timers' const app = new App({ mockDatabase: true, - channelCacheAge: Number.EPSILON, - userCacheAge: Number.EPSILON, minSimilarity: 0, }) diff --git a/packages/koishi-test-utils/src/app.ts b/packages/koishi-test-utils/src/app.ts index a80de60892..0c81374895 100644 --- a/packages/koishi-test-utils/src/app.ts +++ b/packages/koishi-test-utils/src/app.ts @@ -96,7 +96,13 @@ export class MockedApp extends App { public server: MockedServer constructor(options: MockedAppOptions = {}) { - super({ selfId: BASE_SELF_ID, type: 'mock', ...options }) + super({ + type: 'mock', + selfId: BASE_SELF_ID, + channelCacheAge: Number.EPSILON, + userCacheAge: Number.EPSILON, + ...options, + }) this.server = this.adapters.mock as any diff --git a/packages/plugin-github/tests/index.spec.ts b/packages/plugin-github/tests/index.spec.ts index 925d365f45..12a7a9431b 100644 --- a/packages/plugin-github/tests/index.spec.ts +++ b/packages/plugin-github/tests/index.spec.ts @@ -13,8 +13,6 @@ const app = new App({ prefix: '.', mockStart: false, mockDatabase: true, - userCacheAge: Number.EPSILON, - channelCacheAge: Number.EPSILON, }) app.plugin(github) diff --git a/packages/plugin-teach/tests/environment.ts b/packages/plugin-teach/tests/environment.ts index c9231ceffe..2c18b2268c 100644 --- a/packages/plugin-teach/tests/environment.ts +++ b/packages/plugin-teach/tests/environment.ts @@ -108,7 +108,6 @@ function getProduct({ startTime, endTime }: Dialogue, time: number) { export default function (config: Config) { const app = new App({ - userCacheAge: Number.EPSILON, nickname: ['koishi', 'satori'], mockDatabase: true, }) From c342ce0cbbdff951be9bd7bb5e2af0c2cb3d5622 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 22 Jun 2021 22:59:59 +0800 Subject: [PATCH 08/21] fix(common): fix assign & authorize command, fix #292 --- packages/plugin-common/src/admin.ts | 66 ++++++++++++++-------- packages/plugin-common/tests/admin.spec.ts | 17 +++++- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/packages/plugin-common/src/admin.ts b/packages/plugin-common/src/admin.ts index b8cf4f6c05..713334cfd9 100644 --- a/packages/plugin-common/src/admin.ts +++ b/packages/plugin-common/src/admin.ts @@ -1,14 +1,14 @@ import { difference, observe, Time, enumKeys, Random, template, deduplicate, intersection } from 'koishi-utils' -import { Context, User, Channel, Command, Argv, Platform, Session } from 'koishi-core' +import { Context, User, Channel, Command, Argv, Platform, Session, Extend } from 'koishi-core' type AdminAction - = (argv: Argv & { target: T }, ...args: A) + = (argv: Argv> & { target: T }, ...args: A) => void | string | Promise declare module 'koishi-core' { interface Command { - adminUser(callback: AdminAction>): this - adminChannel(callback: AdminAction>): this + adminUser(callback: AdminAction>, autoCreate?: boolean): this + adminChannel(callback: AdminAction>, autoCreate?: boolean): this } interface EventMap { @@ -25,12 +25,14 @@ template.set('admin', { 'current-flags': '当前的标记为:{0}。', // admin helper + 'user-expected': '请指定目标用户。', 'user-not-found': '未找到指定的用户。', 'user-unchanged': '用户数据未改动。', 'user-updated': '用户数据已修改。', 'channel-not-found': '未找到指定的频道。', 'channel-unchanged': '频道数据未改动。', 'channel-updated': '频道数据已修改。', + 'invalid-assignee-platform': '代理者应与目标频道属于同一平台。', 'not-in-group': '当前不在群组上下文中,请使用 -t 参数指定目标频道。', }) @@ -117,14 +119,13 @@ function flagAction(map: FlagMap, { target, options }: FlagArgv, ...flags: strin return template('admin.current-flags', keys.join(', ')) } -Command.prototype.adminUser = function (this: Command, callback) { +Command.prototype.adminUser = function (this: Command, callback, autoCreate) { const { database } = this.app const command = this .userFields(['authority']) .option('target', '-t [user:user] 指定目标用户', { authority: 3 }) - .userFields(({ options }, fields) => { - if (!options.target) return - const [platform] = options.target.split(':') + .userFields(({ session, options }, fields) => { + const platform = options.target ? options.target.split(':')[0] : session.platform fields.add(platform as Platform) }) @@ -140,8 +141,14 @@ Command.prototype.adminUser = function (this: Command, callback) { target = await session.observeUser(fields) } else { const data = await database.getUser(platform, userId, [...fields]) - if (!data) return template('admin.user-not-found') - if (session.user.authority <= data.authority) { + if (!data) { + if (!autoCreate) return template('admin.user-not-found') + const fallback = observe(User.create(platform, userId), async () => { + if (!fallback.authority) return + await database.createUser(platform, userId, fallback) + }) + target = fallback + } else if (session.user.authority <= data.authority) { return template('internal.low-authority') } else { target = observe(data, diff => database.setUser(platform, userId, diff), `user ${options.target}`) @@ -161,7 +168,7 @@ Command.prototype.adminUser = function (this: Command, callback) { return command } -Command.prototype.adminChannel = function (this: Command, callback) { +Command.prototype.adminChannel = function (this: Command, callback, autoCreate) { const { database } = this.app const command = this .userFields(['authority']) @@ -176,8 +183,16 @@ Command.prototype.adminChannel = function (this: Command, callback) { } else if (options.target) { const [platform, channelId] = Argv.parsePid(options.target) const data = await database.getChannel(platform, channelId, [...fields]) - if (!data) return template('admin.channel-not-found') - target = observe(data, diff => database.setChannel(platform, channelId, diff), `channel ${options.target}`) + if (!data) { + if (!autoCreate) return template('admin.channel-not-found') + const fallback = observe(Channel.create(platform, channelId), async () => { + if (!fallback.assignee) return + await database.createChannel(platform, channelId, fallback) + }) + target = fallback + } else { + target = observe(data, diff => database.setChannel(platform, channelId, diff), `channel ${options.target}`) + } } else { return template('admin.not-in-group') } @@ -296,12 +311,11 @@ export default function apply(ctx: Context, config: AdminConfig = {}) { ctx.command('user/authorize ', '权限信息', { authority: 4 }) .alias('auth') .adminUser(async ({ session, target }, authority) => { + if (session.userId === target[session.platform]) return template('admin.user-expected') if (authority >= session.user.authority) return template('internal.low-authority') if (authority === target.authority) return template('admin.user-unchanged') - await ctx.database.createUser(session.platform, target[session.platform], { authority }) - target._merge({ authority }) - return template('admin.user-updated') - }) + target.authority = authority + }, true) ctx.command('user.flag [-s|-S] [...flags]', '标记信息', { authority: 3 }) .userFields(['flag']) @@ -373,12 +387,18 @@ export default function apply(ctx: Context, config: AdminConfig = {}) { .channelFields(['assignee']) .option('noTarget', '-T 移除受理者') .adminChannel(async ({ session, options, target }, value) => { - const assignee = options.noTarget ? null : value ? Argv.parsePid(value)[1] : session.selfId - if (assignee === target.assignee) return template('admin.channel-unchanged') - await ctx.database.createChannel(session.platform, session.channelId, { assignee }) - target._merge({ assignee }) - return template('admin.channel-updated') - }) + if (options.noTarget) { + target.assignee = '' + } else if (!value) { + target.assignee = session.selfId + } else { + const [platform, userId] = Argv.parsePid(value) + if (platform !== Argv.parsePid(options.target)[0]) { + return template('admin.invalid-assignee-platform') + } + target.assignee = userId + } + }, true) ctx.command('channel/switch ', '启用和禁用功能', { authority: 3 }) .channelFields(['disable']) diff --git a/packages/plugin-common/tests/admin.spec.ts b/packages/plugin-common/tests/admin.spec.ts index 7f88c974a2..dcd82057cd 100644 --- a/packages/plugin-common/tests/admin.spec.ts +++ b/packages/plugin-common/tests/admin.spec.ts @@ -2,9 +2,11 @@ import { App } from 'koishi-test-utils' import { User, Channel, defineEnumProperty } from 'koishi-core' import { install } from '@sinonjs/fake-timers' import * as common from 'koishi-plugin-common' +import { expect } from 'chai' const app = new App({ mockDatabase: true }) const session = app.session('123', '321') +const session2 = app.session('123') app.plugin(common) app.command('foo', { maxUsage: 10 }).action(() => 'bar') @@ -38,17 +40,19 @@ before(async () => { describe('Admin Commands', () => { it('user/authorize', async () => { + await session.shouldReply('authorize', '请指定目标用户。') await session.shouldReply('authorize -t nan', '选项 target 输入无效,请指定正确的用户。') - await session.shouldReply('authorize -t @321', '未找到指定的用户。') await session.shouldReply('authorize -t @789', '权限不足。') await session.shouldReply('authorize -t @456 1.5', '参数 value 输入无效,请提供一个正整数。') await session.shouldReply('authorize -t @456 3', '用户数据未改动。') await session.shouldReply('authorize -t @456 4', '权限不足。') await session.shouldReply('authorize -t @456 2', '用户数据已修改。') + await session.shouldReply('authorize -t @111 1', '用户数据已修改。') }) it('user.flag', async () => { await session.shouldReply('user.flag -t @123', '未设置任何标记。') + await session.shouldReply('user.flag -t @321', '未找到指定的用户。') await session.shouldReply('user.flag -l', '全部标记为:ignore, test。') await session.shouldReply('user.flag -s foo', '未找到标记 foo。') await session.shouldReply('user.flag -s test', '用户数据已修改。') @@ -90,12 +94,21 @@ describe('Admin Commands', () => { it('channel/assign', async () => { await app.session('123').shouldReply('assign', '当前不在群组上下文中,请使用 -t 参数指定目标频道。') await session.shouldReply('assign -t nan', '选项 target 输入无效,请指定正确的频道。') - await session.shouldReply('assign -t #123', '未找到指定的频道。') await session.shouldReply('assign -t #321', '频道数据未改动。') await session.shouldReply('assign -t #321 nan', '参数 bot 输入无效,请指定正确的用户。') + await session.shouldReply('assign -t #321 @foo:bar', '代理者应与目标频道属于同一平台。') + await session.shouldReply('assign -t #333', '频道数据已修改。') + + const getChannel = () => expect(app.database.getChannel('mock', '321')).eventually + await getChannel().to.have.property('assignee', '514') + await session.shouldReply('assign -t #321 @123', '频道数据已修改。') + await getChannel().to.have.property('assignee', '123') + await session2.shouldReply('assign -t #321', '频道数据已修改。') + await getChannel().to.have.property('assignee', '514') }) it('channel/switch', async () => { + await session.shouldReply('switch -t #123', '未找到指定的频道。') await session.shouldReply('switch', '当前没有禁用功能。') await session.shouldReply('baz', 'zab') await session.shouldReply('switch baz', '已禁用 baz 功能。') From 12207bdee5adcb30633e9db7f4c5809fec1e7a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=BA=E6=99=BA=E7=9A=84=E5=B0=8F=E9=B1=BC=E5=90=9B?= <44761872+Dragon-Fish@users.noreply.github.com> Date: Thu, 24 Jun 2021 01:39:16 +0800 Subject: [PATCH 09/21] feat(discord): optimize image segment sending, fix #280 (#282) --- packages/adapter-discord/src/bot.ts | 57 +++++++++++++++++++++++++-- packages/adapter-discord/src/index.ts | 3 +- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/adapter-discord/src/bot.ts b/packages/adapter-discord/src/bot.ts index 09051eba7a..84b905d797 100644 --- a/packages/adapter-discord/src/bot.ts +++ b/packages/adapter-discord/src/bot.ts @@ -9,6 +9,8 @@ import { segment } from 'koishi-utils' import FormData from 'form-data' import FileType from 'file-type' +export type HandleExternalAssets = 'auto' | 'download' | 'direct' + export class SenderError extends Error { constructor(url: string, data: any, selfId: string) { super(`Error when trying to request ${url}, data: ${JSON.stringify(data)}`) @@ -126,16 +128,63 @@ export class DiscordBot extends Bot<'discord'> { }) sentMessageId = r.id } else { - try { + const { axiosConfig, discord = {} } = this.app.options + const sendMode = + data.mode as HandleExternalAssets || // define in segment + discord.handleExternalAssets || // define in app options + 'auto' // default + + // Utils + async function sendDownload() { const a = await axios.get(data.url, { + ...axiosConfig, + ...discord.axiosConfig, responseType: 'arraybuffer', + headers: { + accept: 'image/*', + }, }) - const r = await this.sendEmbedMessage(requestUrl, a.data, { + const r = await that.sendEmbedMessage(requestUrl, a.data, { ...addition, }) sentMessageId = r.id - } catch (e) { - throw new SenderError(data.url, data, this.selfId) + } + async function sendDirect() { + const r = await that.request('POST', requestUrl, { + content: data.url, + ...addition, + }) + sentMessageId = r.id + } + + if (sendMode === 'direct') { + // send url directly + await sendDirect() + } else if (sendMode === 'download') { + // download send + await sendDownload() + } else { + // auto mode + await axios + .head(data.url, { + ...axiosConfig, + ...discord.axiosConfig, + headers: { + accept: 'image/*', + }, + }) + .then(async ({ headers }) => { + if (headers['content-type'].includes('image')) { + await sendDirect() + } else { + await sendDownload() + } + }, async () => { + await sendDownload() + }) + .catch(() => { + throw new SenderError(data.url, data, this.selfId) + }) } } } diff --git a/packages/adapter-discord/src/index.ts b/packages/adapter-discord/src/index.ts index e13b6c746f..0a4dde3684 100644 --- a/packages/adapter-discord/src/index.ts +++ b/packages/adapter-discord/src/index.ts @@ -1,6 +1,6 @@ import { Adapter } from 'koishi-core' import { AxiosRequestConfig } from 'axios' -import { DiscordBot } from './bot' +import { DiscordBot, HandleExternalAssets } from './bot' import WsClient from './ws' import * as DC from './types' export * from './bot' @@ -8,6 +8,7 @@ export * from './bot' interface DiscordOptions extends Adapter.WsClientOptions { endpoint?: string axiosConfig?: AxiosRequestConfig + handleExternalAssets?: HandleExternalAssets } declare module 'koishi-core' { From 2087b7c8deeae5495f1970dc693e14d77459ce7e Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 25 Jun 2021 04:48:20 +0800 Subject: [PATCH 10/21] fix(core): support domain type for greedy args --- packages/koishi-core/src/parser.ts | 67 ++++++++++++++---------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/packages/koishi-core/src/parser.ts b/packages/koishi-core/src/parser.ts index 3a76244244..0295ff9177 100644 --- a/packages/koishi-core/src/parser.ts +++ b/packages/koishi-core/src/parser.ts @@ -319,6 +319,32 @@ export namespace Argv { return result } + export function parseValue(source: string, quoted: boolean, kind: string, argv: Argv, decl: Declaration = {}) { + const { name, type, fallback } = decl + + // no explicit parameter & has fallback + const implicit = source === '' && !quoted + if (implicit && fallback !== undefined) return fallback + + // apply domain callback + const transform = resolveType(type) + if (transform) { + try { + return transform(source, argv.session) + } catch (err) { + const message = err['message'] || template('internal.check-syntax') + argv.error = template(`internal.invalid-${kind}`, name, message) + return + } + } + + // default behavior + if (implicit) return true + if (quoted) return source + const n = +source + return n * 0 === 0 ? n : source + } + export interface OptionConfig { value?: any fallback?: any @@ -342,7 +368,6 @@ export namespace Argv { public _arguments: Declaration[] public _options: OptionDeclarationMap = {} - private _error: string private _namedOptions: OptionDeclarationMap = {} private _symbolicOptions: OptionDeclarationMap = {} @@ -430,32 +455,6 @@ export namespace Argv { return true } - private _parseValue(source: string, quoted: boolean, kind: string, session: Session, decl: Declaration = {}) { - const { name, type, fallback } = decl - - // no explicit parameter & has fallback - const implicit = source === '' && !quoted - if (implicit && fallback !== undefined) return fallback - - // apply domain callback - const transform = resolveType(type) - if (transform) { - try { - return transform(source, session) - } catch (err) { - const message = err['message'] || template('internal.check-syntax') - this._error = template(`internal.invalid-${kind}`, name, message) - return - } - } - - // default behavior - if (implicit) return true - if (quoted) return source - const n = +source - return n * 0 === 0 ? n : source - } - parse(argv: Argv): Argv parse(source: string, terminator?: string): Argv parse(argv: string | Argv, terminator?: string): Argv { @@ -464,16 +463,14 @@ export namespace Argv { const args: string[] = [] const options: Record = {} const source = this.name + ' ' + Argv.stringify(argv) - this._error = '' - - while (!this._error && argv.tokens.length) { + while (!argv.error && argv.tokens.length) { const token = argv.tokens[0] let { content, quoted } = token // greedy argument const argDecl = this._arguments[args.length] if (content[0] !== '-' && resolveConfig(argDecl?.type).greedy) { - args.push(Argv.stringify(argv)) + args.push(Argv.parseValue(Argv.stringify(argv), true, 'argument', argv, argDecl)) break } @@ -488,7 +485,7 @@ export namespace Argv { } else { // normal argument if (content[0] !== '-' || quoted) { - args.push(this._parseValue(content, quoted, 'argument', argv.session, argDecl || { type: 'string' })) + args.push(Argv.parseValue(content, quoted, 'argument', argv, argDecl || { type: 'string' })) continue } @@ -539,9 +536,9 @@ export namespace Argv { options[key] = optDecl.values[name] } else { const source = j + 1 < names.length ? '' : param - options[key] = this._parseValue(source, quoted, 'option', argv.session, optDecl) + options[key] = Argv.parseValue(source, quoted, 'option', argv, optDecl) } - if (this._error) break + if (argv.error) break } } @@ -553,7 +550,7 @@ export namespace Argv { } delete argv.tokens - return { options, args, source, rest: argv.rest, error: this._error } + return { options, args, source, rest: argv.rest, error: argv.error || '' } } private stringifyArg(value: any) { From a055b26cb5d641f8604a605a14da99fa8abc134a Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 25 Jun 2021 04:48:54 +0800 Subject: [PATCH 11/21] fix(core): support domain type for shortcuts removed shortcut.greedy, please use arg type instead --- packages/koishi-core/src/app.ts | 8 ++------ packages/koishi-core/src/command.ts | 1 - packages/koishi-core/src/parser.ts | 6 ++---- packages/koishi-core/tests/runtime.spec.ts | 6 +++--- packages/plugin-common/src/admin.ts | 2 +- packages/plugin-eval/src/index.ts | 4 ++-- packages/plugin-tools/src/baidu.ts | 4 ++-- packages/plugin-tools/src/music.ts | 6 +++--- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts index 2ff06e6f53..f8378f6217 100644 --- a/packages/koishi-core/src/app.ts +++ b/packages/koishi-core/src/app.ts @@ -356,18 +356,14 @@ export class App extends Context { const { parsed, quote } = session if (parsed.prefix || quote) return for (const shortcut of this._shortcuts) { - const { name, fuzzy, command, greedy, prefix, options = {}, args = [] } = shortcut + const { name, fuzzy, command, prefix, options = {}, args = [] } = shortcut if (prefix && !parsed.appel || !command.context.match(session)) continue if (typeof name === 'string') { if (!fuzzy && content !== name || !content.startsWith(name)) continue const message = content.slice(name.length) if (fuzzy && !parsed.appel && message.match(/^\S/)) continue - const argv: Argv = greedy - ? { options: {}, args: [message.trim()] } - : command.parse(Argv.parse(message.trim())) + const argv = command.parse(message.trim(), '', [...args], { ...options }) argv.command = command - argv.options = { ...options, ...argv.options } - argv.args = [...args, ...argv.args] return argv } else { const capture = name.exec(content) diff --git a/packages/koishi-core/src/command.ts b/packages/koishi-core/src/command.ts index 5c66fc46b3..a1b9d4813d 100644 --- a/packages/koishi-core/src/command.ts +++ b/packages/koishi-core/src/command.ts @@ -44,7 +44,6 @@ export namespace Command { prefix?: boolean fuzzy?: boolean args?: string[] - greedy?: boolean options?: Record } diff --git a/packages/koishi-core/src/parser.ts b/packages/koishi-core/src/parser.ts index 0295ff9177..816c43c02a 100644 --- a/packages/koishi-core/src/parser.ts +++ b/packages/koishi-core/src/parser.ts @@ -456,12 +456,10 @@ export namespace Argv { } parse(argv: Argv): Argv - parse(source: string, terminator?: string): Argv - parse(argv: string | Argv, terminator?: string): Argv { + parse(source: string, terminator?: string, args?: any[], options?: Record): Argv + parse(argv: string | Argv, terminator?: string, args = [], options = {}): Argv { if (typeof argv === 'string') argv = Argv.parse(argv, terminator) - const args: string[] = [] - const options: Record = {} const source = this.name + ' ' + Argv.stringify(argv) while (!argv.error && argv.tokens.length) { const token = argv.tokens[0] diff --git a/packages/koishi-core/tests/runtime.spec.ts b/packages/koishi-core/tests/runtime.spec.ts index 84d1617828..8f4a461d30 100644 --- a/packages/koishi-core/tests/runtime.spec.ts +++ b/packages/koishi-core/tests/runtime.spec.ts @@ -19,7 +19,7 @@ const session5 = app.session('123', '654') const cmd1 = app.command('cmd1 ', { authority: 2 }) .channelFields(['id']) .shortcut('foo1', { args: ['bar'] }) - .shortcut('foo4', { greedy: true, fuzzy: true }) + .shortcut('foo4', { fuzzy: true }) .option('--bar', '', { authority: 3 }) .option('--baz', '', { notUsage: true }) .action(({ session }, arg) => session.send('cmd1:' + arg)) @@ -171,9 +171,9 @@ describe('Runtime', () => { }) it('one argument & fuzzy', async () => { - await session4.shouldReply('foo4 bar baz', 'cmd1:bar baz') + await session4.shouldReply('foo4 bar baz', 'cmd1:bar') await session4.shouldNotReply('foo4bar baz') - await session4.shouldReply(`[CQ:at,id=${app.selfId}] foo4bar baz`, 'cmd1:bar baz') + await session4.shouldReply(`[CQ:at,id=${app.selfId}] foo4bar baz`, 'cmd1:bar') }) }) diff --git a/packages/plugin-common/src/admin.ts b/packages/plugin-common/src/admin.ts index 713334cfd9..ede464ebb9 100644 --- a/packages/plugin-common/src/admin.ts +++ b/packages/plugin-common/src/admin.ts @@ -222,7 +222,7 @@ export default function apply(ctx: Context, config: AdminConfig = {}) { ctx.command('common/callme [name:text]', '修改自己的称呼') .userFields(['id', 'name']) - .shortcut('叫我', { prefix: true, fuzzy: true, greedy: true }) + .shortcut('叫我', { prefix: true, fuzzy: true }) .action(async ({ session }, name) => { const { user } = session if (!name) { diff --git a/packages/plugin-eval/src/index.ts b/packages/plugin-eval/src/index.ts index abf5ec6e67..67cb1c421d 100644 --- a/packages/plugin-eval/src/index.ts +++ b/packages/plugin-eval/src/index.ts @@ -152,8 +152,8 @@ export function apply(ctx: Context, config: Config = {}) { }) if (prefix) { - command.shortcut(prefix, { greedy: true, fuzzy: true }) - command.shortcut(prefix + prefix[prefix.length - 1], { greedy: true, fuzzy: true, options: { slient: true } }) + command.shortcut(prefix, { fuzzy: true }) + command.shortcut(prefix + prefix[prefix.length - 1], { fuzzy: true, options: { slient: true } }) } Argv.interpolate('${', '}', (source) => { diff --git a/packages/plugin-tools/src/baidu.ts b/packages/plugin-tools/src/baidu.ts index d1af35d134..1a1b67bf30 100644 --- a/packages/plugin-tools/src/baidu.ts +++ b/packages/plugin-tools/src/baidu.ts @@ -74,8 +74,8 @@ export function apply(ctx: Context, options: BaiduOptions = {}) { ctx.command('tools/baidu ', '使用百度百科搜索') .example('百度一下 最终幻想14') - .shortcut('百度一下', { fuzzy: true, greedy: true }) - .shortcut('百度', { fuzzy: true, greedy: true }) + .shortcut('百度一下', { fuzzy: true }) + .shortcut('百度', { fuzzy: true }) .action(async ({ session }, keyword) => { if (!keyword) return session.execute('baidu -h') const url = URL_SEARCH + encodeURI(keyword) diff --git a/packages/plugin-tools/src/music.ts b/packages/plugin-tools/src/music.ts index 0318d329db..654f0e328c 100644 --- a/packages/plugin-tools/src/music.ts +++ b/packages/plugin-tools/src/music.ts @@ -51,9 +51,9 @@ export function apply(ctx: Context, options: MusicOptions = {}) { // typescript cannot infer type from string templates .option('platform', `-p 点歌平台,目前支持 qq, netease,默认为 ${platform}`, { type: 'string' }) .alias('点歌') - .shortcut('来一首', { fuzzy: true, greedy: true }) - .shortcut('点一首', { fuzzy: true, greedy: true }) - .shortcut('整一首', { fuzzy: true, greedy: true }) + .shortcut('来一首', { fuzzy: true }) + .shortcut('点一首', { fuzzy: true }) + .shortcut('整一首', { fuzzy: true }) .action(async ({ options }, keyword) => { if (!options.platform) options.platform = platform const search = platforms[options.platform] From 126832a4e42c594d2ad561eb8adf4c68eec2c3f9 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sat, 26 Jun 2021 02:57:00 +0800 Subject: [PATCH 12/21] feat(adventure): add more event hooks --- packages/plugin-adventure/src/event.ts | 76 +++++++++----------------- packages/plugin-adventure/src/item.ts | 20 +++++-- packages/plugin-adventure/src/phase.ts | 12 ++-- packages/plugin-adventure/src/utils.ts | 13 +++-- 4 files changed, 56 insertions(+), 65 deletions(-) diff --git a/packages/plugin-adventure/src/event.ts b/packages/plugin-adventure/src/event.ts index 7d976cfe55..423b32e879 100644 --- a/packages/plugin-adventure/src/event.ts +++ b/packages/plugin-adventure/src/event.ts @@ -55,29 +55,10 @@ namespace Event { return `已购入${Item.format(itemMap)},花费 ${+moneyLost.toFixed(1)}¥,余额 ${+user.money.toFixed(1)}¥。` } - export const combine = (events: Event[]): Event => (session) => { - let message: string | void - events.forEach(event => message = event(session) || message) - return message - } - - export const updateTimer = (name: string, hours: Adventurer.Infer, reason = ''): Event => ({ user }) => { - // 替身地藏自动抵消无法交互的效果 - if (name === '$system' && user.warehouse['替身地藏']) { - user.warehouse['替身地藏'] -= 1 - user.avatarAchv += 1 - return `${reason}\n$s 扔出替身地藏躲过了一劫。\n$s 失去了替身地藏(SP)!` - } - - // 疗伤加护自动抵消无法使用物品的效果 - if (name === '$use' && checkTimer('$healing', user)) { - return `${reason}\n河童的秘药瞬间治愈了 $s 手上的伤!` - } - - // 当死亡时将酒品归零 - if (name === '$system') { - user.taste = 0 - } + export const updateTimer = (name: string, hours: Adventurer.Infer, reason = ''): Event => (session) => { + const { app, user } = session + const result = app.bail('adventure/before-timer', name, reason, session) + if (result) return result // 如果是新状态则清除调用提示 if (!checkTimer(name, user)) { @@ -116,22 +97,19 @@ namespace Event { return reason } - export const decreaseLuck = (delta: number, reason: string, preventMessage: string): Event => (session) => { - if (session.user.warehouse['疵痕之护符']) { - session.user.warehouse['疵痕之护符'] -= 1 - session.user.avatarAchv += 1 - return preventMessage - } else { - return updateLuck(-delta, reason)(session) - } - } - export const loseMoney = (value: Adventurer.Infer): Event => ({ user }) => { const loss = Math.min(getValue(value, user), user.money) user.money -= loss return `$s 损失了 ${+loss.toFixed(1)}¥!` } + export const gainMoney = (value: Adventurer.Infer): Event => ({ user }) => { + const gain = Math.min(getValue(value, user), user.money) + user.money += gain + user.wealth += gain + return `$s 获得了 ${+gain.toFixed(1)}¥!` + } + export const gain = (...names: string[]): Event => (session) => { const output: string[] = [] for (const name of names) { @@ -178,22 +156,6 @@ namespace Event { } } - export function lose(items: Item.Pack, reason = '$s 失去了$i!'): Visible { - reason = reason.replace('$i', () => Item.format(items)) - const itemMap = Array.isArray(items) - ? Object.fromEntries(items.map(arg => [arg, 1])) - : items - return (session) => { - const output = [reason] - for (const name in itemMap) { - const result = Item.lose(session, name) - if (result) output.push(result) - } - session.app.emit('adventure/lose', itemMap, session, output) - return output.join('\n') - } - } - const rarities = ['N', 'R', 'SR', 'SSR', 'EX'] as Item.Rarity[] export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { @@ -226,6 +188,20 @@ namespace Event { return output.join('\n') } + export function lose(items: Item.Pack, reason = '$s 失去了$i!'): Visible { + reason = reason.replace('$i', () => Item.format(items)) + const itemMap = Array.isArray(items) ? Item.listToMap(items) : items + return (session) => { + const output = [reason] + for (const name in itemMap) { + const result = Item.lose(session, name) + if (result) output.push(result) + } + session.app.emit('adventure/lose', itemMap, session, output) + return output.join('\n') + } + } + export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { const lostList: string[] = [] let length = 0 @@ -252,6 +228,7 @@ namespace Event { if (result) output.push(result) } output.unshift(`$s 失去了 ${length} 件随机物品:${Item.format(lostList)}!`) + session.app.emit('adventure/lose', Item.listToMap(lostList), session, output) return output.join('\n') } @@ -264,6 +241,7 @@ namespace Event { const result = Item.lose(session, name) if (result) output.push(result) } + session.app.emit('adventure/lose', Item.listToMap(recent), session, output) session.user.recent = [] return output.join('\n') } diff --git a/packages/plugin-adventure/src/item.ts b/packages/plugin-adventure/src/item.ts index 38aa74a3e2..c47c70e4b2 100644 --- a/packages/plugin-adventure/src/item.ts +++ b/packages/plugin-adventure/src/item.ts @@ -90,7 +90,7 @@ namespace Item { const MAX_RECENT_ITEMS = 10 - export function gain(session: Session, name: string, count = 1) { + export function gain(session: Adventurer.Session, name: string, count = 1) { const item = Item.data[name] const output: string[] = [] session.user.gains[name] = (session.user.gains[name] || 0) + count @@ -132,7 +132,7 @@ namespace Item { } } - export function checkOverflow(session: Session, names = Object.keys(session.user.warehouse)) { + export function checkOverflow(session: Adventurer.Session, names = Object.keys(session.user.warehouse)) { const itemMap: Record = {} for (const name of names) { const { maxCount, value } = Item.data[name] @@ -150,7 +150,15 @@ namespace Item { } } - async function toItemMap(argv: Argv) { + export function listToMap(list: string[]) { + const map: Record = {} + for (const name of list) { + map[name] = (map[name] || 0) + 1 + } + return map + } + + async function argvToMap(argv: Argv) { const { args } = argv const itemMap: Record = {} for (let i = 0; i < args.length; i++) { @@ -287,7 +295,7 @@ namespace Item { return output.join('\n') } - const buyMap = await toItemMap(argv) + const buyMap = await argvToMap(argv) if (!buyMap) return let moneyLost = 0 @@ -353,7 +361,7 @@ namespace Item { return output.join('\n') } - const sellMap = await toItemMap(argv) + const sellMap = await argvToMap(argv) if (!sellMap) return const user = session.user @@ -390,7 +398,7 @@ namespace Item { await session.observeUser(Adventurer.fields) const progress = getValue(saleAction, user) if (progress) { - const _meta = session as Session + const _meta = session as Adventurer.Session _meta.user['_skip'] = session._skipAll await Phase.setProgress(_meta.user, progress) return Phase.start(_meta) diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index a6954c041f..5a18125a80 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -38,7 +38,7 @@ export namespace Phase { export const phaseMap: Record> = { '': mainPhase } export const salePlots: Record> = {} - export const userSessionMap: Record, NodeJS.Timer]> = {} + export const userSessionMap: Record = {} export const channelUserMap: Record = {} export const activeUsers = new Set() @@ -60,7 +60,7 @@ export namespace Phase { } } - export function sendEscaped(session: Session, message: string | void, ms?: number) { + export function sendEscaped(session: Adventurer.Session, message: string | void, ms?: number) { if (!message) return message = session.app.chain('adventure/text', message, session) return session.sendQueued(message, ms) @@ -140,7 +140,7 @@ export namespace Phase { return phase || (user.progress = '', null) } - export type Action = (session: Session, state?: S) => Promise + export type Action = (session: Adventurer.Session, state?: S) => Promise const HOOK_PENDING_USE = 4182 const HOOK_PENDING_CHOOSE = 4185 @@ -283,7 +283,7 @@ export namespace Phase { } /** display phase texts */ - export async function print(session: Session, texts: string[], canSkip = true, state = {}) { + export async function print(session: Adventurer.Session, texts: string[], canSkip = true, state = {}) { session._canSkip = canSkip if (!session._skipAll || !session._canSkip) { for (const text of texts || []) { @@ -295,7 +295,7 @@ export namespace Phase { } /** handle events */ - async function epilog(session: Session, events: Event[] = []) { + async function epilog(session: Adventurer.Session, events: Event[] = []) { const hints: string[] = [] for (const event of events || []) { const result = event(session) @@ -356,7 +356,7 @@ export namespace Phase { } } - export async function start(session: Session) { + export async function start(session: Adventurer.Session) { const disposeUser = setState(userSessionMap, session.user.id, session) const disposeChannel = setState(channelUserMap, session.cid, session.user.id) try { diff --git a/packages/plugin-adventure/src/utils.ts b/packages/plugin-adventure/src/utils.ts index da6a6b8aa7..4ef53fee95 100644 --- a/packages/plugin-adventure/src/utils.ts +++ b/packages/plugin-adventure/src/utils.ts @@ -1,5 +1,6 @@ import { User, Database, Context, Command, Argv, TableType, FieldCollector, defineEnumProperty } from 'koishi-core' import {} from 'koishi-plugin-mysql' +import * as Koishi from 'koishi-core' import Achievement from './achv' function createCollector(key: T): FieldCollector { @@ -36,14 +37,16 @@ declare module 'koishi-core' { } interface EventMap { - 'adventure/check'(session: Session, hints: string[]): void + 'adventure/check'(session: Adventurer.Session, hints: string[]): void 'adventure/rank'(name: string): [string, string] - 'adventure/text'(text: string, session: Session): string + 'adventure/text'(text: string, session: Adventurer.Session): string 'adventure/use'(userId: string, progress: string): void 'adventure/before-sell'(itemMap: Record, session: Session): string | undefined - 'adventure/before-use'(item: string, session: Session): string | undefined + 'adventure/before-use'(item: string, session: Adventurer.Session): string | undefined 'adventure/lose'(itemMap: Record, session: Session, hints: string[]): void - 'adventure/ending'(session: Session, id: string, hints: string[]): void + 'adventure/gain'(itemMap: Record, session: Session, hints: string[]): void + 'adventure/before-timer'(name: string, reason: string, session: Adventurer.Session): string | undefined + 'adventure/ending'(session: Adventurer.Session, id: string, hints: string[]): void 'adventure/achieve'(session: Session, achv: Achievement, hints: string[]): void } @@ -127,6 +130,8 @@ export interface Adventurer extends Shopper { export namespace Adventurer { export type Field = keyof Adventurer + export type Session = Koishi.Session + export type Infer = InferFrom]> export const fields: Field[] = [ From 1981c0c45586f7da7169b87d6edfd5272ff44e76 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sat, 26 Jun 2021 14:54:47 +0800 Subject: [PATCH 13/21] feat(adventure): Event.gain() enhancement --- packages/plugin-adventure/src/event.ts | 94 ++++++++++++-------------- packages/plugin-adventure/src/item.ts | 16 ++--- packages/plugin-adventure/src/phase.ts | 4 +- packages/plugin-adventure/src/utils.ts | 20 ++---- 4 files changed, 54 insertions(+), 80 deletions(-) diff --git a/packages/plugin-adventure/src/event.ts b/packages/plugin-adventure/src/event.ts index 423b32e879..a3dc214a5d 100644 --- a/packages/plugin-adventure/src/event.ts +++ b/packages/plugin-adventure/src/event.ts @@ -1,4 +1,4 @@ -import { getValue, Shopper, Adventurer } from './utils' +import { getValue, Adventurer } from './utils' import { User, checkTimer, Session, Logger } from 'koishi-core' import Buff from './buff' import Item from './item' @@ -25,7 +25,7 @@ namespace Event { return output.join('\n') } - export const sell = (itemMap: Readonly>): Visible => ({ user, app }) => { + export const sell = (itemMap: Readonly>): Visible => ({ user, app }) => { let moneyGained = 0 const toValue = app.adventure.createSeller(user) for (const name in itemMap) { @@ -40,7 +40,7 @@ namespace Event { return `已售出${Item.format(itemMap)},获得 ${+moneyGained.toFixed(1)}¥,余额 ${+user.money.toFixed(1)}¥。` } - export const buy = (itemMap: Readonly>): Visible => ({ user, app }) => { + export const buy = (itemMap: Readonly>): Visible => ({ user, app }) => { let moneyLost = 0 const toBid = app.adventure.createBuyer(user) for (const name in itemMap) { @@ -97,6 +97,8 @@ namespace Event { return reason } + // Money + export const loseMoney = (value: Adventurer.Infer): Event => ({ user }) => { const loss = Math.min(getValue(value, user), user.money) user.money -= loss @@ -110,52 +112,39 @@ namespace Event { return `$s 获得了 ${+gain.toFixed(1)}¥!` } - export const gain = (...names: string[]): Event => (session) => { + // Item + + function toItemMap(items: Item.Pack) { + if (!Array.isArray(items)) return items + const map: Record = {} + for (const name of items) { + map[name] = (map[name] || 0) + 1 + } + return map + } + + export const gain = (items: Adventurer.Infer, reason = '$s $n获得了$i$r!'): Visible => (session) => { const output: string[] = [] - for (const name of names) { + const itemMap = toItemMap(getValue(items, session.user)) + for (const name in itemMap) { const isOld = name in session.user.warehouse const { rarity, description } = Item.data[name] session._item = name - const result = Item.gain(session, name) - output.push(`$s ${isOld ? '' : '首次'}获得了${session._item}(${rarity})!`) + const count = itemMap[name] + const result = Item.gain(session, name, count) + output.push(reason + .replace('$n', isOld ? '' : '首次') + .replace('$i', (count > 1 ? count + '×' : '') + session._item) + .replace('$r', `(${rarity})`)) if (!session._skipAll) output.push(description) if (result) output.push(result) } - const result = Item.checkOverflow(session, names) + session.app.emit('adventure/gain', itemMap, session, output) + const result = Item.checkOverflow(session, Object.keys(itemMap)) if (result) output.push(result) return output.join('\n') } - export function gainSingle(name: string, reason: string): Event.Visible - export function gainSingle(name: string, count: Adventurer.Infer): Event.Visible - export function gainSingle(name: string, arg: string | Adventurer.Infer): Visible { - return (session) => { - let output: string - if (typeof arg === 'string') { - session._item = name - const result = Item.gain(session, name) - output = arg.replace('$i', session._item) - if (!session._skipAll) output += '\n' + Item.data[name].description - if (result) output += '\n' + result - } else { - const { warehouse, gains } = session.user - const _count = getValue(arg, session.user) - const isOld = name in gains - if (!isOld) { - warehouse[name] = 0 - gains[name] = 0 - } - warehouse[name] += _count - gains[name] += _count - output = `$s ${isOld ? '' : '首次'}获得了${name}×${_count}!` - if (!session._skipAll) output += '\n' + Item.data[name].description - } - const result = Item.checkOverflow(session, [name]) - if (result) output += '\n' + result - return output - } - } - const rarities = ['N', 'R', 'SR', 'SSR', 'EX'] as Item.Rarity[] export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { @@ -183,23 +172,24 @@ namespace Event { } output.unshift(`$s 获得了 ${_count} 件随机物品:${Item.format(gainListFormatted)}!`) - const result = Item.checkOverflow(session, gainListOriginal) + const itemMap = toItemMap(gainListOriginal) + session.app.emit('adventure/gain', itemMap, session, output) + const result = Item.checkOverflow(session, Object.keys(itemMap)) if (result) output.push(result) return output.join('\n') } - export function lose(items: Item.Pack, reason = '$s 失去了$i!'): Visible { - reason = reason.replace('$i', () => Item.format(items)) - const itemMap = Array.isArray(items) ? Item.listToMap(items) : items - return (session) => { - const output = [reason] - for (const name in itemMap) { - const result = Item.lose(session, name) - if (result) output.push(result) - } - session.app.emit('adventure/lose', itemMap, session, output) - return output.join('\n') + export const lose = (items: Adventurer.Infer, reason = '$s 失去了$i!'): Visible => (session) => { + const itemMap = toItemMap(getValue(items, session.user)) + const output = [reason.replace('$i', () => { + return Item.format(Array.isArray(items) ? items : itemMap) + })] + for (const name in itemMap) { + const result = Item.lose(session, name) + if (result) output.push(result) } + session.app.emit('adventure/lose', itemMap, session, output) + return output.join('\n') } export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { @@ -228,7 +218,7 @@ namespace Event { if (result) output.push(result) } output.unshift(`$s 失去了 ${length} 件随机物品:${Item.format(lostList)}!`) - session.app.emit('adventure/lose', Item.listToMap(lostList), session, output) + session.app.emit('adventure/lose', toItemMap(lostList), session, output) return output.join('\n') } @@ -241,7 +231,7 @@ namespace Event { const result = Item.lose(session, name) if (result) output.push(result) } - session.app.emit('adventure/lose', Item.listToMap(recent), session, output) + session.app.emit('adventure/lose', toItemMap(recent), session, output) session.user.recent = [] return output.join('\n') } diff --git a/packages/plugin-adventure/src/item.ts b/packages/plugin-adventure/src/item.ts index c47c70e4b2..6462bb59ba 100644 --- a/packages/plugin-adventure/src/item.ts +++ b/packages/plugin-adventure/src/item.ts @@ -1,5 +1,5 @@ import { Context, checkTimer, Argv, Session, User, isInteger, Random } from 'koishi-core' -import { getValue, Shopper, Adventurer, ReadonlyUser, Show } from './utils' +import { getValue, Adventurer, ReadonlyUser, Show } from './utils' import Event from './event' import Phase from './phase' import Rank from './rank' @@ -150,14 +150,6 @@ namespace Item { } } - export function listToMap(list: string[]) { - const map: Record = {} - for (const name of list) { - map[name] = (map[name] || 0) + 1 - } - return map - } - async function argvToMap(argv: Argv) { const { args } = argv const itemMap: Record = {} @@ -274,7 +266,7 @@ namespace Item { ctx.command('adv/buy [item] [count]', '购入物品', { maxUsage: 100 }) .checkTimer('$system') .checkTimer('$shop') - .userFields(['id', 'authority', 'warehouse', 'money', 'wealth', 'achievement', 'timers', 'name', 'usage', 'progress', 'gains']) + .userFields(Adventurer.fields) .shortcut('购入', { fuzzy: true }) .shortcut('购买', { fuzzy: true }) .shortcut('买入', { fuzzy: true }) @@ -340,7 +332,7 @@ namespace Item { ctx.command('adv/sell [item] [count]', '售出物品', { maxUsage: 100 }) .checkTimer('$system') .checkTimer('$shop') - .userFields(['id', 'authority', 'warehouse', 'money', 'wealth', 'achievement', 'timers', 'progress', 'name', 'usage', 'gains']) + .userFields(Adventurer.fields) .shortcut('售出', { fuzzy: true }) .shortcut('出售', { fuzzy: true }) .shortcut('卖出', { fuzzy: true }) @@ -396,7 +388,7 @@ namespace Item { if (!user.progress && entries.length === 1 && entries[0][1] === 1 && entries[0][0] in Phase.salePlots) { const saleAction = Phase.salePlots[entries[0][0]] await session.observeUser(Adventurer.fields) - const progress = getValue(saleAction, user) + const progress = getValue(saleAction, user) if (progress) { const _meta = session as Adventurer.Session _meta.user['_skip'] = session._skipAll diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index 5a18125a80..d6ae3a54ef 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -1,5 +1,5 @@ import { Context, User, Session, checkTimer, checkUsage, Logger, Random, interpolate, noop, Time } from 'koishi-core' -import { ReadonlyUser, getValue, Adventurer, Shopper, Show } from './utils' +import { ReadonlyUser, getValue, Adventurer, Show } from './utils' import Event from './event' import {} from 'koishi-plugin-common' import {} from 'koishi-plugin-teach' @@ -36,7 +36,7 @@ export namespace Phase { export const mainPhase: Phase = { items: {} } export const phaseMap: Record> = { '': mainPhase } - export const salePlots: Record> = {} + export const salePlots: Record> = {} export const userSessionMap: Record = {} export const channelUserMap: Record = {} diff --git a/packages/plugin-adventure/src/utils.ts b/packages/plugin-adventure/src/utils.ts index 4ef53fee95..2973a5a83b 100644 --- a/packages/plugin-adventure/src/utils.ts +++ b/packages/plugin-adventure/src/utils.ts @@ -41,11 +41,11 @@ declare module 'koishi-core' { 'adventure/rank'(name: string): [string, string] 'adventure/text'(text: string, session: Adventurer.Session): string 'adventure/use'(userId: string, progress: string): void - 'adventure/before-sell'(itemMap: Record, session: Session): string | undefined + 'adventure/before-sell'(itemMap: Record, session: Adventurer.Session): string | undefined 'adventure/before-use'(item: string, session: Adventurer.Session): string | undefined - 'adventure/lose'(itemMap: Record, session: Session, hints: string[]): void - 'adventure/gain'(itemMap: Record, session: Session, hints: string[]): void 'adventure/before-timer'(name: string, reason: string, session: Adventurer.Session): string | undefined + 'adventure/lose'(itemMap: Record, session: Adventurer.Session, hints: string[]): void + 'adventure/gain'(itemMap: Record, session: Adventurer.Session, hints: string[]): void 'adventure/ending'(session: Adventurer.Session, id: string, hints: string[]): void 'adventure/achieve'(session: Session, achv: Achievement, hints: string[]): void } @@ -80,7 +80,6 @@ User.extend(() => ({ progress: '', phases: [], endings: {}, - avatarAchv: 0, drunkAchv: 0, })) @@ -99,7 +98,7 @@ type InferFrom = T extends (...args: any[]) => any ? never : type DeepReadonly = T extends (...args: any[]) => any ? T : { readonly [P in keyof T]: T[P] extends {} ? DeepReadonly : T[P] } -export interface Shopper { +export interface Adventurer { id: string money: number wealth: number @@ -107,13 +106,6 @@ export interface Shopper { timers: Record gains: Record warehouse: Record -} - -export namespace Shopper { - export type Field = keyof Shopper -} - -export interface Adventurer extends Shopper { name: string flag: number luck: number @@ -122,7 +114,6 @@ export interface Adventurer extends Shopper { progress: string phases: string[] endings: Record - avatarAchv: number drunkAchv: number achievement: string[] } @@ -137,7 +128,7 @@ export namespace Adventurer { export const fields: Field[] = [ 'id', 'money', 'warehouse', 'wealth', 'timers', 'gains', 'flag', 'luck', 'taste', 'recent', 'progress', 'phases', - 'endings', 'usage', 'avatarAchv', 'drunkAchv', 'name', 'achievement', + 'endings', 'usage', 'drunkAchv', 'name', 'achievement', ] } @@ -187,6 +178,7 @@ export namespace Show { }) .action(({ session, args, next }) => { const target = session.content.slice(5) + if (!target) return '请输入要查看的图鉴名称。' const item = data[target] if (!item) return next(() => session.send(`你尚未解锁图鉴「${target}」。`)) if (item[0] === 'redirect') { From 4b01056f872b753bfceeb8b7efcd58f6568e5215 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sun, 27 Jun 2021 04:19:56 +0800 Subject: [PATCH 14/21] feat(adventure): enhance item picker --- packages/plugin-adventure/src/event.ts | 30 ++++++++++------------- packages/plugin-adventure/src/index.ts | 19 +++++++++++---- packages/plugin-adventure/src/item.ts | 33 ++++++++++++++------------ packages/plugin-adventure/src/luck.ts | 22 ++++++++--------- packages/plugin-adventure/src/phase.ts | 8 +++++++ 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/packages/plugin-adventure/src/event.ts b/packages/plugin-adventure/src/event.ts index a3dc214a5d..73957c355b 100644 --- a/packages/plugin-adventure/src/event.ts +++ b/packages/plugin-adventure/src/event.ts @@ -99,13 +99,13 @@ namespace Event { // Money - export const loseMoney = (value: Adventurer.Infer): Event => ({ user }) => { + export const loseMoney = (value: Adventurer.Infer): Visible => ({ user }) => { const loss = Math.min(getValue(value, user), user.money) user.money -= loss return `$s 损失了 ${+loss.toFixed(1)}¥!` } - export const gainMoney = (value: Adventurer.Infer): Event => ({ user }) => { + export const gainMoney = (value: Adventurer.Infer): Visible => ({ user }) => { const gain = Math.min(getValue(value, user), user.money) user.money += gain user.wealth += gain @@ -140,22 +140,20 @@ namespace Event { if (result) output.push(result) } session.app.emit('adventure/gain', itemMap, session, output) - const result = Item.checkOverflow(session, Object.keys(itemMap)) - if (result) output.push(result) return output.join('\n') } const rarities = ['N', 'R', 'SR', 'SSR', 'EX'] as Item.Rarity[] - export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { + export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { const _count = getValue(count, session.user) - const gainListOriginal: string[] = [] - const gainListFormatted: string[] = [] + const itemMap: Record = {} + const gainList: string[] = [] const data = {} as Record for (const rarity of rarities) { - data[rarity] = Item.data[rarity].filter(({ name, condition }) => { - return !exclude.includes(name) && (!condition || condition(session.user, false)) + data[rarity] = Item.data[rarity].filter(({ name, beforePick }) => { + return !exclude.includes(name) && !beforePick?.(session) }).map(({ name }) => name) } @@ -167,15 +165,13 @@ namespace Event { session._item = item const result = Item.gain(session, item) if (result) output.push(result) - gainListOriginal.push(item) - gainListFormatted.push(session._item) + session._gains.add(item) + itemMap[item] = (itemMap[item] || 0) + 1 + gainList.push(session._item) } - output.unshift(`$s 获得了 ${_count} 件随机物品:${Item.format(gainListFormatted)}!`) - const itemMap = toItemMap(gainListOriginal) + output.unshift(`$s 获得了 ${_count} 件随机物品:${Item.format(gainList)}!`) session.app.emit('adventure/gain', itemMap, session, output) - const result = Item.checkOverflow(session, Object.keys(itemMap)) - if (result) output.push(result) return output.join('\n') } @@ -192,7 +188,7 @@ namespace Event { return output.join('\n') } - export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { + export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { const lostList: string[] = [] let length = 0 @@ -222,7 +218,7 @@ namespace Event { return output.join('\n') } - export const loseRecent = (count: Adventurer.Infer): Event => (session) => { + export const loseRecent = (count: Adventurer.Infer): Visible => (session) => { const _count = getValue(count, session.user) const recent = session.user.recent.slice(0, _count) if (!recent.length) return diff --git a/packages/plugin-adventure/src/index.ts b/packages/plugin-adventure/src/index.ts index 0ee46c29f2..44a14b610d 100644 --- a/packages/plugin-adventure/src/index.ts +++ b/packages/plugin-adventure/src/index.ts @@ -1,5 +1,5 @@ import { Context, User, isInteger } from 'koishi-core' -import { Show } from './utils' +import { Adventurer, Show } from './utils' import Achievement from './achv' import Affinity from './affinity' import Buff from './buff' @@ -56,12 +56,23 @@ export function apply(ctx: Context, config?: Config) { ctx.plugin(Show) ctx.command('user.add-item', '添加物品', { authority: 4 }) - .userFields(['warehouse']) - .adminUser(({ target }, item, count = '1') => { + .option('effect', '-e 触发效果') + .userFields(({ options }, fields) => { + if (!options.effect) { + return fields.add('warehouse') + } + for (const field of Adventurer.fields) { + fields.add(field) + } + }) + .adminUser(({ target, options, session }, item, count = '1') => { if (!Item.data[item]) return `未找到物品“${item}”。` - const currentCount = target.warehouse[item] || 0 const nCount = Number(count) if (!isInteger(nCount) || nCount <= 0) return '参数错误。' + if (options.effect) { + return Event.gain({ [item]: nCount })(session).replace(/\$s/g, session.username) + } + const currentCount = target.warehouse[item] || 0 target.warehouse[item] = currentCount + nCount }) diff --git a/packages/plugin-adventure/src/item.ts b/packages/plugin-adventure/src/item.ts index 6462bb59ba..7886b6d689 100644 --- a/packages/plugin-adventure/src/item.ts +++ b/packages/plugin-adventure/src/item.ts @@ -5,7 +5,7 @@ import Phase from './phase' import Rank from './rank' type Note = (user: Pick) => string -type Condition = (user: ReadonlyUser, isLast: boolean) => boolean +type BeforePick = (session: Adventurer.Session) => boolean interface Item { name: string @@ -15,12 +15,12 @@ interface Item { value?: number bid?: number onGain?: Event - onLose?: Event<'usage'> + onLose?: Event + beforePick?: BeforePick lottery?: number fishing?: number plot?: boolean note?: Note - condition?: Condition } namespace Item { @@ -54,10 +54,6 @@ namespace Item { data[name].note = note } - export function condition(name: string, condition: Condition) { - data[name].condition = condition - } - export function onGain(name: string, event: Event) { data[name].onGain = event } @@ -66,20 +62,27 @@ namespace Item { data[name].onLose = event } + export function beforePick(name: string, event: BeforePick) { + data[name].beforePick = event + } + export interface Config { createBuyer?: (user: User.Observed<'timers'>) => (name: string) => number createSeller?: (user: User.Observed<'timers'>) => (name: string) => number } - export function pick(items: Item[], user: ReadonlyUser, isLast = false) { - const weightEntries = items.filter(({ lottery, condition }) => { - return lottery !== 0 && (!condition || condition(user, isLast)) - }).map(({ name, lottery }) => [name, lottery ?? 1] as const) - const weight = Object.fromEntries(weightEntries) - return Item.data[Random.weightedPick(weight)] + type Keys = { [K in keyof O]: O[K] extends T ? K : never }[keyof O] + + export function pick(items: Item[], session: Adventurer.Session, key: Keys, fallback: number) { + const weightEntries = items.map<[string, number]>((item) => { + const probability = item[key] ?? fallback + if (!probability || item.beforePick?.(session)) return [item.name, 0] + return [item.name, probability] + }) + return Item.data[Random.weightedPick(Object.fromEntries(weightEntries))] } - export function lose(session: Session<'usage' | 'warehouse'>, name: string, count = 1) { + export function lose(session: Adventurer.Session, name: string, count = 1) { if (session.user.warehouse[name]) { session.user.warehouse[name] -= count } @@ -132,7 +135,7 @@ namespace Item { } } - export function checkOverflow(session: Adventurer.Session, names = Object.keys(session.user.warehouse)) { + export function checkOverflow(session: Adventurer.Session, names: Iterable = session._gains) { const itemMap: Record = {} for (const name of names) { const { maxCount, value } = Item.data[name] diff --git a/packages/plugin-adventure/src/luck.ts b/packages/plugin-adventure/src/luck.ts index 9994244b65..df1fb74e72 100644 --- a/packages/plugin-adventure/src/luck.ts +++ b/packages/plugin-adventure/src/luck.ts @@ -88,7 +88,7 @@ namespace Luck { if (options.tenTimes) { const output = [`恭喜 ${session.username} 获得了:`] for (let index = 10; index > 0; index--) { - const prize = Item.pick(Item.data[Random.weightedPick(probabilities)], user) + const prize = Item.pick(Item.data[Random.weightedPick(probabilities)], session, 'lottery', 1) output.push(`${prize.name}(${prize.rarity})`) } return output.join('\n') @@ -96,15 +96,15 @@ namespace Luck { const affinity = Affinity.get(user) const maxUsage = Math.floor(affinity / 30) + 5 - let times = maxUsage - getUsage('lottery', user) - if (times <= 0) { + session._lotteryLast = maxUsage - getUsage('lottery', user) + if (session._lotteryLast <= 0) { return '调用次数已达上限。' } - const gainList: string[] = [] + session._gains = new Set() const output: string[] = [] function getPrize(output: string[]) { - times -= 1 + session._lotteryLast -= 1 const weights = user.noSR >= 9 ? allowanceProbabilities : probabilities const rarity = Luck.use(user).weightedPick(weights) if (rarity === 'R' || rarity === 'N') { @@ -112,10 +112,10 @@ namespace Luck { } else { user.noSR = 0 } - const item = Item.pick(Item.data[rarity], user, !times) + const item = Item.pick(Item.data[rarity], session, 'lottery', 1) const { name, description } = item const isOld = item.name in user.warehouse - gainList.push(name) + session._gains.add(name) session._item = name const result = Item.gain(session, item.name) if (options.simple && options.quick) { @@ -131,18 +131,18 @@ namespace Luck { getPrize(output) } else { if (options.simple) output.push(`恭喜 ${session.username} 获得了:`) - while (times && !checkTimer('$lottery', user)) { + while (session._lotteryLast && !checkTimer('$lottery', user)) { getPrize(output) } } - const result = Item.checkOverflow(session, gainList) + const result = Item.checkOverflow(session) if (result) output.push(result) - user.usage.lottery = maxUsage - times + user.usage.lottery = maxUsage - session._lotteryLast session.app.emit('adventure/check', session, output) await user._update() - if (!times) output.push('您本日的抽奖次数已用完,请明天再试吧~') + if (!session._lotteryLast) output.push('您本日的抽奖次数已用完,请明天再试吧~') return output.join('\n').replace(/\$s/g, session.username) }) } diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index d6ae3a54ef..c34ba48a6d 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -17,6 +17,10 @@ declare module 'koishi-core' { _canSkip?: boolean /** 即将获得的道具名 */ _item: string + /** 当前获得的物品列表 */ + _gains: Set + /** 剩余抽卡次数 */ + _lotteryLast: number } } @@ -296,6 +300,8 @@ export namespace Phase { /** handle events */ async function epilog(session: Adventurer.Session, events: Event[] = []) { + session._gains = new Set() + const hints: string[] = [] for (const event of events || []) { const result = event(session) @@ -306,6 +312,8 @@ export namespace Phase { } } + const result = Item.checkOverflow(session) + if (result) hints.push(result) session.app.emit('adventure/check', session, hints) await sendEscaped(session, hints.join('\n')) } From b6c052756478e800d0d9a15e0133be225d8fec22 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 01:21:38 +0800 Subject: [PATCH 15/21] feat(adventure): support phase.prepare() --- packages/plugin-adventure/src/affinity.ts | 4 +-- packages/plugin-adventure/src/event.ts | 33 ++++++++++++----------- packages/plugin-adventure/src/item.ts | 14 +++++----- packages/plugin-adventure/src/luck.ts | 4 +-- packages/plugin-adventure/src/phase.ts | 28 +++++++++---------- packages/plugin-adventure/src/utils.ts | 12 +++++---- packages/plugin-adventure/tsconfig.json | 2 +- 7 files changed, 49 insertions(+), 48 deletions(-) diff --git a/packages/plugin-adventure/src/affinity.ts b/packages/plugin-adventure/src/affinity.ts index e53cedcf35..a88022241b 100644 --- a/packages/plugin-adventure/src/affinity.ts +++ b/packages/plugin-adventure/src/affinity.ts @@ -26,7 +26,7 @@ type AffinityCallback = (user: Pick, date type TheoreticalAffinityCallback = () => AffinityResult type HintCallback = (user: Pick, now: Date) => Iterable -export interface Affinity { +interface Affinity { order: number callback: AffinityCallback theoretical: TheoreticalAffinityCallback @@ -35,7 +35,7 @@ export interface Affinity { const affinityList: Affinity[] = [] const hintList: HintCallback[] = [] -export namespace Affinity { +namespace Affinity { export const fields = new Set(['name', 'affinity']) export const hintFields = new Set() diff --git a/packages/plugin-adventure/src/event.ts b/packages/plugin-adventure/src/event.ts index 73957c355b..e2e4a9eab6 100644 --- a/packages/plugin-adventure/src/event.ts +++ b/packages/plugin-adventure/src/event.ts @@ -1,5 +1,5 @@ import { getValue, Adventurer } from './utils' -import { User, checkTimer, Session, Logger } from 'koishi-core' +import { User, checkTimer, Logger } from 'koishi-core' import Buff from './buff' import Item from './item' import Luck from './luck' @@ -7,10 +7,10 @@ import Phase from './phase' const logger = new Logger('cosmos').extend('event') -type Event = (session: Session) => string | void +type Event = (session: Adventurer.Session, state?: T) => string | void namespace Event { - export type Visible = (session: Session) => string + export type Visible = (session: Adventurer.Session, state?: T) => string export const ending = (id: string): Visible => (session) => { const { app, user } = session @@ -55,7 +55,7 @@ namespace Event { return `已购入${Item.format(itemMap)},花费 ${+moneyLost.toFixed(1)}¥,余额 ${+user.money.toFixed(1)}¥。` } - export const updateTimer = (name: string, hours: Adventurer.Infer, reason = ''): Event => (session) => { + export const updateTimer = (name: string, hours: Adventurer.Infer, reason = ''): Event => (session, state) => { const { app, user } = session const result = app.bail('adventure/before-timer', name, reason, session) if (result) return result @@ -67,7 +67,7 @@ namespace Event { // 生死流转仅对显式状态生效 const scale = reason && checkTimer('$dirt', user) ? 1800000 : 3600000 - checkTimer(name, user, getValue(hours, user) * scale) + checkTimer(name, user, getValue(hours, user, state) * scale) return reason } @@ -123,9 +123,9 @@ namespace Event { return map } - export const gain = (items: Adventurer.Infer, reason = '$s $n获得了$i$r!'): Visible => (session) => { + export const gain = (items: Adventurer.Infer, reason = '$s $n获得了$i$r!'): Visible => (session, state) => { const output: string[] = [] - const itemMap = toItemMap(getValue(items, session.user)) + const itemMap = toItemMap(getValue(items, session.user, state)) for (const name in itemMap) { const isOld = name in session.user.warehouse const { rarity, description } = Item.data[name] @@ -134,7 +134,7 @@ namespace Event { const result = Item.gain(session, name, count) output.push(reason .replace('$n', isOld ? '' : '首次') - .replace('$i', (count > 1 ? count + '×' : '') + session._item) + .replace('$i', session._item + (count > 1 ? '×' + count : '')) .replace('$r', `(${rarity})`)) if (!session._skipAll) output.push(description) if (result) output.push(result) @@ -145,8 +145,8 @@ namespace Event { const rarities = ['N', 'R', 'SR', 'SSR', 'EX'] as Item.Rarity[] - export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { - const _count = getValue(count, session.user) + export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session, state) => { + const _count = getValue(count, session.user, state) const itemMap: Record = {} const gainList: string[] = [] @@ -162,6 +162,7 @@ namespace Event { const rarity = Luck.use(session.user).weightedPick(Luck.probabilities) const index = Math.floor(Math.random() * data[rarity].length) const [item] = data[rarity].splice(index, 1) + if (!data[rarity].length) delete data[rarity] session._item = item const result = Item.gain(session, item) if (result) output.push(result) @@ -175,8 +176,8 @@ namespace Event { return output.join('\n') } - export const lose = (items: Adventurer.Infer, reason = '$s 失去了$i!'): Visible => (session) => { - const itemMap = toItemMap(getValue(items, session.user)) + export const lose = (items: Adventurer.Infer, reason = '$s 失去了$i!'): Visible => (session, state) => { + const itemMap = toItemMap(getValue(items, session.user, state)) const output = [reason.replace('$i', () => { return Item.format(Array.isArray(items) ? items : itemMap) })] @@ -188,7 +189,7 @@ namespace Event { return output.join('\n') } - export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { + export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session, state) => { const lostList: string[] = [] let length = 0 @@ -201,7 +202,7 @@ namespace Event { length += data[rarity].length if (!data[rarity].length) probabilities[rarity] = 0 } - length = Math.min(length, getValue(count, session.user)) + length = Math.min(length, getValue(count, session.user, state)) const output: string[] = [] for (let i = 0; i < length; i += 1) { @@ -218,8 +219,8 @@ namespace Event { return output.join('\n') } - export const loseRecent = (count: Adventurer.Infer): Visible => (session) => { - const _count = getValue(count, session.user) + export const loseRecent = (count: Adventurer.Infer): Visible => (session, state) => { + const _count = getValue(count, session.user, state) const recent = session.user.recent.slice(0, _count) if (!recent.length) return const output = [`$s 失去了最后获得的 ${recent.length} 件物品:${Item.format(recent)}!`] diff --git a/packages/plugin-adventure/src/item.ts b/packages/plugin-adventure/src/item.ts index 7886b6d689..72a1f89c1e 100644 --- a/packages/plugin-adventure/src/item.ts +++ b/packages/plugin-adventure/src/item.ts @@ -1,11 +1,10 @@ -import { Context, checkTimer, Argv, Session, User, isInteger, Random } from 'koishi-core' +import { Context, checkTimer, Argv, User, isInteger, Random } from 'koishi-core' import { getValue, Adventurer, ReadonlyUser, Show } from './utils' import Event from './event' import Phase from './phase' import Rank from './rank' type Note = (user: Pick) => string -type BeforePick = (session: Adventurer.Session) => boolean interface Item { name: string @@ -16,9 +15,8 @@ interface Item { bid?: number onGain?: Event onLose?: Event - beforePick?: BeforePick + beforePick?: Adventurer.Callback lottery?: number - fishing?: number plot?: boolean note?: Note } @@ -58,11 +56,11 @@ namespace Item { data[name].onGain = event } - export function onLose(name: string, event: Event<'usage'>) { + export function onLose(name: string, event: Event) { data[name].onLose = event } - export function beforePick(name: string, event: BeforePick) { + export function beforePick(name: string, event: Adventurer.Callback) { data[name].beforePick = event } @@ -73,9 +71,9 @@ namespace Item { type Keys = { [K in keyof O]: O[K] extends T ? K : never }[keyof O] - export function pick(items: Item[], session: Adventurer.Session, key: Keys, fallback: number) { + export function pick(items: Item[], session: Adventurer.Session, key?: Keys, fallback = 0) { const weightEntries = items.map<[string, number]>((item) => { - const probability = item[key] ?? fallback + const probability = key ? item[key] ?? fallback : 1 if (!probability || item.beforePick?.(session)) return [item.name, 0] return [item.name, probability] }) diff --git a/packages/plugin-adventure/src/luck.ts b/packages/plugin-adventure/src/luck.ts index df1fb74e72..aa2a74be3b 100644 --- a/packages/plugin-adventure/src/luck.ts +++ b/packages/plugin-adventure/src/luck.ts @@ -34,8 +34,8 @@ namespace Luck { export const probabilities: Record = { N: 500, R: 300, - SR: 150, - SSR: 49, + SR: 160, + SSR: 39, EX: 1, SP: 0, } diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index c34ba48a6d..78ce003a15 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -24,22 +24,22 @@ declare module 'koishi-core' { } } -export interface Phase { - prepare?: () => S +interface Phase { + prepare?: Adventurer.Callback texts?: string[] items?: Record> choices?: Phase.Choice[] options?: Phase.ChooseOptions - next?: string | Phase.Action + next?: string | Phase.Action itemsWhenDreamy?: string[] - events?: Event[] + events?: Event[] } -export namespace Phase { +namespace Phase { const logger = new Logger('adventure') export const mainPhase: Phase = { items: {} } - export const phaseMap: Record> = { '': mainPhase } + export const phaseMap: Record = { '': mainPhase } export const salePlots: Record> = {} export const userSessionMap: Record = {} @@ -70,26 +70,26 @@ export namespace Phase { return session.sendQueued(message, ms) } - export function use(name: string, next: string, phase: Adventurer.Infer): void + export function use(name: string, next: string, phase: Phase): void export function use(name: string, next: (user: ReadonlyUser) => string): void - export function use(name: string, next: ReadonlyUser.Infer, phase?: Adventurer.Infer) { + export function use(name: string, next: ReadonlyUser.Infer, phase?: Phase) { mainPhase.items[name] = next if (typeof next === 'string' && phase) { phaseMap[next] = phase } } - export function sell(name: string, next: string, phase: Adventurer.Infer): void + export function sell(name: string, next: string, phase: Phase): void export function sell(name: string, next: (user: ReadonlyUser) => string): void - export function sell(name: string, next: ReadonlyUser.Infer, phase?: Adventurer.Infer) { + export function sell(name: string, next: ReadonlyUser.Infer, phase?: Phase) { salePlots[name] = next if (typeof next === 'string' && phase) { phaseMap[next] = phase } } - export function phase(id: string, phase: Adventurer.Infer) { - return phaseMap[id] = phase + export function phase(id: string, phase: Phase): void { + phaseMap[id] = phase } export const endingMap: Record = {} @@ -140,7 +140,7 @@ export namespace Phase { } export function getPhase(user: Adventurer) { - const phase = getValue(phaseMap[user.progress], user) + const phase = phaseMap[user.progress] return phase || (user.progress = '', null) } @@ -328,7 +328,7 @@ export namespace Phase { logger.debug('%s phase %c', session.userId, user.progress) const { items, choices, next, options, prepare = noop } = phase - const state = prepare() + const state = prepare(session) await print(session, phase.texts, user.phases.includes(user.progress), state) await epilog(session, phase.events) diff --git a/packages/plugin-adventure/src/utils.ts b/packages/plugin-adventure/src/utils.ts index 2973a5a83b..eb2128a56a 100644 --- a/packages/plugin-adventure/src/utils.ts +++ b/packages/plugin-adventure/src/utils.ts @@ -93,7 +93,7 @@ Database.extend('koishi-plugin-mysql', ({ Domain, tables }) => { tables.user.achvCount = () => 'list_length(`achievement`)' }) -type InferFrom = T extends (...args: any[]) => any ? never : T | ((...args: R) => T) +type InferFrom = [T] extends [(...args: any[]) => any] ? never : T | ((...args: R) => T) type DeepReadonly = T extends (...args: any[]) => any ? T : { readonly [P in keyof T]: T[P] extends {} ? DeepReadonly : T[P] } @@ -123,7 +123,9 @@ export namespace Adventurer { export type Session = Koishi.Session - export type Infer = InferFrom]> + export type Callback = (session: Session) => T + + export type Infer = InferFrom, state?: T]> export const fields: Field[] = [ 'id', 'money', 'warehouse', 'wealth', 'timers', 'gains', @@ -135,11 +137,11 @@ export namespace Adventurer { export type ReadonlyUser = DeepReadonly export namespace ReadonlyUser { - export type Infer = InferFrom]> + export type Infer = InferFrom } -export function getValue(source: ReadonlyUser.Infer, user: Pick): U { - return typeof source === 'function' ? (source as any)(user) : source +export function getValue(source: ReadonlyUser.Infer, user: ReadonlyUser, state?: T): U { + return typeof source === 'function' ? (source as any)(user, state) : source } export namespace Show { diff --git a/packages/plugin-adventure/tsconfig.json b/packages/plugin-adventure/tsconfig.json index 74ac2c8ddb..b9a2903071 100644 --- a/packages/plugin-adventure/tsconfig.json +++ b/packages/plugin-adventure/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../../tsconfig.base", "compilerOptions": { - "outDir": "lib", "rootDir": "src", + "outFile": "temp/index.d.ts", }, "include": [ "src", From b22612ce0c9b6801ef61a67c4a42e48e03b875e4 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 04:20:10 +0800 Subject: [PATCH 16/21] feat(adventure): enhance choose options --- build/dtsc.ts | 2 +- docs/.vuepress/config.js | 9 ++- docs/plugins/adventure/events.md | 19 ++++++ docs/plugins/adventure/index.md | 14 ++++ packages/plugin-adventure/src/phase.ts | 94 ++++++++++++++++++-------- 5 files changed, 106 insertions(+), 32 deletions(-) create mode 100644 docs/plugins/adventure/events.md create mode 100644 docs/plugins/adventure/index.md diff --git a/build/dtsc.ts b/build/dtsc.ts index f28ca05e3a..c34e1303fd 100644 --- a/build/dtsc.ts +++ b/build/dtsc.ts @@ -112,7 +112,7 @@ async function bundleAll(names: readonly string[]) { } } -const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-webui'] +const targets = ['koishi-utils', 'koishi-core', 'plugin-adventure', 'plugin-mysql', 'plugin-mongo', 'plugin-webui'] const corePlugins = ['common', 'eval', 'puppeteer', 'teach'] function precedence(name: string) { diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 767d777979..c8578839ab 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -148,7 +148,14 @@ module.exports = { '/plugins/eval/sandbox.md', '/plugins/eval/config.md', ], - }, { + }, ...process.env.NODE_ENV === 'production' ? [] : [{ + text: '冒险系统 (Adventure)', + isGroup: true, + children: [ + '/plugins/adventure/index.md', + '/plugins/adventure/events.md', + ], + }], { text: '其他官方插件', isGroup: true, children: [ diff --git a/docs/plugins/adventure/events.md b/docs/plugins/adventure/events.md new file mode 100644 index 0000000000..dfb0e523dd --- /dev/null +++ b/docs/plugins/adventure/events.md @@ -0,0 +1,19 @@ +--- +sidebarDepth: 2 +--- + +# 事件 + +## Event API + +## 扩展事件 + +### adventure/lose(itemMap, session, hints) + +在剧情中失去物品时触发。因售卖、物品数量溢出所导致的物品失去不计。 + +### adventure/gain(itemMap, session, hints) + +在剧情中获得物品时触发。因抽卡、购买所导致的物品获得不计。 + + diff --git a/docs/plugins/adventure/index.md b/docs/plugins/adventure/index.md new file mode 100644 index 0000000000..30ddc1eda3 --- /dev/null +++ b/docs/plugins/adventure/index.md @@ -0,0 +1,14 @@ +--- +sidebarDepth: 2 +--- + +# 基本用法 + +::: tip 提示 +本章介绍的功能都由 koishi-plugin-adventure 插件提供。 +::: + +::: danger 注意 +koishi-plugin-adventure 现处于早期开发中,API 随时可能发生破坏性变更,同时也不保证此文档的即时性。 +::: + diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index 78ce003a15..c96feee2ff 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -26,13 +26,13 @@ declare module 'koishi-core' { interface Phase { prepare?: Adventurer.Callback - texts?: string[] - items?: Record> + texts?: Adventurer.Infer + items?: Record> choices?: Phase.Choice[] options?: Phase.ChooseOptions - next?: string | Phase.Action + next?: string | Phase.Action itemsWhenDreamy?: string[] - events?: Event[] + events?: Event[] } namespace Phase { @@ -70,7 +70,7 @@ namespace Phase { return session.sendQueued(message, ms) } - export function use(name: string, next: string, phase: Phase): void + export function use(name: string, next: string, phase: Phase): void export function use(name: string, next: (user: ReadonlyUser) => string): void export function use(name: string, next: ReadonlyUser.Infer, phase?: Phase) { mainPhase.items[name] = next @@ -79,7 +79,7 @@ namespace Phase { } } - export function sell(name: string, next: string, phase: Phase): void + export function sell(name: string, next: string, phase: Phase): void export function sell(name: string, next: (user: ReadonlyUser) => string): void export function sell(name: string, next: ReadonlyUser.Infer, phase?: Phase) { salePlots[name] = next @@ -150,21 +150,40 @@ namespace Phase { const HOOK_PENDING_CHOOSE = 4185 export interface Choice { - name?: string - text: string + /** 选项名 */ + name: string + /** 实际显示的文本,默认与 `name` 相同 */ + text?: string + /** 实际显示的序号,设置为 null 将不显示此选项(仍然可通过输入 `name` 的方式触发) */ + order?: string + /** 触发选项后跳转到的下个阶段 */ next: Adventurer.Infer + /** 选项出现的条件 */ when?(user: ReadonlyUser): boolean } export interface ChooseOptions { + template?: string + /** 当仅有一个选项时,跳过此选择支 */ autoSelect?: boolean + /** + * 超时未选后的默认行为 + * - 当未设置时表现为在所有非隐藏分支中随机选择 + * - 如果这里指定为隐藏分支,则提示文本仍然显示为随机选择,但实际效果会进入该隐藏分支 + */ + onTimeout?: string + /** + * 醉酒后的默认行为 + * - 当未设置时表现为在所有非隐藏分支中随机选择 + * - 当设置了 `onTimeout` 时,醉酒状态将失效 + */ + onDrunk?: string onSelect?(name: string, user: Adventurer): void - onDrunk?(user: Adventurer): number } export const choose = (choices: Choice[], options: ChooseOptions = {}): Action => async (session) => { const { user, app } = session - const { autoSelect, onSelect, onDrunk } = options + const { autoSelect, onTimeout, onDrunk, onSelect } = options choices = choices.filter(({ when }) => !when || when(user)) if (choices.length === 1 && autoSelect) { @@ -172,53 +191,68 @@ namespace Phase { return getValue(choices[0].next, user) } - const choiceMap: Record = {} + let fallback: Choice + const orderMap: Record = {} + const choiceMap: Record = {} const output = choices.map((choice, index) => { - choiceMap[index] = choice - return `${String.fromCharCode(65 + index)}. ${choice.text}` - }).join('\n') + const { name, order = String.fromCharCode(65 + index), text = name } = choice + choiceMap[text.toUpperCase()] = choice + if (name === onTimeout) fallback = choice + if (!order) return + choiceMap[order] = choice + orderMap[name] = order + return `${order}. ${text}` + }).filter(Boolean).join('\n') function applyChoice(choice: Choice) { - const { text, next, name = text } = choice + const { name, next } = choice if (onSelect) onSelect(name, user) return getValue(next, user) } - if (checkTimer('$drunk', user)) { + if (fallback && checkTimer('$drunk', user)) { await sendEscaped(session, output) - const index = onDrunk?.(user) ?? Random.int(choices.length) - logger.debug('%s choose drunk %c', session.userId, String.fromCharCode(65 + index)) - user.drunkAchv += 1 - const hints = [`$s 醉迷恍惚,随手选择了 ${String.fromCharCode(65 + index)}。`] + const choice = onDrunk + ? choices.find(c => c.name === onDrunk) + : Random.pick(choices.filter(c => c.order !== null)) + logger.debug('%s choose drunk %c', session.userId, choice.name) + if (onDrunk) user.drunkAchv += 1 + const hints: string[] = [] + if (choice.order !== null) { + hints.push(`$s 醉迷恍惚,随手选择了 ${orderMap[choice.name]}。`) + } app.emit('adventure/check', session, hints) await sendEscaped(session, hints.join('\n')) - return applyChoice(choices[index]) + return applyChoice(choice) } session._skipCurrent = false - await sendEscaped(session, '请输入选项对应的字母继续游戏。若 2 分钟内未选择,则默认随机选择。\n' + output, 0) + const behavior = fallback && fallback.order !== null ? `将自动选择${fallback.name}` : '默认随机选择' + const template = options.template || `请输入选项对应的字母继续游戏。若 2 分钟内未选择,则${behavior}。\n{{ choices }}` + await sendEscaped(session, interpolate(template, { choices: output }), 0) const { predecessors } = app.getSessionState(session) predecessors[HOOK_PENDING_CHOOSE] = null return new Promise((resolve) => { + // 超时行为 const timer = setTimeout(() => { logger.debug('%s choose timeout', session.userId) - _resolve(applyChoice(Random.pick(choices))) + _resolve(applyChoice(fallback ?? Random.pick(choices))) }, 120000) + // 使用物品进入隐藏分支 const disposeListener = app.on('adventure/use', (userId, progress) => { if (userId !== user.id) return _resolve(progress) }) + // 正常选择 const disposeMiddleware = session.middleware((session, next) => { - const message = session.content.trim().toUpperCase() - if (message.length !== 1) return next() - logger.debug('%s choose %c', session.userId, message) - const key = message.charCodeAt(0) - 65 - if (!choiceMap[key]) return next() - _resolve(applyChoice(choiceMap[key])) + const choice = choiceMap[session.content.trim().toUpperCase()] + if (!choice) return next() + logger.debug('%s choose %c', session.userId, choice.name) + _resolve(applyChoice(choice)) }) function _resolve(value: string) { @@ -329,7 +363,7 @@ namespace Phase { const { items, choices, next, options, prepare = noop } = phase const state = prepare(session) - await print(session, phase.texts, user.phases.includes(user.progress), state) + await print(session, getValue(phase.texts, user, state), user.phases.includes(user.progress), state) await epilog(session, phase.events) // resolve next phase From bd54c8656c742bd1d598892a7a09341c21b94d5d Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 14:21:53 +0800 Subject: [PATCH 17/21] feat(adventure): support add-item -p --- build/dtsc.ts | 2 +- packages/plugin-adventure/src/index.ts | 6 +++--- packages/plugin-adventure/src/phase.ts | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/dtsc.ts b/build/dtsc.ts index c34e1303fd..a6b95938b5 100644 --- a/build/dtsc.ts +++ b/build/dtsc.ts @@ -112,7 +112,7 @@ async function bundleAll(names: readonly string[]) { } } -const targets = ['koishi-utils', 'koishi-core', 'plugin-adventure', 'plugin-mysql', 'plugin-mongo', 'plugin-webui'] +const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-webui', 'plugin-adventure'] const corePlugins = ['common', 'eval', 'puppeteer', 'teach'] function precedence(name: string) { diff --git a/packages/plugin-adventure/src/index.ts b/packages/plugin-adventure/src/index.ts index 44a14b610d..4ff478f8c2 100644 --- a/packages/plugin-adventure/src/index.ts +++ b/packages/plugin-adventure/src/index.ts @@ -56,9 +56,9 @@ export function apply(ctx: Context, config?: Config) { ctx.plugin(Show) ctx.command('user.add-item', '添加物品', { authority: 4 }) - .option('effect', '-e 触发效果') + .option('passive', '-p 不触发效果') .userFields(({ options }, fields) => { - if (!options.effect) { + if (options.passive) { return fields.add('warehouse') } for (const field of Adventurer.fields) { @@ -69,7 +69,7 @@ export function apply(ctx: Context, config?: Config) { if (!Item.data[item]) return `未找到物品“${item}”。` const nCount = Number(count) if (!isInteger(nCount) || nCount <= 0) return '参数错误。' - if (options.effect) { + if (!options.passive) { return Event.gain({ [item]: nCount })(session).replace(/\$s/g, session.username) } const currentCount = target.warehouse[item] || 0 diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index c96feee2ff..81fb0892ba 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -38,8 +38,8 @@ interface Phase { namespace Phase { const logger = new Logger('adventure') - export const mainPhase: Phase = { items: {} } - export const phaseMap: Record = { '': mainPhase } + export const mainEntry: Phase = { items: {} } + export const registry: Record = { '': mainEntry } export const salePlots: Record> = {} export const userSessionMap: Record = {} @@ -73,9 +73,9 @@ namespace Phase { export function use(name: string, next: string, phase: Phase): void export function use(name: string, next: (user: ReadonlyUser) => string): void export function use(name: string, next: ReadonlyUser.Infer, phase?: Phase) { - mainPhase.items[name] = next + mainEntry.items[name] = next if (typeof next === 'string' && phase) { - phaseMap[next] = phase + registry[next] = phase } } @@ -84,12 +84,12 @@ namespace Phase { export function sell(name: string, next: ReadonlyUser.Infer, phase?: Phase) { salePlots[name] = next if (typeof next === 'string' && phase) { - phaseMap[next] = phase + registry[next] = phase } } export function phase(id: string, phase: Phase): void { - phaseMap[id] = phase + registry[id] = phase } export const endingMap: Record = {} @@ -140,7 +140,7 @@ namespace Phase { } export function getPhase(user: Adventurer) { - const phase = phaseMap[user.progress] + const phase = registry[user.progress] return phase || (user.progress = '', null) } @@ -608,7 +608,7 @@ namespace Phase { return start(session) } else { if (!item || session._skipAll) return - const next = !userSessionMap[session.user.id] && getValue(mainPhase.items[item], user) + const next = !userSessionMap[session.user.id] && getValue(mainEntry.items[item], user) if (next) { return `物品“${item}”当前不可用,请尝试输入“继续当前剧情”。` } else { From 3215e85dbfa994ca314a7ed9c22107d2332e83dc Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 14:47:45 +0800 Subject: [PATCH 18/21] build: teach plugin have single entry --- build/dtsc.ts | 17 +++++++++++++++-- packages/plugin-adventure/src/affinity.ts | 4 ++-- packages/plugin-teach/tsconfig.json | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/build/dtsc.ts b/build/dtsc.ts index a6b95938b5..94d16e6dcd 100644 --- a/build/dtsc.ts +++ b/build/dtsc.ts @@ -41,6 +41,7 @@ async function bundle(path: string) { const moduleRE = `"(${files.join('|')})"` const internalImport = new RegExp('import\\(' + moduleRE + '\\)\\.', 'g') const internalExport = new RegExp('^ {4}export .+ from ' + moduleRE + ';$') + const internalInject = new RegExp('^declare module ' + moduleRE + ' {$') const importMap: Record> = {} const namespaceMap: Record = {} @@ -76,7 +77,7 @@ async function bundle(path: string) { } }).map((line) => { if (cap = /^declare module ["'](.+)["'] \{( \})?$/.exec(line)) { - if (cap[2]) return + if (cap[2]) return '' identifier = namespaceMap[cap[1]] return identifier ? `declare namespace ${identifier} {` : '' } else if (line === '}') { @@ -87,6 +88,18 @@ async function bundle(path: string) { .replace(internalImport, '') .replace(/import\("index"\)/g, 'import(".")') .replace(/^((module|class|namespace) .+ \{)$/, (_) => `declare ${_}`) + } else { + return '' + } + }).map((line) => { + if (cap = internalInject.exec(line)) { + identifier = '@internal' + return '' + } else if (line === '}') { + return identifier ? identifier = '' : '}' + } else { + if (identifier) line = line.slice(4) + return line.replace(/^((class|namespace) .+ \{)$/, (_) => `export ${_}`) } }).filter(line => line).join(EOL) @@ -112,7 +125,7 @@ async function bundleAll(names: readonly string[]) { } } -const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-webui', 'plugin-adventure'] +const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-teach', 'plugin-webui', 'plugin-adventure'] const corePlugins = ['common', 'eval', 'puppeteer', 'teach'] function precedence(name: string) { diff --git a/packages/plugin-adventure/src/affinity.ts b/packages/plugin-adventure/src/affinity.ts index a88022241b..825808cff7 100644 --- a/packages/plugin-adventure/src/affinity.ts +++ b/packages/plugin-adventure/src/affinity.ts @@ -3,7 +3,7 @@ import { Time, Random } from 'koishi-utils' import Profile from './profile' import Rank from './rank' -declare module 'koishi-plugin-teach/lib/utils' { +declare module 'koishi-plugin-teach' { interface DialogueTest { matchAffinity?: number mismatchAffinity?: number @@ -15,7 +15,7 @@ declare module 'koishi-plugin-teach/lib/utils' { } } -declare module 'koishi-plugin-teach/lib/receiver' { +declare module 'koishi-plugin-teach' { interface SessionState { noAffinityTest?: boolean } diff --git a/packages/plugin-teach/tsconfig.json b/packages/plugin-teach/tsconfig.json index 74ac2c8ddb..b9a2903071 100644 --- a/packages/plugin-teach/tsconfig.json +++ b/packages/plugin-teach/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../../tsconfig.base", "compilerOptions": { - "outDir": "lib", "rootDir": "src", + "outFile": "temp/index.d.ts", }, "include": [ "src", From a7af9c5f883a1f0bfa829bd3796fbda764a07983 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 14:50:31 +0800 Subject: [PATCH 19/21] chore: tweak --- build/dtsc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/dtsc.ts b/build/dtsc.ts index 94d16e6dcd..f82a7961d2 100644 --- a/build/dtsc.ts +++ b/build/dtsc.ts @@ -125,7 +125,7 @@ async function bundleAll(names: readonly string[]) { } } -const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-teach', 'plugin-webui', 'plugin-adventure'] +const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-webui', 'plugin-teach', 'plugin-adventure'] const corePlugins = ['common', 'eval', 'puppeteer', 'teach'] function precedence(name: string) { From 215d5e5a074a5a0a6e97fa1930db52db95750bcb Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 14:59:31 +0800 Subject: [PATCH 20/21] build: common plugin have single entry --- build/dtsc.ts | 14 ++++++++++++-- packages/plugin-common/src/admin.ts | 2 +- packages/plugin-common/src/index.ts | 5 ++--- packages/plugin-common/tsconfig.json | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/build/dtsc.ts b/build/dtsc.ts index f82a7961d2..d67f0d2e63 100644 --- a/build/dtsc.ts +++ b/build/dtsc.ts @@ -125,8 +125,18 @@ async function bundleAll(names: readonly string[]) { } } -const targets = ['koishi-utils', 'koishi-core', 'plugin-mysql', 'plugin-mongo', 'plugin-webui', 'plugin-teach', 'plugin-adventure'] -const corePlugins = ['common', 'eval', 'puppeteer', 'teach'] +const targets = [ + 'koishi-utils', + 'koishi-core', + 'plugin-common', + 'plugin-mysql', + 'plugin-mongo', + 'plugin-webui', + 'plugin-teach', + 'plugin-adventure', +] + +const corePlugins = ['eval', 'puppeteer'] function precedence(name: string) { if (name.startsWith('adapter')) return 1 diff --git a/packages/plugin-common/src/admin.ts b/packages/plugin-common/src/admin.ts index ede464ebb9..a7d86897e5 100644 --- a/packages/plugin-common/src/admin.ts +++ b/packages/plugin-common/src/admin.ts @@ -213,7 +213,7 @@ export interface AdminConfig { generateToken?: () => string } -export default function apply(ctx: Context, config: AdminConfig = {}) { +export function admin(ctx: Context, config: AdminConfig = {}) { if (config.admin === false) return ctx = ctx.select('database') diff --git a/packages/plugin-common/src/index.ts b/packages/plugin-common/src/index.ts index 62a7d5ac76..3fe69e0f3e 100644 --- a/packages/plugin-common/src/index.ts +++ b/packages/plugin-common/src/index.ts @@ -1,10 +1,9 @@ import { Context } from 'koishi-core' -import admin, { AdminConfig } from './admin' +import { admin, AdminConfig } from './admin' import basic, { BasicConfig } from './basic' import handler, { HandlerConfig } from './handler' -export { admin } - +export * from './admin' export * from './basic' export * from './handler' diff --git a/packages/plugin-common/tsconfig.json b/packages/plugin-common/tsconfig.json index 74ac2c8ddb..b9a2903071 100644 --- a/packages/plugin-common/tsconfig.json +++ b/packages/plugin-common/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../../tsconfig.base", "compilerOptions": { - "outDir": "lib", "rootDir": "src", + "outFile": "temp/index.d.ts", }, "include": [ "src", From e1ac88212b4dd3bc0ba62ffb71715efa1ae4a288 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jun 2021 15:05:32 +0800 Subject: [PATCH 21/21] chore: bump versions --- packages/adapter-discord/package.json | 6 +++--- packages/adapter-kaiheila/package.json | 4 ++-- packages/adapter-onebot/package.json | 8 ++++---- packages/adapter-telegram/package.json | 6 +++--- packages/adapter-tomon/package.json | 6 +++--- packages/koishi-core/package.json | 6 +++--- packages/koishi-dev-utils/package.json | 4 ++-- packages/koishi-test-utils/package.json | 6 +++--- packages/koishi-utils/package.json | 4 ++-- packages/koishi/ecosystem.json | 14 +++++++------- packages/koishi/package.json | 6 +++--- packages/plugin-adventure/package.json | 12 ++++++------ packages/plugin-assets/package.json | 2 +- packages/plugin-chat/package.json | 4 ++-- packages/plugin-chess/package.json | 4 ++-- packages/plugin-common/package.json | 8 ++++---- packages/plugin-dice/package.json | 6 +++--- packages/plugin-eval/package.json | 6 +++--- packages/plugin-github/package.json | 4 ++-- packages/plugin-image-search/package.json | 2 +- packages/plugin-mongo/package.json | 2 +- packages/plugin-monitor/package.json | 2 +- packages/plugin-mysql/package.json | 2 +- packages/plugin-puppeteer/package.json | 6 +++--- packages/plugin-rss/package.json | 4 ++-- packages/plugin-schedule/package.json | 4 ++-- packages/plugin-teach/package.json | 6 +++--- packages/plugin-tools/package.json | 4 ++-- packages/plugin-webui/package.json | 4 ++-- 29 files changed, 76 insertions(+), 76 deletions(-) diff --git a/packages/adapter-discord/package.json b/packages/adapter-discord/package.json index c7fe38a90e..a49940e545 100644 --- a/packages/adapter-discord/package.json +++ b/packages/adapter-discord/package.json @@ -1,7 +1,7 @@ { "name": "koishi-adapter-discord", "description": "Discord adapter for Koishi", - "version": "1.2.0", + "version": "1.3.0", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ @@ -28,11 +28,11 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "@types/ws": "^7.4.2", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/adapter-kaiheila/package.json b/packages/adapter-kaiheila/package.json index 0c8372036d..a36698e26b 100644 --- a/packages/adapter-kaiheila/package.json +++ b/packages/adapter-kaiheila/package.json @@ -24,10 +24,10 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/adapter-onebot/package.json b/packages/adapter-onebot/package.json index 0e85d13b6f..a3842798e1 100644 --- a/packages/adapter-onebot/package.json +++ b/packages/adapter-onebot/package.json @@ -1,7 +1,7 @@ { "name": "koishi-adapter-onebot", "description": "CQHTTP adapter for Koishi", - "version": "3.0.9", + "version": "3.1.0", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ @@ -27,16 +27,16 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "@types/ws": "^7.4.2", "get-port": "^5.1.1", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", - "koishi-utils": "^4.2.2", + "koishi-utils": "^4.2.3", "qface": "1.1.0", "ws": "^7.4.5" } diff --git a/packages/adapter-telegram/package.json b/packages/adapter-telegram/package.json index 6ead09b4ab..87004b4cd0 100644 --- a/packages/adapter-telegram/package.json +++ b/packages/adapter-telegram/package.json @@ -28,14 +28,14 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", "form-data": "^4.0.0", - "koishi-utils": "^4.2.2" + "koishi-utils": "^4.2.3" } } diff --git a/packages/adapter-tomon/package.json b/packages/adapter-tomon/package.json index cb1d1f119c..03c0e5c327 100644 --- a/packages/adapter-tomon/package.json +++ b/packages/adapter-tomon/package.json @@ -24,15 +24,15 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "@types/pako": "^1.0.1", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", - "koishi-utils": "^4.2.2", + "koishi-utils": "^4.2.3", "pako": "^2.0.3", "ws": "^7.4.5" } diff --git a/packages/koishi-core/package.json b/packages/koishi-core/package.json index ef0ca19493..7045e6f893 100644 --- a/packages/koishi-core/package.json +++ b/packages/koishi-core/package.json @@ -1,7 +1,7 @@ { "name": "koishi-core", "description": "Core features for Koishi", - "version": "3.12.0", + "version": "3.12.1", "main": "lib/index.js", "typings": "lib/index.d.ts", "engines": { @@ -30,7 +30,7 @@ ], "devDependencies": { "@types/koa": "^2.13.1", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "@koa/router": "^10.0.0", @@ -41,7 +41,7 @@ "fastest-levenshtein": "^1.0.12", "koa": "^2.13.1", "koa-bodyparser": "^4.3.0", - "koishi-utils": "^4.2.2", + "koishi-utils": "^4.2.3", "lru-cache": "^6.0.0" } } diff --git a/packages/koishi-dev-utils/package.json b/packages/koishi-dev-utils/package.json index d937c68d99..2898c60b00 100644 --- a/packages/koishi-dev-utils/package.json +++ b/packages/koishi-dev-utils/package.json @@ -31,7 +31,7 @@ "utilities" ], "peerDependencies": { - "koishi-core": "^3.12.0", - "koishi-utils": "^4.2.2" + "koishi-core": "^3.12.1", + "koishi-utils": "^4.2.3" } } diff --git a/packages/koishi-test-utils/package.json b/packages/koishi-test-utils/package.json index 95808644f8..1eb7937d55 100644 --- a/packages/koishi-test-utils/package.json +++ b/packages/koishi-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "koishi-test-utils", "description": "Test utilities for Koishi", - "version": "6.0.0", + "version": "6.0.1", "main": "lib/index.js", "typings": "lib/index.d.ts", "engines": { @@ -37,8 +37,8 @@ "dependencies": { "chai": "^4.3.4", "chai-as-promised": "^7.1.1", - "koishi-core": "^3.12.0", - "koishi-utils": "^4.2.2" + "koishi-core": "^3.12.1", + "koishi-utils": "^4.2.3" }, "devDependencies": { "@types/chai": "^4.2.17", diff --git a/packages/koishi-utils/package.json b/packages/koishi-utils/package.json index dedeecd1da..60f6df7f8e 100644 --- a/packages/koishi-utils/package.json +++ b/packages/koishi-utils/package.json @@ -1,7 +1,7 @@ { "name": "koishi-utils", "description": "Utilities for Koishi", - "version": "4.2.2", + "version": "4.2.3", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ @@ -29,7 +29,7 @@ ], "devDependencies": { "@types/supports-color": "^8.1.0", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "supports-color": "^8.1.0" diff --git a/packages/koishi/ecosystem.json b/packages/koishi/ecosystem.json index df25533828..3e41f9b926 100644 --- a/packages/koishi/ecosystem.json +++ b/packages/koishi/ecosystem.json @@ -1,6 +1,6 @@ { "koishi-adapter-discord": { - "version": "1.2.0", + "version": "1.3.0", "description": "Discord adapter for Koishi" }, "koishi-adapter-kaiheila": { @@ -8,7 +8,7 @@ "description": "Kaiheila adapter for Koishi" }, "koishi-adapter-onebot": { - "version": "3.0.9", + "version": "3.1.0", "description": "CQHTTP adapter for Koishi" }, "koishi-adapter-telegram": { @@ -20,7 +20,7 @@ "description": "A classic assets provider for Koishi" }, "koishi-plugin-chat": { - "version": "0.5.2", + "version": "0.5.3", "description": "Display and respond to messages for Koishi" }, "koishi-plugin-chess": { @@ -28,7 +28,7 @@ "description": "Playing chess games in Koishi" }, "koishi-plugin-common": { - "version": "4.2.7", + "version": "4.3.0", "description": "Common plugins for Koishi" }, "koishi-plugin-cryptocurrency": { @@ -36,7 +36,7 @@ "description": "Show market prices of cryptocurrencies for Koishi" }, "koishi-plugin-eval": { - "version": "3.2.0", + "version": "3.2.1", "description": "Execute JavaScript and create addons in Koishi" }, "koishi-plugin-github": { @@ -64,11 +64,11 @@ "description": "Create scheduled tasks for Koishi" }, "koishi-plugin-teach": { - "version": "2.1.5", + "version": "2.2.0", "description": "Teach plugin for Koishi" }, "koishi-plugin-tools": { - "version": "2.1.2", + "version": "2.1.3", "description": "Some simple tools for Koishi" }, "koishi-plugin-webui": { diff --git a/packages/koishi/package.json b/packages/koishi/package.json index 1fb8384432..3885ead3e5 100644 --- a/packages/koishi/package.json +++ b/packages/koishi/package.json @@ -1,7 +1,7 @@ { "name": "koishi", "description": "A QQ bot framework based on CQHTTP", - "version": "3.12.0", + "version": "3.12.1", "main": "index.js", "typings": "index.d.ts", "engines": { @@ -40,8 +40,8 @@ "chokidar": "^3.5.1", "js-yaml": "^4.1.0", "kleur": "^4.1.4", - "koishi-core": "^3.12.0", - "koishi-utils": "^4.2.2", + "koishi-core": "^3.12.1", + "koishi-utils": "^4.2.3", "prompts": "^2.4.1" } } diff --git a/packages/plugin-adventure/package.json b/packages/plugin-adventure/package.json index 0e336add75..58281d3cb3 100644 --- a/packages/plugin-adventure/package.json +++ b/packages/plugin-adventure/package.json @@ -1,6 +1,6 @@ { "name": "koishi-plugin-adventure", - "version": "0.2.2", + "version": "0.3.0", "description": "Adventure game framework for Koishi", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -28,13 +28,13 @@ "adventure" ], "peerDependencies": { - "koishi-core": "^3.12.0", - "koishi-plugin-common": "^4.2.7", + "koishi-core": "^3.12.1", + "koishi-plugin-common": "^4.3.0", "koishi-plugin-mysql": "^3.3.2", - "koishi-plugin-teach": "^2.1.5", - "koishi-utils": "^4.2.2" + "koishi-plugin-teach": "^2.2.0", + "koishi-utils": "^4.2.3" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" } } diff --git a/packages/plugin-assets/package.json b/packages/plugin-assets/package.json index d8b5930ec8..3e907be3af 100644 --- a/packages/plugin-assets/package.json +++ b/packages/plugin-assets/package.json @@ -29,7 +29,7 @@ "server" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/plugin-chat/package.json b/packages/plugin-chat/package.json index 14990114bc..6aa1af524d 100644 --- a/packages/plugin-chat/package.json +++ b/packages/plugin-chat/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-chat", "description": "Display and respond to messages for Koishi", - "version": "0.5.2", + "version": "0.5.3", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ @@ -30,7 +30,7 @@ "server" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "koishi-plugin-webui": "^4.6.2" diff --git a/packages/plugin-chess/package.json b/packages/plugin-chess/package.json index 1ec0173d88..dcab8cdb60 100644 --- a/packages/plugin-chess/package.json +++ b/packages/plugin-chess/package.json @@ -29,8 +29,8 @@ "game" ], "peerDependencies": { - "koishi-core": "^3.12.0", + "koishi-core": "^3.12.1", "koishi-plugin-puppeteer": "^2.1.4", - "koishi-utils": "^4.2.2" + "koishi-utils": "^4.2.3" } } diff --git a/packages/plugin-common/package.json b/packages/plugin-common/package.json index e3464cc235..961787aaaa 100644 --- a/packages/plugin-common/package.json +++ b/packages/plugin-common/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-common", "description": "Common plugins for Koishi", - "version": "4.2.7", + "version": "4.3.0", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ @@ -30,10 +30,10 @@ "plugin" ], "peerDependencies": { - "koishi-core": "^3.12.0", - "koishi-utils": "^4.2.2" + "koishi-core": "^3.12.1", + "koishi-utils": "^4.2.3" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" } } diff --git a/packages/plugin-dice/package.json b/packages/plugin-dice/package.json index 9269b35860..5817482a1e 100644 --- a/packages/plugin-dice/package.json +++ b/packages/plugin-dice/package.json @@ -31,10 +31,10 @@ "dice" ], "peerDependencies": { - "koishi-core": "^3.12.0", - "koishi-utils": "^4.2.2" + "koishi-core": "^3.12.1", + "koishi-utils": "^4.2.3" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" } } diff --git a/packages/plugin-eval/package.json b/packages/plugin-eval/package.json index 4b4e76779f..4b918cbfe8 100644 --- a/packages/plugin-eval/package.json +++ b/packages/plugin-eval/package.json @@ -1,6 +1,6 @@ { "name": "koishi-plugin-eval", - "version": "3.2.0", + "version": "3.2.1", "description": "Execute JavaScript and create addons in Koishi", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -33,7 +33,7 @@ "code" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "optionalDependencies": { "@babel/core": "^7.14.0", @@ -50,6 +50,6 @@ "devDependencies": { "@types/babel__core": "^7.1.14", "@types/js-yaml": "^4.0.1", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" } } diff --git a/packages/plugin-github/package.json b/packages/plugin-github/package.json index 1ed119a0f6..c3203d4289 100644 --- a/packages/plugin-github/package.json +++ b/packages/plugin-github/package.json @@ -33,10 +33,10 @@ "koishi-plugin-mongo": "^2.2.3", "koishi-plugin-mysql": "^3.3.2", "koishi-plugin-puppeteer": "^2.1.4", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "@octokit/webhooks-types": "^3.75.2", diff --git a/packages/plugin-image-search/package.json b/packages/plugin-image-search/package.json index c8c8038fa9..bfe68f712b 100644 --- a/packages/plugin-image-search/package.json +++ b/packages/plugin-image-search/package.json @@ -33,7 +33,7 @@ "pixiv" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/plugin-mongo/package.json b/packages/plugin-mongo/package.json index 9e00e8e7d2..03521d1fe1 100644 --- a/packages/plugin-mongo/package.json +++ b/packages/plugin-mongo/package.json @@ -32,7 +32,7 @@ "mysql" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "@types/mongodb": "^3.6.12", diff --git a/packages/plugin-monitor/package.json b/packages/plugin-monitor/package.json index 836cb11bb9..6578371c51 100644 --- a/packages/plugin-monitor/package.json +++ b/packages/plugin-monitor/package.json @@ -17,6 +17,6 @@ }, "homepage": "https://github.com/koishijs/koishi#readme", "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" } } diff --git a/packages/plugin-mysql/package.json b/packages/plugin-mysql/package.json index d8d2741da9..8aa464e7cd 100644 --- a/packages/plugin-mysql/package.json +++ b/packages/plugin-mysql/package.json @@ -31,7 +31,7 @@ "mysql" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "@types/mysql": "^2.15.18", diff --git a/packages/plugin-puppeteer/package.json b/packages/plugin-puppeteer/package.json index 119001b0f0..0be771d483 100644 --- a/packages/plugin-puppeteer/package.json +++ b/packages/plugin-puppeteer/package.json @@ -32,11 +32,11 @@ "@types/pngjs": "^6.0.0", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.3", - "koishi-plugin-eval": "^3.2.0", - "koishi-test-utils": "^6.0.0" + "koishi-plugin-eval": "^3.2.1", + "koishi-test-utils": "^6.0.1" }, "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "chrome-finder": "^1.0.7", diff --git a/packages/plugin-rss/package.json b/packages/plugin-rss/package.json index 6e3f0dafbb..95efc418ff 100644 --- a/packages/plugin-rss/package.json +++ b/packages/plugin-rss/package.json @@ -31,10 +31,10 @@ "rss" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "rss-feed-emitter": "^3.2.2" diff --git a/packages/plugin-schedule/package.json b/packages/plugin-schedule/package.json index f26f2f9c39..cfff3d44d4 100644 --- a/packages/plugin-schedule/package.json +++ b/packages/plugin-schedule/package.json @@ -31,9 +31,9 @@ "devDependencies": { "koishi-plugin-mongo": "^2.2.3", "koishi-plugin-mysql": "^3.3.2", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" } } diff --git a/packages/plugin-teach/package.json b/packages/plugin-teach/package.json index 2ffacfe5cc..8e4ed6d413 100644 --- a/packages/plugin-teach/package.json +++ b/packages/plugin-teach/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-teach", "description": "Teach plugin for Koishi", - "version": "2.1.5", + "version": "2.2.0", "main": "lib/index.js", "typings": "lib/index.d.ts", "engines": { @@ -34,13 +34,13 @@ "conversation" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "koishi-plugin-mongo": "^2.2.3", "koishi-plugin-mysql": "^3.3.2", "koishi-plugin-webui": "^4.6.2", - "koishi-test-utils": "^6.0.0" + "koishi-test-utils": "^6.0.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/plugin-tools/package.json b/packages/plugin-tools/package.json index d0d4d16e47..9ea02bd62d 100644 --- a/packages/plugin-tools/package.json +++ b/packages/plugin-tools/package.json @@ -1,6 +1,6 @@ { "name": "koishi-plugin-tools", - "version": "2.1.2", + "version": "2.1.3", "description": "Some simple tools for Koishi", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -21,7 +21,7 @@ "@types/qrcode": "^1.4.0" }, "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "dependencies": { "axios": "^0.21.1", diff --git a/packages/plugin-webui/package.json b/packages/plugin-webui/package.json index 2030614caa..ecb35732a0 100644 --- a/packages/plugin-webui/package.json +++ b/packages/plugin-webui/package.json @@ -29,7 +29,7 @@ "webui" ], "peerDependencies": { - "koishi-core": "^3.12.0" + "koishi-core": "^3.12.1" }, "devDependencies": { "@fortawesome/fontawesome-free": "^5.15.3", @@ -39,7 +39,7 @@ "echarts-wordcloud": "^2.0.0", "koishi-plugin-mongo": "^2.2.3", "koishi-plugin-mysql": "^3.3.2", - "koishi-test-utils": "^6.0.0", + "koishi-test-utils": "^6.0.1", "sass": "^1.32.12", "vite": "^2.2.4", "vue": "^3.0.11",