UNPKG

miraipie

Version:

the most powerful nodejs development kit for mirai-api-http

323 lines (317 loc) 13.3 kB
import WebSocket from 'ws'; import { makeApiAdapter } from '../adapter'; import { ApiResponse, ChatMessage, Event, GroupConfig, GroupMemberInfo, MIRAI_API_HTTP_VERSION, NudgeKind, PostGroupAnnouncement, ResponseCode, SingleMessage, SyncMessage, } from '../mirai'; import { sleep } from '../utils'; function* generateSyncId(): Generator<number> { let syncId = 1; while (true) { syncId = (syncId + 1) % 100; yield syncId; } } /** * mirai-api-http 提供的 websocket adapter<br/> * @see <a href="https://github.com/project-mirai/mirai-api-http/blob/master/docs/adapter/WebsocketAdapter.md">文档<a/> */ const WebsocketApiAdapter = makeApiAdapter({ id: 'ws', supportVersion: MIRAI_API_HTTP_VERSION, configMeta: { qq: { type: Number, description: 'mirai-api-http服务的QQ号', }, verifyKey: { type: String, required: true, description: 'mirai-api-http配置中的verifyKey', }, host: { type: String, required: true, description: 'mirai-api-http服务的主机地址', default: () => '127.0.0.1', }, port: { type: Number, required: true, description: 'mirai-api-http服务的端口号', default: () => 23333, }, timeout: { type: Number, description: '发送请求的超时时间(毫秒)', default: () => 10000, }, ssl: { type: Boolean, description: '是否使用SSL', default: () => false, }, }, data: { /** 响应队列 */ queue: new Map<number, ApiResponse>(), /** syncId 生成器 */ syncIdGenerator: generateSyncId(), /** * websocket 连接 * @type WebSocket */ ws: null, }, methods: { async request<T extends ApiResponse>(command: string, content: object = {}, subCommand: string = null): Promise<T> { // 生成syncId const syncId = this.syncIdGenerator.next().value; // 判断是否正在监听 if (!this.listening) { this.logger.error(`进行 '${command}' 操作前必须启动监听`); return; } // 构造请求对象 const data = {syncId, command, subCommand, content}; this.ws.send(JSON.stringify(data), (err) => { if (err) this.logger.error(`发送请求错误, 请求命令字: ${command}, 请求原始数据:`, data, err.message); }); // 等待响应 for (let counter = 0; counter * 200 < this.configs.timeout; counter++) { if (this.queue.has(syncId)) break; await sleep(200); } // 返回响应 const resp = this.queue.get(syncId); if (resp) { this.queue.delete(syncId); if ('code' in resp && resp['code'] !== ResponseCode.Success) { this.logger.warn(`发送的请求未能达到预期的效果, 请求命令字: ${command}, 错误原因: ${(resp as any).msg}, 请求原始数据:`, data); } return resp as T; } else { this.logger.error('发送的请求已超时, 请求原始数据:', data); } }, }, async listen() { if (!this.listening) { const id = this.configs.qq; this.ws = new WebSocket(`ws${this.configs.ssl ? 's' : ''}://${this.configs.host}:${this.configs.port}/all?verifyKey=${this.configs.verifyKey}&qq=${id}`); this.ws.on('error', (err) => { this.logger.error(`监听器启动出错:`, err.message); }); this.ws.on('message', (buffer: Buffer) => { const message: { syncId: string, data: ChatMessage | SyncMessage | Event | ApiResponse } = JSON.parse(buffer.toString()); if ('syncId' in message) { if (message.syncId === '-1') { const data = message.data as ChatMessage | SyncMessage | Event; if (data.type.endsWith('Message')) this.emit('message', data as ChatMessage); else if (data.type.endsWith('SyncMessage')) this.emit('sync', data as SyncMessage); else if (data.type.endsWith('Event')) this.emit('event', data as Event); } else if (message.syncId === '') { const data = message.data as ApiResponse; if (data.code === ResponseCode.Success) { this.listening = true; this.emit('listen'); this.logger.info('已启动监听'); } else { this.logger.error(`监听器启动失败, 错误原因: ${data.msg}`); this.ws.close(); } } else { this.queue.set(parseInt(message.syncId), message.data as ApiResponse); } } else { this.logger.warn('未能解析的数据:', message); } }); } }, stop() { if (this.ws && this.ws.readyState === this.ws.OPEN) { this.ws.close(); this.listening = false; this.logger.info('已停止监听'); this.emit('stop'); } }, async getAbout() { return await this.request('about'); }, async getMessageFromId(messageId: number) { return await this.request('messageFromId', {target: messageId}); }, async getFriendList() { return await this.request('friendList'); }, async getGroupList() { return await this.request('groupList'); }, async getMemberList(groupId: number) { return await this.request('memberList', {target: groupId}); }, async getBotProfile() { return await this.request('botProfile'); }, async getFriendProfile(friendId: number) { return await this.request('friendProfile', {target: friendId}); }, async getMemberProfile(memberId: number, groupId: number) { return await this.request('memberProfile', {target: groupId, memberId}); }, async getUserProfile(userId: number) { return this.request('userProfile', {target: userId}); }, async sendFriendMessage(friendId: number, messageChain: SingleMessage[], quoteMessageId?: number) { return await this.request('sendFriendMessage', {qq: friendId, messageChain, quote: quoteMessageId}); }, async sendGroupMessage(groupId: number, messageChain: SingleMessage[], quoteMessageId?: number) { return await this.request('sendGroupMessage', {group: groupId, messageChain, quote: quoteMessageId}); }, async sendTempMessage(memberId: number, groupId: number, messageChain: SingleMessage[], quoteMessageId?: number) { return await this.request('sendTempMessage', { qq: memberId, group: groupId, messageChain, quote: quoteMessageId, }); }, async sendNudge(targetId: number, subjectId: number, kind: NudgeKind) { return await this.request('sendNudge', {target: targetId, subject: subjectId, kind}); }, async recall(messageId: number) { return await this.request('recall', {target: messageId}); }, async getFileList(directoryId: string, directoryPath: string, groupId: number, friendId: number, offset: number = 1, size: number = 100, withDownloadInfo: boolean = true) { return await this.request('file_list', { id: directoryId, path: directoryPath, group: groupId, qq: friendId, offset, size, withDownloadInfo, }); }, async getFileInfo(fileId: string, path: string, groupId: number, friendId: number, withDownloadInfo: boolean = true) { return await this.request('file_info', { id: fileId, path: path, group: groupId, qq: friendId, withDownloadInfo, }); }, async createFileDirectory(parentDirectoryId: string, parentDirectoryPath: string, directoryName: string, groupId: number, friendId: number) { return await this.request('file_mkdir', { id: parentDirectoryId, path: parentDirectoryPath, group: groupId, qq: friendId, directoryName, }); }, async deleteFile(id: string, path: string, groupId: number, friendId: number) { return await this.request('file_delete', {id, path, group: groupId, qq: friendId}); }, async moveFile(id: string, path: string, groupId: number, friendId: number, moveToDirectoryId: string, moveToDirectoryPath: string) { return await this.request('file_move', { id, path, group: groupId, qq: friendId, moveTo: moveToDirectoryId, moveToPath: moveToDirectoryPath, }); }, async renameFile(id: string, path: string, groupId: number, friendId: number, name: string) { return await this.request('file_rename', {id, path, group: groupId, qq: friendId, renameTo: name}); }, async deleteFriend(friendId: number) { return await this.request('deleteFriend', {target: friendId}); }, async muteMember(memberId: number, groupId: number, time: number) { return await this.request('mute', {target: groupId, memberId, time}); }, async unmuteMember(memberId: number, groupId: number) { return await this.request('unmute', {target: groupId, memberId}); }, async kickMember(memberId: number, groupId: number, message: string) { return await this.request('kick', {target: groupId, memberId, msg: message}); }, async quitGroup(groupId: number) { return await this.request('quit', {target: groupId}); }, async muteAll(groupId: number) { return await this.request('muteAll', {target: groupId}); }, async unmuteAll(groupId: number) { return await this.request('unmuteAll', {target: groupId}); }, async setEssence(messageId: number) { return await this.request('setEssence', {target: messageId}); }, async getGroupConfig(groupId: number) { return await this.request('groupConfig', {target: groupId}, 'get'); }, async setGroupConfig(groupId: number, config: GroupConfig) { return await this.request('groupConfig', {target: groupId, config}, 'update'); }, async getMemberInfo(memberId: number, groupId: number) { return await this.request('memberInfo', {target: groupId, memberId}, 'get'); }, async setMemberInfo(memberId: number, groupId: number, info: GroupMemberInfo) { return await this.request('memberInfo', {target: groupId, memberId, info}, 'update'); }, async setMemberAdmin(memberId: number, groupId: number, admin: boolean = true) { return await this.request('memberAdmin', {target: groupId, memberId, assign: admin}); }, async getGroupAnnouncements(groupId: number, offset: number, size: number) { return this.request('anno_list', {id: groupId, offset, size}); }, async postGroupAnnouncement(groupId: number, announcement: PostGroupAnnouncement) { return this.request('anno_publish', {target: groupId, ...announcement}); }, async deleteGroupAnnouncement(groupId: number, announcementId: number) { return this.request('anno_delete', {id: groupId, fid: announcementId}); }, async executeCommand(command: SingleMessage[]) { return this.request('cmd_execute', {command}); }, async registerCommand(name: string, alias: string[], usage: string, description: string) { return this.request('cmd_register', {name, alias, usage, description}); }, async handleNewFriendRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) { return await this.request('resp_newFriendRequestEvent', {eventId, fromId, groupId, operate, message}); }, async handleMemberJoinRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) { return await this.request('resp_memberJoinRequestEvent', {eventId, fromId, groupId, operate, message}); }, async handleBotInvitedJoinGroupRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) { return await this.request('resp_botInvitedJoinGroupRequestEvent', {eventId, fromId, groupId, operate, message}); }, async uploadImage() { this.logger.error('暂不支持的操作: uploadImage'); return null; }, async uploadVoice() { this.logger.error('暂不支持的操作: uploadVoice'); return null; }, async uploadGroupFile() { this.logger.error('暂不支持的操作: uploadGroupFile'); return null; }, }); export = WebsocketApiAdapter;