diff --git a/client-protocol/Message.ts b/client-protocol/Message.ts index 57dc679..1f4c571 100644 --- a/client-protocol/Message.ts +++ b/client-protocol/Message.ts @@ -3,6 +3,63 @@ import MessageBean from "./bean/MessageBean.ts" import LingChairClient from "./LingChairClient.ts" import Chat from "./Chat.ts" import User from "./User.ts" +import CallbackError from "./CallbackError.ts" +import ApiCallbackMessage from "./ApiCallbackMessage.ts" + +import marked from 'marked' + +class ChatMention extends BaseClientObject { + declare chat_id?: string + declare user_id?: string + constructor(client: LingChairClient, { + user_id, + chat_id, + }: { + user_id?: string, + chat_id?: string, + }) { + super(client) + this.user_id = user_id + this.chat_id = chat_id + } + async getChat() { + return await Chat.getById(this.client, this.chat_id as string) + } + async getUser() { + return await User.getById(this.client, this.user_id as string) + } +} + +type FileType = 'Video' | 'Image' | 'File' +type MentionType = 'ChatMention' | 'UserMention' + +class ChatAttachment extends BaseClientObject { + declare file_hash: string + constructor(client: LingChairClient, file_hash: string) { + super(client) + this.file_hash = file_hash + } + async blob() { + try { + return await this.blobOrThrow() + } catch (_) { + return null + } + } + async blobOrThrow() { + const url = this.client.getUrlForFileByHash(this.file_hash) + const re = await fetch(url!) + const blob = await re.blob() + if (!re.ok) throw new CallbackError({ + msg: await blob.text(), + code: re.status, + } as ApiCallbackMessage) + return blob + } + getFileHash() { + return this.file_hash + } +} export default class Message extends BaseClientObject { declare bean: MessageBean @@ -27,6 +84,63 @@ export default class Message extends BaseClientObject { getText() { return this.bean.text } + parseWithTransformers({ + attachment, + mention, + }: { + attachment?: ({ text, fileType, attachment }: { text: string, fileType: FileType, attachment: ChatAttachment }) => string, + mention?: ({ text, mentionType, mention }: { text: string, mentionType: MentionType, mention: ChatMention }) => string, + }) { + new marked.Marked({ + extensions: [ + { + name: 'image', + renderer: ({ text, href }) => { + const mentionType = /^(UserMention|ChatMention)=.*/.exec(text)?.[1] as MentionType + const fileType = (/^(Video|File|Image)=.*/.exec(text)?.[1] || 'Image') as FileType + + if (fileType != null && /tws:\/\/file\?hash=[A-Za-z0-9]+$/.test(href)) { + const file_hash = /^tws:\/\/file\?hash=(.*)/.exec(href)?.[1]! + return attachment ? attachment({ text: text, attachment: new ChatAttachment(this.client, file_hash), fileType: fileType, }) : text + } + if (mentionType != null && /^tws:\/\/chat\?id=[A-Za-z0-9]+/.test(href)) { + const id = /^tws:\/\/chat\?id=(.*)/.exec(href)?.[1]! + return mention ? mention({ + text: text, + mention: new ChatMention(this.client, { + [({ + ChatMention: 'chat_id', + UserMention: 'user_id', + })[mentionType]]: id + }), + mentionType: mentionType, + }) : text + } + }, + } + ] + }).parse(this.getText()) + } + getAttachments() { + const attachments: ChatAttachment[] = [] + this.parseWithTransformers({ + attachment({ attachment }) { + attachments.push(attachment) + return '' + } + }) + return attachments + } + getMentions() { + const mentions: ChatMention[] = [] + this.parseWithTransformers({ + mention({ mention }) { + mentions.push(mention) + return '' + } + }) + return mentions + } getUserId() { return this.bean.user_id } diff --git a/client-protocol/deno.jsonc b/client-protocol/deno.jsonc index 818504e..7b07fb4 100644 --- a/client-protocol/deno.jsonc +++ b/client-protocol/deno.jsonc @@ -1,6 +1,7 @@ { "imports": { "socket.io-client": "npm:socket.io-client@4.8.1", - "lingchair-internal-shared": "../internal-shared/mod.ts" + "lingchair-internal-shared": "../internal-shared/mod.ts", + "marked": "npm:marked@16.3.0" } } \ No newline at end of file