fix something idon't no

This commit is contained in:
Admin 2026-04-24 13:36:49 +07:00
parent d96884ab75
commit 23ea081757
10 changed files with 196 additions and 134 deletions

BIN
.DS_Store vendored

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,26 @@
import js from '@eslint/js' import js from "@eslint/js";
import globals from 'globals' import globals from "globals";
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from 'typescript-eslint' import tseslint from "typescript-eslint";
import { globalIgnores } from 'eslint/config' import { globalIgnores } from "eslint/config";
export default tseslint.config([ export default tseslint.config([
globalIgnores(['dist']), globalIgnores(["dist"]),
{ {
files: ['**/*.{ts,tsx}'], files: ["**/*.{ts,tsx}"],
extends: [ extends: [
js.configs.recommended, js.configs.recommended,
tseslint.configs.recommended, tseslint.configs.recommended,
reactHooks.configs['recommended-latest'], reactHooks.configs["recommended-latest"],
reactRefresh.configs.vite, reactRefresh.configs.vite,
], ],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
}, },
rules: {
"@typescript-eslint/no-unused-vars": ["off"],
}, },
]) },
]);

View File

@ -42,7 +42,7 @@ function initSocket() {
// Socket.IO events // Socket.IO events
socket.on("connect", () => { socket.on("connect", () => {
console.log("✅ Socket.IO connected"); console.log("✅ Socket.IO connectedd");
broadcastToPorts({ broadcastToPorts({
type: "socket", type: "socket",
event: "connect", event: "connect",
@ -60,6 +60,7 @@ function initSocket() {
eventsToListen.forEach((event) => { eventsToListen.forEach((event) => {
socket?.on(event, (data: any) => { socket?.on(event, (data: any) => {
console.log({ event });
broadcastToPorts({ broadcastToPorts({
type: "socket", type: "socket",
event, event,
@ -143,7 +144,7 @@ chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "reloadEvery4Hours") { if (alarm.name === "reloadEvery4Hours") {
console.log( console.log(
"🔄 Reloading all tabs (every 4 hours)", "🔄 Reloading all tabs (every 4 hours)",
new Date().toLocaleString() new Date().toLocaleString(),
); );
// Reload tất cả các tab đang mở // Reload tất cả các tab đang mở

View File

@ -44,11 +44,3 @@ contentService.startSyncConversations();
// AUTO SYNC MESAGE PREFIX (INTERNAL) // AUTO SYNC MESAGE PREFIX (INTERNAL)
contentService.autoSyncConversationPrefixMessages(); contentService.autoSyncConversationPrefixMessages();
// setTimeout(async () => {
// const a = new TeamsChatService();
// const conversations = await a.handleGetConversations();
// console.log({ conversations });
// }, 10000);

View File

@ -23,7 +23,7 @@ export class ContentService {
document, document,
null, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, XPathResult.FIRST_ORDERED_NODE_TYPE,
null null,
).singleNodeValue; ).singleNodeValue;
} }
@ -74,7 +74,7 @@ export class ContentService {
} }
private async _waitForMessagesToAppear( private async _waitForMessagesToAppear(
timeoutMs = 10000 timeoutMs = 10000,
): Promise<HTMLElement[]> { ): Promise<HTMLElement[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const chatPaneList = document.getElementById("chat-pane-list"); const chatPaneList = document.getElementById("chat-pane-list");
@ -114,46 +114,46 @@ export class ContentService {
}); });
} }
private async _waitForNewMessages( // private async _waitForNewMessages(
existingItems: HTMLElement[], // existingItems: HTMLElement[],
timeoutMs = 10000 // timeoutMs = 10000
): Promise<HTMLElement[]> { // ): Promise<HTMLElement[]> {
return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {
const chatPaneList = document.getElementById("chat-pane-list"); // const chatPaneList = document.getElementById("chat-pane-list");
if (!chatPaneList) { // if (!chatPaneList) {
return reject(new Error("#chat-pane-list not found")); // return reject(new Error("#chat-pane-list not found"));
} // }
const getChildren = () => // const getChildren = () =>
Array.from(chatPaneList.children) as HTMLElement[]; // Array.from(chatPaneList.children) as HTMLElement[];
const existingSet = new Set(existingItems); // const existingSet = new Set(existingItems);
let timeoutHandle: any = null; // let timeoutHandle: any = null;
const observer = new MutationObserver(() => { // const observer = new MutationObserver(() => {
const currentChildren = getChildren(); // const currentChildren = getChildren();
const newItems = currentChildren.filter((el) => !existingSet.has(el)); // const newItems = currentChildren.filter((el) => !existingSet.has(el));
if (newItems.length > 0) { // if (newItems.length > 0) {
observer.disconnect(); // observer.disconnect();
if (timeoutHandle) clearTimeout(timeoutHandle); // if (timeoutHandle) clearTimeout(timeoutHandle);
resolve(newItems); // resolve(newItems);
} // }
}); // });
observer.observe(chatPaneList, { // observer.observe(chatPaneList, {
childList: true, // childList: true,
subtree: true, // subtree: true,
}); // });
timeoutHandle = setTimeout(() => { // timeoutHandle = setTimeout(() => {
observer.disconnect(); // observer.disconnect();
reject(new Error("Timeout waiting for new messages")); // reject(new Error("Timeout waiting for new messages"));
}, timeoutMs); // }, timeoutMs);
}); // });
} // }
private async _waitToloadMessages(stableTime = 300): Promise<any> { private async _waitToloadMessages(stableTime = 300): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -189,24 +189,24 @@ export class ContentService {
}); });
} }
private _getTypeGeo() { // private _getTypeGeo() {
const el = document.querySelector(".ck-placeholder"); // const el = document.querySelector(".ck-placeholder");
if (el) { // if (el) {
const rect = el.getBoundingClientRect(); // const rect = el.getBoundingClientRect();
return { // return {
top: rect.top, // top: rect.top,
left: rect.left, // left: rect.left,
right: rect.right, // right: rect.right,
bottom: rect.bottom, // bottom: rect.bottom,
width: rect.width, // width: rect.width,
height: rect.height, // height: rect.height,
}; // };
} // }
return null; // return null;
} // }
private async _rightClickMessage(date_time: number) { private async _rightClickMessage(date_time: number) {
const selector = this.service.elTags.container_chat; const selector = this.service.elTags.container_chat;
@ -234,7 +234,7 @@ export class ContentService {
scrollContainer.scrollTop = Math.max( scrollContainer.scrollTop = Math.max(
0, 0,
scrollContainer.scrollTop - scrollStep scrollContainer.scrollTop - scrollStep,
); );
await delay(200); // Đợi scroll và DOM render lại await delay(200); // Đợi scroll và DOM render lại
@ -385,6 +385,8 @@ export class ContentService {
initialMessages = await this._waitForMessagesToAppear(); // Đợi có item đầu tiên initialMessages = await this._waitForMessagesToAppear(); // Đợi có item đầu tiên
await this._waitToloadMessages(); await this._waitToloadMessages();
console.log({ initialMessages });
} }
await this._clickIfExists(this.service.elTags.close_reply_btn); await this._clickIfExists(this.service.elTags.close_reply_btn);
@ -392,7 +394,7 @@ export class ContentService {
await this._rightClickMessage(time); await this._rightClickMessage(time);
const replyBtn: any = document.querySelector( const replyBtn: any = document.querySelector(
this.service.elTags.reply_btn this.service.elTags.reply_btn,
); );
if (replyBtn) { if (replyBtn) {
@ -410,7 +412,7 @@ export class ContentService {
fixedHeightChatInput(retry = 20, interval = 2000) { fixedHeightChatInput(retry = 20, interval = 2000) {
const tryFind = () => { const tryFind = () => {
const el = document.querySelector( const el = document.querySelector(
this.service.elTags.chat_input this.service.elTags.chat_input,
) as HTMLElement; ) as HTMLElement;
if (el) { if (el) {
@ -419,7 +421,7 @@ export class ContentService {
} else if (retry > 0) { } else if (retry > 0) {
setTimeout( setTimeout(
() => this.fixedHeightChatInput(retry - 1, interval), () => this.fixedHeightChatInput(retry - 1, interval),
interval interval,
); );
} else { } else {
console.warn("✘ Element not found with provided XPath after retries"); console.warn("✘ Element not found with provided XPath after retries");
@ -448,7 +450,7 @@ export class ContentService {
// Lấy attribute aria-labelledby // Lấy attribute aria-labelledby
const ariaValue = document const ariaValue = document
.querySelector( .querySelector(
'[aria-labelledby^="cn-normal-notification-main-content-"]' '[aria-labelledby^="cn-normal-notification-main-content-"]',
) )
?.getAttribute("aria-labelledby"); ?.getAttribute("aria-labelledby");
@ -478,7 +480,7 @@ export class ContentService {
if (!roomId) { if (!roomId) {
console.warn( console.warn(
"[Monitor] Could not extract room_id from aria-labelledby" "[Monitor] Could not extract room_id from aria-labelledby",
); );
return; return;
} }
@ -563,7 +565,7 @@ export class ContentService {
() => { () => {
this.getConversations(); this.getConversations();
}, },
120000 120000,
); );
console.log("✅ startSyncConversations running"); console.log("✅ startSyncConversations running");
} else { } else {
@ -572,13 +574,73 @@ export class ContentService {
} }
// Interval chạy, chỉ add task vào queue // Interval chạy, chỉ add task vào queue
autoSyncConversationPrefixMessages() { // autoSyncConversationPrefixMessages() {
(window as any)._chatIntervals = (window as any)?._chatIntervals || {}; // (window as any)._chatIntervals = (window as any)?._chatIntervals || {};
// if (!(window as any)._chatIntervals.syncPrefixMessages) {
// (window as any)._chatIntervals.syncPrefixMessages = (
// window as any
// ).setInterval(() => {
// (window as any)["is_sync_conversation_prefix"] = true;
// queue.add(async () => {
// try {
// const { data } =
// (await conversationApi.getConversationByPrefix()) as {
// data: ChatItem[];
// };
// if (!data) return;
// for (const chat of data) {
// this._clickToConversation(chat.id as string);
// await delay(5000);
// const currentRoom = this.service.getCurrentRoomInfo();
// if (!currentRoom || currentRoom.room_id !== chat.id) return;
// const filesMessages = this.filesMessages();
// if (filesMessages.length) {
// await this.clickToRefreshToken(filesMessages);
// }
// const messages = this.service.extractAllMessages();
// await messageApi.createAndSendToZulip(messages);
// await delay(5000);
// }
// } catch (err) {
// console.error(
// "❌ autoSyncConversationPrefixMessages error:",
// (err as any)?.message,
// );
// messageApi.sendLog({
// type: "error",
// message: `auto_to_sync_conersations: ${(err as any)?.message}`,
// });
// }
// });
// }, 60000);
// console.log("✅ autoSyncConversationPrefixMessages running with PQueue");
// } else {
// console.log(" autoSyncConversationPrefixMessages already running");
// }
// }
autoSyncConversationPrefixMessages() {
(window as any)._chatIntervals = (window as any)._chatIntervals || {};
if ((window as any)._chatIntervals.syncPrefixMessages) {
console.log("autoSyncConversationPrefixMessages already running");
return;
}
let isRunning = false;
(window as any)._chatIntervals.syncPrefixMessages = window.setInterval(
() => {
if (isRunning) {
console.log("syncPrefixMessages skipped (still running)");
return;
}
isRunning = true;
if (!(window as any)._chatIntervals.syncPrefixMessages) {
(window as any)._chatIntervals.syncPrefixMessages = (
window as any
).setInterval(() => {
queue.add(async () => { queue.add(async () => {
try { try {
const { data } = const { data } =
@ -586,7 +648,7 @@ export class ContentService {
data: ChatItem[]; data: ChatItem[];
}; };
if (!data) return; if (!data?.length) return;
for (const chat of data) { for (const chat of data) {
this._clickToConversation(chat.id as string); this._clickToConversation(chat.id as string);
@ -594,37 +656,39 @@ export class ContentService {
await delay(5000); await delay(5000);
const currentRoom = this.service.getCurrentRoomInfo(); const currentRoom = this.service.getCurrentRoomInfo();
if (!currentRoom || currentRoom.room_id !== chat.id) {
if (!currentRoom || currentRoom.room_id !== chat.id) return; console.warn("room mismatch, skip:", chat.id);
continue;
}
const filesMessages = this.filesMessages(); const filesMessages = this.filesMessages();
if (filesMessages.length) { if (filesMessages.length) {
await this.clickToRefreshToken(filesMessages); await this.clickToRefreshToken(filesMessages);
} }
const messages = this.service.extractAllMessages(); const messages = this.service.extractAllMessages();
await messageApi.createAndSendToZulip(messages); await messageApi.createAndSendToZulip(messages);
await delay(5000); await delay(5000);
} }
} catch (err) { } catch (err) {
console.error( console.error(
"autoSyncConversationPrefixMessages error:", "autoSyncConversationPrefixMessages error:",
(err as any)?.message (err as any)?.message,
); );
messageApi.sendLog({ messageApi.sendLog({
type: "error", type: "error",
message: `auto_to_sync_conersations: ${(err as any)?.message}`, message: `auto_to_sync_conersations: ${(err as any)?.message}`,
}); });
} finally {
isRunning = false; // 🔑 cực kỳ quan trọng
} }
}); });
}, 40000); },
60000,
);
console.log("✅ autoSyncConversationPrefixMessages running with PQueue"); console.log("autoSyncConversationPrefixMessages running safely");
} else {
console.log(" autoSyncConversationPrefixMessages already running");
}
} }
} }

View File

@ -2,15 +2,13 @@
import { messageApi } from "@/api/message-api.service"; import { messageApi } from "@/api/message-api.service";
export class TeamsChatService { export class TeamsChatService {
private readonly MY_NAME = "Apactech com"; // private readonly MY_NAME = "Apactech com";
public lastMessage?: IMessage; public lastMessage?: IMessage;
public initialHistories: IMessage[] = []; public initialHistories: IMessage[] = [];
public elTags = { public elTags = {
container_scroll: container_scroll: '//*[@data-testid="simple-collab-rail"]',
"/html/body/div[1]/div/div/div/div[5]/div[1]/div[1]/div[2]/div[1]/div[1]/div", conatainer_conversations: '//*[@data-testid="simple-collab-dnd-rail"]',
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"]', container_chat: '[data-testid="message-wrapper"]',
root_id: '[aria-selected="true"] [id^="chat-list-item"]', root_id: '[aria-selected="true"] [id^="chat-list-item"]',
@ -24,13 +22,13 @@ export class TeamsChatService {
private _getImageFormEl(el: HTMLElement): HTMLImageElement[] { private _getImageFormEl(el: HTMLElement): HTMLImageElement[] {
// Tìm tất cả img có data-gallery-src trong el // Tìm tất cả img có data-gallery-src trong el
let sharedImages = Array.from( let sharedImages = Array.from(
el.querySelectorAll("img[data-gallery-src]") el.querySelectorAll("img[data-gallery-src]"),
) as HTMLImageElement[]; ) as HTMLImageElement[];
// Nếu không tìm thấy thì thử tìm trong parentElement // Nếu không tìm thấy thì thử tìm trong parentElement
if (sharedImages.length === 0 && el.parentElement) { if (sharedImages.length === 0 && el.parentElement) {
sharedImages = Array.from( sharedImages = Array.from(
el.parentElement.querySelectorAll("img[data-gallery-src]") el.parentElement.querySelectorAll("img[data-gallery-src]"),
) as HTMLImageElement[]; ) as HTMLImageElement[];
} }
@ -174,7 +172,7 @@ export class TeamsChatService {
// Lấy emoji // Lấy emoji
const emojiImgs = Array.from( const emojiImgs = Array.from(
el.querySelectorAll("img[itemtype]") el.querySelectorAll("img[itemtype]"),
) as HTMLImageElement[]; ) as HTMLImageElement[];
const emojiAlts = emojiImgs const emojiAlts = emojiImgs
.map((img) => img.getAttribute("alt") || "") .map((img) => img.getAttribute("alt") || "")
@ -189,11 +187,11 @@ export class TeamsChatService {
public parseMessageElement(el: Element, isMine = false): IMessage | null { public parseMessageElement(el: Element, isMine = false): IMessage | null {
const timestampEl = el.querySelector( const timestampEl = el.querySelector(
isMine ? ".fui-ChatMyMessage__timestamp" : ".fui-ChatMessage__timestamp" isMine ? ".fui-ChatMyMessage__timestamp" : ".fui-ChatMessage__timestamp",
) as HTMLElement | null; ) as HTMLElement | null;
const authorEl = el.querySelector( const authorEl = el.querySelector(
isMine ? ".fui-ChatMyMessage__author" : ".fui-ChatMessage__author" isMine ? ".fui-ChatMyMessage__author" : ".fui-ChatMessage__author",
) as HTMLElement | null; ) as HTMLElement | null;
if (!timestampEl) return null; if (!timestampEl) return null;
@ -207,7 +205,7 @@ export class TeamsChatService {
: Number(timestampEl.id.replace("timestamp-", "")); : Number(timestampEl.id.replace("timestamp-", ""));
const contentEl = document.querySelector( const contentEl = document.querySelector(
`#content-${dateTime}` `#content-${dateTime}`,
) as HTMLElement | null; ) as HTMLElement | null;
(contentEl as any)["date_time"] = dateTime; (contentEl as any)["date_time"] = dateTime;
@ -242,13 +240,13 @@ export class TeamsChatService {
extractAllMessages(): IMessage[] { extractAllMessages(): IMessage[] {
const myMessages: IMessage[] = Array.from( const myMessages: IMessage[] = Array.from(
document.querySelectorAll(".fui-ChatMyMessage") document.querySelectorAll(".fui-ChatMyMessage"),
) )
.map((el) => this.parseMessageElement(el, true)) .map((el) => this.parseMessageElement(el, true))
.filter((msg): msg is IMessage => msg !== null); .filter((msg): msg is IMessage => msg !== null);
const otherMessages: IMessage[] = Array.from( const otherMessages: IMessage[] = Array.from(
document.querySelectorAll(".fui-ChatMessage") document.querySelectorAll(".fui-ChatMessage"),
) )
.map((el) => this.parseMessageElement(el, false)) .map((el) => this.parseMessageElement(el, false))
.filter((msg): msg is IMessage => msg !== null); .filter((msg): msg is IMessage => msg !== null);
@ -267,7 +265,7 @@ export class TeamsChatService {
const allMessages = this.extractAllMessages(); const allMessages = this.extractAllMessages();
const lastIndex = allMessages.findIndex( const lastIndex = allMessages.findIndex(
(msg) => msg.time === this.lastMessage?.time (msg) => msg.time === this.lastMessage?.time,
); );
const newMessages = allMessages.slice(lastIndex + 1); const newMessages = allMessages.slice(lastIndex + 1);
@ -302,14 +300,14 @@ export class TeamsChatService {
} }
private async _getConversationsInfo( private async _getConversationsInfo(
xpath: string = this.elTags.conatainer_conversations xpath: string = this.elTags.conatainer_conversations,
): Promise<ChatItem[]> { ): Promise<ChatItem[]> {
const result = document.evaluate( const result = document.evaluate(
xpath, xpath,
document, document,
null, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, XPathResult.FIRST_ORDERED_NODE_TYPE,
null null,
).singleNodeValue as HTMLElement | null; ).singleNodeValue as HTMLElement | null;
if (!result) { if (!result) {
@ -321,15 +319,15 @@ export class TeamsChatService {
return []; return [];
} }
// Lọc phần tử con có role="none" const chatItems = Array.from(
const matchedChildren = Array.from(result.children).filter( result.querySelectorAll('[data-item-type="chat"]'),
(child: Element) => {
return child.getAttribute("role") === "none";
}
); );
const data: ChatItem[] = matchedChildren.map((child: Element): ChatItem => { const data: ChatItem[] = chatItems.map((child: Element): ChatItem => {
const id = child.id || null; const treeItemValue = child.getAttribute("data-fui-tree-item-value") || "";
const lastSegment = treeItemValue.split("/").pop() || "";
const id = lastSegment.includes("|") ? lastSegment.split("|")[1] : null;
const titleId = `title-chat-list-item_${id}`; const titleId = `title-chat-list-item_${id}`;
const titleElement = document.getElementById(titleId); const titleElement = document.getElementById(titleId);
const spanText = titleElement?.innerText || null; const spanText = titleElement?.innerText || null;
@ -360,7 +358,7 @@ export class TeamsChatService {
maxStableRounds?: number; // Số vòng scroll không thay đổi trước khi dừng 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) 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 maxScrolls?: number; // Giới hạn số lần scroll tối đa
} },
): Promise<void> { ): Promise<void> {
const { const {
maxStableRounds = 5, maxStableRounds = 5,
@ -373,7 +371,7 @@ export class TeamsChatService {
document, document,
null, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, XPathResult.FIRST_ORDERED_NODE_TYPE,
null null,
).singleNodeValue as HTMLElement | null; ).singleNodeValue as HTMLElement | null;
if (!container) { if (!container) {

View File

@ -11,12 +11,13 @@ import * as fs from 'fs';
import { SocketIoAdapter } from './socket-adapter'; import { SocketIoAdapter } from './socket-adapter';
async function bootstrap() { async function bootstrap() {
const httpsOptions = { // const httpsOptions = {
key: fs.readFileSync('ssl/localhost+1-key.pem'), // key: fs.readFileSync('ssl/localhost+1-key.pem'),
cert: fs.readFileSync('ssl/localhost+1.pem'), // cert: fs.readFileSync('ssl/localhost+1.pem'),
}; // };
const app = await NestFactory.create(AppModule, { httpsOptions }); const app = await NestFactory.create(AppModule);
// const app = await NestFactory.create(AppModule, { httpsOptions });
const prefix_version = process.env.PREFIX_VERSION; const prefix_version = process.env.PREFIX_VERSION;
@ -54,12 +55,14 @@ async function bootstrap() {
useContainer(app.select(AppModule), { fallbackOnErrors: true }); useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.useWebSocketAdapter(new SocketIoAdapter(app));
await app.listen(Number(process.env.APP_API_PORT)); await app.listen(Number(process.env.APP_API_PORT));
// ===== App cho WebSocket (WS hoặc WSS) ===== // // ===== App cho WebSocket (WS hoặc WSS) =====
const wsApp = await NestFactory.create(AppModule); // const wsApp = await NestFactory.create(AppModule);
wsApp.useWebSocketAdapter(new SocketIoAdapter(wsApp)); // wsApp.useWebSocketAdapter(new SocketIoAdapter(wsApp));
await wsApp.listen(Number(process.env.APP_SOCKET_PORT)); // await wsApp.listen(Number(process.env.APP_SOCKET_PORT));
} }
bootstrap(); bootstrap();

View File

@ -30,6 +30,7 @@ export class MessagesGateway implements OnGatewayConnection {
for (const event of eventsToForward) { for (const event of eventsToForward) {
this.event.on(event, (data) => { this.event.on(event, (data) => {
console.log({ event, data });
this.server.emit(event, data); this.server.emit(event, data);
}); });
} }