Skip to content

Commit

Permalink
Merge branch 'satorijs:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
idranme authored Jul 16, 2024
2 parents fafb916 + eebde78 commit 5d1e54c
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 48 deletions.
4 changes: 3 additions & 1 deletion adapters/discord/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export interface User {
username: string
/** the user's 4-digit discord-tag */
discriminator: string
/** the user's display name, if it is set. For bots, this is the application name */
global_name?: string
/** the user's avatar hash */
avatar?: string
/** whether the user belongs to an OAuth2 application */
Expand Down Expand Up @@ -106,7 +108,7 @@ export enum VisibilityType {
EVERYONE = 1,
}

export interface UserUpdateEvent extends User {}
export interface UserUpdateEvent extends User { }

declare module './gateway' {
interface GatewayEvents {
Expand Down
11 changes: 11 additions & 0 deletions adapters/discord/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const sanitizeCode = (val: string) => val.replace(/(?<=`)(?=`)/g, '\u200b

export const decodeUser = (user: Discord.User): Universal.User => ({
id: user.id,
nick: user.global_name,
name: user.username,
userId: user.id,
avatar: user.avatar && `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`,
Expand Down Expand Up @@ -94,6 +95,16 @@ export async function decodeMessage(
})
}

if (data.sticker_items) {
message.content += data.sticker_items.map(s => h('sticker', {
id: s.id,
format_type: s.format_type,
name: s.name,
}, [
h.image(`https://media.discordapp.net/stickers/${s.id}.webp?size=160`),
])).join('')
}

// embed 的 update event 太阴间了 只有 id embeds channel_id guild_id 四个成员
if (data.attachments?.length) {
if (!/\s$/.test(message.content)) message.content += ' '
Expand Down
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
14 changes: 6 additions & 8 deletions adapters/qq/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export function decodeGroupMessage(
payload: Universal.MessageLike = message,
) {
message.id = data.id
const date = data.timestamp.slice(0, data.timestamp.indexOf('m=')).trim().replace(/\+(\d{4}) CST/, 'GMT+$1')
message.timestamp = new Date(date).valueOf()
message.elements = []
if (data.content.length) message.elements.push(h.text(data.content))
for (const attachment of (data.attachments ?? [])) {
Expand All @@ -53,8 +51,11 @@ export function decodeGroupMessage(
}
message.content = message.elements.join('')

message.guild = { id: data.group_id }
message.user = { id: data.author.id }
if (!payload) return message
const date = data.timestamp.slice(0, data.timestamp.indexOf('m=')).trim().replace(/\+(\d{4}) CST/, 'GMT+$1')
payload.timestamp = new Date(date).valueOf()
payload.guild = data.group_id && { id: data.group_id }
payload.user = { id: data.author.id, avatar: `https://q.qlogo.cn/qqapp/${bot.config.id}/${data.author.id}/640` }
return message
}

Expand Down Expand Up @@ -165,15 +166,12 @@ export async function adaptSession<C extends Context = Context>(bot: QQBot<C>, i
session.type = 'message'
session.isDirect = false
decodeGroupMessage(bot, input.d, session.event.message = {}, session.event)
session.guildId = session.event.message.guild.id
session.channelId = session.guildId
session.userId = session.event.message.user.id
session.elements.unshift(h.at(session.selfId))
} else if (input.t === 'C2C_MESSAGE_CREATE') {
session.type = 'message'
session.isDirect = true
decodeGroupMessage(bot, input.d, session.event.message = {}, session.event)
session.userId = input.d.author.id
session.channelId = session.userId
} else if (input.t === 'FRIEND_ADD') {
session.type = 'friend-added'
Expand Down Expand Up @@ -216,7 +214,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 5d1e54c

Please sign in to comment.