From 4581297dac2c65d48b20332ea0cbc59d1ea0635a Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Tue, 1 Jul 2025 20:16:57 -0400 Subject: [PATCH] add all files --- README.md | 4 +- content.js | 428 ++++++++++++++++++++++++++++++++++++++ icons/teams-alias-128.png | Bin 0 -> 11227 bytes icons/teams-alias-16.png | Bin 0 -> 966 bytes icons/teams-alias-32.png | Bin 0 -> 1803 bytes icons/teams-alias-48.png | Bin 0 -> 2350 bytes manifest.json | 23 ++ package-lock.json | 22 ++ package.json | 14 ++ popup.html | 64 ++++++ popup.js | 64 ++++++ 11 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 content.js create mode 100644 icons/teams-alias-128.png create mode 100644 icons/teams-alias-16.png create mode 100644 icons/teams-alias-32.png create mode 100644 icons/teams-alias-48.png create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 popup.html create mode 100644 popup.js diff --git a/README.md b/README.md index efe52ae..56ffca7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# teams-alias +# Teams 别名插件 -Teams 别名插件 \ No newline at end of file +给 Microsoft Teams 好友的名称添加别名。 diff --git a/content.js b/content.js new file mode 100644 index 0000000..f414c96 --- /dev/null +++ b/content.js @@ -0,0 +1,428 @@ +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(); diff --git a/icons/teams-alias-128.png b/icons/teams-alias-128.png new file mode 100644 index 0000000000000000000000000000000000000000..a0552366659540f28c69aa3ebaf864cd86869d51 GIT binary patch literal 11227 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 CiZ1W~ literal 0 HcmV?d00001 diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..a010f07 --- /dev/null +++ b/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 3, + "name": "Teams 别名管理", + "version": "0.1.0", + "icons": { + "16": "icons/teams-alias-16.png", + "32": "icons/teams-alias-32.png", + "48": "icons/teams-alias-48.png", + "128": "icons/teams-alias-128.png" + }, + "description": "给 Teams 好友设置别名", + "permissions": ["storage", "scripting"], + "host_permissions": ["https://teams.live.com/v2*"], + "content_scripts": [ + { + "matches": ["https://teams.live.com/v2*"], + "js": ["content.js"] + } + ], + "action": { + "default_popup": "popup.html" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..222a5bf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "teams-alias", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "teams-alias", + "version": "0.1.0", + "license": "GPL-3.0-only", + "dependencies": { + "chrome-types": "^0.1.347" + } + }, + "node_modules/chrome-types": { + "version": "0.1.347", + "resolved": "https://registry.npmjs.org/chrome-types/-/chrome-types-0.1.347.tgz", + "integrity": "sha512-c8lbQnYfghYJ8hQ6XivJzex3NJS7RAdcD81uGylnvrRqzF2QMei/wVhsE6+PsZDOipNxbFMxjhf242ZuGhYZzQ==", + "license": "Apache-2.0" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b93586a --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "teams-alias", + "version": "0.1.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "GPL-3.0-only", + "description": "", + "dependencies": { + "chrome-types": "^0.1.347" + } +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..0755758 --- /dev/null +++ b/popup.html @@ -0,0 +1,64 @@ + + + + + + Teams 别名管理 + + +

Teams 别名管理

+ + + + + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..db1a793 --- /dev/null +++ b/popup.js @@ -0,0 +1,64 @@ +// 导出数据 +document.getElementById('export').addEventListener('click', async () => { + const { aliases } = await chrome.storage.local.get('aliases'); + const blob = new Blob([JSON.stringify(aliases || {}, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const timestamp = new Date() + .toLocaleString('sv-SE') // ISO-like 格式 + .replace(' ', '_') + .replace(/:/g, '-'); // 避免非法文件名 + + const a = document.createElement('a'); + a.href = url; + a.download = `teams-alias-${timestamp}.json`; + a.click(); + + URL.revokeObjectURL(url); +}); + +// 通用导入处理函数 +function handleFileImport(mode = 'overwrite') { + const fileInput = document.getElementById('fileInput'); + + const handler = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = async function (event) { + try { + const importedData = JSON.parse(event.target.result); + if (typeof importedData !== 'object' || importedData === null) { + alert('文件格式错误'); + return; + } + + const { aliases: existingAliases = {} } = await chrome.storage.local.get('aliases'); + + const updatedAliases = (mode === 'merge') + ? { ...importedData, ...existingAliases } // 保留现有的不被导入的覆盖 + : importedData; + + await chrome.storage.local.set({ aliases: updatedAliases }); + + alert(mode === 'merge' ? '导入并合并成功' : '导入成功'); + } catch (err) { + alert('无法解析 JSON 文件'); + } finally { + fileInput.removeEventListener('change', handler); + fileInput.value = ''; // 重置 input 以便后续重复导入同一文件 + } + }; + reader.readAsText(file); + }; + + fileInput.addEventListener('change', handler, { once: true }); + fileInput.click(); +} + +// 导入(覆盖) +document.getElementById('import').addEventListener('click', () => handleFileImport('overwrite')); + +// 更新导入(合并) +document.getElementById('update-import').addEventListener('click', () => handleFileImport('merge'));