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();