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" import CallbackError from "./CallbackError.ts" import ApiCallbackMessage from "./ApiCallbackMessage.ts" import * as marked from 'marked' import { text } from "node:stream/consumers"; class ChatMention extends BaseClientObject { declare chat_id?: string declare user_id?: string declare text?: string constructor(client: LingChairClient, { user_id, chat_id, text, }: { user_id?: string, chat_id?: string, text: string, }) { super(client) this.user_id = user_id this.chat_id = chat_id this.text = text } 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) } getText() { return this.text } } type FileType = 'Video' | 'Image' | 'File' type MentionType = 'ChatMention' | 'UserMention' class ChatAttachment extends BaseClientObject { declare file_hash: string declare file_name: string constructor(client: LingChairClient, { file_hash, file_name }: { file_hash: string, file_name: string }) { super(client) this.file_name = file_name this.file_hash = file_hash } async blob() { try { return await this.blobOrThrow() } catch (_) { return null } } fetch(init?: RequestInit) { const url = this.client.getUrlForFileByHash(this.file_hash) return fetch(url!, init) } async blobOrThrow() { const re = await this.fetch() const blob = await re.blob() if (!re.ok) throw new CallbackError({ msg: await blob.text(), code: re.status, } as ApiCallbackMessage) return blob } async getMimeType() { try { return await this.getMimeTypeOrThrow() } catch (_) { return null } } async getMimeTypeOrThrow() { const re = await this.fetch({ method: 'HEAD' }) if (re.ok) { const t = re.headers.get('content-type') if (t) return t throw new Error("Unable to get Content-Type") } throw new CallbackError({ msg: await re.text(), code: re.status, } as ApiCallbackMessage) } async getLength() { try { return await this.getLengthOrThrow() } catch (_) { return null } } async getLengthOrThrow() { const re = await this.fetch({ method: 'HEAD' }) if (re.ok) { const contentLength = re.headers.get('content-length') if (contentLength) return parseInt(contentLength) throw new Error("Unable to get Content-Length") } throw new CallbackError({ msg: await re.text(), code: re.status, } as ApiCallbackMessage) } getFileHash() { return this.file_hash } getFileName() { return this.file_name } } 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 } 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, }) { return new marked.Marked({ async: false, extensions: [ { name: 'text', renderer: ({ text }) => text, }, { name: 'heading', renderer({ tokens }) { return this.parser.parseInline(tokens!) }, }, { name: 'paragraph', renderer({ tokens }) { return this.parser.parseInline(tokens!) }, }, { 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]! let file_name: string = /^(Video|File|Image)=(.*)/.exec(text)?.[2] || text file_name.trim() == '' && (file_name = 'Unnamed_File') return attachment ? attachment({ text: text, attachment: new ChatAttachment(this.client, { file_hash, file_name }), fileType: fileType, }) : text } if (mentionType != null && /^tws:\/\/chat\?id=[A-Za-z0-9]+/.test(href)) { const id = /^tws:\/\/chat\?id=(.*)/.exec(href)?.[1]! const label = /^(User|Chat)Mention=(.*)/.exec(text)?.[2] || '' return mention ? mention({ text: text, mention: new ChatMention(this.client, { [({ ChatMention: 'chat_id', UserMention: 'user_id', })[mentionType]]: id, text: label, }), mentionType: mentionType, }) : text } }, } ] }).parse(this.getText()) as string } 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 } async getUser() { return await User.getById(this.client, this.bean.user_id as string) } getTime() { return this.bean.time } }