Files
teams-alias/content.js
Julian Freeman 4581297dac add all files
2025-07-01 20:16:57 -04:00

429 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const PERSON_ID_PREFIX = 'chat-topic-person-';
const ICON_ID_PREFIX = "presence-pill-";
const CHAT_ROSTER_PREFIX = "chat-roster-item-name-";
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-";
let debounceTimer = null;
let isMutating = false;
async function saveAlias(id, alias) {
const key = id.replace(PERSON_ID_PREFIX, "");
const result = await chrome.storage.local.get('aliases');
const aliases = result.aliases || {};
// 新的别名为空就删除
if (alias) {
aliases[key] = alias;
} else {
delete aliases[key];
}
await chrome.storage.local.set({ aliases });
}
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 existingBtn = document.querySelector(`[data-floating-btn-for="${id}"]`);
if (!existingBtn) {
const rect = el.getBoundingClientRect();
const button = document.createElement('button');
button.textContent = '设置别名';
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 = '#0078d4';
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', async () => {
const current = (await getAlias(id.replace(PERSON_ID_PREFIX, ""))) || document.getElementById(id)?.textContent || '';
const newAlias = prompt("请输入别名:", current);
// 如果是空字符串还是得进入
if (newAlias !== null) {
const el = document.getElementById(id); // 🔥 重新获取最新的元素
if (el && newAlias) el.textContent = newAlias.trim();
await saveAlias(id.replace(PERSON_ID_PREFIX, ""), newAlias.trim());
}
if (newAlias === "") {
alert("别名已删除");
}
});
document.body.appendChild(button);
}
// 应用别名(异步)
getAlias(id.replace(PERSON_ID_PREFIX, "")).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;
// 向上查找 3 个父元素
for (let i = 0; i < 3; i++) {
if (parent.parentElement) {
parent = parent.parentElement;
} else {
return; // 如果不足3层就跳过
}
}
// 获取后一个兄弟元素
const nextSibling = parent.nextElementSibling;
if (!nextSibling) return;
// 向下查找第 2 个子元素(层级式)
let target = nextSibling;
for (let i = 0; i < 2; i++) {
if (target.children.length > 0) {
target = target.children[0]; // 每层往下取第一个子元素
} else {
return; // 不足2层跳过
}
}
// 判断符合才修改
if (target.getAttribute('data-tid') === 'chat-list-item-title') {
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));
}
// 初始化逻辑
function init() {
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,
});
applyToAll(); // 初始执行
// 兜底:每 2 秒再扫一次(避免漏掉异步更新)
// setInterval(() => {
// applyToAll();
// }, 2000);
}
init();