Skip to content

Commit

Permalink
fix: 增加消息表态相关操作,服务器管理相关操作,消息管理香瓜你操作
Browse files Browse the repository at this point in the history
  • Loading branch information
lc-cn committed Jul 14, 2024
1 parent d7173f1 commit 439ec12
Show file tree
Hide file tree
Showing 15 changed files with 445 additions and 10,235 deletions.
57 changes: 9 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,13 @@ npm i kook-client # or yarn add kook-client
const {Client} = require('kook-client')
// 创建机器人
const client = new Client({
appid: '', // kook机器人的appID (必填)
secret: '', // kook机器人的secret (必填)
sandbox: true, // 是否是沙箱环境 默认 false
removeAt: true, // 移除第一个at 默认 false
logLevel: 'info', // 日志等级 默认 info
maxRetry: 10, // 最大重连次数 默认 10
intents: [
'GROUP_AT_MESSAGE_CREATE', // 群聊@消息事件 没有群权限请注释
'C2C_MESSAGE_CREATE', // 私聊事件 没有私聊权限请注释
'GUILD_MESSAGES', // 私域机器人频道消息事件 公域机器人请注释
'PUBLIC_GUILD_MESSAGES', // 公域机器人频道消息事件 私域机器人请注释
'DIRECT_MESSAGE', // 频道私信事件
'GUILD_MESSAGE_REACTIONS', // 频道消息表态事件
'GUILDS', // 频道变更事件
'GUILD_MEMBERS', // 频道成员变更事件
'DIRECT_MESSAGE', // 频道私信事件
], // (必填)
logLevel:'info', // 日志等级
ignore:'bot', // 忽略消息配置,可选值为:bot|self
token:'', // 机器人秘钥
mode:'websocket' // 链接模式
})
// 启动机器人
client.start()
client.connect()
```

## 发送消息
Expand All @@ -46,45 +33,19 @@ const client = new Client({
// 只有启动后,才能发送
client.start().then(() => {
// 频道被动回复
client.on('message.guild', (e) => {
client.on('message.channel', (e) => {
e.reply('hello world')
})
// 频道私信被动回复
client.on('message.direct', (e) => {
e.reply('hello world')
})
// 群聊被动回复
client.on('message.group', (e) => {
e.reply('hello world')
})
// 私聊被动回复
client.on('message.private', (e) => {
e.reply('hello world')
})
// 主动发送频道消息
client.sendGuildMessage(channel_id, 'hello')
// 主动发送群消息
client.sendGroupMessage(group_id, 'hello')
client.sendChannelMsg(channel_id, 'hello')
// 主动发送私聊消息
client.sendPrivateMessage(user_id, 'hello')
// 主动发送频道消息,注:需要先调用client.createDirectSession(guild_id,user_id)创建私信会话,此处传入的guild_id为创建的session会话中返回的guild_id
client.sendDirectMessage(guild_id, 'hello')
client.sendPrivateMsg(user_id, 'hello')
})
```

## API

| 功能 | 方法名 | 参数1 | 参数2 | 参数3 | 参数4 | 返回值 |
|--------------|--------------------|-------------|------------|-----|-----|------------------------------------------------------------------------------------------------------------------|
| 获取当前机器人信息 | getSelfInfo | | | | |[获取用户详情](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/manage/user/me.html#content-type) |
| 获取频道列表 | getGuildList | | | | | [Guild](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/manage/guild/model.html)[] |
| 获取频道详情 | getGuildInfo | guild_id | | | | [Guild](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/manage/guild/model.html) |
| 获取频道成员列表 | getGuildMemberList | guild_id | | | | [Member](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/role/member/model.html#member)[] |
| 获取频道成员详情 | getGuildMemberInfo | guild_id | member_id | | | [Member](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/role/member/model.html#member) |
| 获取子频道列表 | getChannelList | guild_id | | | | [Channel](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/manage/channel/model.html#channel)[] |
| 获取子频道详情 | getChannelInfo | guild_id | channel_id | | | [Channel](https://client.q.qq.com/wiki/develop/api-231017/server-inter/channel/manage/channel/model.html#channel) |
| 发送频道消息 | sendGuildMessage | channel_id | message | | | - |
| 发送频道私信消息 | sendDirectMessage | guild_id | message | | | - |
| 发送群消息 | sendGroupMessage | group_id | message | | | - |
| 发送私信消息 | sendPrivateMessage | user_openid | message | | | - |
| 更多API文档信息待补充 | | | | | | - |
文档待更新
File renamed without changes.
58 changes: 51 additions & 7 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import {BaseClient} from "./core/baseClient";
import {Guild} from "./entries/guild";
import {Channel} from "@/entries/channel";
import {User} from "@/entries/user";

import {GuildMember} from "@/entries/guildMember";
import {ChannelMember} from "@/entries/channelMember";
import {EventMap} from "@/event";
import {Quotable, Sendable} from "@/elements";
type MemberMap=Map<string,User.Info>
export class Client extends BaseClient {
guilds:Map<string,Guild.Info>=new Map<string, Guild.Info>()
channels:Map<string,Channel.Info>=new Map<string, Channel.Info>()
guildMembers:Map<string,MemberMap>=new Map<string, MemberMap>()
channelMembers:Map<string,MemberMap>=new Map<string, MemberMap>()
users:Map<string,User.Info>=new Map<string, User.Info>()
pickGuild=Guild.as.bind(this)
pickGuildMember=GuildMember.as.bind(this)
pickChannel=Channel.as.bind(this)
pickChannelMember=ChannelMember.as.bind(this)
pickUser=User.as.bind(this)
constructor(config: Client.Config) {
super(Object.assign({},Client.defaultConfig,config))
Expand Down Expand Up @@ -75,14 +83,16 @@ export class Client extends BaseClient {
/**
* 获取频道成员列表
* @param guild_id
* @param channel_id
*/
async getGuildUserList(guild_id: string):Promise<User.Info[]> {
async getGuildUserList(guild_id: string,channel_id?:string):Promise<User.Info[]> {
const _getGuildMemberList = async (page=1,page_size=100) => {
const res = await this.request.get(`/v3/guild/user-list`, {
params: {
page,
page_size,
guild_id
guild_id,
channel_id
}
}).catch(() => ({data: {items:[]}}))// 公域没有权限,做个兼容
if (!res.data.items?.length) return []
Expand All @@ -92,16 +102,31 @@ export class Client extends BaseClient {
}
return await _getGuildMemberList()
}
async sendPrivateMsg(user_id:string,message:Sendable,quote?:Quotable){
return this.pickUser(user_id).sendMsg(message,quote)
}
async sendChannelMsg(channel_id:string,message:Sendable,quote?:Quotable){
return this.pickChannel(channel_id).sendMsg(message,quote)
}
async #initChannels(guild_id:string){
const channels = await this.getChannelList(guild_id)
for(const channelInfo of channels){
this.channels.set(channelInfo.id,channelInfo)
for(const channel of channels){
this.channelMembers.set(channel.id,new Map<string, User.Info>())
this.channels.set(channel.id,channel)
const channelMembers=await this.getGuildUserList(guild_id,channel.id)
for(const temp of channelMembers){
const userInfo=this.users.get(temp.id)||temp
this.channelMembers.get(channel.id)?.set(userInfo.id,userInfo)
}
}
}
async #initUsers(guild_id:string){
this.guildMembers.set(guild_id,new Map<string, User.Info>())
const users = await this.getGuildUserList(guild_id)
for(const userInfo of users){
this.users.set(userInfo.id,userInfo)
for(const temp of users){
const user=this.users.get(temp.id)||temp
this.guildMembers.get(guild_id)?.set(user.id,user)
this.users.set(user.id,user)
}
}
async init(){
Expand Down Expand Up @@ -148,3 +173,22 @@ export function defineConfig(config:Client.Config){
export function createClient(config:Client.Config){
return new Client(config)
}
export interface Client extends BaseClient{
on<T extends keyof EventMap>(event: T, listener: EventMap[T]): this;

on<S extends string|symbol>(
event: S & Exclude<S, keyof EventMap>,
listener: (...args:any[])=>any,
): this;
emit<T extends keyof EventMap>(event:T,...args:Parameters<EventMap[T]>):boolean
emit<S extends string|symbol>(event: S & Exclude<S, keyof EventMap>,...args:any[]):boolean
once<T extends keyof EventMap>(event: T, listener: EventMap[T]): this;

once<S extends string|symbol>(
event: S & Exclude<S, keyof EventMap>,
listener: (...args:any[])=>any,
): this;
off<T extends keyof EventMap>(event: T): this;

off<S extends string|symbol>(event: S & Exclude<S, keyof EventMap>): this;
}
2 changes: 1 addition & 1 deletion src/constans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export enum OpCode {

export const UnsupportedMethodError = new Error('暂未支持')
export enum ChannelType{
Group='GROUP',
Channel='GROUP',
Private='PERSON',
Notice='BROADCAST'
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/receiver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {EventEmitter} from "events";
import {createDecipheriv} from 'crypto'
import {BaseClient, Client, genGroupId, parseGroupId} from "@";
import {BaseClient, Client} from "@";
import {ChannelType, OpCode} from "@/constans";
import {ChannelMessageEvent, PrivateMessageEvent} from "@/event";
export abstract class Receiver extends EventEmitter{
Expand All @@ -13,8 +13,8 @@ export abstract class Receiver extends EventEmitter{
}
#transformEvent(event:Receiver.EventPacket['d']){
switch (event.channel_type){
case ChannelType.Group:
return this.#transformGroupEvent(event)
case ChannelType.Channel:
return this.#transformChannelEvent(event)
case ChannelType.Private:
return this.#transformPrivateEvent(event)
case ChannelType.Notice:
Expand All @@ -23,7 +23,7 @@ export abstract class Receiver extends EventEmitter{
throw new Error(`unknown channel type ${event.channel_type}`)
}
}
#transformGroupEvent(event:Receiver.EventPacket['d']){
#transformChannelEvent(event:Receiver.EventPacket['d']){
if(event.type===255) return this.#transformNoticeEvent(event)
if(event.extra?.author?.bot && this.c.config.ignore==='bot') return
if(event.author_id===this.c.self_id && this.c.config.ignore==='self') return
Expand Down
104 changes: 92 additions & 12 deletions src/entries/channel.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,110 @@
import {ChannelType, UnsupportedMethodError} from "@/constans";
import {ChannelType} from "@/constans";
import {Contact} from "@/entries/contact";
import {Client, Message, Quotable, Sendable} from "@";
import {User} from "@/entries/user";
import {ChannelMessageEvent} from "@/event";
export class Channel extends Contact{
constructor(c:Client,public info:Channel.Info) {
super(c);
}
async update(newInfo:Omit<Channel.Info, 'id'>){
const {data}=await this.c.request.post('/v3/channel/update',{
channel_id:this.info.id,
...newInfo
})
// 更新缓存
this.c.channels.set(this.info.id,data)
Channel.map.delete(this.info)
Channel.map.set(data,this)
this.info=data
}
async getMsg(message_id: string): Promise<Message> {
const {data}=await this.c.request.get('/v3/message/view',{
params:{
msg_id:message_id
}
})
return ChannelMessageEvent.fromDetail(this.c,this.info.id,data)
}

async delete(){
const result=await this.c.request.post('/v3/channel/delete',{
channel_id:this.info.id
})
if(result['code']!==0) throw new Error(result['message'])
// 清空本地缓存
this.c.channels.delete(this.info.id)
this.c.channelMembers.delete(this.info.id)
Channel.map.delete(this.info)
}

/**
* 获取指定消息之前的聊天历史
* @param message_id {string} 消息id 不传则查询最新消息
* @param len {number} 获取的聊天历史长度 默认50条
*/
async getChatHistory(message_id?:string,len:number=50){
const result= await this.c.request.post('/v3/message/list',{
target_id:this.info.id,
msg_id:message_id,
page_size:len
})
return (result?.data?.items||[]).map((item:Message.Detail)=>{
return ChannelMessageEvent.fromDetail(this.c,this.info.id,item)
})
}
async sendMsg(message: Sendable, quote?: Quotable): Promise<Message.Ret> {
let content=await Message.processMessage.call(this.c,message)
const replyExec=/^\(reply\)([^(]+)\(reply\)/.exec(content)
if(!quote && replyExec){
quote={
message_id:replyExec[1]
}
content=content.replace(replyExec[0],'')
}
let isCard:boolean;
[message,quote,isCard]=await Message.processMessage.call(this.c,message,quote)
const {data}=await this.c.request.post('/v3/message/create',{
target_id:this.info.id,
content,
type:9,
quote:quote&& quote.message_id
content:message,
type:isCard?10:9,
quote:quote?.message_id
})
if(!data) throw new Error('发送消息失败')
this.c.logger.info(`send to Channel(${this.info.id}): `,message)
return data
}

async recallMsg(message_id: string): Promise<boolean> {
const result=await this.c.request.post('/v3/message/delete',{
msg_id:message_id
})
return result['code']===0
}

async updateMsg(message_id: string, newMessage: Sendable,quote?:Quotable): Promise<boolean> {
[newMessage,quote]=await Message.processMessage.call(this.c,newMessage,quote)
const result=await this.c.request.post('/v3/message/update',{
msg_id:message_id,
content:newMessage,
quote:quote?.message_id
})
return result['code']===0
}
async getMsgReactions(message_id:string,emoji?:string):Promise<User.Info[]>{
const result=await this.c.request.get('/v3/message/reaction-list',{
params:{
msg_id:message_id,
emoji:emoji?encodeURI(emoji):null
}
})
return result.data
}
async addMsgReaction(message_id:string,emoji:string){
await this.c.request.post('/v3/message/add-reaction',{
msg_id:message_id,
emoji
})
}
async deleteMsgReaction(message_id:string,emoji:string,user_id?:string){
await this.c.request.post('/v3/message/delete-reaction',{
msg_id:message_id,
emoji,
user_id
})
}
}
export namespace Channel {
export const map:WeakMap<Info,Channel>=new WeakMap<Channel.Info, Channel>()
Expand Down
19 changes: 19 additions & 0 deletions src/entries/channelMember.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {User} from "@/entries/user";
import {Client} from "@";

export class ChannelMember extends User{
constructor(c:Client,public channel_id:string,info:User.Info) {
super(c,info);
}
}
export namespace ChannelMember{
export const map:WeakMap<User.Info,ChannelMember>=new WeakMap<User.Info, ChannelMember>()
export function as(this:Client,channel_id:string,user_id:string){
const memberInfo=this.channelMembers.get(channel_id)?.get(user_id)
if(!memberInfo) throw new Error(`频道(${channel_id}) 不存在成员(${user_id})`)
if(map.has(memberInfo)) return map.get(memberInfo)
const member=new ChannelMember(this,channel_id,memberInfo)
map.set(memberInfo,member)
return member
}
}
7 changes: 5 additions & 2 deletions src/entries/contact.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { BaseClient, Quotable, Sendable, Message} from "@";
import {Quotable, Sendable, Message, Client} from "@";

export abstract class Contact {
protected constructor(public c: BaseClient) {
protected constructor(public c: Client) {
}

abstract sendMsg(message:Sendable,quote?:Quotable):Promise<Message.Ret>
abstract updateMsg(message_id:string,newMessage:Sendable,quote?:Quotable):Promise<boolean>
abstract recallMsg(message_id:string):Promise<boolean>
abstract getMsg(message_id:string):Promise<Message>
}
Loading

0 comments on commit 439ec12

Please sign in to comment.