From f0ca0fbbd49617cb8d55f583efee4da319cf8456 Mon Sep 17 00:00:00 2001 From: CrescentLeaf Date: Sun, 9 Nov 2025 01:00:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E6=96=B0=E7=9A=84=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=8D=8F=E8=AE=AE=E5=BA=93=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-protocol/Chat.ts | 229 ++++++++++++++++++++++++ client-protocol/JoinRequest.ts | 66 +++++++ client-protocol/LingChairClient.ts | 2 + client-protocol/Message.ts | 39 ++++ client-protocol/bean/ChatBean.ts | 2 +- client-protocol/bean/JoinRequestBean.ts | 4 +- client-protocol/bean/MessageBean.ts | 3 +- client-protocol/test.ts | 11 +- server/api/ChatApi.ts | 31 +++- 9 files changed, 378 insertions(+), 9 deletions(-) create mode 100644 client-protocol/Chat.ts create mode 100644 client-protocol/JoinRequest.ts create mode 100644 client-protocol/Message.ts diff --git a/client-protocol/Chat.ts b/client-protocol/Chat.ts new file mode 100644 index 0000000..375bb84 --- /dev/null +++ b/client-protocol/Chat.ts @@ -0,0 +1,229 @@ +import BaseClientObject from "./BaseClientObject.ts" +import BaseChatSettingsBean from "./bean/BaseChatSettingsBean.ts" +import ChatBean from "./bean/ChatBean.ts" +import JoinRequestBean from "./bean/JoinRequestBean.ts" +import MessageBean from "./bean/MessageBean.ts" +import CallbackError from "./CallbackError.ts" +import JoinRequest from "./JoinRequest.ts" +import LingChairClient from "./LingChairClient.ts" +import Message from "./Message.ts" + +export default class Chat extends BaseClientObject { + declare bean: ChatBean + constructor(client: LingChairClient, bean: ChatBean) { + super(client) + this.bean = bean + } + /* + * ================================================ + * 实例化方法 + * ================================================ + */ + static async getById(client: LingChairClient, id: string) { + try { + return await this.getByIdOrThrow(client, id) + } catch (_) { + return null + } + } + static async getByIdOrThrow(client: LingChairClient, id: string) { + const re = await client.invoke("Chat.getInfo", { + token: client.access_token, + target: id, + }) + if (re.code == 200) + return new Chat(client, re.data as unknown as ChatBean) + throw new CallbackError(re) + } + /** + * ================================================ + * 创建对话 (另类实例化方法) + * ================================================ + */ + static async getOrCreatePrivateChat(client: LingChairClient, user_id: string) { + try { + return await this.getOrCreatePrivateChatOrThrow(client, user_id) + } catch (_) { + return null + } + } + static async getOrCreatePrivateChatOrThrow(client: LingChairClient, user_id: string) { + const re = await client.invoke("Chat.getIdForPrivate", { + token: client.access_token, + target: user_id, + }) + if (re.code != 200) throw new CallbackError(re) + return new Chat(client, re.data as unknown as ChatBean) + } + static async createGroup(client: LingChairClient, title: string, name?: string) { + try { + return await this.createGroupOrThrow(client, title, name) + } catch (_) { + return null + } + } + static async createGroupOrThrow(client: LingChairClient, title: string, name?: string) { + const re = await client.invoke("Chat.createGroup", { + token: client.access_token, + title, + name, + }) + if (re.code != 200) throw new CallbackError(re) + return new Chat(client, re.data as unknown as ChatBean) + } + /** + * ================================================ + * 对话消息 + * ================================================ + */ + async getMessages(page: number = 0) { + return (await this.getMessageBeans(page)).map((v) => new Message(this.client, v)) + } + async getMessagesOrThrow(page: number = 0) { + return (await this.getMessageBeansOrThrow(page)).map((v) => new Message(this.client, v)) + } + async getMessageBeans(page: number = 0) { + try { + return await this.getMessageBeansOrThrow(page) + } catch (_) { + return [] + } + } + async getMessageBeansOrThrow(page: number = 0) { + const re = await this.client.invoke("Chat.getMessageHistory", { + token: this.client.access_token, + page, + target: this.bean.id, + }) + if (re.code == 200) return re.data!.messages as MessageBean[] + throw new CallbackError(re) + } + async sendMessage(text: string) { + try { + return await this.sendMessageOrThrow(text) + } catch (_) { + return null + } + } + async sendMessageOrThrow(text: string) { + const re = await this.client.invoke("Chat.sendMessage", { + token: this.client.access_token, + text, + target: this.bean.id, + }) + if (re.code == 200) + return new Message(this.client, re.data!.message as MessageBean) + throw new CallbackError(re) + } + /** + * ================================================ + * 加入对话申请 + * ================================================ + */ + async getJoinRequests() { + try { + return await this.getJoinRequestsOrThrow() + } catch (_) { + return [] + } + } + async getJoinRequestsOrThrow() { + const join_requests = await this.getJoinRequestBeansOrThrow() + return join_requests.map((jr) => new JoinRequest(this.client, jr, this.bean.id)) + } + async getJoinRequestBeans() { + try { + return await this.getJoinRequestBeansOrThrow() + } catch (_) { + return [] + } + } + async getJoinRequestBeansOrThrow() { + const re = await this.client.invoke("Chat.getJoinRequests", { + token: this.client.access_token + }) + if (re.code == 200) + return re.data!.join_requests as JoinRequestBean[] + throw new CallbackError(re) + } + /** + * ================================================ + * 对话信息 + * ================================================ + */ + async setAvatarFileHash(file_hash: string) { + try { + await this.setAvatarFileHashOrThrow(file_hash) + return true + } catch (_) { + return false + } + } + async setAvatarFileHashOrThrow(file_hash: string) { + const re = await this.client.invoke("Chat.setAvatar", { + token: this.client.access_token, + file_hash, + target: this.bean.id, + }) + if (re.code != 200) throw new CallbackError(re) + this.bean.avatar_file_hash = file_hash + } + async updateSettings(args: BaseChatSettingsBean) { + try { + await this.updateSettingsOrThrow(args) + return true + } catch (_) { + return false + } + } + async updateSettingsOrThrow(args: BaseChatSettingsBean) { + const re = await this.client.invoke("Chat.updateSettings", { + token: this.client.access_token, + target: this.bean.id, + settings: args + }) + if (re.code != 200) throw new CallbackError(re) + this.bean.settings = args + } + async getTheOtherUserId() { + try { + return await this.getTheOtherUserIdOrThrow() + } catch (_) { + return null + } + } + async getTheOtherUserIdOrThrow() { + const re = await this.client.invoke("Chat.updateSettings", { + token: this.client.access_token, + target: this.bean.id, + }) + if (re.code != 200) throw new CallbackError(re) + return re.data!.user_id as string + } + /* + * ================================================ + * 基本 Bean + * ================================================ + */ + getId() { + return this.bean.id + } + getTitle() { + return this.bean.title + } + getType() { + return this.bean.type + } + isMember() { + return this.bean.is_member + } + isAdmin() { + return this.bean.is_admin + } + getAvatarFileHash() { + return this.bean.avatar_file_hash + } + getSettings() { + return this.bean.settings + } +} diff --git a/client-protocol/JoinRequest.ts b/client-protocol/JoinRequest.ts new file mode 100644 index 0000000..cbc28a4 --- /dev/null +++ b/client-protocol/JoinRequest.ts @@ -0,0 +1,66 @@ +import BaseClientObject from "./BaseClientObject.ts" +import JoinRequestBean from "./bean/JoinRequestBean.ts" +import CallbackError from "./CallbackError.ts" +import LingChairClient from "./LingChairClient.ts" +import JoinRequestAction from "./type/JoinRequestAction.ts" + +export default class JoinRequest extends BaseClientObject { + declare bean: JoinRequestBean + declare chat_id: string + constructor(client: LingChairClient, bean: JoinRequestBean, chat_id: string) { + super(client) + this.bean = bean + this.chat_id = chat_id + } + /* + * ================================================ + * 操作 + * ================================================ + */ + async accept() { + return await this.process('accept') + } + async acceptOrThrow() { + return await this.processOrThrow('accept') + } + async remove() { + return await this.process('remove') + } + async removOrThrow() { + return await this.processOrThrow('remove') + } + async process(action: JoinRequestAction) { + try { + await this.processOrThrow(action) + return true + } catch (_) { + return false + } + } + async processOrThrow(action: JoinRequestAction) { + const re = await this.client.invoke("Chat.processJoinRequest", { + token: this.client.access_token, + chat_id: this.chat_id, + user_id: this.bean.user_id, + action, + }) + if (re.code != 200) throw new CallbackError(re) + } + /* + * ================================================ + * 基本 Bean + * ================================================ + */ + getAvatarFileHash() { + return this.bean.avatar_file_hash + } + getUserId() { + return this.bean.user_id + } + getNickName() { + return this.bean.title + } + getReason() { + return this.bean.reason + } +} diff --git a/client-protocol/LingChairClient.ts b/client-protocol/LingChairClient.ts index a5818cf..9c496f1 100644 --- a/client-protocol/LingChairClient.ts +++ b/client-protocol/LingChairClient.ts @@ -6,9 +6,11 @@ import ApiCallbackMessage from './ApiCallbackMessage.ts' import User from "./User.ts" import UserMySelf from "./UserMySelf.ts" import CallbackError from "./CallbackError.ts" +import Chat from "./Chat.ts" export { User, + Chat, UserMySelf, } diff --git a/client-protocol/Message.ts b/client-protocol/Message.ts new file mode 100644 index 0000000..57dc679 --- /dev/null +++ b/client-protocol/Message.ts @@ -0,0 +1,39 @@ +import BaseClientObject from "./BaseClientObject.ts" +import MessageBean from "./bean/MessageBean.ts" +import LingChairClient from "./LingChairClient.ts" +import Chat from "./Chat.ts" +import User from "./User.ts" + +export default class Message extends BaseClientObject { + declare bean: MessageBean + constructor(client: LingChairClient, bean: MessageBean) { + super(client) + this.bean = bean + } + /* + * ================================================ + * 基本 Bean + * ================================================ + */ + getId() { + return this.bean.id + } + getChatId() { + return this.bean.chat_id + } + async getChat() { + return await Chat.getById(this.client, this.bean.chat_id as string) + } + getText() { + return this.bean.text + } + getUserId() { + return this.bean.user_id + } + async getUser() { + return await User.getById(this.client, this.bean.user_id as string) + } + getTime() { + return this.bean.time + } +} \ No newline at end of file diff --git a/client-protocol/bean/ChatBean.ts b/client-protocol/bean/ChatBean.ts index eabae02..5240359 100644 --- a/client-protocol/bean/ChatBean.ts +++ b/client-protocol/bean/ChatBean.ts @@ -1,5 +1,5 @@ import BaseChatSettingsBean from "./BaseChatSettingsBean.ts" -import ChatType from "./ChatType.ts" +import ChatType from "../type/ChatType.ts" export default class ChatBean { declare type: ChatType diff --git a/client-protocol/bean/JoinRequestBean.ts b/client-protocol/bean/JoinRequestBean.ts index eae0966..3b7fc8d 100644 --- a/client-protocol/bean/JoinRequestBean.ts +++ b/client-protocol/bean/JoinRequestBean.ts @@ -1,7 +1,7 @@ export default class JoinRequestBean { declare user_id: string - declare title: string - declare avatar?: string + declare nickname: string + declare avatar_file_hash?: string declare reason?: string [key: string]: unknown diff --git a/client-protocol/bean/MessageBean.ts b/client-protocol/bean/MessageBean.ts index ab89417..32ea5e3 100644 --- a/client-protocol/bean/MessageBean.ts +++ b/client-protocol/bean/MessageBean.ts @@ -1,6 +1,7 @@ export default class MessageBean { declare id: number declare text: string - declare user_id: string + declare user_id?: string + declare chat_id?: string declare time: string } diff --git a/client-protocol/test.ts b/client-protocol/test.ts index 5632261..0fcdc54 100644 --- a/client-protocol/test.ts +++ b/client-protocol/test.ts @@ -1,4 +1,4 @@ -import LingChairClient, { User, UserMySelf } from "./LingChairClient.ts" +import LingChairClient, { Chat, UserMySelf } from "./LingChairClient.ts" import OnMessageData from "./type/OnMessageData.ts" const client = new LingChairClient({ @@ -9,7 +9,10 @@ await client.auth({ account: '满月', password: '满月', }) -client.on('Client.onMessage', (data: OnMessageData) => { - console.log(data) +client.on('Client.onMessage', async (data: OnMessageData) => { + const chat = await Chat.getById(client, data.chat) + const regexp = /^test (.*)/g.exec(data.msg.text) + if (regexp?.[0] != null) { + chat?.sendMessage(`Hello, ${regexp[1]}`) + } }) -console.log(await (await UserMySelf.getMySelf(client))?.getMyRecentChatBeans()) diff --git a/server/api/ChatApi.ts b/server/api/ChatApi.ts index d35cd9e..4620745 100644 --- a/server/api/ChatApi.ts +++ b/server/api/ChatApi.ts @@ -76,7 +76,7 @@ export default class ChatApi extends BaseApi { code: 200, msg: "成功", data: { - msg: m, + message: m, } } }) @@ -196,6 +196,8 @@ export default class ChatApi extends BaseApi { return { user_id: user?.bean.id, reason: v.reason, + nickname: user!.getNickName(), + // TODO: 这个得删掉, 应该用 nickname title: user!.getNickName(), avatar_file_hash: user!.getAvatarFileHash() ? user!.getAvatarFileHash() : null, } @@ -342,7 +344,18 @@ export default class ChatApi extends BaseApi { code: 200, msg: '成功', data: { + // TODO: 移除这个, 将本方法重命名为 getOrCreatePrivateChat + // 并重构原 Web 客户端所引用的内容 chat_id: chat.bean.id, + + id: chat.bean.id, + name: chat.bean.name, + type: chat.bean.type, + title: chat.getTitle(user), + avatar_file_hash: chat.getAvatarFileHash(user) ? chat.getAvatarFileHash(user) : undefined, + settings: JSON.parse(chat.bean.settings), + is_member: true, + is_admin: true, } } }) @@ -384,7 +397,23 @@ export default class ChatApi extends BaseApi { code: 200, msg: '成功', data: { + // TODO: 移除这个 + // 并重构原 Web 客户端所引用的内容 chat_id: chat.bean.id, + + id: chat.bean.id, + name: chat.bean.name, + type: chat.bean.type, + title: chat.getTitle(), + avatar_file_hash: chat.getAvatarFileHash() ? chat.getAvatarFileHash() : undefined, + settings: { + ...JSON.parse(chat.bean.settings), + // 下面两个比较特殊, 用于群设置 + group_name: chat.bean.name, + group_title: chat.getTitle(), + }, + is_member: UserChatLinker.checkUserIsLinkedToChat(token.author, chat!.bean.id), + is_admin: chat.checkUserIsAdmin(token.author), } } })