const PERSON_ID_PREFIX = 'chat-topic-person-'; const ICON_ID_PREFIX = "presence-pill-"; const CHAT_ROSTER_PREFIX = "chat-roster-item-name-"; const CHAT_TITLE_PREFIX = "title-chat-list-item_"; const SUGGEST_PEOPLE_PREFIX = "AUTOSUGGEST_SUGGESTION_PEOPLE"; const ROSTER_AVATAR_PREFIX = "roster-avatar-img-"; const SERP_PEOPLE_CARD_PREFIX = "serp-people-card-content-"; const PEOPLE_PICKER_PREFIX = "people-picker-entry-"; const PEOPLE_PICKER_SEL_PREFIX = "people-picker-selected-user-"; const CHAT_CATEGORY_STORAGE_KEY = "chatCategories"; const CHAT_CACHE_STORAGE_KEY = "chatCategoryChatCache"; const CHAT_ORGANIZER_ROOT_ID = "teams-alias-chat-organizer-root"; const CHAT_ORGANIZER_STYLE_ID = "teams-alias-chat-organizer-style"; const CHAT_ORGANIZER_BUTTON_ID = "teams-alias-chat-organizer-button"; let debounceTimer = null; let isMutating = false; let organizerStateLoaded = false; let organizerSaveTimer = null; const chatOrganizerState = { categories: [], chatCache: {}, visibleChats: {}, isModalOpen: false, selectedCategoryId: "", feedback: "", draftCategoryName: "", draftChatId: "", visibleChatsExpanded: false, expandedCategoryIds: {} }; function getOrganizerRoot() { return document.getElementById(CHAT_ORGANIZER_ROOT_ID); } function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function normalizeChatId(rawValue) { if (!rawValue) return ""; let value = String(rawValue).trim(); if (value.startsWith(CHAT_TITLE_PREFIX)) { value = value.slice(CHAT_TITLE_PREFIX.length); } return value.startsWith("19:") ? value : ""; } function createCategoryId() { return `cat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } function setOrganizerFeedback(message) { chatOrganizerState.feedback = message || ""; renderChatOrganizer(); } function scheduleOrganizerStateSave() { clearTimeout(organizerSaveTimer); organizerSaveTimer = setTimeout(async () => { await chrome.storage.local.set({ [CHAT_CATEGORY_STORAGE_KEY]: chatOrganizerState.categories, [CHAT_CACHE_STORAGE_KEY]: chatOrganizerState.chatCache }); }, 300); } async function loadChatOrganizerState() { if (organizerStateLoaded) return; const result = await chrome.storage.local.get([ CHAT_CATEGORY_STORAGE_KEY, CHAT_CACHE_STORAGE_KEY ]); chatOrganizerState.categories = Array.isArray(result[CHAT_CATEGORY_STORAGE_KEY]) ? result[CHAT_CATEGORY_STORAGE_KEY] : []; chatOrganizerState.chatCache = result[CHAT_CACHE_STORAGE_KEY] || {}; chatOrganizerState.selectedCategoryId = chatOrganizerState.categories[0]?.id || ""; organizerStateLoaded = true; } function getCategoryById(categoryId) { return chatOrganizerState.categories.find(category => category.id === categoryId) || null; } function getChatMeta(chatId) { return chatOrganizerState.visibleChats[chatId] || chatOrganizerState.chatCache[chatId] || null; } function inferAvatarFallback(name) { const base = (name || "?").trim(); return base.slice(0, 2).toUpperCase(); } function findChatTitleElement(chatId) { const normalizedChatId = normalizeChatId(chatId); if (!normalizedChatId) return null; return document.getElementById(`${CHAT_TITLE_PREFIX}${normalizedChatId}`); } function findClickableChatElement(chatId) { const titleElement = findChatTitleElement(chatId); if (!titleElement) return null; const clickable = titleElement.closest('button, a, [role="button"], [role="link"], [role="listitem"]'); if (clickable) return clickable; let current = titleElement; for (let depth = 0; depth < 8 && current; depth += 1) { if (typeof current.click === "function") { return current; } current = current.parentElement; } return titleElement; } function deepLinkToChat(chatId) { const normalizedChatId = normalizeChatId(chatId); if (!normalizedChatId) return false; const candidates = [ `${window.location.origin}/l/chat/${encodeURIComponent(normalizedChatId)}/conversations`, `https://teams.microsoft.com/l/chat/${encodeURIComponent(normalizedChatId)}/conversations` ]; for (const url of candidates) { try { window.location.href = url; return true; } catch (error) {} } return false; } function openChatById(chatId) { const clickableElement = findClickableChatElement(chatId); if (clickableElement) { clickableElement.click(); return true; } return deepLinkToChat(chatId); } function extractChatAvatar(titleElement) { const rowRoot = titleElement.closest('[role="listitem"], button, a, [role="button"]') || titleElement.parentElement; if (!rowRoot) return ""; const avatarImage = rowRoot.querySelector("img"); if (avatarImage?.src) { return avatarImage.src; } const avatarCandidate = rowRoot.querySelector('[style*="background-image"]'); const inlineStyle = avatarCandidate?.style?.backgroundImage || ""; const matched = inlineStyle.match(/url\(["']?(.*?)["']?\)/); return matched?.[1] || ""; } function scanVisibleChats() { const titleElements = document.querySelectorAll(`[id^="${CHAT_TITLE_PREFIX}"]`); let changed = false; const nextVisibleChats = {}; titleElements.forEach(titleElement => { const chatId = normalizeChatId(titleElement.id); if (!chatId) return; const name = titleElement.textContent?.trim() || chatOrganizerState.chatCache[chatId]?.name || chatId; const avatarUrl = extractChatAvatar(titleElement) || chatOrganizerState.chatCache[chatId]?.avatarUrl || ""; const chatMeta = { id: chatId, name, avatarUrl, updatedAt: Date.now() }; nextVisibleChats[chatId] = chatMeta; const cached = chatOrganizerState.chatCache[chatId]; if (!cached || cached.name !== chatMeta.name || cached.avatarUrl !== chatMeta.avatarUrl) { chatOrganizerState.chatCache[chatId] = chatMeta; changed = true; } }); const visibleIds = Object.keys(nextVisibleChats); const previousVisibleIds = Object.keys(chatOrganizerState.visibleChats); if (visibleIds.length !== previousVisibleIds.length || visibleIds.some(id => !chatOrganizerState.visibleChats[id])) { changed = true; } chatOrganizerState.visibleChats = nextVisibleChats; if (changed) { scheduleOrganizerStateSave(); renderChatOrganizer(); } } function addChatToCategory(categoryId, rawChatId) { const category = getCategoryById(categoryId); const chatId = normalizeChatId(rawChatId); if (!category) { setOrganizerFeedback("请选择一个分类。"); return; } if (!chatId) { setOrganizerFeedback("聊天 ID 格式无效,必须以 19: 开头。"); return; } if (category.chatIds.includes(chatId)) { setOrganizerFeedback("该聊天已存在于当前分类。"); return; } category.chatIds.push(chatId); const meta = getChatMeta(chatId); if (meta) { chatOrganizerState.chatCache[chatId] = { ...chatOrganizerState.chatCache[chatId], ...meta, updatedAt: Date.now() }; } else { chatOrganizerState.chatCache[chatId] = { id: chatId, name: chatId, avatarUrl: "", updatedAt: Date.now() }; } scheduleOrganizerStateSave(); setOrganizerFeedback("已加入分类。"); } function removeChatFromCategory(categoryId, chatId) { const category = getCategoryById(categoryId); if (!category) return; category.chatIds = category.chatIds.filter(id => id !== chatId); scheduleOrganizerStateSave(); setOrganizerFeedback("已移除聊天。"); } function createCategory(name) { const trimmedName = (name || "").trim(); if (!trimmedName) { setOrganizerFeedback("分类名称不能为空。"); return; } const category = { id: createCategoryId(), name: trimmedName, chatIds: [] }; chatOrganizerState.categories.push(category); chatOrganizerState.expandedCategoryIds[category.id] = true; chatOrganizerState.selectedCategoryId = category.id; scheduleOrganizerStateSave(); setOrganizerFeedback("分类已创建。"); } function deleteCategory(categoryId) { chatOrganizerState.categories = chatOrganizerState.categories.filter(category => category.id !== categoryId); delete chatOrganizerState.expandedCategoryIds[categoryId]; if (chatOrganizerState.selectedCategoryId === categoryId) { chatOrganizerState.selectedCategoryId = chatOrganizerState.categories[0]?.id || ""; } scheduleOrganizerStateSave(); setOrganizerFeedback("分类已删除。"); } function isCategoryExpanded(categoryId) { return Boolean(chatOrganizerState.expandedCategoryIds[categoryId]); } function toggleCategoryExpanded(categoryId) { chatOrganizerState.expandedCategoryIds[categoryId] = !isCategoryExpanded(categoryId); renderChatOrganizer(); } function toggleVisibleChatsExpanded() { chatOrganizerState.visibleChatsExpanded = !chatOrganizerState.visibleChatsExpanded; renderChatOrganizer(); } function renderChatCard(chatId, categoryId) { const meta = getChatMeta(chatId) || { id: chatId, name: chatId, avatarUrl: "" }; const avatar = meta.avatarUrl ? `` : `${escapeHtml(inferAvatarFallback(meta.name))}`; return `
`; } function renderVisibleChatCandidate(chatId) { const meta = chatOrganizerState.visibleChats[chatId]; if (!meta) return ""; const avatar = meta.avatarUrl ? `` : `${escapeHtml(inferAvatarFallback(meta.name))}`; return `
${avatar} ${escapeHtml(meta.name || chatId)} ${escapeHtml(chatId)}
`; } function ensureChatOrganizerUI() { if (!document.body || getOrganizerRoot()) return; if (!document.getElementById(CHAT_ORGANIZER_STYLE_ID)) { const style = document.createElement("style"); style.id = CHAT_ORGANIZER_STYLE_ID; style.textContent = ` #${CHAT_ORGANIZER_BUTTON_ID} { position: fixed; top: 12px; left: 12px; z-index: 2147483644; border: none; border-radius: 999px; background: #2563eb; color: #fff; font-size: 13px; font-weight: 600; padding: 10px 14px; cursor: pointer; box-shadow: 0 10px 30px rgba(37, 99, 235, 0.25); } #${CHAT_ORGANIZER_ROOT_ID} { position: fixed; inset: 0; z-index: 2147483643; display: none; } #${CHAT_ORGANIZER_ROOT_ID}.is-open { display: block; } .teams-alias-overlay { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.4); backdrop-filter: blur(3px); } .teams-alias-modal { position: absolute; top: 64px; left: 24px; width: min(920px, calc(100vw - 48px)); max-height: calc(100vh - 88px); overflow: auto; border-radius: 18px; background: #ffffff; box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22); color: #0f172a; font-family: "Segoe UI", sans-serif; } .teams-alias-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 18px 20px 12px; border-bottom: 1px solid #e5e7eb; } .teams-alias-modal-header h2 { margin: 0; font-size: 18px; } .teams-alias-close { border: none; background: #e2e8f0; color: #0f172a; border-radius: 999px; width: 32px; height: 32px; cursor: pointer; } .teams-alias-modal-body { padding: 16px 20px 20px; display: grid; gap: 16px; } .teams-alias-panel { border: 1px solid #e5e7eb; border-radius: 14px; padding: 14px; background: #f8fafc; } .teams-alias-panel h3 { margin: 0 0 12px; font-size: 14px; } .teams-alias-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; } .teams-alias-row input, .teams-alias-row select { min-width: 180px; flex: 1; border: 1px solid #cbd5e1; border-radius: 10px; padding: 10px 12px; font-size: 13px; background: #fff; } .teams-alias-row button, .teams-alias-visible-chat button, .teams-alias-chat-remove { border: none; border-radius: 10px; padding: 9px 12px; font-size: 13px; cursor: pointer; background: #2563eb; color: #fff; } .teams-alias-category-list, .teams-alias-visible-list, .teams-alias-chat-list { display: grid; gap: 10px; } .teams-alias-category-item { display: flex; justify-content: space-between; align-items: center; gap: 12px; border: 1px solid #dbeafe; border-radius: 12px; padding: 12px; background: #fff; } .teams-alias-section-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; } .teams-alias-section-header h3 { margin: 0; } .teams-alias-ghost-button, .teams-alias-category-actions button { border: 1px solid #cbd5e1; border-radius: 10px; padding: 8px 12px; font-size: 12px; background: #fff; color: #0f172a; cursor: pointer; } .teams-alias-category-actions { display: flex; gap: 8px; align-items: center; } .teams-alias-category-meta { display: grid; gap: 4px; } .teams-alias-category-name { font-size: 14px; font-weight: 600; } .teams-alias-category-count, .teams-alias-chat-id, .teams-alias-feedback { font-size: 12px; color: #475569; } .teams-alias-chat-card, .teams-alias-visible-chat { display: flex; align-items: center; gap: 10px; border: 1px solid #e2e8f0; border-radius: 12px; padding: 10px; background: #fff; } .teams-alias-chat-open { flex: 1; display: flex; align-items: center; gap: 10px; border: none; padding: 0; background: transparent; cursor: pointer; color: inherit; text-align: left; } .teams-alias-chat-avatar { width: 36px; height: 36px; border-radius: 999px; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; background: #dbeafe; color: #1d4ed8; font-size: 12px; font-weight: 700; flex-shrink: 0; } .teams-alias-chat-avatar-image { width: 100%; height: 100%; object-fit: cover; } .teams-alias-chat-main { min-width: 0; display: grid; gap: 3px; } .teams-alias-chat-name { font-size: 13px; font-weight: 600; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .teams-alias-empty { color: #64748b; font-size: 13px; } .teams-alias-collapsed { display: none; } @media (max-width: 900px) { .teams-alias-modal { left: 12px; right: 12px; width: auto; top: 56px; } } `; document.head.appendChild(style); } const organizerButton = document.createElement("button"); organizerButton.id = CHAT_ORGANIZER_BUTTON_ID; organizerButton.textContent = "聊天分类"; organizerButton.addEventListener("click", () => { chatOrganizerState.isModalOpen = true; renderChatOrganizer(); }); const root = document.createElement("div"); root.id = CHAT_ORGANIZER_ROOT_ID; root.addEventListener("click", event => { const actionElement = event.target.closest("[data-action]"); if (event.target.classList.contains("teams-alias-overlay")) { chatOrganizerState.isModalOpen = false; renderChatOrganizer(); return; } if (!actionElement) return; const action = actionElement.getAttribute("data-action"); if (action === "close-modal") { chatOrganizerState.isModalOpen = false; renderChatOrganizer(); return; } if (action === "create-category") { const input = root.querySelector('[data-role="new-category-name"]'); createCategory(input?.value || ""); chatOrganizerState.draftCategoryName = ""; return; } if (action === "delete-category") { deleteCategory(actionElement.getAttribute("data-category-id")); return; } if (action === "toggle-category") { toggleCategoryExpanded(actionElement.getAttribute("data-category-id")); return; } if (action === "toggle-visible-chats") { toggleVisibleChatsExpanded(); return; } if (action === "add-chat-by-id") { const input = root.querySelector('[data-role="chat-id-input"]'); addChatToCategory(chatOrganizerState.selectedCategoryId, input?.value || ""); chatOrganizerState.draftChatId = ""; return; } if (action === "quick-add") { addChatToCategory(chatOrganizerState.selectedCategoryId, actionElement.getAttribute("data-chat-id")); return; } if (action === "remove-chat") { removeChatFromCategory( actionElement.getAttribute("data-category-id"), actionElement.getAttribute("data-chat-id") ); return; } if (action === "open-chat") { const chatId = actionElement.getAttribute("data-chat-id"); const opened = openChatById(chatId); setOrganizerFeedback(opened ? "已尝试打开聊天。" : "未找到该聊天,且 deep link 跳转失败。"); } }); root.addEventListener("change", event => { const target = event.target; if (target.matches('[data-role="category-select"]')) { chatOrganizerState.selectedCategoryId = target.value; renderChatOrganizer(); } }); root.addEventListener("input", event => { const target = event.target; if (target.matches('[data-role="new-category-name"]')) { chatOrganizerState.draftCategoryName = target.value; return; } if (target.matches('[data-role="chat-id-input"]')) { chatOrganizerState.draftChatId = target.value; } }); document.body.appendChild(organizerButton); document.body.appendChild(root); renderChatOrganizer(); } function renderChatOrganizer() { const root = getOrganizerRoot(); if (!root) return; root.classList.toggle("is-open", chatOrganizerState.isModalOpen); if (!chatOrganizerState.isModalOpen) { root.innerHTML = ""; return; } const categoryOptions = chatOrganizerState.categories.map(category => ` `).join(""); const categoriesMarkup = chatOrganizerState.categories.length ? chatOrganizerState.categories.map(category => `
${escapeHtml(category.name)} ${category.chatIds.length} 个聊天
${category.chatIds.length ? category.chatIds.map(chatId => renderChatCard(chatId, category.id)).join("") : '
当前分类还没有聊天。
'}
`).join("") : '
还没有分类,先创建一个。
'; const visibleChatIds = Object.keys(chatOrganizerState.visibleChats); const visibleChatsMarkup = visibleChatIds.length ? visibleChatIds.map(chatId => renderVisibleChatCandidate(chatId)).join("") : '
当前页面还没有识别到左侧聊天列表。
'; root.innerHTML = `
`; } async function getAlias(id) { const key = id.replace(PERSON_ID_PREFIX, ""); const result = await chrome.storage.local.get('aliases'); const aliases = result.aliases || {}; return aliases[key] || null; } // 设置别名显示 + 按钮添加 function applyAliasAndButton(el) { const id = el.id; if (!id || !id.startsWith(PERSON_ID_PREFIX)) return; const rawId = id.replace(PERSON_ID_PREFIX, ""); const existingBtn = document.querySelector(`[data-floating-btn-for="${id}"]`); if (!existingBtn) { const rect = el.getBoundingClientRect(); const button = document.createElement('button'); button.textContent = '显示ID'; // 改为显示ID button.style.position = 'fixed'; button.style.left = `${rect.left + window.scrollX}px`; button.style.top = `${rect.bottom + window.scrollY + 20}px`; button.style.zIndex = '99999'; button.style.padding = '4px 8px'; button.style.fontSize = '12px'; button.style.backgroundColor = '#6c757d'; // 灰色,表示只是信息查看 button.style.color = '#fff'; button.style.border = 'none'; button.style.borderRadius = '4px'; button.style.cursor = 'pointer'; button.setAttribute('data-floating-btn-for', id); button.addEventListener('click', () => { // 弹窗显示 ID,方便复制 prompt("该用户的账号ID为 (请复制):", rawId); }); document.body.appendChild(button); } // 应用别名(异步) getAlias(rawId).then(alias => { if (alias && el.textContent !== alias) { el.textContent = alias; } }); } // 主要查找右侧消息列表中的名字并修改 function applyRightChatAlias(el) { let id = el.id; if (!id || !id.startsWith(ICON_ID_PREFIX)) return; let parent = el; // 向上查找 4 个父元素 for (let i = 0; i < 4; i++) { if (parent.parentElement) { parent = parent.parentElement; } else { return; // 如果不足4层,就跳过 } } // 获取前一个兄弟元素 const prevSibling = parent.previousElementSibling; if (!prevSibling) return; // 向下查找第 4 个子元素(层级式) let target = prevSibling; for (let i = 0; i < 4; i++) { if (target.children.length > 0) { target = target.children[0]; // 每层往下取第一个子元素 } else { return; // 不足4层,跳过 } } // 判断符合才修改 if (target.getAttribute('data-tid') === 'message-author-name') { getAlias(id.replace(ICON_ID_PREFIX, "")).then(alias => { if (alias && target.textContent !== alias) { target.textContent = alias; } }); } } // 主要查找左侧消息列表中的名字并修改 function applyLeftChatAlias(el) { let id = el.id; if (!id || !id.startsWith(ICON_ID_PREFIX)) return; let parent = el; // 向上查找 4 个父元素 for (let i = 0; i < 4; i++) { if (parent.parentElement) { parent = parent.parentElement; } else { return; // 如果不足4层,就跳过 } } // 获取后一个兄弟元素 const nextSibling = parent.nextElementSibling; if (!nextSibling) return; // 向下查找第 7 个子元素(层级式) let target = nextSibling; for (let i = 0; i < 7; i++) { if (target.children.length > 0) { target = target.children[0]; // 每层往下取第一个子元素 } else { return; // 不足7层,跳过 } } // 判断符合才修改 if (target.id.startsWith('title-chat-list-item')) { getAlias(id.replace(ICON_ID_PREFIX, "")).then(alias => { if (alias && target.textContent !== alias) { target.textContent = alias; } }); } } // 修改群组人员的名称 function applyChatRosterAlias(el) { let id = el.id; if (!id || !id.startsWith(CHAT_ROSTER_PREFIX)) return; getAlias(id.replace(CHAT_ROSTER_PREFIX, "")).then(alias => { if (alias && el.textContent !== alias) { el.textContent = alias; } }); } // 群组添加人员的别名 function applyPeoplePickerAlias(el) { let tid = el.getAttribute('data-tid'); if (!tid || !tid.startsWith(PEOPLE_PICKER_PREFIX)) return; let child = el.children[1]; // 向下查找第 3 个子元素(层级式) for (let i = 0; i < 3; i++) { if (child.children.length > 0) { child = child.children[0]; // 每层往下取第一个子元素 } else { return; // 不足3层,跳过 } } if (child.tagName.toLowerCase() === 'span') { let id = "8:" + tid.replace(PEOPLE_PICKER_PREFIX, ""); getAlias(id).then(alias => { if (alias && child.textContent !== alias) { child.textContent = alias; } }); } } // 群组添加人员时选中的人员 function applyPeoplePickerSelectedAlias(el) { let tid = el.getAttribute('data-tid'); if (!tid || !tid.startsWith(PEOPLE_PICKER_SEL_PREFIX)) return; let id = "8:" + tid.replace(PEOPLE_PICKER_SEL_PREFIX, ""); getAlias(id).then(alias => { if (alias && el.textContent !== alias) { el.textContent = alias; } }); } // 追加搜索框中的人员别名 function applySuggestPeopleAlias(el) { let tid = el.getAttribute('data-tid'); if (!tid || !tid.startsWith(SUGGEST_PEOPLE_PREFIX)) return; let child = el.children[1]; // 第二个子元素 // 向下查找第 2 个子元素(层级式) for (let i = 0; i < 2; i++) { if (child.children.length > 0) { child = child.children[0]; // 每层往下取第一个子元素 } else { return; // 不足2层,跳过 } } if (child.getAttribute('data-tid') !== 'AUTOSUGGEST_SUGGESTION_TITLE') return; let id = tid.replace(SUGGEST_PEOPLE_PREFIX, ""); getAlias(id).then(alias => { if (alias) { let lastSpan = child.lastElementChild; if (lastSpan.id.startsWith("suggest-alias-attached")) { if (lastSpan.textContent === `[${alias}]`) return; lastSpan.textContent = `[${alias}]`; } else { const span = document.createElement('span'); span.id = `suggest-alias-attached-${id}`; span.textContent = `[${alias}]`; span.style.marginLeft = '4px'; span.style.color = document.documentElement.classList.contains("theme-tfl-default") ? '#ed0833' : '#78ef0b'; child.appendChild(span); } } }); } // 追加人员搜索中的别名 function applySerpPeopleAlias(el) { let id = el.id; if (!id || !id.startsWith(SERP_PEOPLE_CARD_PREFIX)) return; let child = el.children[2]; // 向下查找第 4 个子元素(层级式) for (let i = 0; i < 4; i++) { if (child.children.length > 0) { child = child.children[0]; // 每层往下取第一个子元素 } else { return; // 不足4层,跳过 } } id = id.replace(SERP_PEOPLE_CARD_PREFIX, ""); getAlias(id).then(alias => { if (alias) { let lastSpan = child.lastElementChild; if (lastSpan.id.startsWith("people-card-attached")) { if (lastSpan.textContent === `[${alias}]`) return; lastSpan.textContent = `[${alias}]`; } else { const span = document.createElement('span'); span.id = `people-card-attached-${id}`; span.textContent = `[${alias}]`; span.style.marginLeft = '4px'; span.style.color = document.documentElement.classList.contains("theme-tfl-default") ? '#ed0833' : '#78ef0b'; child.appendChild(span); } } }); } // 修改通话中的人名 function applyCallingAlias(el) { if (el.getAttribute('data-cid') !== 'calling-participant-stream') return; let child = el.children[1]; // 向下查找第 5 个子元素(层级式) for (let i = 0; i < 5; i++) { if (child.children.length > 0) { child = child.children[0]; // 每层往下取第一个子元素 } else { return; // 不足5层,跳过 } } if (child.tagName.toLowerCase() === 'span') { getAlias(el.getAttribute('data-acc-element-id')).then(alias => { if (alias && child.textContent !== alias) { child.textContent = alias; } }); } } // 修改通话右侧人名的别名 function applyRosterAvatarAlias(el) { let id = el.id; if (!id || !id.startsWith(ROSTER_AVATAR_PREFIX)) return; getAlias(id.replace(ROSTER_AVATAR_PREFIX, "")).then(alias => { if (alias && el.textContent !== alias) { el.textContent = alias; } }); } // 查看此通话中的人员 function applyPeopleInCall(el) { let id = el.id; if (!id || !id.startsWith(ICON_ID_PREFIX)) return; let parent = el; // 向上查找 2 个父元素 for (let i = 0; i < 2; i++) { if (parent.parentElement) { parent = parent.parentElement; } else { return; // 如果不足2层,就跳过 } } // 一个是查看此通话中的参与者,一个是呼叫其他人加入 if ([ "audio_dropin_add_participants_dialog_renderer", "audio-drop-in-live-roster" ].includes(parent.getAttribute("data-tid"))) { let target = parent.nextElementSibling; if (!target) return; if (target.tagName.toLowerCase() === "span") { getAlias(id.replace(ICON_ID_PREFIX, "")).then(alias => { if (alias && target.textContent !== alias) { target.textContent = alias; } }); } } } // 回应表情的人员别名 function applyReactionAlias(el) { if (el.getAttribute('data-tid') !== 'diverse-reaction-user-list-item') return; try { const tabster = JSON.parse(el.getAttribute("data-tabster")); let child = el.children[1]; let target = child.children[0]; let id = tabster.observed.names[0]; getAlias(id).then(alias => { if (alias && target.textContent !== alias) { target.textContent = alias; } }); } catch (error) {} } // 查找所有目标元素应用别名和按钮 function applyToAll() { document.querySelectorAll('[data-floating-btn-for]').forEach(btn => btn.remove()); const allPersons = document.querySelectorAll(`[id^="${PERSON_ID_PREFIX}"]`); allPersons.forEach(el => applyAliasAndButton(el)); const allIcons = document.querySelectorAll(`[id^="${ICON_ID_PREFIX}"]`); allIcons.forEach(el => { applyRightChatAlias(el); applyLeftChatAlias(el); applyPeopleInCall(el); }); const allChatRoster = document.querySelectorAll(`[id^="${CHAT_ROSTER_PREFIX}"]`); allChatRoster.forEach(el => applyChatRosterAlias(el)); const allSuggestPeople = document.querySelectorAll(`[data-tid^="${SUGGEST_PEOPLE_PREFIX}"]`); allSuggestPeople.forEach(el => applySuggestPeopleAlias(el)); const allCalling = document.querySelectorAll(`[data-cid="calling-participant-stream"]`); allCalling.forEach(el => applyCallingAlias(el)); const allRosterAvatar = document.querySelectorAll(`[id^="${ROSTER_AVATAR_PREFIX}"]`); allRosterAvatar.forEach(el => applyRosterAvatarAlias(el)); const allSerpPeople = document.querySelectorAll(`[id^="${SERP_PEOPLE_CARD_PREFIX}"]`); allSerpPeople.forEach(el => applySerpPeopleAlias(el)); const allPeoplePicker = document.querySelectorAll(`[data-tid^="${PEOPLE_PICKER_PREFIX}"]`); allPeoplePicker.forEach(el => applyPeoplePickerAlias(el)); const allPeoplePickerSelected = document.querySelectorAll(`[data-tid^="${PEOPLE_PICKER_SEL_PREFIX}"]`); allPeoplePickerSelected.forEach(el => applyPeoplePickerSelectedAlias(el)); const allReaction = document.querySelectorAll(`[data-tid="diverse-reaction-user-list-item"]`); allReaction.forEach(el => applyReactionAlias(el)); ensureChatOrganizerUI(); scanVisibleChats(); } // 初始化逻辑 function init() { loadChatOrganizerState().then(() => { ensureChatOrganizerUI(); scanVisibleChats(); }); const observer = new MutationObserver(() => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { if (isMutating) return; // 🧠 防止自己触发自己 isMutating = true; applyToAll(); // 页面内容变动后重新应用 // 给浏览器一点时间完成 DOM 更新后再允许 observer 响应 setTimeout(() => { isMutating = false; }, 500); // 至少比这个高才行,不然会一直触发 }, 300); }); observer.observe(document.body, { childList: true, subtree: true, }); // 自动同步检查 chrome.storage.local.get('lastSync').then(({ lastSync }) => { const now = Date.now(); // 24 小时 = 86400000 ms if (!lastSync || (now - lastSync > 86400000)) { console.log("Teams Alias: 正在进行后台同步..."); if (typeof fetchAliasesFromDB === 'function') { fetchAliasesFromDB().then(aliases => { chrome.storage.local.set({ aliases, lastSync: now }); console.log("Teams Alias: 自动同步成功"); }).catch(err => { console.error("Teams Alias: 自动同步失败", err); }); } else { console.warn("Teams Alias: fetchAliasesFromDB 未定义,无法同步。"); } } }); applyToAll(); // 初始执行 // 兜底:每 2 秒再扫一次(避免漏掉异步更新) // setInterval(() => { // applyToAll(); // }, 2000); } init();