diff --git a/.mocharc.js b/.mocharc.js index dbe41bce71..0b04880c4a 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -6,6 +6,7 @@ module.exports = { 'packages/koishi-utils/tests/*.spec.ts', 'packages/koishi-test-utils/tests/*.spec.ts', 'packages/plugin-common/tests/admin.spec.ts', + 'packages/plugin-common/tests/handler.spec.ts', 'packages/plugin-common/tests/sender.spec.ts', 'packages/plugin-eval/tests/*.spec.ts', 'packages/plugin-github/tests/*.spec.ts', diff --git a/package.json b/package.json index 5354dff5ba..91a9515c3e 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,9 @@ "kleur": "^4.1.1", "latest-version": "^5.1.0", "mocha": "^8.1.3", + "nock": "^13.0.4", "open": "^7.2.1", - "ora": "^5.0.0", + "ora": "^5.1.0", "p-map": "^4.0.0", "prompts": "^2.3.2", "rimraf": "^3.0.2", diff --git a/packages/adapter-cqhttp/package.json b/packages/adapter-cqhttp/package.json index 9576e7e21a..efa427b405 100644 --- a/packages/adapter-cqhttp/package.json +++ b/packages/adapter-cqhttp/package.json @@ -31,18 +31,18 @@ "koishi" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { "@types/ms": "^0.7.31", "@types/ws": "^7.2.6", "get-port": "^5.1.1", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "axios": "^0.20.0", "ms": "^2.1.2", "ws": "^7.3.1", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/koishi-core/package.json b/packages/koishi-core/package.json index 03230705a1..d1152edf91 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": "2.2.1", + "version": "2.2.2", "main": "dist/index.js", "typings": "dist/index.d.ts", "engines": { @@ -36,16 +36,16 @@ "devDependencies": { "@types/koa": "^2.11.4", "@types/lru-cache": "^5.1.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "@types/koa-bodyparser": "^4.3.0", "@types/koa-router": "^7.4.1", + "fastest-levenshtein": "^1.0.12", "koa": "^2.13.0", "koa-bodyparser": "^4.3.0", "koa-router": "^9.4.0", - "koishi-utils": "^3.1.4", - "leven": "^3.1.0", + "koishi-utils": "^3.1.5", "lru-cache": "^6.0.0" } } diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts index 9fc967e0df..9df952f29a 100644 --- a/packages/koishi-core/src/app.ts +++ b/packages/koishi-core/src/app.ts @@ -3,7 +3,7 @@ import { Context, Middleware, NextFunction } from './context' import { Group, User, Database } from './database' import { BotOptions, Server } from './server' import { Session } from './session' -import { simplify, defineProperty, Time, Observed, coerce, escapeRegExp } from 'koishi-utils' +import { simplify, defineProperty, Time, Observed, coerce, escapeRegExp, makeArray } from 'koishi-utils' import help from './plugins/help' import shortcut from './plugins/shortcut' import suggest from './plugins/suggest' @@ -105,7 +105,7 @@ export class App extends Context { prepare() { const { nickname, prefix } = this.options - const nicknames = Array.isArray(nickname) ? nickname : nickname ? [nickname] : [] + const nicknames = makeArray(nickname) const prefixes = Array.isArray(prefix) ? prefix : [prefix || ''] this._nameRE = createLeadingRE(nicknames, '@?', '([,,]\\s*|\\s+)') this._prefixRE = createLeadingRE(prefixes) @@ -144,7 +144,7 @@ export class App extends Context { let capture: RegExpMatchArray, atSelf = false // eslint-disable-next-line no-cond-assign - if (capture = message.match(/^\[CQ:reply,id=(-?\d+)\]/)) { + if (capture = message.match(/^\[CQ:reply,id=(-?\d+)\]\s*/)) { session.$reply = +capture[1] message = message.slice(capture[0].length) } diff --git a/packages/koishi-core/src/plugins/suggest.ts b/packages/koishi-core/src/plugins/suggest.ts index b5d68e2ede..42f24a856e 100644 --- a/packages/koishi-core/src/plugins/suggest.ts +++ b/packages/koishi-core/src/plugins/suggest.ts @@ -3,7 +3,7 @@ import { Session } from '../session' import { Message } from './message' import { getCommands } from './help' import { format } from 'util' -import leven from 'leven' +import { distance } from 'fastest-levenshtein' declare module '../session' { interface Session { @@ -62,13 +62,13 @@ Session.prototype.$suggest = function $suggest(this: Session, options: SuggestOp let suggestions: string[], minDistance = Infinity for (const name of items) { - const distance = leven(name, target) - if (name.length <= 2 || distance > name.length * coefficient) continue - if (distance === minDistance) { + const dist = distance(name, target) + if (name.length <= 2 || dist > name.length * coefficient) continue + if (dist === minDistance) { suggestions.push(name) - } else if (distance < minDistance) { + } else if (dist < minDistance) { suggestions = [name] - minDistance = distance + minDistance = dist } } if (!suggestions) return next(() => this.$send(prefix)) @@ -90,8 +90,8 @@ Session.prototype.$suggest = function $suggest(this: Session, options: SuggestOp export default function apply(ctx: Context) { ctx.middleware((session, next) => { - const { $argv, $parsed, $prefix, $appel, messageType } = session - if ($argv || messageType !== 'private' && $prefix === null && !$appel) return next() + const { $argv, $reply, $parsed, $prefix, $appel, messageType } = session + if ($argv || $reply || messageType !== 'private' && $prefix === null && !$appel) return next() const target = $parsed.split(/\s/, 1)[0].toLowerCase() if (!target) return next() diff --git a/packages/koishi-test-utils/package.json b/packages/koishi-test-utils/package.json index ae0ce2b876..3f902cb013 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": "5.0.0", + "version": "5.0.1", "main": "dist/index.js", "typings": "dist/index.d.ts", "engines": { @@ -40,8 +40,8 @@ "dependencies": { "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "koishi-core": "^2.2.1", - "koishi-utils": "^3.1.4" + "koishi-core": "^2.2.2", + "koishi-utils": "^3.1.5" }, "devDependencies": { "@types/chai": "^4.2.12", diff --git a/packages/koishi-test-utils/src/app.ts b/packages/koishi-test-utils/src/app.ts index f69e0110d8..48631eeddf 100644 --- a/packages/koishi-test-utils/src/app.ts +++ b/packages/koishi-test-utils/src/app.ts @@ -1,10 +1,18 @@ import { AppOptions, App, Server, Session, AppStatus } from 'koishi-core' import { assert } from 'chai' +import { Socket } from 'net' +import * as http from 'http' import * as memory from './memory' export const BASE_SELF_ID = 514 -class MockedAppServer extends Server { +interface MockedResponse { + code: number + body: string + headers: Record +} + +class MockedServer extends Server { constructor(app: App) { super(app) this.bots.forEach(bot => bot.ready = true) @@ -13,9 +21,46 @@ class MockedAppServer extends Server { _close() {} async _listen() {} + + get(path: string, headers?: Record) { + return this.receive('GET', path, headers, '') + } + + post(path: string, body: any, headers?: Record) { + return this.receive('POST', path, { + ...headers, + 'content-type': 'application/json', + }, JSON.stringify(body)) + } + + receive(method: string, path: string, headers: Record, content: string) { + const socket = new Socket() + const req = new http.IncomingMessage(socket) + req.url = path + req.method = method + Object.assign(req.headers, headers) + req.headers['content-length'] = '' + content.length + return new Promise((resolve) => { + const res = new http.ServerResponse(req) + let body = '' + res.write = (chunk: any) => { + body += chunk + return true + } + res.end = (chunk: any) => { + res.write(chunk) + const code = res.statusCode + const headers = res.getHeaders() + resolve({ code, body, headers }) + } + this.server.emit('request', req, res) + req.emit('data', content) + req.emit('end') + }) + } } -Server.types.mock = MockedAppServer +Server.types.mock = MockedServer interface MockedAppOptions extends AppOptions { mockStart?: boolean @@ -23,7 +68,7 @@ interface MockedAppOptions extends AppOptions { } export class MockedApp extends App { - public server: MockedAppServer + public server: MockedServer constructor(options: MockedAppOptions = {}) { super({ selfId: BASE_SELF_ID, type: 'mock', ...options }) @@ -90,7 +135,7 @@ export class TestSession { if (length >= count) _resolve() } const dispose = this.app.on('middleware', (session) => { - if (session.$uuid === uuid) _resolve() + if (session.$uuid === uuid) process.nextTick(_resolve) }) const uuid = this.app.receive({ ...this.meta, $send, message }) }) diff --git a/packages/koishi-utils/package.json b/packages/koishi-utils/package.json index e1f62a10f6..b2ba032d1d 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": "3.1.4", + "version": "3.1.5", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -33,7 +33,7 @@ ], "devDependencies": { "@types/supports-color": "^5.3.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "supports-color": "^7.2.0" diff --git a/packages/koishi-utils/src/misc.ts b/packages/koishi-utils/src/misc.ts index 3433c8208e..54b2f2886f 100644 --- a/packages/koishi-utils/src/misc.ts +++ b/packages/koishi-utils/src/misc.ts @@ -51,3 +51,9 @@ export function coerce(val: any) { const { stack } = val instanceof Error ? val : new Error(val as any) return stack } + +export function makeArray(source: T | T[]) { + return Array.isArray(source) ? source + : source === null || source === undefined ? [] + : [source] +} diff --git a/packages/koishi/ecosystem.json b/packages/koishi/ecosystem.json index cb4e923abb..57a6cd433b 100644 --- a/packages/koishi/ecosystem.json +++ b/packages/koishi/ecosystem.json @@ -8,7 +8,7 @@ "description": "Chess Plugin for Koishi" }, "koishi-plugin-common": { - "version": "3.0.0-beta.16", + "version": "3.0.0", "description": "Common plugins for Koishi" }, "koishi-plugin-eval": { @@ -20,7 +20,7 @@ "description": "Execute JavaScript in Koishi" }, "koishi-plugin-github": { - "version": "2.0.2", + "version": "2.0.3", "description": "GitHub webhook plugin for Koishi" }, "koishi-plugin-image-search": { @@ -51,11 +51,11 @@ "description": "Schedule plugin for Koishi" }, "koishi-plugin-status": { - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.15", "description": "Show Status of Koishi" }, "koishi-plugin-teach": { - "version": "1.0.3", + "version": "1.0.4", "description": "Teach plugin for Koishi" }, "koishi-plugin-tools": { diff --git a/packages/koishi/package.json b/packages/koishi/package.json index 3559e58375..2635fe2185 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": "2.2.1", + "version": "2.2.2", "main": "dist/index.js", "typings": "dist/index.d.ts", "engines": { @@ -41,8 +41,8 @@ "cac": "^6.6.1", "kleur": "^4.1.1", "koishi-adapter-cqhttp": "^1.0.3", - "koishi-core": "^2.2.1", - "koishi-plugin-common": "^3.0.0-beta.16", + "koishi-core": "^2.2.2", + "koishi-plugin-common": "^3.0.0", "prompts": "^2.3.2" } } diff --git a/packages/plugin-chess/package.json b/packages/plugin-chess/package.json index 85a0039af6..1beb19af50 100644 --- a/packages/plugin-chess/package.json +++ b/packages/plugin-chess/package.json @@ -33,10 +33,10 @@ "game" ], "peerDependencies": { - "koishi-core": "^2.2.1", + "koishi-core": "^2.2.2", "koishi-plugin-puppeteer": "^1.0.0" }, "dependencies": { - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-common/package.json b/packages/plugin-common/package.json index 3d0270521c..44f67f710d 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": "3.0.0-beta.16", + "version": "3.0.0", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -31,12 +31,12 @@ "plugin" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-common/src/contextify.ts b/packages/plugin-common/src/contextify.ts deleted file mode 100644 index 087e03a277..0000000000 --- a/packages/plugin-common/src/contextify.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Context, getTargetId, Group, User, Session } from 'koishi-core' -import {} from 'koishi-adapter-cqhttp' - -export function apply(ctx: Context) { - ctx.command('contextify ', '在特定上下文中触发指令', { authority: 3 }) - .alias('ctxf') - .userFields(['authority']) - .before(session => !session.$app.database) - .option('user', '-u [id] 使用私聊上下文') - .option('group', '-g [id] 使用群聊上下文') - .option('member', '-m [id] 使用当前群/讨论组成员上下文') - .option('type', '-t [type] 确定发送信息的子类型') - .usage([ - '私聊的子类型包括 other(默认),friend,group。', - '群聊的子类型包括 normal(默认),notice,anonymous。', - '讨论组聊天没有子类型。', - ].join('\n')) - .action(async ({ session, options }, message) => { - if (!message) return '请输入要触发的指令。' - - if (options.member) { - if (session.messageType === 'private') { - return '无法在私聊上下文使用 --member 选项。' - } - options.group = session.groupId - options.user = options.member - } - - if (!options.user && !options.group) { - return '请提供新的上下文。' - } - - const newSession = new Session(ctx.app, session) - newSession.$send = session.$send.bind(session) - newSession.$sendQueued = session.$sendQueued.bind(session) - - delete newSession.groupId - - if (options.group) { - newSession.groupId = +options.group - newSession.messageType = 'group' - newSession.subType = options.type || 'normal' - delete newSession.$group - await newSession.$observeGroup(Group.fields) - } else { - newSession.messageType = 'private' - newSession.subType = options.type || 'other' - } - - if (options.user) { - const id = getTargetId(options.user) - if (!id) return '未指定目标。' - - newSession.userId = id - newSession.sender.userId = id - - delete newSession.$user - const user = await newSession.$observeUser(User.fields) - if (session.$user.authority <= user.authority) { - return '权限不足。' - } - } - - if (options.group) { - const info = await session.$bot.getGroupMemberInfo(newSession.groupId, newSession.userId).catch(() => ({})) - Object.assign(newSession.sender, info) - } else if (options.user) { - const info = await session.$bot.getStrangerInfo(newSession.userId).catch(() => ({})) - Object.assign(newSession.sender, info) - } - - return newSession.$execute(message) - }) -} diff --git a/packages/plugin-common/src/handler.ts b/packages/plugin-common/src/handler.ts index 16aa767cda..a0c4616a03 100644 --- a/packages/plugin-common/src/handler.ts +++ b/packages/plugin-common/src/handler.ts @@ -1,5 +1,5 @@ import { App, Session } from 'koishi-core' -import { simplify } from 'koishi-utils' +import { simplify, makeArray } from 'koishi-utils' import {} from 'koishi-adapter-cqhttp' export interface Respondent { @@ -50,7 +50,7 @@ export default function apply(ctx: App, options: HandlerOptions = {}) { return result !== undefined && session.$bot.setGroupAddRequest(session.flag, session.subType, result) }) - const { blackList = [], respondents = [], throttle, welcome = defaultMessage } = options + const { blackList = [], respondents = [], welcome = defaultMessage } = options blackList.length && ctx.prependMiddleware((session, next) => { for (const word of blackList) { @@ -68,7 +68,7 @@ export default function apply(ctx: App, options: HandlerOptions = {}) { return next() }) - const throttleConfig = !throttle ? [] : Array.isArray(throttle) ? throttle : [throttle] + const throttleConfig = makeArray(options.throttle) if (throttleConfig.length) { const counters: Record = {} for (const { interval, messages } of throttleConfig) { diff --git a/packages/plugin-common/src/index.ts b/packages/plugin-common/src/index.ts index 4e0b94bf86..8f043a7102 100644 --- a/packages/plugin-common/src/index.ts +++ b/packages/plugin-common/src/index.ts @@ -9,11 +9,6 @@ export * from './info' export * from './repeater' export interface Config extends HandlerOptions, RepeaterOptions, SenderConfig { - admin?: false - broadcast?: false - contextify?: false - echo?: false - info?: false debug?: DebugOptions } @@ -24,8 +19,7 @@ export function apply(ctx: Context, config: Config = {}) { ctx.plugin(repeater, config) ctx.plugin(sender, config) - if (config.admin !== false) ctx.plugin(require('./admin')) - if (config.contextify !== false) ctx.plugin(require('./contextify')) - if (config.debug) ctx.plugin(require('./debug'), config.debug) - if (config.info !== false) ctx.plugin(require('./info')) + ctx.plugin(require('./admin')) + ctx.plugin(require('./debug'), config.debug) + ctx.plugin(require('./info')) } diff --git a/packages/plugin-common/src/sender.ts b/packages/plugin-common/src/sender.ts index 265b49be41..6705b87cf7 100644 --- a/packages/plugin-common/src/sender.ts +++ b/packages/plugin-common/src/sender.ts @@ -1,14 +1,12 @@ -import { Context, Group } from 'koishi-core' +import { Context, getTargetId, Group, Session, User } from 'koishi-core' import { CQCode } from 'koishi-utils' export interface SenderConfig { - broadcast?: false - echo?: false + operator?: number } export default function apply(ctx: Context, config: SenderConfig = {}) { - config.broadcast !== false && ctx - .command('broadcast ', '全服广播', { authority: 4 }) + ctx.command('broadcast ', '全服广播', { authority: 4 }) .before(session => !session.$app.database) .option('forced', '-f 无视 silent 标签进行广播') .option('only', '-o 仅向当前 Bot 负责的群进行广播') @@ -26,8 +24,7 @@ export default function apply(ctx: Context, config: SenderConfig = {}) { await session.$bot.broadcast(groups.map(g => g.id), message) }) - config.echo !== false && ctx - .command('echo ', '向当前上下文发送消息', { authority: 2 }) + ctx.command('echo ', '向当前上下文发送消息', { authority: 2 }) .option('anonymous', '-a 匿名发送消息', { authority: 3 }) .option('forceAnonymous', '-A 匿名发送消息', { authority: 3 }) .option('unescape', '-e 发送非转义的消息', { authority: 3 }) @@ -46,4 +43,96 @@ export default function apply(ctx: Context, config: SenderConfig = {}) { return message }) + + const interactions: Record = {} + + config.operator && ctx.command('feedback', '发送反馈信息给作者') + .userFields(['name', 'id']) + .action(async ({ session }, text) => { + if (!text) return '请输入要发送的文本。' + const { $username: name, userId } = session + const nickname = name === '' + userId ? userId : `${name} (${userId})` + const message = `收到来自 ${nickname} 的反馈信息:\n${text}` + const id = await session.$bot.sendPrivateMsg(config.operator, message) + interactions[id] = userId + return '反馈信息发送成功!' + }) + + ctx.middleware((session, next) => { + const { $reply, $parsed } = session + const userId = interactions[$reply] + if (!$parsed || !userId) return next() + return session.$bot.sendPrivateMsg(userId, $parsed) + }) + + ctx.command('contextify ', '在特定上下文中触发指令', { authority: 3 }) + .alias('ctxf') + .userFields(['authority']) + .before(session => !session.$app.database) + .option('user', '-u [id] 使用私聊上下文') + .option('group', '-g [id] 使用群聊上下文') + .option('member', '-m [id] 使用当前群/讨论组成员上下文') + .option('type', '-t [type] 确定发送信息的子类型') + .usage([ + '私聊的子类型包括 other(默认),friend,group。', + '群聊的子类型包括 normal(默认),notice,anonymous。', + '讨论组聊天没有子类型。', + ].join('\n')) + .action(async ({ session, options }, message) => { + if (!message) return '请输入要触发的指令。' + + if (options.member) { + if (session.messageType === 'private') { + return '无法在私聊上下文使用 --member 选项。' + } + options.group = session.groupId + options.user = options.member + } + + if (!options.user && !options.group) { + return '请提供新的上下文。' + } + + const newSession = new Session(ctx.app, session) + newSession.$send = session.$send.bind(session) + newSession.$sendQueued = session.$sendQueued.bind(session) + + delete newSession.groupId + + if (!options.group) { + newSession.messageType = 'private' + newSession.subType = options.type || 'other' + delete newSession.$group + } else if (options.group !== session.groupId) { + newSession.groupId = +options.group + newSession.messageType = 'group' + newSession.subType = options.type || 'normal' + delete newSession.$group + await newSession.$observeGroup(Group.fields) + } + + if (options.user) { + const id = getTargetId(options.user) + if (!id) return '未指定目标。' + + newSession.userId = id + newSession.sender.userId = id + + delete newSession.$user + const user = await newSession.$observeUser(User.fields) + if (session.$user.authority <= user.authority) { + return '权限不足。' + } + } + + if (options.group) { + const info = await session.$bot.getGroupMemberInfo(newSession.groupId, newSession.userId).catch(() => ({})) + Object.assign(newSession.sender, info) + } else if (options.user) { + const info = await session.$bot.getStrangerInfo(newSession.userId).catch(() => ({})) + Object.assign(newSession.sender, info) + } + + return newSession.$execute(message) + }) } diff --git a/packages/plugin-common/tests/admin.spec.ts b/packages/plugin-common/tests/admin.spec.ts index e187a44e52..5a449fd198 100644 --- a/packages/plugin-common/tests/admin.spec.ts +++ b/packages/plugin-common/tests/admin.spec.ts @@ -1,12 +1,12 @@ import { App } from 'koishi-test-utils' import { User, Group } from 'koishi-core' import { install } from '@sinonjs/fake-timers' -import * as admin from '../src/admin' +import * as common from 'koishi-plugin-common' const app = new App({ mockDatabase: true }) const session = app.session(123, 321) -app.plugin(admin) +app.plugin(common) app.command('foo', { maxUsage: 10 }).action(({ session }) => session.$send('bar')) app.command('bar', { minInterval: 1000 }).action(({ session }) => session.$send('foo')) diff --git a/packages/plugin-common/tests/contextify.spec.ts b/packages/plugin-common/tests/contextify.spec.ts deleted file mode 100644 index fe354b14c3..0000000000 --- a/packages/plugin-common/tests/contextify.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { MockedApp } from 'koishi-test-utils' -import { contextify } from '../src' -import 'koishi-database-memory' - -const app = new MockedApp({ database: { memory: {} } }) -const session1 = app.session(123) -const session2 = app.session(123, 654) - -app.plugin(contextify) -app.command('show-context') - .action(({ meta }) => { - return meta.$send(`${meta.userId},${meta.$ctxType},${meta.$ctxId},${meta.$user?.id},${meta.$group?.id}`) - }) - -beforeAll(async () => { - await app.start() - await app.database.getUser(123, 3) - await app.database.getUser(456, 2) - await app.database.getUser(789, 4) - await app.database.getGroup(654, app.selfId) -}) - -test('check input', async () => { - await session1.shouldReply('ctxf -u 456', '请输入要触发的指令。') - await session2.shouldReply('ctxf -m foo show-context', '未指定目标。') - await session1.shouldReply('ctxf show-context', '请提供新的上下文。') - await session1.shouldReply('ctxf -u 789 show-context', '权限不足。') - await session1.shouldReply('ctxf -m 456 show-context', '无法在私聊上下文使用 --member 选项。') -}) - -test('user context', async () => { - await session1.shouldReply('ctxf -u 456 show-context', '456,user,456,456,undefined') - await session1.shouldReply('ctxf -g 654 show-context', '123,group,654,123,654') - await session1.shouldReply('ctxf -d 654 show-context', '123,discuss,654,123,undefined') - await session1.shouldReply('ctxf -u 456 -g 654 show-context', '456,group,654,456,654') - await session1.shouldReply('ctxf -u 456 -d 654 show-context', '456,discuss,654,456,undefined') -}) - -test('group context', async () => { - await session2.shouldReply('ctxf -u 456 show-context', '456,user,456,456,undefined') - await session2.shouldReply('ctxf -m 456 show-context', '456,group,654,456,654') -}) diff --git a/packages/plugin-common/tests/handler.spec.ts b/packages/plugin-common/tests/handler.spec.ts index 70089d6812..06f21c291c 100644 --- a/packages/plugin-common/tests/handler.spec.ts +++ b/packages/plugin-common/tests/handler.spec.ts @@ -1,122 +1,141 @@ +import { expect } from 'chai' +import { fn } from 'jest-mock' +import { Meta } from 'koishi-core' import { App } from 'koishi-test-utils' import { sleep } from 'koishi-utils' -import handler from '../src/handler' -import 'koishi-database-memory' +import * as common from 'koishi-plugin-common' -let app: App +const app = new App({ mockDatabase: true }) +const options: common.Config = {} -describe('type: undefined', () => { - before(async () => { - app = new App() - app.plugin(handler) - await app.start() - }) +app.plugin(common, options) - test('friend add', async () => { - app.receiveFriendRequest(321) - await sleep(0) - app.shouldHaveNoRequests() - }) +before(async () => { + await app.database.getGroup(123, app.selfId) +}) - test('group add', async () => { - app.receiveGroupRequest('add', 321) - await sleep(0) - app.shouldHaveNoRequests() - }) +const receive = (meta: Meta) => { + app.receive(meta) + return sleep(0) +} - test('group invite', async () => { - app.receiveGroupRequest('invite', 321) - await sleep(0) - app.shouldHaveNoRequests() - }) +const receiveFriendRequest = (userId: number) => receive({ + postType: 'request', + requestType: 'friend', + flag: 'flag', + userId, }) -describe('type: string', () => { - before(async () => { - app = new App() - app.plugin(handler, { - handleFriend: 'foo', - handleGroupAdd: 'bar', - handleGroupInvite: 'baz', - }) - await app.start() - }) +const receiveGroupRequest = (subType: 'add' | 'invite', userId: number) => receive({ + postType: 'request', + requestType: 'group', + groupId: 10000, + flag: 'flag', + subType, + userId, +}) - test('friend add', async () => { - app.receiveFriendRequest(321) - await sleep(0) - app.shouldHaveLastRequest('set_friend_add_request', { approve: true, remark: 'foo' }) - }) +const receiveGroupIncrease = (groupId: number, userId: number) => receive({ + postType: 'notice', + noticeType: 'group_increase', + subType: 'invite', + userId, + groupId, +}) - test('group add', async () => { - app.receiveGroupRequest('add', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: false, reason: 'bar' }) - }) +const setFriendAddRequest = app.bots[0].setFriendAddRequest = fn(async () => {}) +const setGroupAddRequest = app.bots[0].setGroupAddRequest = fn(async () => {}) +const sendGroupMsg = app.bots[0].sendGroupMsg = fn(async () => 0) - test('group invite', async () => { - app.receiveGroupRequest('invite', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: false, reason: 'baz' }) - }) -}) +describe('Common Handlers', () => { + it('request handler: undefined', async () => { + setFriendAddRequest.mockClear() + await receiveFriendRequest(321) + expect(setFriendAddRequest.mock.calls).to.have.length(0) -describe('type: boolean', () => { - before(async () => { - app = new App() - app.plugin(handler, { - handleFriend: false, - handleGroupAdd: false, - handleGroupInvite: false, - }) - await app.start() - }) + setGroupAddRequest.mockClear() + await receiveGroupRequest('add', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(0) - test('friend add', async () => { - app.receiveFriendRequest(321) - await sleep(0) - app.shouldHaveLastRequest('set_friend_add_request', { approve: false }) + setGroupAddRequest.mockClear() + await receiveGroupRequest('invite', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(0) }) - test('group add', async () => { - app.receiveGroupRequest('add', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: false }) - }) + it('request handler: string', async () => { + options.onFriend = 'foo' + options.onGroupAdd = 'bar' + options.onGroupInvite = 'baz' - test('group invite', async () => { - app.receiveGroupRequest('invite', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: false }) - }) -}) + setFriendAddRequest.mockClear() + await receiveFriendRequest(321) + expect(setFriendAddRequest.mock.calls).to.have.length(1) + expect(setFriendAddRequest.mock.calls).to.have.shape([['flag', 'foo']]) -describe('type: function', () => { - before(async () => { - app = new App() - app.plugin(handler, { - handleFriend: () => true, - handleGroupAdd: () => true, - handleGroupInvite: () => true, - }) - await app.start() + setGroupAddRequest.mockClear() + await receiveGroupRequest('add', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'add', 'bar']]) + + setGroupAddRequest.mockClear() + await receiveGroupRequest('invite', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'invite', 'baz']]) }) - test('friend add', async () => { - app.receiveFriendRequest(321) - await sleep(0) - app.shouldHaveLastRequest('set_friend_add_request', { approve: true }) + it('request handler: boolean', async () => { + options.onFriend = false + options.onGroupAdd = false + options.onGroupInvite = false + + setFriendAddRequest.mockClear() + await receiveFriendRequest(321) + expect(setFriendAddRequest.mock.calls).to.have.length(1) + expect(setFriendAddRequest.mock.calls).to.have.shape([['flag', false]]) + + setGroupAddRequest.mockClear() + await receiveGroupRequest('add', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'add', false]]) + + setGroupAddRequest.mockClear() + await receiveGroupRequest('invite', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'invite', false]]) }) - test('group add', async () => { - app.receiveGroupRequest('add', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: true }) + it('request handler: function', async () => { + options.onFriend = () => true + options.onGroupAdd = () => true + options.onGroupInvite = () => true + + setFriendAddRequest.mockClear() + await receiveFriendRequest(321) + expect(setFriendAddRequest.mock.calls).to.have.length(1) + expect(setFriendAddRequest.mock.calls).to.have.shape([['flag', true]]) + + setGroupAddRequest.mockClear() + await receiveGroupRequest('add', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'add', true]]) + + setGroupAddRequest.mockClear() + await receiveGroupRequest('invite', 321) + expect(setGroupAddRequest.mock.calls).to.have.length(1) + expect(setGroupAddRequest.mock.calls).to.have.shape([['flag', 'invite', true]]) }) - test('group invite', async () => { - app.receiveGroupRequest('invite', 321) - await sleep(0) - app.shouldHaveLastRequest('set_group_add_request', { approve: true }) + it('welcome', async () => { + sendGroupMsg.mockClear() + await receiveGroupIncrease(321, 456) + expect(sendGroupMsg.mock.calls).to.have.length(0) + + sendGroupMsg.mockClear() + await receiveGroupIncrease(123, app.selfId) + expect(sendGroupMsg.mock.calls).to.have.length(0) + + sendGroupMsg.mockClear() + await receiveGroupIncrease(123, 456) + expect(sendGroupMsg.mock.calls).to.have.length(1) }) }) diff --git a/packages/plugin-common/tests/sender.spec.ts b/packages/plugin-common/tests/sender.spec.ts index 6df5043303..67da45de55 100644 --- a/packages/plugin-common/tests/sender.spec.ts +++ b/packages/plugin-common/tests/sender.spec.ts @@ -1,36 +1,90 @@ import { App } from 'koishi-test-utils' import { fn } from 'jest-mock' import { expect } from 'chai' -import sender from '../src/sender' +import { StrangerInfo } from 'koishi-core' +import { GroupMemberInfo } from 'koishi-adapter-cqhttp' +import * as common from 'koishi-plugin-common' const app = new App({ mockDatabase: true }) -const session = app.session(123) +const session1 = app.session(123) +const session2 = app.session(123, 456) -app.plugin(sender) +app.plugin(common, { + operator: 999, +}) + +app.command('show-context') + .userFields(['id']) + .groupFields(['id']) + .action(({ session }) => { + return `${session.userId},${session.$user?.id},${session.$group?.id}` + }) before(async () => { await app.database.getUser(123, 4) - await app.database.getGroup(456, 514) + await app.database.getUser(456, 3) + await app.database.getUser(789, 5) + await app.database.getGroup(456, app.selfId) }) describe('Sender Commands', () => { it('echo', async () => { - await session.shouldReply('echo', '请输入要发送的文本。') - await session.shouldReply('echo foo', 'foo') - await session.shouldReply('echo -e []', '[]') - await session.shouldReply('echo -A foo', '[CQ:anonymous]foo') - await session.shouldReply('echo -a foo', '[CQ:anonymous,ignore=true]foo') + await session1.shouldReply('echo', '请输入要发送的文本。') + await session1.shouldReply('echo foo', 'foo') + await session1.shouldReply('echo -e []', '[]') + await session1.shouldReply('echo -A foo', '[CQ:anonymous]foo') + await session1.shouldReply('echo -a foo', '[CQ:anonymous,ignore=true]foo') }) it('broadcast', async () => { const sendGroupMsg = app.bots[0].sendGroupMsg = fn() - await session.shouldReply('broadcast', '请输入要发送的文本。') + await session1.shouldReply('broadcast', '请输入要发送的文本。') expect(sendGroupMsg.mock.calls).to.have.length(0) - await session.shouldNotReply('broadcast foo') + await session1.shouldNotReply('broadcast foo') expect(sendGroupMsg.mock.calls).to.have.length(1) - await session.shouldNotReply('broadcast -o foo') + await session1.shouldNotReply('broadcast -o foo') expect(sendGroupMsg.mock.calls).to.have.length(2) - await session.shouldNotReply('broadcast -of foo') + await session1.shouldNotReply('broadcast -of foo') expect(sendGroupMsg.mock.calls).to.have.length(3) }) + + it('feedback', async () => { + const sendPrivateMsg = app.bots[0].sendPrivateMsg = fn(async () => 1000) + await session1.shouldReply('feedback', '请输入要发送的文本。') + expect(sendPrivateMsg.mock.calls).to.have.length(0) + await session1.shouldReply('feedback foo', '反馈信息发送成功!') + expect(sendPrivateMsg.mock.calls).to.have.length(1) + expect(sendPrivateMsg.mock.calls).to.have.shape([[999, '收到来自 123 的反馈信息:\nfoo']]) + + sendPrivateMsg.mockClear() + await session1.shouldNotReply('bar') + expect(sendPrivateMsg.mock.calls).to.have.length(0) + await session1.shouldNotReply('[CQ:reply,id=1000] bar') + expect(sendPrivateMsg.mock.calls).to.have.length(1) + expect(sendPrivateMsg.mock.calls).to.have.shape([[123, 'bar']]) + }) + + describe('Contextify', () => { + app.bots[0].getStrangerInfo = fn(async () => ({} as StrangerInfo)) + app.bots[0].getGroupMemberInfo = fn(async () => ({} as GroupMemberInfo)) + + it('check input', async () => { + await session1.shouldReply('ctxf -u 456', '请输入要触发的指令。') + await session2.shouldReply('ctxf -m foo show-context', '未指定目标。') + await session1.shouldReply('ctxf show-context', '请提供新的上下文。') + await session1.shouldReply('ctxf -u 789 show-context', '权限不足。') + await session1.shouldReply('ctxf -m 456 show-context', '无法在私聊上下文使用 --member 选项。') + }) + + it('user context', async () => { + await session1.shouldReply('ctxf -u 456 show-context', '456,456,undefined') + await session1.shouldReply('ctxf -g 456 show-context', '123,123,456') + await session1.shouldReply('ctxf -u 456 -g 456 show-context', '456,456,456') + }) + + it('group context', async () => { + await session2.shouldReply('ctxf -u 456 show-context', '456,456,undefined') + await session2.shouldReply('ctxf -m 456 show-context', '456,456,456') + }) + }) }) diff --git a/packages/plugin-common/tests/welcome.spec.ts b/packages/plugin-common/tests/welcome.spec.ts deleted file mode 100644 index 6c35d10e8c..0000000000 --- a/packages/plugin-common/tests/welcome.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { MockedApp } from 'koishi-test-utils' -import { Meta } from 'koishi-core' -import { sleep } from 'koishi-utils' -import { welcome } from '../src' -import 'koishi-database-memory' - -const shared: Meta = { - postType: 'notice', - noticeType: 'group_increase', - subType: 'invite', - groupId: 123, - userId: 456, -} - -it('basic support', async () => { - const app = new MockedApp() - app.plugin(welcome) - - app.receive(shared) - await sleep(0) - app.shouldHaveLastRequest('send_group_msg', { groupId: 123, message: `欢迎新大佬 [CQ:at,qq=456]!` }) - - app.receive({ ...shared, userId: app.selfId }) - await sleep(0) - app.shouldHaveNoRequests() -}) - -it('check assignee', async () => { - const app = new MockedApp({ database: { memory: {} } }) - app.plugin(welcome, 'welcome') - - await app.start() - await app.database.getGroup(123, app.selfId) - - app.receive({ ...shared, groupId: 321 }) - await sleep(0) - app.shouldHaveNoRequests() - - app.receive(shared) - await sleep(0) - app.shouldHaveLastRequest('send_group_msg', { groupId: 123, message: 'welcome' }) - - await app.stop() -}) diff --git a/packages/plugin-eval-addons/package.json b/packages/plugin-eval-addons/package.json index 49d16dcf94..b53e5a2ffd 100644 --- a/packages/plugin-eval-addons/package.json +++ b/packages/plugin-eval-addons/package.json @@ -36,13 +36,13 @@ "code" ], "peerDependencies": { - "koishi-core": "^2.2.1", + "koishi-core": "^2.2.2", "koishi-plugin-eval": "^2.0.1" }, "dependencies": { "js-yaml": "^3.14.0", "json5": "^2.1.3", - "koishi-utils": "^3.1.4", + "koishi-utils": "^3.1.5", "simple-git": "^2.20.1", "typescript": "^4.0.2" }, diff --git a/packages/plugin-eval/package.json b/packages/plugin-eval/package.json index f6460d250f..d1964ba7ce 100644 --- a/packages/plugin-eval/package.json +++ b/packages/plugin-eval/package.json @@ -37,12 +37,12 @@ "code" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-github/package.json b/packages/plugin-github/package.json index b3826402c3..2167e9317d 100644 --- a/packages/plugin-github/package.json +++ b/packages/plugin-github/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-github", "description": "GitHub webhook plugin for Koishi", - "version": "2.0.2", + "version": "2.0.3", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -33,14 +33,15 @@ "webhook" ], "devDependencies": { - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2", + "koishi-plugin-puppeteer": "^1.0.0" }, "dependencies": { "@octokit/webhooks": "^7.11.2", "axios": "^0.20.0", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-github/src/events.ts b/packages/plugin-github/src/events.ts index 1b9f3aebeb..1d28126cd0 100644 --- a/packages/plugin-github/src/events.ts +++ b/packages/plugin-github/src/events.ts @@ -97,6 +97,11 @@ export interface ReplyPayloads { link?: string react?: string reply?: [url: string, params?: Record] + shot?: { + url: string + selector: string + padding?: number[] + } } type Payload = GetWebhookPayloadTypeFromEvent['payload'] @@ -109,23 +114,33 @@ export function addListeners(on: (event: T, handler: E .replace(/\n\s*\n/g, '\n') } + interface CommantReplyPayloads extends ReplyPayloads { + padding?: number[] + } + type CommentEvent = 'commit_comment' | 'issue_comment' | 'pull_request_review_comment' - type CommandHandler = (payload: Payload) => [target: string, replies: ReplyPayloads] + type CommantHandler = (payload: Payload) => [target: string, replies: CommantReplyPayloads] - function onComment(event: E, handler: CommandHandler) { + function onComment(event: E, handler: CommantHandler) { on(event as CommentEvent, (payload) => { const { user, body, html_url, url } = payload.comment - if (user.type === 'bot') return + if (user.type === 'Bot') return const [target, replies] = handler(payload) if (payload.action === 'deleted') { return [`[GitHub] ${user.login} deleted a comment on ${target}`] } + const index = html_url.indexOf('#') const operation = payload.action === 'created' ? 'commented' : 'edited a comment' return [`[GitHub] ${user.login} ${operation} on ${target}\n${formatMarkdown(body)}`, { link: html_url, react: url + `/reactions`, + shot: { + url: html_url.slice(0, index), + selector: html_url.slice(index), + padding: replies.padding, + }, ...replies, }] }) @@ -151,13 +166,14 @@ export function addListeners(on: (event: T, handler: E const type = issue['pull_request'] ? 'pull request' : 'issue' return [`${type} ${full_name}#${number}`, { reply: [comments_url], + padding: [16, 16, 16, 88], }] }) on('issues.opened', ({ repository, issue }) => { const { full_name } = repository const { user, url, html_url, comments_url, title, body, number } = issue - if (user.type === 'bot') return + if (user.type === 'Bot') return return [[ `[GitHub] ${user.login} opened an issue ${full_name}#${number}`, @@ -173,7 +189,7 @@ export function addListeners(on: (event: T, handler: E on('issues.closed', ({ repository, issue }) => { const { full_name } = repository const { user, url, html_url, comments_url, title, number } = issue - if (user.type === 'bot') return + if (user.type === 'Bot') return return [`[GitHub] ${user.login} closed issue ${full_name}#${number}\n${title}`, { link: html_url, @@ -196,7 +212,7 @@ export function addListeners(on: (event: T, handler: E const { full_name } = repository const { number, comments_url } = pull_request const { user, html_url, body } = review - if (user.type === 'bot') return + if (user.type === 'Bot') return return [[ `[GitHub] ${user.login} reviewed pull request ${full_name}#${number}`, @@ -204,12 +220,12 @@ export function addListeners(on: (event: T, handler: E ].join('\n'), { link: html_url, reply: [comments_url] }] }) - on('pull_request.closed', ({ repository, pull_request }) => { + on('pull_request.closed', ({ repository, pull_request, sender }) => { const { full_name } = repository - const { user, html_url, issue_url, comments_url, title, number } = pull_request - if (user.type === 'bot') return + const { html_url, issue_url, comments_url, title, number, merged } = pull_request - return [`[GitHub] ${user.login} closed pull request ${full_name}#${number}\n${title}`, { + const type = merged ? 'merged' : 'closed' + return [`[GitHub] ${sender.login} ${type} pull request ${full_name}#${number}\n${title}`, { link: html_url, react: issue_url + '/reactions', reply: [comments_url], @@ -219,7 +235,7 @@ export function addListeners(on: (event: T, handler: E on('pull_request.opened', ({ repository, pull_request }) => { const { full_name, owner } = repository const { user, html_url, issue_url, comments_url, title, base, head, body, number } = pull_request - if (user.type === 'bot') return + if (user.type === 'Bot') return const prefix = new RegExp(`^${owner.login}:`) const baseLabel = base.label.replace(prefix, '') diff --git a/packages/plugin-github/src/index.ts b/packages/plugin-github/src/index.ts index 562e6315d6..f1a8397898 100644 --- a/packages/plugin-github/src/index.ts +++ b/packages/plugin-github/src/index.ts @@ -2,10 +2,11 @@ /* eslint-disable quote-props */ import { Context, Session } from 'koishi-core' -import { camelize, CQCode, defineProperty, Time } from 'koishi-utils' +import { camelize, CQCode, defineProperty, Logger, Time } from 'koishi-utils' import { encode } from 'querystring' -import { addListeners, defaultEvents, EventConfig, ReplyPayloads } from './events' +import { addListeners, defaultEvents, ReplyPayloads } from './events' import { Config, GitHub } from './server' +import {} from 'koishi-plugin-puppeteer' export * from './server' @@ -41,7 +42,10 @@ export function apply(ctx: Context, config: Config = {}) { router.get(config.authorize, async (ctx) => { const targetId = parseInt(ctx.query.state) - if (Number.isNaN(targetId)) throw new Error('Invalid targetId') + if (Number.isNaN(targetId)) { + ctx.body = 'Invalid targetId' + return ctx.status = 400 + } const { code, state } = ctx.query const data = await github.getTokens({ code, state, redirect_uri: redirect }) await database.setUser(targetId, { @@ -76,8 +80,38 @@ export function apply(ctx: Context, config: Config = {}) { const replyHandlers: ReplyHandlers = { link: (url, session) => session.$send(url), - react: (url, session, content) => github.request(url, session, { content }, 'application/vnd.github.squirrel-girl-preview'), - reply: ([url, params], session, content) => github.request(url, session, { ...params, body: formatReply(content) }), + react: (url, session, content) => github.post({ + url, + session, + body: { content }, + headers: { accept: 'application/vnd.github.squirrel-girl-preview' }, + }), + reply: ([url, params], session, content) => github.post({ + url, + session, + body: { ...params, body: formatReply(content) }, + }), + async shot({ url, selector, padding = [] }, session) { + const page = await app.browser.newPage() + let buffer: Buffer + try { + await page.goto(url) + const el = await page.$(selector) + const clip = await el.boundingBox() + const [top = 0, right = 0, bottom = 0, left = 0] = padding + clip.x -= left + clip.y -= top + clip.width += left + right + clip.height += top + bottom + buffer = await page.screenshot({ clip }) + } catch (error) { + new Logger('puppeteer').warn(error) + return session.$send('截图失败。') + } finally { + await page.close() + } + return session.$send(`[CQ:image,file=base64://${buffer.toString('base64')}]`) + }, } const interactions: Record = {} @@ -116,7 +150,7 @@ export function apply(ctx: Context, config: Config = {}) { }) addListeners((event, handler) => { - const base = camelize(event.split('.', 1)[0]) as keyof EventConfig + const base = camelize(event.split('.', 1)[0]) github.on(event, async (callback) => { const { repository } = callback.payload diff --git a/packages/plugin-github/src/server.ts b/packages/plugin-github/src/server.ts index 78e81f7527..975faabc32 100644 --- a/packages/plugin-github/src/server.ts +++ b/packages/plugin-github/src/server.ts @@ -42,6 +42,13 @@ export interface OAuth { scope: string } +interface PostOptions { + url: string + session: ReplySession + body: any + headers?: Record +} + type ReplySession = Session<'ghAccessToken' | 'ghRefreshToken'> const logger = new Logger('github') @@ -62,13 +69,15 @@ export class GitHub extends Webhooks { return data } - async _request(url: string, session: ReplySession, params: any, accept: string) { - logger.debug('POST', url, params) - await axios.post(url, params, { + async _request(options: PostOptions) { + const { url, session, body, headers } = options + logger.debug('POST', url, body) + await axios.post(url, body, { timeout: this.config.requestTimeout, headers: { - accept, + accept: 'application/vnd.github.v3+json', authorization: `token ${session.$user.ghAccessToken}`, + ...headers, }, }) } @@ -80,13 +89,14 @@ export class GitHub extends Webhooks { return session.$execute({ command: 'github', args: [name] }) } - async request(url: string, session: ReplySession, params: any, accept = 'application/vnd.github.v3+json') { + async post(options: PostOptions) { + const { session } = options if (!session.$user.ghAccessToken) { - return this.authorize(session, '如果想使用此功能,请对机器人进行授权。输入你的 GitHub 用户名。') + return this.authorize(session, '要使用此功能,请对机器人进行授权。输入你的 GitHub 用户名。') } try { - return await this._request(url, session, params, accept) + return await this._request(options) } catch (error) { const { response } = error as AxiosError if (response?.status !== 401) { @@ -102,12 +112,12 @@ export class GitHub extends Webhooks { }) session.$user.ghAccessToken = data.access_token session.$user.ghRefreshToken = data.refresh_token - } catch (error) { + } catch { return this.authorize(session, '令牌已失效,需要重新授权。输入你的 GitHub 用户名。') } try { - await this._request(url, session, params, accept) + await this._request(options) } catch (error) { logger.warn(error) return session.$send('发送失败。') diff --git a/packages/plugin-github/tests/fixtures/issue_comment.created.3.json b/packages/plugin-github/tests/fixtures/issue_comment.created.3.json new file mode 100644 index 0000000000..e125bd761f --- /dev/null +++ b/packages/plugin-github/tests/fixtures/issue_comment.created.3.json @@ -0,0 +1,225 @@ +{ + "action": "created", + "issue": { + "url": "https://api.github.com/repos/koishijs/koishi/issues/101", + "repository_url": "https://api.github.com/repos/koishijs/koishi", + "labels_url": "https://api.github.com/repos/koishijs/koishi/issues/101/labels{/name}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/issues/101/comments", + "events_url": "https://api.github.com/repos/koishijs/koishi/issues/101/events", + "html_url": "https://github.com/koishijs/koishi/pull/101", + "id": 696725360, + "node_id": "MDExOlB1bGxSZXF1ZXN0NDgyNzY4OTA2", + "number": 101, + "title": "来壮大列表了(不是", + "user": { + "login": "arily", + "id": 10007589, + "node_id": "MDQ6VXNlcjEwMDA3NTg5", + "avatar_url": "https://avatars1.githubusercontent.com/u/10007589?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/arily", + "html_url": "https://github.com/arily", + "followers_url": "https://api.github.com/users/arily/followers", + "following_url": "https://api.github.com/users/arily/following{/other_user}", + "gists_url": "https://api.github.com/users/arily/gists{/gist_id}", + "starred_url": "https://api.github.com/users/arily/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/arily/subscriptions", + "organizations_url": "https://api.github.com/users/arily/orgs", + "repos_url": "https://api.github.com/users/arily/repos", + "events_url": "https://api.github.com/users/arily/events{/privacy}", + "received_events_url": "https://api.github.com/users/arily/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 1, + "created_at": "2020-09-09T11:18:01Z", + "updated_at": "2020-09-09T11:19:48Z", + "closed_at": null, + "author_association": "NONE", + "active_lock_reason": null, + "pull_request": { + "url": "https://api.github.com/repos/koishijs/koishi/pulls/101", + "html_url": "https://github.com/koishijs/koishi/pull/101", + "diff_url": "https://github.com/koishijs/koishi/pull/101.diff", + "patch_url": "https://github.com/koishijs/koishi/pull/101.patch" + }, + "body": "套壳框架xd", + "performed_via_github_app": null + }, + "comment": { + "url": "https://api.github.com/repos/koishijs/koishi/issues/comments/689496545", + "html_url": "https://github.com/koishijs/koishi/pull/101#issuecomment-689496545", + "issue_url": "https://api.github.com/repos/koishijs/koishi/issues/101", + "id": 689496545, + "node_id": "MDEyOklzc3VlQ29tbWVudDY4OTQ5NjU0NQ==", + "user": { + "login": "codecov[bot]", + "id": 22429695, + "node_id": "MDM6Qm90MjI0Mjk2OTU=", + "avatar_url": "https://avatars2.githubusercontent.com/in/254?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/codecov%5Bbot%5D", + "html_url": "https://github.com/apps/codecov", + "followers_url": "https://api.github.com/users/codecov%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/codecov%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/codecov%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/codecov%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/codecov%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/codecov%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/codecov%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/codecov%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/codecov%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "created_at": "2020-09-09T11:19:48Z", + "updated_at": "2020-09-09T11:19:48Z", + "author_association": "NONE", + "body": "# [Codecov](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=h1) Report\n> Merging [#101](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=desc) into [master](https://codecov.io/gh/koishijs/koishi/commit/572d976eb27790d1e8e3eddecc6a901cd9476905?el=desc) will **not change** coverage.\n> The diff coverage is `n/a`.\n\n[![Impacted file tree graph](https://codecov.io/gh/koishijs/koishi/pull/101/graphs/tree.svg?width=650&height=150&src=pr&token=ENpAXNOUwW)](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=tree)\n\n```diff\n@@ Coverage Diff @@\n## master #101 +/- ##\n=======================================\n Coverage 92.60% 92.60% \n=======================================\n Files 52 52 \n Lines 8015 8015 \n Branches 1720 1720 \n=======================================\n Hits 7422 7422 \n Misses 593 593 \n```\n\n\n\n------\n\n[Continue to review full report at Codecov](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=continue).\n> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)\n> `Δ = absolute (impact)`, `ø = not affected`, `? = missing data`\n> Powered by [Codecov](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=footer). Last update [572d976...38261a3](https://codecov.io/gh/koishijs/koishi/pull/101?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).\n", + "performed_via_github_app": null + }, + "repository": { + "id": 225572038, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjU1NzIwMzg=", + "name": "koishi", + "full_name": "koishijs/koishi", + "private": false, + "owner": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/koishijs/koishi", + "description": "QQ bot out of the box", + "fork": false, + "url": "https://api.github.com/repos/koishijs/koishi", + "forks_url": "https://api.github.com/repos/koishijs/koishi/forks", + "keys_url": "https://api.github.com/repos/koishijs/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/koishijs/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/koishijs/koishi/teams", + "hooks_url": "https://api.github.com/repos/koishijs/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/koishijs/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/koishijs/koishi/events", + "assignees_url": "https://api.github.com/repos/koishijs/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/koishijs/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/koishijs/koishi/tags", + "blobs_url": "https://api.github.com/repos/koishijs/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/koishijs/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/koishijs/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/koishijs/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/koishijs/koishi/languages", + "stargazers_url": "https://api.github.com/repos/koishijs/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/koishijs/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/koishijs/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/koishijs/koishi/subscription", + "commits_url": "https://api.github.com/repos/koishijs/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/koishijs/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/koishijs/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/koishijs/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/koishijs/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/koishijs/koishi/merges", + "archive_url": "https://api.github.com/repos/koishijs/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/koishijs/koishi/downloads", + "issues_url": "https://api.github.com/repos/koishijs/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/koishijs/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/koishijs/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/koishijs/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/koishijs/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/koishijs/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/koishijs/koishi/deployments", + "created_at": "2019-12-03T08:47:29Z", + "updated_at": "2020-09-06T15:08:28Z", + "pushed_at": "2020-09-09T11:18:02Z", + "git_url": "git://github.com/koishijs/koishi.git", + "ssh_url": "git@github.com:koishijs/koishi.git", + "clone_url": "https://github.com/koishijs/koishi.git", + "svn_url": "https://github.com/koishijs/koishi", + "homepage": "https://koishi.js.org", + "size": 2894, + "stargazers_count": 193, + "watchers_count": 193, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 14, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 7, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 14, + "open_issues": 7, + "watchers": 193, + "default_branch": "master" + }, + "organization": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "url": "https://api.github.com/orgs/koishijs", + "repos_url": "https://api.github.com/orgs/koishijs/repos", + "events_url": "https://api.github.com/orgs/koishijs/events", + "hooks_url": "https://api.github.com/orgs/koishijs/hooks", + "issues_url": "https://api.github.com/orgs/koishijs/issues", + "members_url": "https://api.github.com/orgs/koishijs/members{/member}", + "public_members_url": "https://api.github.com/orgs/koishijs/public_members{/member}", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "description": "" + }, + "sender": { + "login": "codecov[bot]", + "id": 22429695, + "node_id": "MDM6Qm90MjI0Mjk2OTU=", + "avatar_url": "https://avatars2.githubusercontent.com/in/254?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/codecov%5Bbot%5D", + "html_url": "https://github.com/apps/codecov", + "followers_url": "https://api.github.com/users/codecov%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/codecov%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/codecov%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/codecov%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/codecov%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/codecov%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/codecov%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/codecov%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/codecov%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + } +} \ No newline at end of file diff --git a/packages/plugin-github/tests/fixtures/pull_request.closed.json b/packages/plugin-github/tests/fixtures/pull_request.closed.1.json similarity index 100% rename from packages/plugin-github/tests/fixtures/pull_request.closed.json rename to packages/plugin-github/tests/fixtures/pull_request.closed.1.json diff --git a/packages/plugin-github/tests/fixtures/pull_request.closed.2.json b/packages/plugin-github/tests/fixtures/pull_request.closed.2.json new file mode 100644 index 0000000000..8e8927b6bf --- /dev/null +++ b/packages/plugin-github/tests/fixtures/pull_request.closed.2.json @@ -0,0 +1,514 @@ +{ + "action": "closed", + "number": 101, + "pull_request": { + "url": "https://api.github.com/repos/koishijs/koishi/pulls/101", + "id": 482768906, + "node_id": "MDExOlB1bGxSZXF1ZXN0NDgyNzY4OTA2", + "html_url": "https://github.com/koishijs/koishi/pull/101", + "diff_url": "https://github.com/koishijs/koishi/pull/101.diff", + "patch_url": "https://github.com/koishijs/koishi/pull/101.patch", + "issue_url": "https://api.github.com/repos/koishijs/koishi/issues/101", + "number": 101, + "state": "closed", + "locked": false, + "title": "来壮大列表了(不是", + "user": { + "login": "arily", + "id": 10007589, + "node_id": "MDQ6VXNlcjEwMDA3NTg5", + "avatar_url": "https://avatars1.githubusercontent.com/u/10007589?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/arily", + "html_url": "https://github.com/arily", + "followers_url": "https://api.github.com/users/arily/followers", + "following_url": "https://api.github.com/users/arily/following{/other_user}", + "gists_url": "https://api.github.com/users/arily/gists{/gist_id}", + "starred_url": "https://api.github.com/users/arily/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/arily/subscriptions", + "organizations_url": "https://api.github.com/users/arily/orgs", + "repos_url": "https://api.github.com/users/arily/repos", + "events_url": "https://api.github.com/users/arily/events{/privacy}", + "received_events_url": "https://api.github.com/users/arily/received_events", + "type": "User", + "site_admin": false + }, + "body": "套壳框架xd", + "created_at": "2020-09-09T11:18:01Z", + "updated_at": "2020-09-09T12:55:52Z", + "closed_at": "2020-09-09T12:55:52Z", + "merged_at": "2020-09-09T12:55:52Z", + "merge_commit_sha": "a0b7c055221cef1d466afa2e545875cfbdcc28ca", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/koishijs/koishi/pulls/101/commits", + "review_comments_url": "https://api.github.com/repos/koishijs/koishi/pulls/101/comments", + "review_comment_url": "https://api.github.com/repos/koishijs/koishi/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/issues/101/comments", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/38261a323cec572d2ca0afd115aaab2bf8779614", + "head": { + "label": "arily:patch-1", + "ref": "patch-1", + "sha": "38261a323cec572d2ca0afd115aaab2bf8779614", + "user": { + "login": "arily", + "id": 10007589, + "node_id": "MDQ6VXNlcjEwMDA3NTg5", + "avatar_url": "https://avatars1.githubusercontent.com/u/10007589?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/arily", + "html_url": "https://github.com/arily", + "followers_url": "https://api.github.com/users/arily/followers", + "following_url": "https://api.github.com/users/arily/following{/other_user}", + "gists_url": "https://api.github.com/users/arily/gists{/gist_id}", + "starred_url": "https://api.github.com/users/arily/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/arily/subscriptions", + "organizations_url": "https://api.github.com/users/arily/orgs", + "repos_url": "https://api.github.com/users/arily/repos", + "events_url": "https://api.github.com/users/arily/events{/privacy}", + "received_events_url": "https://api.github.com/users/arily/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 294088773, + "node_id": "MDEwOlJlcG9zaXRvcnkyOTQwODg3NzM=", + "name": "koishi", + "full_name": "arily/koishi", + "private": false, + "owner": { + "login": "arily", + "id": 10007589, + "node_id": "MDQ6VXNlcjEwMDA3NTg5", + "avatar_url": "https://avatars1.githubusercontent.com/u/10007589?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/arily", + "html_url": "https://github.com/arily", + "followers_url": "https://api.github.com/users/arily/followers", + "following_url": "https://api.github.com/users/arily/following{/other_user}", + "gists_url": "https://api.github.com/users/arily/gists{/gist_id}", + "starred_url": "https://api.github.com/users/arily/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/arily/subscriptions", + "organizations_url": "https://api.github.com/users/arily/orgs", + "repos_url": "https://api.github.com/users/arily/repos", + "events_url": "https://api.github.com/users/arily/events{/privacy}", + "received_events_url": "https://api.github.com/users/arily/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/arily/koishi", + "description": "QQ bot out of the box", + "fork": true, + "url": "https://api.github.com/repos/arily/koishi", + "forks_url": "https://api.github.com/repos/arily/koishi/forks", + "keys_url": "https://api.github.com/repos/arily/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/arily/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/arily/koishi/teams", + "hooks_url": "https://api.github.com/repos/arily/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/arily/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/arily/koishi/events", + "assignees_url": "https://api.github.com/repos/arily/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/arily/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/arily/koishi/tags", + "blobs_url": "https://api.github.com/repos/arily/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/arily/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/arily/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/arily/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/arily/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/arily/koishi/languages", + "stargazers_url": "https://api.github.com/repos/arily/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/arily/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/arily/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/arily/koishi/subscription", + "commits_url": "https://api.github.com/repos/arily/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/arily/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/arily/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/arily/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/arily/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/arily/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/arily/koishi/merges", + "archive_url": "https://api.github.com/repos/arily/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/arily/koishi/downloads", + "issues_url": "https://api.github.com/repos/arily/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/arily/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/arily/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/arily/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/arily/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/arily/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/arily/koishi/deployments", + "created_at": "2020-09-09T11:12:13Z", + "updated_at": "2020-09-09T11:12:15Z", + "pushed_at": "2020-09-09T11:15:30Z", + "git_url": "git://github.com/arily/koishi.git", + "ssh_url": "git@github.com:arily/koishi.git", + "clone_url": "https://github.com/arily/koishi.git", + "svn_url": "https://github.com/arily/koishi", + "homepage": "https://koishi.js.org", + "size": 2902, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "delete_branch_on_merge": false + } + }, + "base": { + "label": "koishijs:master", + "ref": "master", + "sha": "572d976eb27790d1e8e3eddecc6a901cd9476905", + "user": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 225572038, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjU1NzIwMzg=", + "name": "koishi", + "full_name": "koishijs/koishi", + "private": false, + "owner": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/koishijs/koishi", + "description": "QQ bot out of the box", + "fork": false, + "url": "https://api.github.com/repos/koishijs/koishi", + "forks_url": "https://api.github.com/repos/koishijs/koishi/forks", + "keys_url": "https://api.github.com/repos/koishijs/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/koishijs/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/koishijs/koishi/teams", + "hooks_url": "https://api.github.com/repos/koishijs/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/koishijs/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/koishijs/koishi/events", + "assignees_url": "https://api.github.com/repos/koishijs/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/koishijs/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/koishijs/koishi/tags", + "blobs_url": "https://api.github.com/repos/koishijs/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/koishijs/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/koishijs/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/koishijs/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/koishijs/koishi/languages", + "stargazers_url": "https://api.github.com/repos/koishijs/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/koishijs/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/koishijs/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/koishijs/koishi/subscription", + "commits_url": "https://api.github.com/repos/koishijs/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/koishijs/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/koishijs/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/koishijs/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/koishijs/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/koishijs/koishi/merges", + "archive_url": "https://api.github.com/repos/koishijs/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/koishijs/koishi/downloads", + "issues_url": "https://api.github.com/repos/koishijs/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/koishijs/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/koishijs/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/koishijs/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/koishijs/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/koishijs/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/koishijs/koishi/deployments", + "created_at": "2019-12-03T08:47:29Z", + "updated_at": "2020-09-06T15:08:28Z", + "pushed_at": "2020-09-09T12:55:52Z", + "git_url": "git://github.com/koishijs/koishi.git", + "ssh_url": "git@github.com:koishijs/koishi.git", + "clone_url": "https://github.com/koishijs/koishi.git", + "svn_url": "https://github.com/koishijs/koishi", + "homepage": "https://koishi.js.org", + "size": 2894, + "stargazers_count": 193, + "watchers_count": 193, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 14, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 6, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 14, + "open_issues": 6, + "watchers": 193, + "default_branch": "master", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "delete_branch_on_merge": false + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/koishijs/koishi/pulls/101" + }, + "html": { + "href": "https://github.com/koishijs/koishi/pull/101" + }, + "issue": { + "href": "https://api.github.com/repos/koishijs/koishi/issues/101" + }, + "comments": { + "href": "https://api.github.com/repos/koishijs/koishi/issues/101/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/koishijs/koishi/pulls/101/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/koishijs/koishi/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/koishijs/koishi/pulls/101/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/koishijs/koishi/statuses/38261a323cec572d2ca0afd115aaab2bf8779614" + } + }, + "author_association": "NONE", + "active_lock_reason": null, + "merged": true, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + }, + "comments": 1, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 2, + "additions": 1, + "deletions": 0, + "changed_files": 1 + }, + "repository": { + "id": 225572038, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjU1NzIwMzg=", + "name": "koishi", + "full_name": "koishijs/koishi", + "private": false, + "owner": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/koishijs/koishi", + "description": "QQ bot out of the box", + "fork": false, + "url": "https://api.github.com/repos/koishijs/koishi", + "forks_url": "https://api.github.com/repos/koishijs/koishi/forks", + "keys_url": "https://api.github.com/repos/koishijs/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/koishijs/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/koishijs/koishi/teams", + "hooks_url": "https://api.github.com/repos/koishijs/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/koishijs/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/koishijs/koishi/events", + "assignees_url": "https://api.github.com/repos/koishijs/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/koishijs/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/koishijs/koishi/tags", + "blobs_url": "https://api.github.com/repos/koishijs/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/koishijs/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/koishijs/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/koishijs/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/koishijs/koishi/languages", + "stargazers_url": "https://api.github.com/repos/koishijs/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/koishijs/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/koishijs/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/koishijs/koishi/subscription", + "commits_url": "https://api.github.com/repos/koishijs/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/koishijs/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/koishijs/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/koishijs/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/koishijs/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/koishijs/koishi/merges", + "archive_url": "https://api.github.com/repos/koishijs/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/koishijs/koishi/downloads", + "issues_url": "https://api.github.com/repos/koishijs/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/koishijs/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/koishijs/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/koishijs/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/koishijs/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/koishijs/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/koishijs/koishi/deployments", + "created_at": "2019-12-03T08:47:29Z", + "updated_at": "2020-09-06T15:08:28Z", + "pushed_at": "2020-09-09T12:55:52Z", + "git_url": "git://github.com/koishijs/koishi.git", + "ssh_url": "git@github.com:koishijs/koishi.git", + "clone_url": "https://github.com/koishijs/koishi.git", + "svn_url": "https://github.com/koishijs/koishi", + "homepage": "https://koishi.js.org", + "size": 2894, + "stargazers_count": 193, + "watchers_count": 193, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 14, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 6, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 14, + "open_issues": 6, + "watchers": 193, + "default_branch": "master" + }, + "organization": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "url": "https://api.github.com/orgs/koishijs", + "repos_url": "https://api.github.com/orgs/koishijs/repos", + "events_url": "https://api.github.com/orgs/koishijs/events", + "hooks_url": "https://api.github.com/orgs/koishijs/hooks", + "issues_url": "https://api.github.com/orgs/koishijs/issues", + "members_url": "https://api.github.com/orgs/koishijs/members{/member}", + "public_members_url": "https://api.github.com/orgs/koishijs/public_members{/member}", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "description": "" + }, + "sender": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/packages/plugin-github/tests/fixtures/push.commit.json b/packages/plugin-github/tests/fixtures/push.1.json similarity index 100% rename from packages/plugin-github/tests/fixtures/push.commit.json rename to packages/plugin-github/tests/fixtures/push.1.json diff --git a/packages/plugin-github/tests/fixtures/push.delete.json b/packages/plugin-github/tests/fixtures/push.2.json similarity index 100% rename from packages/plugin-github/tests/fixtures/push.delete.json rename to packages/plugin-github/tests/fixtures/push.2.json diff --git a/packages/plugin-github/tests/fixtures/push.tag.json b/packages/plugin-github/tests/fixtures/push.3.json similarity index 100% rename from packages/plugin-github/tests/fixtures/push.tag.json rename to packages/plugin-github/tests/fixtures/push.3.json diff --git a/packages/plugin-github/tests/fixtures/release.created.json b/packages/plugin-github/tests/fixtures/release.created.json new file mode 100644 index 0000000000..2304ef89de --- /dev/null +++ b/packages/plugin-github/tests/fixtures/release.created.json @@ -0,0 +1,178 @@ +{ + "action": "created", + "release": { + "url": "https://api.github.com/repos/koishijs/koishi/releases/30811450", + "assets_url": "https://api.github.com/repos/koishijs/koishi/releases/30811450/assets", + "upload_url": "https://uploads.github.com/repos/koishijs/koishi/releases/30811450/assets{?name,label}", + "html_url": "https://github.com/koishijs/koishi/releases/tag/2.2.1", + "id": 30811450, + "node_id": "MDc6UmVsZWFzZTMwODExNDUw", + "tag_name": "2.2.1", + "target_commitish": "master", + "name": "Koishi 2.2.1", + "draft": false, + "author": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2020-09-06T15:08:14Z", + "published_at": "2020-09-06T15:09:47Z", + "assets": [ + + ], + "tarball_url": "https://api.github.com/repos/koishijs/koishi/tarball/2.2.1", + "zipball_url": "https://api.github.com/repos/koishijs/koishi/zipball/2.2.1", + "body": "## Features\n\n- **github:** support experimental event filter (2610cd82bd7be5a02d494a4a16baeb9cb3be9305)\n- **github:** support xxx_comment.edited/deleted (e8c4e70813bb16b35a4498efe0b9f854c98591b5)\n- **github:** support app.github server api (52134c5af4483ded37997a5e573fa4507e5659d2)\n- **utils:** support logger.stream (91ceff7274ded2b513c2d3662698cfe936bcc1b3)\n- **common:** refactor admin command (f6a7e5c19b5e98e3c95a7fbbab6a110bd1030c31)\n- **common:** check context for group admin commands (dc611569be7268631652bef0258d6a6a080d1758)\n- **schedule:** optimize logger (1f0394d27e8f9d5db8e175614f841e08dad95bcd)\n- **common:** support user.flag -l (f59be5b449a452f25734f6c5e7eb4f075b1ddffa)\n- **cqhttp:** send modified session.message (9a457fdb82c8bb659ae0480bffb397043982d699)\n\n## Bug Fixes\n\n- **utils:** fix double % escape (134df6006634e5a149118d970fa973371a25a498)\n- **schedule:** fix mongo session serializing (b67a705a038ff096beed0dad5213387df5f2ec60)\n\n## Dependencies\n\n- plugin-mongo > mongodb@3.6.1 (cb8ffc6f0fc4e67288459ad4cc8aa49c5880ce71)\n\n" + }, + "repository": { + "id": 225572038, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjU1NzIwMzg=", + "name": "koishi", + "full_name": "koishijs/koishi", + "private": false, + "owner": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/koishijs/koishi", + "description": "QQ bot out of the box", + "fork": false, + "url": "https://api.github.com/repos/koishijs/koishi", + "forks_url": "https://api.github.com/repos/koishijs/koishi/forks", + "keys_url": "https://api.github.com/repos/koishijs/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/koishijs/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/koishijs/koishi/teams", + "hooks_url": "https://api.github.com/repos/koishijs/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/koishijs/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/koishijs/koishi/events", + "assignees_url": "https://api.github.com/repos/koishijs/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/koishijs/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/koishijs/koishi/tags", + "blobs_url": "https://api.github.com/repos/koishijs/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/koishijs/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/koishijs/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/koishijs/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/koishijs/koishi/languages", + "stargazers_url": "https://api.github.com/repos/koishijs/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/koishijs/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/koishijs/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/koishijs/koishi/subscription", + "commits_url": "https://api.github.com/repos/koishijs/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/koishijs/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/koishijs/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/koishijs/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/koishijs/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/koishijs/koishi/merges", + "archive_url": "https://api.github.com/repos/koishijs/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/koishijs/koishi/downloads", + "issues_url": "https://api.github.com/repos/koishijs/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/koishijs/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/koishijs/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/koishijs/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/koishijs/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/koishijs/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/koishijs/koishi/deployments", + "created_at": "2019-12-03T08:47:29Z", + "updated_at": "2020-09-06T15:08:28Z", + "pushed_at": "2020-09-06T15:09:47Z", + "git_url": "git://github.com/koishijs/koishi.git", + "ssh_url": "git@github.com:koishijs/koishi.git", + "clone_url": "https://github.com/koishijs/koishi.git", + "svn_url": "https://github.com/koishijs/koishi", + "homepage": "https://koishi.js.org", + "size": 2844, + "stargazers_count": 193, + "watchers_count": 193, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 13, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 5, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 13, + "open_issues": 5, + "watchers": 193, + "default_branch": "master" + }, + "organization": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "url": "https://api.github.com/orgs/koishijs", + "repos_url": "https://api.github.com/orgs/koishijs/repos", + "events_url": "https://api.github.com/orgs/koishijs/events", + "hooks_url": "https://api.github.com/orgs/koishijs/hooks", + "issues_url": "https://api.github.com/orgs/koishijs/issues", + "members_url": "https://api.github.com/orgs/koishijs/members{/member}", + "public_members_url": "https://api.github.com/orgs/koishijs/public_members{/member}", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "description": "" + }, + "sender": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/packages/plugin-github/tests/fixtures/release.released.json b/packages/plugin-github/tests/fixtures/release.released.json new file mode 100644 index 0000000000..4e5d9bffa0 --- /dev/null +++ b/packages/plugin-github/tests/fixtures/release.released.json @@ -0,0 +1,178 @@ +{ + "action": "released", + "release": { + "url": "https://api.github.com/repos/koishijs/koishi/releases/30811450", + "assets_url": "https://api.github.com/repos/koishijs/koishi/releases/30811450/assets", + "upload_url": "https://uploads.github.com/repos/koishijs/koishi/releases/30811450/assets{?name,label}", + "html_url": "https://github.com/koishijs/koishi/releases/tag/2.2.1", + "id": 30811450, + "node_id": "MDc6UmVsZWFzZTMwODExNDUw", + "tag_name": "2.2.1", + "target_commitish": "master", + "name": "Koishi 2.2.1", + "draft": false, + "author": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2020-09-06T15:08:14Z", + "published_at": "2020-09-06T15:09:47Z", + "assets": [ + + ], + "tarball_url": "https://api.github.com/repos/koishijs/koishi/tarball/2.2.1", + "zipball_url": "https://api.github.com/repos/koishijs/koishi/zipball/2.2.1", + "body": "## Features\n\n- **github:** support experimental event filter (2610cd82bd7be5a02d494a4a16baeb9cb3be9305)\n- **github:** support xxx_comment.edited/deleted (e8c4e70813bb16b35a4498efe0b9f854c98591b5)\n- **github:** support app.github server api (52134c5af4483ded37997a5e573fa4507e5659d2)\n- **utils:** support logger.stream (91ceff7274ded2b513c2d3662698cfe936bcc1b3)\n- **common:** refactor admin command (f6a7e5c19b5e98e3c95a7fbbab6a110bd1030c31)\n- **common:** check context for group admin commands (dc611569be7268631652bef0258d6a6a080d1758)\n- **schedule:** optimize logger (1f0394d27e8f9d5db8e175614f841e08dad95bcd)\n- **common:** support user.flag -l (f59be5b449a452f25734f6c5e7eb4f075b1ddffa)\n- **cqhttp:** send modified session.message (9a457fdb82c8bb659ae0480bffb397043982d699)\n\n## Bug Fixes\n\n- **utils:** fix double % escape (134df6006634e5a149118d970fa973371a25a498)\n- **schedule:** fix mongo session serializing (b67a705a038ff096beed0dad5213387df5f2ec60)\n\n## Dependencies\n\n- plugin-mongo > mongodb@3.6.1 (cb8ffc6f0fc4e67288459ad4cc8aa49c5880ce71)\n\n" + }, + "repository": { + "id": 225572038, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjU1NzIwMzg=", + "name": "koishi", + "full_name": "koishijs/koishi", + "private": false, + "owner": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/koishijs", + "html_url": "https://github.com/koishijs", + "followers_url": "https://api.github.com/users/koishijs/followers", + "following_url": "https://api.github.com/users/koishijs/following{/other_user}", + "gists_url": "https://api.github.com/users/koishijs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/koishijs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/koishijs/subscriptions", + "organizations_url": "https://api.github.com/users/koishijs/orgs", + "repos_url": "https://api.github.com/users/koishijs/repos", + "events_url": "https://api.github.com/users/koishijs/events{/privacy}", + "received_events_url": "https://api.github.com/users/koishijs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/koishijs/koishi", + "description": "QQ bot out of the box", + "fork": false, + "url": "https://api.github.com/repos/koishijs/koishi", + "forks_url": "https://api.github.com/repos/koishijs/koishi/forks", + "keys_url": "https://api.github.com/repos/koishijs/koishi/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/koishijs/koishi/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/koishijs/koishi/teams", + "hooks_url": "https://api.github.com/repos/koishijs/koishi/hooks", + "issue_events_url": "https://api.github.com/repos/koishijs/koishi/issues/events{/number}", + "events_url": "https://api.github.com/repos/koishijs/koishi/events", + "assignees_url": "https://api.github.com/repos/koishijs/koishi/assignees{/user}", + "branches_url": "https://api.github.com/repos/koishijs/koishi/branches{/branch}", + "tags_url": "https://api.github.com/repos/koishijs/koishi/tags", + "blobs_url": "https://api.github.com/repos/koishijs/koishi/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/koishijs/koishi/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/koishijs/koishi/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/koishijs/koishi/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/koishijs/koishi/statuses/{sha}", + "languages_url": "https://api.github.com/repos/koishijs/koishi/languages", + "stargazers_url": "https://api.github.com/repos/koishijs/koishi/stargazers", + "contributors_url": "https://api.github.com/repos/koishijs/koishi/contributors", + "subscribers_url": "https://api.github.com/repos/koishijs/koishi/subscribers", + "subscription_url": "https://api.github.com/repos/koishijs/koishi/subscription", + "commits_url": "https://api.github.com/repos/koishijs/koishi/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/koishijs/koishi/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/koishijs/koishi/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/koishijs/koishi/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/koishijs/koishi/contents/{+path}", + "compare_url": "https://api.github.com/repos/koishijs/koishi/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/koishijs/koishi/merges", + "archive_url": "https://api.github.com/repos/koishijs/koishi/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/koishijs/koishi/downloads", + "issues_url": "https://api.github.com/repos/koishijs/koishi/issues{/number}", + "pulls_url": "https://api.github.com/repos/koishijs/koishi/pulls{/number}", + "milestones_url": "https://api.github.com/repos/koishijs/koishi/milestones{/number}", + "notifications_url": "https://api.github.com/repos/koishijs/koishi/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/koishijs/koishi/labels{/name}", + "releases_url": "https://api.github.com/repos/koishijs/koishi/releases{/id}", + "deployments_url": "https://api.github.com/repos/koishijs/koishi/deployments", + "created_at": "2019-12-03T08:47:29Z", + "updated_at": "2020-09-06T15:08:28Z", + "pushed_at": "2020-09-06T15:09:47Z", + "git_url": "git://github.com/koishijs/koishi.git", + "ssh_url": "git@github.com:koishijs/koishi.git", + "clone_url": "https://github.com/koishijs/koishi.git", + "svn_url": "https://github.com/koishijs/koishi", + "homepage": "https://koishi.js.org", + "size": 2844, + "stargazers_count": 193, + "watchers_count": 193, + "language": "TypeScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 13, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 5, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 13, + "open_issues": 5, + "watchers": 193, + "default_branch": "master" + }, + "organization": { + "login": "koishijs", + "id": 58179220, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MTc5MjIw", + "url": "https://api.github.com/orgs/koishijs", + "repos_url": "https://api.github.com/orgs/koishijs/repos", + "events_url": "https://api.github.com/orgs/koishijs/events", + "hooks_url": "https://api.github.com/orgs/koishijs/hooks", + "issues_url": "https://api.github.com/orgs/koishijs/issues", + "members_url": "https://api.github.com/orgs/koishijs/members{/member}", + "public_members_url": "https://api.github.com/orgs/koishijs/public_members{/member}", + "avatar_url": "https://avatars3.githubusercontent.com/u/58179220?v=4", + "description": "" + }, + "sender": { + "login": "Shigma", + "id": 33423008, + "node_id": "MDQ6VXNlcjMzNDIzMDA4", + "avatar_url": "https://avatars3.githubusercontent.com/u/33423008?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Shigma", + "html_url": "https://github.com/Shigma", + "followers_url": "https://api.github.com/users/Shigma/followers", + "following_url": "https://api.github.com/users/Shigma/following{/other_user}", + "gists_url": "https://api.github.com/users/Shigma/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Shigma/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Shigma/subscriptions", + "organizations_url": "https://api.github.com/users/Shigma/orgs", + "repos_url": "https://api.github.com/users/Shigma/repos", + "events_url": "https://api.github.com/users/Shigma/events{/privacy}", + "received_events_url": "https://api.github.com/users/Shigma/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/packages/plugin-github/tests/index.snap.js b/packages/plugin-github/tests/index.snap.js index f2bb53587a..063ff9403a 100644 --- a/packages/plugin-github/tests/index.snap.js +++ b/packages/plugin-github/tests/index.snap.js @@ -27,10 +27,14 @@ module.exports[`issues.opened`] = ` Title: Wie kann man um das Koishi zu installieren? Ich verstecke Englisch und Chinesisch nicht! Gab es Personen, die mir helfen kann?` -module.exports['pull_request.closed'] = ` +module.exports['pull_request.closed.1'] = ` [GitHub] Shigma closed pull request koishijs/koishi#97 Storage API, fix #96` +module.exports['pull_request.closed.2'] = ` +[GitHub] Shigma merged pull request koishijs/koishi#101 +来壮大列表了(不是` + // URL: https://github.com/koishijs/koishi/pull/20 module.exports[`pull_request.opened.1`] = ` [GitHub] simon300000 opened a pull request koishijs/koishi#20 (develop <- experimental) @@ -55,12 +59,12 @@ Path: packages/test-utils/src/mocks.ts Naming is so hard......` // URL: https://github.com/koishijs/koishi/compare/976c6e8f09a4...3ae7e7044d06 -module.exports[`push.commit`] = ` +module.exports[`push.1`] = ` [GitHub] Shigma pushed to koishijs/koishi:develop [d7ff34] chore: adjust [3ae7e7] fix(core): create major context at demand` -module.exports[`push.tag`] = ` +module.exports[`push.3`] = ` [GitHub] Shigma published tag koishijs/koishi@1.5.0` module.exports[`star.created`] = ` diff --git a/packages/plugin-github/tests/index.spec.ts b/packages/plugin-github/tests/index.spec.ts index a0f47548ac..779c184480 100644 --- a/packages/plugin-github/tests/index.spec.ts +++ b/packages/plugin-github/tests/index.spec.ts @@ -1,9 +1,11 @@ import { App, BASE_SELF_ID } from 'koishi-test-utils' +import { install, InstalledClock } from '@sinonjs/fake-timers' import { Random } from 'koishi-utils' -import { fn, spyOn } from 'jest-mock' +import { fn, Mock, spyOn } from 'jest-mock' import { expect } from 'chai' import { readdirSync } from 'fs-extra' import { resolve } from 'path' +import nock from 'nock' import * as github from 'koishi-plugin-github' const app = new App({ @@ -16,6 +18,9 @@ app.plugin(github, { repos: { 'koishijs/koishi': [123] }, }) +const session1 = app.session(123) +const session2 = app.session(456) + // override listen const listen = spyOn(app.server, 'listen') listen.mockReturnValue(Promise.resolve()) @@ -25,31 +30,147 @@ const sendGroupMsg = app.bots[0].sendGroupMsg = fn() before(async () => { await app.start() + await app.database.getUser(123, 3) + await app.database.getUser(456, 3) await app.database.getGroup(123, BASE_SELF_ID) }) const snapshot = require('./index.snap') -function check(file: string) { - it(file, async () => { - sendGroupMsg.mockClear() - const payload = require(`./fixtures/${file}`) - const [name] = file.split('.', 1) - await app.github.receive({ id: Random.uuid(), name, payload }) - if (snapshot[file]) { - expect(sendGroupMsg.mock.calls).to.have.length(1) - expect(sendGroupMsg.mock.calls[0][1]).to.equal(snapshot[file].trim()) - } else { - expect(sendGroupMsg.mock.calls).to.have.length(0) - } - }) +const apiScope = nock('https://api.github.com') +const tokenInterceptor = nock('https://github.com').post('/login/oauth/access_token') + +const ghAccessToken = Random.uuid() +const ghRefreshToken = Random.uuid() +const payload = { + access_token: ghAccessToken, + refresh_token: ghRefreshToken, } describe('GitHub Plugin', () => { + let clock: InstalledClock + + before(() => { + clock = install() + }) + + after(() => { + clock.runAll() + clock.uninstall() + }) + + describe('Basic Support', () => { + it('authorize server', async () => { + tokenInterceptor.reply(200, payload) + await expect(app.server.get('/github/authorize')).to.eventually.have.property('code', 400) + await expect(app.server.get('/github/authorize?state=123')).to.eventually.have.property('code', 200) + await expect(app.database.getUser(123)).to.eventually.have.shape({ + ghAccessToken, + ghRefreshToken, + }) + }) + + it('webhook server', async () => { + await expect(app.server.post('/github/webhook', {})).to.eventually.have.property('code', 400) + }) + + it('github command', async () => { + await session1.shouldReply('github', '请输入用户名。') + await session1.shouldReply('github satori', /^请点击下面的链接继续操作:/) + }) + }) + + let counter = 10000 + const idMap: Record = {} + describe('Webhook Events', () => { const files = readdirSync(resolve(__dirname, 'fixtures')) - files.forEach(file => { - check(file.slice(0, -5)) + files.forEach((file) => { + const title = file.slice(0, -5) + it(title, async () => { + sendGroupMsg.mockClear() + sendGroupMsg.mockImplementation(() => { + return Promise.resolve(idMap[title] = ++counter) + }) + + const payload = require(`./fixtures/${title}`) + const [name] = title.split('.', 1) + await app.github.receive({ id: Random.uuid(), name, payload }) + if (snapshot[title]) { + expect(sendGroupMsg.mock.calls).to.have.length(1) + expect(sendGroupMsg.mock.calls[0][1]).to.equal(snapshot[title].trim()) + } else { + expect(sendGroupMsg.mock.calls).to.have.length(0) + } + }) + }) + }) + + describe('Quick Interactions', () => { + it('no operation', async () => { + await session1.shouldNotReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}]`) + await session1.shouldNotReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}] .noop`) + }) + + it('link', async () => { + await session1.shouldReply( + `[CQ:reply,id=${idMap['issue_comment.created.1']}] .link`, + 'https://github.com/koishijs/koishi/issues/19#issuecomment-576277946', + ) + }) + + type MockedReplyCallback = (err: NodeJS.ErrnoException, result: nock.ReplyFnResult) => void + type MockedReply = Mock + + const mockResponse = (uri: string, payload: nock.ReplyFnResult) => { + const mock: MockedReply = fn((uri, body, callback) => callback(null, payload)) + apiScope.post(uri).reply(mock) + return mock + } + + it('react', async () => { + const reaction = mockResponse('/repos/koishijs/koishi/issues/comments/576277946/reactions', [200]) + await session1.shouldNotReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}] laugh`) + expect(reaction.mock.calls).to.have.length(1) + }) + + it('reply', async () => { + const comment = mockResponse('/repos/koishijs/koishi/issues/19/comments', [200]) + await session1.shouldNotReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}] test`) + expect(comment.mock.calls).to.have.length(1) + }) + + it('token not found', async () => { + await session2.shouldReply( + `[CQ:reply,id=${idMap['issue_comment.created.1']}] test`, + '要使用此功能,请对机器人进行授权。输入你的 GitHub 用户名。', + ) + await session2.shouldReply('satori', /^请点击下面的链接继续操作:/) + }) + + it('request error', async () => { + apiScope.post('/repos/koishijs/koishi/issues/19/comments').replyWithError('foo') + await session1.shouldReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}] test`, '发送失败。') + }) + + it('refresh token', async () => { + const unauthorized = mockResponse('/repos/koishijs/koishi/issues/19/comments', [401]) + tokenInterceptor.reply(401) + await session1.shouldReply( + `[CQ:reply,id=${idMap['issue_comment.created.1']}] test`, + '令牌已失效,需要重新授权。输入你的 GitHub 用户名。', + ) + expect(unauthorized.mock.calls).to.have.length(1) + await session1.shouldReply('', '输入超时。') + }) + + it('reauthorize', async () => { + const unauthorized = mockResponse('/repos/koishijs/koishi/issues/19/comments', [401]) + tokenInterceptor.reply(200, payload) + const notFound = mockResponse('/repos/koishijs/koishi/issues/19/comments', [404]) + await session1.shouldReply(`[CQ:reply,id=${idMap['issue_comment.created.1']}] test`, '发送失败。') + expect(unauthorized.mock.calls).to.have.length(1) + expect(notFound.mock.calls).to.have.length(1) }) }) }) diff --git a/packages/plugin-image-search/package.json b/packages/plugin-image-search/package.json index 677fea2dc4..582ce7e6b3 100644 --- a/packages/plugin-image-search/package.json +++ b/packages/plugin-image-search/package.json @@ -37,12 +37,12 @@ "pixiv" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { "axios": "^0.20.0", "cheerio": "^1.0.0-rc.3", - "koishi-utils": "^3.1.4", + "koishi-utils": "^3.1.5", "nhentai-api": "^3.0.2" } } diff --git a/packages/plugin-mongo/package.json b/packages/plugin-mongo/package.json index bacf4f657c..aa9eb0cc18 100644 --- a/packages/plugin-mongo/package.json +++ b/packages/plugin-mongo/package.json @@ -39,7 +39,7 @@ "@types/mongodb": "^3.5.27" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { "mongodb": "^3.6.1" diff --git a/packages/plugin-monitor/package.json b/packages/plugin-monitor/package.json index 41888b16ed..c522a44f7c 100644 --- a/packages/plugin-monitor/package.json +++ b/packages/plugin-monitor/package.json @@ -21,9 +21,9 @@ }, "homepage": "https://github.com/koishijs/koishi#readme", "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-mysql/package.json b/packages/plugin-mysql/package.json index 014bc07997..7e494ccbc2 100644 --- a/packages/plugin-mysql/package.json +++ b/packages/plugin-mysql/package.json @@ -36,10 +36,10 @@ "@types/mysql": "^2.15.15" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { - "koishi-utils": "^3.1.4", + "koishi-utils": "^3.1.5", "mysql": "^2.18.1" } } diff --git a/packages/plugin-puppeteer/package.json b/packages/plugin-puppeteer/package.json index 664e66a4ab..a0ca26470b 100644 --- a/packages/plugin-puppeteer/package.json +++ b/packages/plugin-puppeteer/package.json @@ -36,15 +36,15 @@ "devDependencies": { "@types/pngjs": "^3.4.2", "@types/puppeteer-core": "^2.0.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { "chrome-finder": "^1.0.7", "pngjs": "^5.0.0", "puppeteer-core": "^5.2.1", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-rss/package.json b/packages/plugin-rss/package.json index c81ca80e9e..1dc783369c 100644 --- a/packages/plugin-rss/package.json +++ b/packages/plugin-rss/package.json @@ -36,13 +36,13 @@ "rss" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "rss-feed-emitter": "^3.2.2", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-schedule/package.json b/packages/plugin-schedule/package.json index 65332ed14b..675c8b373b 100644 --- a/packages/plugin-schedule/package.json +++ b/packages/plugin-schedule/package.json @@ -36,12 +36,12 @@ "devDependencies": { "koishi-plugin-mongo": "^1.0.3", "koishi-plugin-mysql": "^2.0.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-status/package.json b/packages/plugin-status/package.json index a4010b77d1..8323b356ac 100644 --- a/packages/plugin-status/package.json +++ b/packages/plugin-status/package.json @@ -1,7 +1,7 @@ { "name": "koishi-plugin-status", "description": "Show Status of Koishi", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.15", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ @@ -32,16 +32,16 @@ "status" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { "@types/cross-spawn": "^6.0.2", "koishi-plugin-mongo": "^1.0.3", "koishi-plugin-mysql": "^2.0.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "cross-spawn": "^7.0.3", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } } diff --git a/packages/plugin-status/src/index.ts b/packages/plugin-status/src/index.ts index 6c4a17a6f8..7a09ab5dff 100644 --- a/packages/plugin-status/src/index.ts +++ b/packages/plugin-status/src/index.ts @@ -1,9 +1,16 @@ import { Context, App, BotStatusCode } from 'koishi-core' import { cpus, totalmem, freemem } from 'os' +import { Time } from 'koishi-utils' import { ActiveData } from './database' export * from './database' +declare module 'koishi-core/dist/context' { + interface EventMap { + 'status'(status: Status, output: string[]): void + } +} + declare module 'koishi-core/dist/server' { interface BotOptions { label?: string @@ -14,7 +21,9 @@ declare module 'koishi-core/dist/server' { } } -export interface Config {} +export interface Config { + refresh?: number +} let usage = getCpuUsage() let appRate: number @@ -75,11 +84,11 @@ export interface BotStatus { rate?: number } -type StatusModifier = (this: App, status: Status, config: Config) => void | Promise -const statusModifiers: StatusModifier[] = [] +type StatusCallback = (this: App, status: Status, config: Config) => void | Promise +const callbacks: [callback: StatusCallback, local: boolean][] = [] -export function extendStatus(callback: StatusModifier) { - statusModifiers.push(callback) +export function extendStatus(callback: StatusCallback, local = false) { + callbacks.push([callback, local]) } const startTime = Date.now() @@ -88,6 +97,7 @@ export const name = 'status' export function apply(ctx: Context, config: Config) { const app = ctx.app + const { refresh = Time.minute } = config app.on('before-command', ({ session }) => { session.$user['lastCall'] = new Date() @@ -117,7 +127,7 @@ export function apply(ctx: Context, config: Config) { if (!app.router) return app.router.get('/status', async (ctx) => { - const status = await getStatus(config, true).catch((error) => { + const status = await getStatus(true).catch((error) => { app.logger('status').warn(error) return null }) @@ -138,9 +148,10 @@ export function apply(ctx: Context, config: Config) { .shortcut('运行情况', { prefix: true }) .shortcut('运行状态', { prefix: true }) .action(async () => { - const { bots: apps, cpu, memory, startTime, activeUsers, activeGroups } = await getStatus(config) + const status = await getStatus() + const { bots, cpu, memory, startTime, activeUsers, activeGroups } = status - const output = apps + const output = bots .filter(bot => bot.code !== BotStatusCode.BOT_IDLE) .map(({ label, selfId, code, rate }) => { return `${label || selfId}:${code ? '无法连接' : `工作中(${rate}/min)`}` @@ -156,10 +167,11 @@ export function apply(ctx: Context, config: Config) { `内存使用率:${(memory.app * 100).toFixed()}% / ${(memory.total * 100).toFixed()}%`, ) + ctx.emit('status', status, output) return output.join('\n') }) - async function _getStatus(config: Config, extend: boolean) { + async function _getStatus(extend: boolean) { const [data, bots] = await Promise.all([ app.database.getActiveData(), Promise.all(app.bots.map(async (bot): Promise => ({ @@ -172,19 +184,19 @@ export function apply(ctx: Context, config: Config) { const memory = memoryRate() const cpu = { app: appRate, total: usedRate } const status: Status = { ...data, bots, memory, cpu, timestamp, startTime } - if (extend) { - await Promise.all(statusModifiers.map(modifier => modifier.call(app, status, config))) - } + await Promise.all(callbacks.map(([callback, local]) => { + if (local || extend) return callback.call(app, status, config) + })) return status } let cachedStatus: Promise let timestamp: number - async function getStatus(config: Config, extend = false): Promise { + async function getStatus(extend = false): Promise { const now = Date.now() - if (now - timestamp < 60000) return cachedStatus + if (now - timestamp < refresh) return cachedStatus timestamp = now - return cachedStatus = _getStatus(config, extend) + return cachedStatus = _getStatus(extend) } } diff --git a/packages/plugin-teach/package.json b/packages/plugin-teach/package.json index f9e6724604..5a40edbf7b 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": "1.0.3", + "version": "1.0.4", "main": "dist/index.js", "typings": "dist/index.d.ts", "engines": { @@ -38,17 +38,17 @@ "conversation" ], "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "devDependencies": { "koishi-plugin-mongo": "^1.0.3", "koishi-plugin-mysql": "^2.0.0", - "koishi-test-utils": "^5.0.0" + "koishi-test-utils": "^5.0.1" }, "dependencies": { "axios": "^0.20.0", - "koishi-utils": "^3.1.4", - "leven": "^3.1.0", + "fastest-levenshtein": "^1.0.12", + "koishi-utils": "^3.1.5", "regexpp": "^3.1.0" } } diff --git a/packages/plugin-teach/src/internal.ts b/packages/plugin-teach/src/internal.ts index 31fd67b6b6..3dabeac6bc 100644 --- a/packages/plugin-teach/src/internal.ts +++ b/packages/plugin-teach/src/internal.ts @@ -5,7 +5,7 @@ import { RegExpValidator } from 'regexpp' import { defineProperty } from 'koishi-utils' import { formatQuestionAnswers } from './search' import { format } from 'util' -import leven from 'leven' +import { distance } from 'fastest-levenshtein' declare module 'koishi-core/dist/command' { interface CommandConfig { @@ -75,9 +75,9 @@ export default function apply(ctx: Context, config: Dialogue.Config) { function maybeAnswer(question: string, dialogues: Dialogue[]) { return dialogues.every(dialogue => { - const dist = leven(question, dialogue.answer) + const dist = distance(question, dialogue.answer) return dist < dialogue.answer.length / 2 - && dist < leven(question, dialogue.question) + && dist < distance(question, dialogue.question) }) } diff --git a/packages/plugin-teach/src/plugins/preventLoop.ts b/packages/plugin-teach/src/plugins/preventLoop.ts index e2bd410736..ff1e05a640 100644 --- a/packages/plugin-teach/src/plugins/preventLoop.ts +++ b/packages/plugin-teach/src/plugins/preventLoop.ts @@ -1,4 +1,5 @@ import { Context } from 'koishi-core' +import { makeArray } from 'koishi-utils' import { Dialogue } from '../utils' export interface LoopConfig { @@ -27,7 +28,7 @@ export default function apply(ctx: Context, config: Dialogue.Config) { const preventLoopConfig: LoopConfig[] = !preventLoop ? [] : typeof preventLoop === 'number' ? [{ length: preventLoop, participants: 1 }] - : Array.isArray(preventLoop) ? preventLoop : [preventLoop] + : makeArray(preventLoop) const initiatorCount = Math.max(0, ...preventLoopConfig.map(c => c.length)) ctx.on('dialogue/state', (state) => { diff --git a/packages/plugin-teach/src/plugins/throttle.ts b/packages/plugin-teach/src/plugins/throttle.ts index 2514c35c11..9ef1932017 100644 --- a/packages/plugin-teach/src/plugins/throttle.ts +++ b/packages/plugin-teach/src/plugins/throttle.ts @@ -1,4 +1,5 @@ import { Context } from 'koishi-core' +import { makeArray } from 'koishi-utils' import { Dialogue } from '../utils' export interface ThrottleConfig { @@ -21,11 +22,7 @@ declare module '../receiver' { } export default function apply(ctx: Context, config: Dialogue.Config) { - const { throttle } = config - - const throttleConfig = !throttle ? [] - : Array.isArray(throttle) ? throttle - : [throttle] + const throttleConfig = makeArray(config.throttle) const counters: Record = {} for (const { interval, responses } of throttleConfig) { counters[interval] = responses diff --git a/packages/plugin-teach/src/receiver.ts b/packages/plugin-teach/src/receiver.ts index 2479ff8192..74a63511f0 100644 --- a/packages/plugin-teach/src/receiver.ts +++ b/packages/plugin-teach/src/receiver.ts @@ -1,5 +1,5 @@ import { Context, User, Session, NextFunction, Command } from 'koishi-core' -import { CQCode, simplify, noop, escapeRegExp, Random } from 'koishi-utils' +import { CQCode, simplify, noop, escapeRegExp, Random, makeArray } from 'koishi-utils' import { Dialogue, DialogueTest } from './utils' declare module 'koishi-core/dist/app' { @@ -250,8 +250,8 @@ export async function triggerDialogue(ctx: Context, session: Session, config: Di export default function (ctx: Context, config: Dialogue.Config) { const { nickname = ctx.app.options.nickname, maxRedirections = 3 } = config - const nicknames = Array.isArray(nickname) ? nickname : nickname ? [nickname] : [] - const nicknameRE = new RegExp(`^((${nicknames.map(escapeRegExp).join('|')})[,,]?\\s*)+`) + const nicknames = makeArray(nickname).map(escapeRegExp) + const nicknameRE = new RegExp(`^((${nicknames.join('|')})[,,]?\\s*)+`) ctx.app._dialogueStates = {} diff --git a/packages/plugin-tools/package.json b/packages/plugin-tools/package.json index cbeb17084c..c424a03edb 100644 --- a/packages/plugin-tools/package.json +++ b/packages/plugin-tools/package.json @@ -25,13 +25,13 @@ "@types/qrcode": "^1.3.5" }, "peerDependencies": { - "koishi-core": "^2.2.1" + "koishi-core": "^2.2.2" }, "dependencies": { "axios": "^0.20.0", "cheerio": "^1.0.0-rc.3", "qrcode": "^1.4.4", "xml-js": "^1.6.11", - "koishi-utils": "^3.1.4" + "koishi-utils": "^3.1.5" } }