Skip to content

Commit

Permalink
feat(github): subscription support
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 24, 2021
1 parent c67ffee commit 6ef52a5
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 10 deletions.
56 changes: 46 additions & 10 deletions packages/plugin-github/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ function authorize(ctx: Context, config: Config) {
const { app, database } = ctx

const tokens: Record<string, string> = {}
const path = config.path + '/authorize'

ctx.router.get(path, async (ctx) => {
ctx.router.get(config.path + '/authorize', async (ctx) => {
const token = ctx.query.state
if (!token || Array.isArray(token)) return ctx.status = 400
const id = tokens[token]
Expand Down Expand Up @@ -58,33 +57,33 @@ function authorize(ctx: Context, config: Config) {

ctx.command('github.repos [name]', 'GitHub 仓库')
.userFields(['ghAccessToken', 'ghRefreshToken'])
.option('add', '-a [name] 添加仓库')
.option('delete', '-d [name] 移除仓库')
.option('add', '-a 监听一个新的仓库')
.option('delete', '-d 移除已监听的仓库')
.action(async ({ session, options }, name) => {
if (options.add || options.delete) {
if (!name) return '请输入仓库名。'
if (!/^\w+\/\w+$/.test(name)) return '请输入正确的仓库名。'
if (!/^[\w-]+\/[\w-]+$/.test(name)) return '请输入正确的仓库名。'
if (!session.user.ghAccessToken) {
return ctx.app.github.authorize(session, '要使用此功能,请对机器人进行授权。输入你的 GitHub 用户名。')
}

const url = `https://api.github.com/repos/${name}/hooks`
const [repo] = await ctx.database.get('github', 'name', [name])
if (options.add) {
if (repo) return `已添加过仓库 ${name}。`
if (repo) return `已经添加过仓库 ${name}。`
const secret = Random.uuid()
const id = await ctx.app.github.request(url, 'POST', session, {
const { id } = await ctx.app.github.request(url, 'POST', session, {
events: ['*'],
config: {
secret,
url: app.options.selfUrl + path,
url: app.options.selfUrl + config.path + '/webhook',
},
})
await ctx.database.create('github', { name, id, secret })
return '添加仓库成功!'
} else {
const [repo] = await ctx.database.get('github', 'name', [name])
if (!repo) return `未添加过仓库 ${name}。`
if (!repo) return `尚未添加过仓库 ${name}。`
await ctx.app.github.request(`${url}/${repo.id}`, 'DELETE', session)
return '移除仓库成功!'
}
Expand All @@ -94,6 +93,39 @@ function authorize(ctx: Context, config: Config) {
if (!repos.length) return '当前没有监听的仓库。'
return repos.map(repo => repo.name).join('\n')
})

ctx.command('github [name]')
.channelFields(['githubWebhooks'])
.option('list', '-l 查看当前频道订阅的仓库列表')
.option('add', '-a 为当前频道添加仓库订阅')
.option('delete', '-d 从当前频道移除仓库订阅')
.action(async ({ session, options }, name) => {
if (options.list) {
if (!session.channel) return '当前不是群聊上下文。'
const names = Object.keys(session.channel.githubWebhooks)
if (!names.length) return '当前没有订阅的仓库。'
return names.join('\n')
}

if (options.add || options.delete) {
if (!session.channel) return '当前不是群聊上下文。'
if (!name) return '请输入仓库名。'
if (!/^[\w-]+\/[\w-]+$/.test(name)) return '请输入正确的仓库名。'
const [repo] = await ctx.database.get('github', 'name', [name])
if (!repo) return `尚未添加过仓库 ${name}。`

const webhooks = session.channel.githubWebhooks
if (options.add) {
if (webhooks[name]) return `已经在当前频道订阅过仓库 ${name}。`
webhooks[name] = {}
return '添加订阅成功!'
} else if (options.delete) {
if (!webhooks[name]) return `尚未在当前频道订阅过仓库 ${name}。`
delete webhooks[name]
return '移除订阅成功!'
}
}
})
}

export const name = 'github'
Expand All @@ -106,6 +138,7 @@ export function apply(ctx: Context, config: Config = {}) {
const github = app.github = new GitHub(app, config)

ctx.command('github', 'GitHub 相关功能').alias('gh')
.action(({ session }) => session.execute('help github'))

ctx.command('github.recent', '查看最近的通知')
.action(async () => {
Expand All @@ -128,7 +161,7 @@ export function apply(ctx: Context, config: Config = {}) {
async function getSecret(name: string) {
if (!ctx.database) return config.repos.find(repo => repo.name === name)?.secret
const [data] = await ctx.database.get('github', 'name', [name])
return data.secret
return data?.secret
}

function safeParse(source: string) {
Expand All @@ -146,6 +179,9 @@ export function apply(ctx: Context, config: Config = {}) {
const fullEvent = payload.action ? `${event}/${payload.action}` : event
app.logger('github').debug('received %s (%s)', fullEvent, id)
const secret = await getSecret(payload.repository.full_name)
// 202:服务器已接受请求,但尚未处理
// 在 github.repos -a 时确保获得一个 2xx 的状态码
if (!secret) return ctx.status = 202
if (signature !== `sha256=${createHmac('sha256', secret).update(ctx.request.rawBody).digest('hex')}`) {
return ctx.status = 403
}
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-github/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Database.extend('koishi-plugin-mysql', ({ tables, Domain }) => {
tables.user.ghRefreshToken = 'varchar(50)'
tables.channel.githubWebhooks = new Domain.Json()
tables.github = Object.assign<any, any>(['primary key (`name`)'], {
id: 'int',
name: 'varchar(50)',
secret: 'varchar(50)',
})
Expand Down
9 changes: 9 additions & 0 deletions packages/plugin-mongo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ Database.extend(MongoDatabase, ({ tables }) => {
})

Database.extend(MongoDatabase, {
async getAll(table, fields) {
const { primary } = this.getConfig(table)
let cursor = this.db.collection(table).find()
if (fields) cursor = cursor.project(projection(fields))
const data = await cursor.toArray()
for (const item of data) item[primary] = item._id
return data
},

async get(table, key, value, fields) {
if (!value.length) return []
const { primary } = this.getConfig(table)
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-mysql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ declare module 'koishi-core' {
}

Database.extend(MysqlDatabase, {
async getAll(table, fields) {
return this.select(table, fields)
},

async get(table, key, values, fields) {
if (!values.length) return []
return this.select(table, fields, this.$in(table, key, values))
Expand Down

0 comments on commit 6ef52a5

Please sign in to comment.