/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { queue } from "@/lib/queue"; import { TeamsChatService } from "./teams-chat.service"; import { EVENTS } from "@/lib/event"; import { typeingService } from "./typing.service"; import { delay } from "@/features/app"; export class ContentService { service: TeamsChatService; port; constructor(port: any) { this.service = new TeamsChatService(); this.port = port; } _forceHeightObserver: any; getElementByXPath(xpath: string) { return document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; } forceHeight(el: HTMLElement, height = "100px") { if (!el) return; el.style.setProperty("height", height, "important"); // Gỡ bỏ auto-resize (nếu là textarea) el.style.setProperty("resize", "none", "important"); // Lặp lại khi element bị focus/input const keepHeight = () => { el.style.setProperty("height", height, "important"); }; el.addEventListener("focus", keepHeight); el.addEventListener("input", keepHeight); el.addEventListener("blur", keepHeight); // Thêm mutation observer nếu bị script khác chỉnh sửa const observer = new MutationObserver(() => { el.style.setProperty("height", height, "important"); }); observer.observe(el, { attributes: true, attributeFilter: ["style"], }); // Lưu lại để debug nếu cần this._forceHeightObserver = observer; } private _clickToConversation(id: string) { const el = document.getElementById(`chat-list-item_${id}`); if (el) { el.scrollIntoView({ behavior: "smooth", // hoặc "auto" block: "center", // cuộn sao cho phần tử nằm giữa view }); // Đợi 1 chút cho scroll xong (nếu cần) setTimeout(() => el.click(), 200); // delay 200ms là đủ mượt } return el?.click(); } private async _waitForMessagesToAppear( timeoutMs = 10000 ): Promise { return new Promise((resolve, reject) => { const chatPaneList = document.getElementById("chat-pane-list"); let timeout: any = null; if (!chatPaneList) { return reject(new Error("#chat-pane-list not found")); } const getChildren = () => Array.from(chatPaneList.children) as HTMLElement[]; const checkAndResolve = () => { const children = getChildren(); if (children.length > 0) { observer.disconnect(); if (timeout) clearTimeout(timeout); resolve(children); } }; const observer = new MutationObserver(() => { checkAndResolve(); }); observer.observe(chatPaneList, { childList: true, subtree: true, }); // Kiểm tra ngay lập tức nếu đã có sẵn item checkAndResolve(); timeout = setTimeout(() => { observer.disconnect(); reject(new Error("Timeout waiting for chat messages to appear")); }, timeoutMs); }); } private async _waitForNewMessages( existingItems: HTMLElement[], timeoutMs = 10000 ): Promise { return new Promise((resolve, reject) => { const chatPaneList = document.getElementById("chat-pane-list"); if (!chatPaneList) { return reject(new Error("#chat-pane-list not found")); } const getChildren = () => Array.from(chatPaneList.children) as HTMLElement[]; const existingSet = new Set(existingItems); let timeoutHandle: any = null; const observer = new MutationObserver(() => { const currentChildren = getChildren(); const newItems = currentChildren.filter((el) => !existingSet.has(el)); if (newItems.length > 0) { observer.disconnect(); if (timeoutHandle) clearTimeout(timeoutHandle); resolve(newItems); } }); observer.observe(chatPaneList, { childList: true, subtree: true, }); timeoutHandle = setTimeout(() => { observer.disconnect(); reject(new Error("Timeout waiting for new messages")); }, timeoutMs); }); } private async _waitToloadMessages(stableTime = 300): Promise { return new Promise((resolve) => { const chatPaneList = document.getElementById("chat-pane-list"); if (!chatPaneList) { throw new Error("#chat-pane-list not found"); } let timeout: NodeJS.Timeout | null = null; let lastChildren: HTMLElement[] = []; const observer = new MutationObserver(() => { // Reset lại mỗi khi có thay đổi if (timeout) clearTimeout(timeout); // Cập nhật danh sách item mới nhất lastChildren = Array.from(chatPaneList.children) as HTMLElement[]; timeout = setTimeout(() => { observer.disconnect(); // Khi không có thay đổi trong khoảng stableTime resolve(lastChildren); }, stableTime); }); // Quan sát cả subtree nếu có nested thay đổi observer.observe(chatPaneList, { childList: true, subtree: true, attributes: true, characterData: true, }); }); } private _getTypeGeo() { const el = document.querySelector(".ck-placeholder"); if (el) { const rect = el.getBoundingClientRect(); return { top: rect.top, left: rect.left, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height, }; } return null; } private async _rightClickMessage(date_time: number) { const xpath = this.service.elTags.container_chat; const itemId = `content-${date_time}`; const findElementByXPath = (xpath: string): HTMLElement | null => { const result = document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ); return result.singleNodeValue as HTMLElement | null; }; const maxScrollAttempts = 30; const scrollStep = 200; let attempts = 0; let wrapper = findElementByXPath(xpath); if (!wrapper) { console.error("Wrapper not found:", xpath); return; } let element = wrapper.querySelector(`#${itemId}`); while (!element && attempts < maxScrollAttempts) { const scrollContainer = wrapper; const prevScrollTop = scrollContainer.scrollTop; scrollContainer.scrollTop = Math.max( 0, scrollContainer.scrollTop - scrollStep ); await delay(200); // Đợi scroll và DOM render lại wrapper = findElementByXPath(xpath); if (!wrapper) break; element = wrapper.querySelector(`#${itemId}`); if (scrollContainer.scrollTop === prevScrollTop) break; attempts++; } if (!element) { console.error("Không tìm thấy phần tử:", itemId); return; } // Scroll phần tử vào giữa màn hình element.scrollIntoView({ behavior: "auto", block: "center" }); await delay(100); // Đợi scroll const rect = element.getBoundingClientRect(); const event = new MouseEvent("contextmenu", { bubbles: true, cancelable: true, view: window, button: 2, buttons: 2, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, }); element.dispatchEvent(event); } private _clickIfExists(xpath: string): Promise { return new Promise((resolve) => { const result = document.evaluate( xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ); const element = result.singleNodeValue as HTMLElement | null; if (element) { element.scrollIntoView({ behavior: "auto", block: "center" }); setTimeout(() => { element.click(); resolve(true); }, 100); // Đợi scroll một chút rồi click } else { console.warn("Không tìm thấy phần tử:", xpath); resolve(false); } }); } async getConversations(_: IMsg) { queue.add(async () => { console.log("[Queue] Handling GET_CONVERSATIONS"); const data = await this.service.handleGetConversations(); // Gửi dữ liệu ngược về background this.port.postMessage({ type: "socket-response", event: EVENTS.RECEIVE_CONVERSATIONS, data, } as IMsg); }); } async getConversation(msg: IMsg<{ id: string }>) { queue.add(async () => { console.log("[Queue] Handling GET_CONVERSATION"); if (!msg.data?.id) return; const { room_id } = this.service.getCurrentRoomInfo(); // Nếu đang active room thì không cần chờ load if (room_id != msg.data.id) { this._clickToConversation(msg.data.id); await this._waitForMessagesToAppear(); await this._waitToloadMessages(); } const data = this.service.extractAllMessages(); this.port.postMessage({ type: "socket-response", event: EVENTS.RECEIVE_CONVERSATION, data, } as IMsg); }); } async sendMessage({ data: { conversation_id, message }, }: IMsg<{ conversation_id: string; message: string }>) { queue.add(async () => { console.log("[Queue] Handling SEND_MESSAGE"); const { room_id } = this.service.getCurrentRoomInfo(); // Nếu đang active room thì không cần chờ load if (room_id != conversation_id) { this._clickToConversation(conversation_id); } await delay(200); await typeingService.send(message); // // Sroll xuống // this.service._scrollToBottomByXPath(); // const initialMessages = await this._waitForMessagesToAppear(3000); // Đợi có item đầu tiên // console.log("Initial messages:", initialMessages); // const newMessages = await this._waitForNewMessages(initialMessages, 3000); // console.log("New messages appeared:", newMessages); // await this._waitToloadMessages(); // // Lấy hết message mới nhất // const data = this.service.extractAllMessages(); // // Gửi lên server cập nhật // this.port.postMessage({ // type: "socket-response", // event: EVENTS.RECEIVE_CONVERSATION, // data, // } as IMsg); }); } async replyMessage({ data: { conversation_id, message, time }, }: IMsg<{ conversation_id: string; message: string; time: number }>) { queue.add(async () => { console.log("[Queue] Handling REPLY_MESSAGE"); let initialMessages = null; const { room_id } = this.service.getCurrentRoomInfo(); // Nếu đang active room thì không cần chờ load if (room_id != conversation_id) { this._clickToConversation(conversation_id); initialMessages = await this._waitForMessagesToAppear(); // Đợi có item đầu tiên await this._waitToloadMessages(); } this._clickIfExists(this.service.elTags.close_reply_btn); await this._rightClickMessage(time); const replyBtn: any = document.querySelector( this.service.elTags.reply_btn ); if (replyBtn) { replyBtn.click(); } await delay(200); console.log({ message }); await typeingService.send(message); // if (!initialMessages) return; // // Sroll xuống // this.service._scrollToBottomByXPath(); // // Theo dỗi lấy detech new message // const newMessages = await this._waitForNewMessages(initialMessages, 3000); // console.log("New messages appeared:", newMessages); // await this._waitToloadMessages(); // // Lấy hết message mới nhất // const data = this.service.extractAllMessages(); // // Gửi lên server cập nhật // this.port.postMessage({ // type: "socket-response", // event: EVENTS.RECEIVE_CONVERSATION, // data, // } as IMsg); }); } fixedHeightChatInput(retry = 20, interval = 1000) { const tryFind = () => { const el = this.getElementByXPath( this.service.elTags.chat_input ) as HTMLElement; if (el) { this.forceHeight(el, "100px"); console.log("✔ Fixed height applied to chat input"); } else if (retry > 0) { setTimeout( () => this.fixedHeightChatInput(retry - 1, interval), interval ); } else { console.warn("✘ Element not found with provided XPath after retries"); } }; tryFind(); } }