/* eslint-disable @typescript-eslint/no-explicit-any */ import { messageApi } from "@/api/message-api.service"; export class TeamsChatService { private readonly MY_NAME = "Apactech com"; public lastMessage?: IMessage; public initialHistories: IMessage[] = []; public elTags = { container_scroll: "/html/body/div[1]/div/div/div/div[5]/div[1]/div[1]/div[2]/div[1]/div[1]/div", conatainer_conversations: "/html/body/div[1]/div/div/div/div[5]/div[1]/div[1]/div[2]/div[1]/div[1]/div/div[1]", container_chat: '[data-testid="message-wrapper"]', root_id: '[aria-selected="true"] [id^="chat-list-item"]', room_name: '[data-tid="chat-title"]', close_reply_btn: '[data-track-action-scenario="messageQuotedReplyDismissed"]', reply_btn: '[aria-label="Reply"]', chat_input: '[placeholder="Type a message"]', }; private _getImageFormEl(el: HTMLElement): HTMLImageElement[] { // Tìm tất cả img có data-gallery-src trong el let sharedImages = Array.from( el.querySelectorAll("img[data-gallery-src]") ) as HTMLImageElement[]; // Nếu không tìm thấy thì thử tìm trong parentElement if (sharedImages.length === 0 && el.parentElement) { sharedImages = Array.from( el.parentElement.querySelectorAll("img[data-gallery-src]") ) as HTMLImageElement[]; } console.log({ el, data_time: (el as any).date_time, text: (el.querySelector('[data-tid="overlay-count-text"]') as any) ?.innerText, }); return sharedImages; // Luôn trả về array (có thể rỗng) } public getCurrentRoomInfo(): { room_id?: string; room_name?: string } { // const roomId = document // .querySelector(this.elTags.root_id) // ?.id?.split(":")[1]; const roomId = document .querySelector(this.elTags.root_id) ?.id?.replace("chat-list-item_", ""); const roomName = ( document.querySelector(this.elTags.room_name) as HTMLElement )?.innerText; return { room_id: roomId, room_name: roomName }; } private _getMessageByEl(el: HTMLElement | null): string | string[] { if (!el) return ""; // Lấy text ban đầu và loại bỏ khoảng trắng dư let message = el.innerText?.trim() || ""; // Lấy danh sách ảnh (nếu có) const sharedImages = this._getImageFormEl(el) || []; if (sharedImages.length > 0) { const arrMessage = sharedImages.map( (img) => img.getAttribute("src") || "" ); if (message.length > 0) { // Kiểm tra overlay const overlay = document .getElementById(`message-body-${(el as any)?.date_time}`) ?.querySelector('[data-tid="overlay-count-text"]'); // Nếu overlay text === message gốc → chỉ trả về ảnh if (overlay && (overlay as HTMLElement).innerText === message) { return arrMessage; } // Nếu không, thêm message vào cuối arrMessage.push(message); } return arrMessage; } // Tìm tất cả emoji trong element const emojiImgs = Array.from( el.querySelectorAll("img[itemtype]") ) as HTMLImageElement[]; const emojiAlts = emojiImgs .map((img) => img.getAttribute("alt") || "") .filter(Boolean); // Nếu có emoji thì nối vào message if (emojiAlts.length > 0) { message = `${message}${emojiAlts.join("")}`; } return message.trim(); } public parseMessageElement(el: Element, isMine = false): IMessage | null { const timestampEl = el.querySelector( isMine ? ".fui-ChatMyMessage__timestamp" : ".fui-ChatMessage__timestamp" ) as HTMLElement | null; const authorEl = el.querySelector( isMine ? ".fui-ChatMyMessage__author" : ".fui-ChatMessage__author" ) as HTMLElement | null; if (!timestampEl) return null; const datetimeAttr = timestampEl.getAttribute("datetime"); if (!datetimeAttr) return null; // const dateTime = new Date(datetimeAttr).getTime(); const dateTime = Number.isNaN(timestampEl.id.replace("timestamp-", "")) ? new Date(datetimeAttr).getTime() : Number(timestampEl.id.replace("timestamp-", "")); const contentEl = document.querySelector( `#content-${dateTime}` ) as HTMLElement | null; (contentEl as any)["date_time"] = dateTime; const { room_id, room_name } = this.getCurrentRoomInfo(); return { name: authorEl?.innerText, message: this._getMessageByEl(contentEl), time: dateTime, room_id, room_name, date_time: new Date(datetimeAttr).getTime(), }; } extractAllMessages(): IMessage[] { const myMessages: IMessage[] = Array.from( document.querySelectorAll(".fui-ChatMyMessage") ) .map((el) => this.parseMessageElement(el, true)) .filter((msg): msg is IMessage => msg !== null); const otherMessages: IMessage[] = Array.from( document.querySelectorAll(".fui-ChatMessage") ) .map((el) => this.parseMessageElement(el, false)) .filter((msg): msg is IMessage => msg !== null); console.log({ myMessages, otherMessages }); return [...myMessages, ...otherMessages].sort((a, b) => a.time - b.time); } private handleNewMessage(message: IMessage) { // Gửi tới một server AI để phản hồi (nếu cần) // fetch('https://127.0.0.1:8443/reply', { ... }) console.log("%c[New incoming message]", "color: #007acc;", message); } public async detectNewMessages() { const allMessages = this.extractAllMessages(); const lastIndex = allMessages.findIndex( (msg) => msg.time === this.lastMessage?.time ); const newMessages = allMessages.slice(lastIndex + 1); if (newMessages.length === 0) { console.log("[Monitor] No new messages..."); return; } const newMsg = newMessages[0]; // if (newMsg.name === this.MY_NAME) { // console.log("[Monitor] My new message:", newMsg); // await messageApi.sendSingleMessage(newMsg); // } else { // console.log("[Monitor] New incoming message:", newMsg); // this.handleNewMessage(newMsg); // messageApi.sendSingleMessage(newMsg); // } this.lastMessage = allMessages.pop(); return newMsg; } public async start(interval = 10000) { console.log("[Monitor] Starting..."); this.initialHistories = this.extractAllMessages(); this.lastMessage = this.initialHistories.pop(); // await messageApi.sendBulkMessages(this.initialHistories); setInterval(async () => await this.detectNewMessages(), interval); } private async _getConversationsInfo( xpath: string = this.elTags.conatainer_conversations ): Promise { const result = document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue as HTMLElement | null; if (!result) { console.log("Không tìm thấy phần tử theo XPath."); messageApi.sendLog({ type: "error", message: `Not found selector: ${xpath}`, }); return []; } // Lọc phần tử con có role="none" const matchedChildren = Array.from(result.children).filter( (child: Element) => { return child.getAttribute("role") === "none"; } ); const data: ChatItem[] = matchedChildren.map((child: Element): ChatItem => { const id = child.id || null; const titleId = `title-chat-list-item_${id}`; const titleElement = document.getElementById(titleId); const spanText = titleElement?.innerText || null; let type: ChatItemType = null; if (id?.includes("@thread.skype")) { type = "group"; } else if (id?.includes("@oneToOne.skype")) { type = "personal"; } else { type = "group"; } return { id, name: spanText, type, }; }); return data; } public async _scrollToBottomByXPath( xpath: string = this.elTags.container_scroll, options?: { maxStableRounds?: number; // Số vòng scroll không thay đổi trước khi dừng delay?: number; // Thời gian chờ giữa mỗi lần scroll (ms) maxScrolls?: number; // Giới hạn số lần scroll tối đa } ): Promise { const { maxStableRounds = 5, delay = 300, maxScrolls = 100, } = options || {}; const container = document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue as HTMLElement | null; if (!container) { console.warn("❌ Không tìm thấy phần tử với XPath:", xpath); messageApi.sendLog({ type: "error", message: `Not found selector: ${xpath}`, }); return; } return new Promise((resolve) => { let lastHeight = -1; let stableCount = 0; let scrolls = 0; const interval = setInterval(() => { const currentHeight = container.scrollHeight; container.scrollTop = currentHeight; if (currentHeight === lastHeight) { stableCount++; } else { stableCount = 0; lastHeight = currentHeight; } scrolls++; if (stableCount >= maxStableRounds || scrolls >= maxScrolls) { clearInterval(interval); resolve(); } }, delay); }); } async handleGetConversations() { await this._scrollToBottomByXPath(); return this._getConversationsInfo(); } }