Skip to content

Commit

Permalink
feat: update qq adapter, support new auth (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
bili-22 committed Jul 16, 2024
1 parent 8d1a614 commit 4c68dc2
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 40 deletions.
4 changes: 2 additions & 2 deletions adapters/qq/src/bot/guild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export class QQGuildBot<C extends Context = Context> extends Bot<C> {
this.parent = config.parent
this.parent.guildBot = this
this.platform = 'qqguild'
this.internal = new GuildInternal(this, () => config.parent.guildHttp)
this.http = config.parent.guildHttp
this.internal = new GuildInternal(this, () => config.parent.http)
this.http = config.parent.http
}

get status() {
Expand Down
23 changes: 15 additions & 8 deletions adapters/qq/src/bot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export class QQBot<C extends Context = Context> extends Bot<C, QQBot.Config> {

internal: GroupInternal
http: HTTP
guildHttp: HTTP

private _token: string
private _timer: NodeJS.Timeout
Expand All @@ -29,12 +28,16 @@ export class QQBot<C extends Context = Context> extends Bot<C, QQBot.Config> {
if (config.sandbox) {
endpoint = endpoint.replace(/^(https?:\/\/)/, '$1sandbox.')
}
this.guildHttp = ctx.http.extend({
endpoint,
headers: {
'Authorization': `Bot ${this.config.id}.${this.config.token}`,
},
})
// 如果是 bot 类型, 使用固定 token
if (this.config.authType === 'bot') {
this.http = this.ctx.http.extend({
endpoint,
headers: {
'Authorization': `Bot ${this.config.id}.${this.config.token}`,
'X-Union-Appid': this.config.id,
},
})
}

this.ctx.plugin(QQGuildBot, {
parent: this,
Expand Down Expand Up @@ -126,6 +129,7 @@ export namespace QQBot {
export interface Config extends QQ.Options, WsClient.Options {
intents?: number
retryWhen: number[]
manualAcknowledge: boolean
}

export const Config: Schema<Config> = Schema.intersect([
Expand All @@ -136,10 +140,13 @@ export namespace QQBot {
type: Schema.union(['public', 'private'] as const).description('机器人类型。').required(),
sandbox: Schema.boolean().description('是否开启沙箱模式。').default(false),
endpoint: Schema.string().role('link').description('要连接的服务器地址。').default('https://api.sgroup.qq.com/'),
authType: Schema.union(['bot', 'bearer'] as const).description('采用的验证方式。').default('bot'),
authType: Schema.union(['bot', 'bearer'] as const).description('采用的验证方式。').default('bearer'),
intents: Schema.bitset(QQ.Intents).description('需要订阅的机器人事件。'),
retryWhen: Schema.array(Number).description('发送消息遇到平台错误码时重试。').default([]),
}),
WsClient.Options,
Schema.object({
manualAcknowledge: Schema.boolean().description('手动响应回调消息。').default(false),
}).description('高级设置'),
] as const)
}
9 changes: 8 additions & 1 deletion adapters/qq/src/internal/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ declare module './internal' {
message: string
data: any
}>
sendPrivateMessage(openid: string, data: QQ.Message.Request): Promise<any>
sendPrivateMessage(openid: string, data: QQ.Message.Request): Promise<{
id: string
timestamp: string
} & {
code: number
message: string
data: any
}>
sendFilePrivate(openid: string, data: QQ.Message.File.Request): Promise<any>
sendFileGuild(group_openid: string, data: QQ.Message.File.Request): Promise<any>
acknowledgeInteraction(interaction_id: string, data: {
Expand Down
47 changes: 25 additions & 22 deletions adapters/qq/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export class QQMessageEncoder<C extends Context = Context> extends MessageEncode
private content: string = ''
private passiveId: string
private passiveSeq: number
private passiveEventId: string
private useMarkdown = false
private rows: QQ.Button[][] = []
private attachedFile: QQ.Message.File.Response
Expand All @@ -202,19 +203,23 @@ export class QQMessageEncoder<C extends Context = Context> extends MessageEncode
async flush() {
if (!this.content.trim() && !this.rows.flat().length && !this.attachedFile) return
this.trimButtons()
let msg_id: string, msg_seq: number
let msg_id: string, msg_seq: number, event_id: string
if (this.options?.session?.messageId && Date.now() - this.options.session.timestamp < MSG_TIMEOUT) {
this.options.session['seq'] ||= 0
msg_id = this.options.session.messageId
msg_seq = ++this.options.session['seq']
} else if (this.options?.session?.qq['id'] && Date.now() - this.options.session.timestamp < MSG_TIMEOUT) {
event_id = this.options.session.qq['id']
}
if (this.passiveId) msg_id = this.passiveId
if (this.passiveSeq) msg_seq = this.passiveSeq
if (this.passiveEventId) event_id = this.passiveEventId
const data: QQ.Message.Request = {
content: this.content,
msg_type: QQ.Message.Type.TEXT,
msg_id,
msg_seq,
event_id,
}
if (this.attachedFile) {
if (!data.content.length) data.content = ' '
Expand All @@ -240,27 +245,24 @@ export class QQMessageEncoder<C extends Context = Context> extends MessageEncode
session.type = 'send'
const send = async () => {
try {
if (this.session.isDirect) {
const { sendResult: { msg_id } } = await this.bot.internal.sendPrivateMessage(this.session.channelId, data)
session.messageId = msg_id
} else {
const resp = await this.bot.internal.sendMessage(this.session.channelId, data)
if (resp.id) {
session.messageId = resp.id
session.timestamp = new Date(resp.timestamp).valueOf()
session.channelId = this.session.channelId
session.guildId = this.session.guildId
const resp = this.session.isDirect
? await this.bot.internal.sendPrivateMessage(this.session.channelId, data)
: await this.bot.internal.sendMessage(this.session.channelId, data)
if (resp.id) {
session.messageId = resp.id
session.timestamp = new Date(resp.timestamp).valueOf()
session.channelId = this.session.channelId
session.guildId = this.session.guildId
session.app.emit(session, 'send', session)
this.results.push(session.event.message)
} else if (resp.code === 304023 && this.bot.config.intents & QQ.Intents.MESSAGE_AUDIT) {
try {
const auditData: QQ.MessageAudited = await this.audit(resp.data.message_audit.audit_id)
session.messageId = auditData.message_id
session.app.emit(session, 'send', session)
this.results.push(session.event.message)
} else if (resp.code === 304023 && this.bot.config.intents & QQ.Intents.MESSAGE_AUDIT) {
try {
const auditData: QQ.MessageAudited = await this.audit(resp.data.message_audit.audit_id)
session.messageId = auditData.message_id
session.app.emit(session, 'send', session)
this.results.push(session.event.message)
} catch (e) {
this.bot.logger.error(e)
}
} catch (e) {
this.bot.logger.error(e)
}
}
} catch (e) {
Expand Down Expand Up @@ -386,8 +388,9 @@ export class QQMessageEncoder<C extends Context = Context> extends MessageEncode
if (type === 'text') {
this.content += attrs.content
} else if (type === 'passive') {
this.passiveId = attrs.messageId
this.passiveSeq = Number(attrs.seq)
if (attrs.messageId) this.passiveId = attrs.messageId
if (attrs.seq) this.passiveSeq = Number(attrs.seq)
if (attrs.eventId) this.passiveEventId = attrs.eventId
} else if ((type === 'img' || type === 'image') && (attrs.src || attrs.url)) {
await this.flush()
const data = await this.sendFile(type, attrs)
Expand Down
1 change: 1 addition & 0 deletions adapters/qq/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export interface GetGatewayBotResponse {
}

export interface PayloadStructure<O extends Opcode, T extends keyof GatewayEvents, D> {
id: string
/** opcode for the payload */
op: O
/** event data */
Expand Down
2 changes: 1 addition & 1 deletion adapters/qq/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export async function adaptSession<C extends Context = Context>(bot: QQBot<C>, i

// {message: 'get header appid failed', code: 630006}
// {"message":"check app privilege not pass","code":11253
bot.internal.acknowledgeInteraction(input.d.id, { code: 0 }).catch(() => { })
if (!bot.config.manualAcknowledge) bot.internal.acknowledgeInteraction(input.d.id, { code: 0 }).catch(() => { })
} else if (input.t === 'GUILD_MEMBER_ADD' || input.t === 'GUILD_MEMBER_DELETE' || input.t === 'GUILD_MEMBER_UPDATE') {
session.type = {
GUILD_MEMBER_ADD: 'guild-member-added',
Expand Down
12 changes: 6 additions & 6 deletions adapters/qq/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class WsClient<C extends Context = Context> extends Adapter.WsClient<C, Q
_ping: NodeJS.Timeout

async prepare() {
await this.bot.getAccessToken()
if (this.bot.config.authType === 'bearer') await this.bot.getAccessToken()
let { url } = await this.bot.internal.getGateway()
url = url.replace('api.sgroup.qq.com', new URL(this.bot.config.endpoint).host)
this.bot.logger.debug('url: %s', url)
Expand All @@ -28,13 +28,14 @@ export class WsClient<C extends Context = Context> extends Adapter.WsClient<C, Q
const parsed: Payload = JSON.parse(data.toString())
this.bot.logger.debug('websocket receives %o', parsed)
if (parsed.op === Opcode.HELLO) {
const token = await this.bot.getAccessToken()
const token = this.bot.config.authType === 'bearer'
? `QQBot ${await this.bot.getAccessToken()}`
: `Bot ${this.bot.config.id}.${this.bot.config.token}`
if (this._sessionId) {
this.socket.send(JSON.stringify({
op: Opcode.RESUME,
d: {
token: `QQBot ${token}`,
// token: `Bot ${this.bot.config.id}.${this.bot.config.token}`,
token,
session_id: this._sessionId,
seq: this._s,
},
Expand All @@ -43,8 +44,7 @@ export class WsClient<C extends Context = Context> extends Adapter.WsClient<C, Q
this.socket.send(JSON.stringify({
op: Opcode.IDENTIFY,
d: {
// token: `Bot ${this.bot.config.id}.${this.bot.config.token}`,
token: `QQBot ${token}`,
token,
intents: this.bot.config.intents,
shard: [0, 1],
},
Expand Down

0 comments on commit 4c68dc2

Please sign in to comment.