From f549059e4761524c4efafa1b1a813b0577c9d34d Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Mon, 5 Jan 2026 12:22:01 -0400 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=A4=96=E9=83=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 ++ .gitignore | 1 + content.js | 55 ++++++++++++++++--------------- manifest.json | 9 ++++-- package.json | 3 +- popup.html | 6 ++-- popup.js | 64 ++++++++++++------------------------- scripts/generate-env.js | 37 +++++++++++++++++++++ utils.js | 71 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 170 insertions(+), 78 deletions(-) create mode 100644 .env.example create mode 100644 scripts/generate-env.js create mode 100644 utils.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..296c8b3 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NOCODB_TOKEN= +NOCODB_LIST_URL=https://nocodb.example.com/api/v2/tables/xxx/records \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2309cc8..d1a5195 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ web_modules/ .env.test.local .env.production.local .env.local +env.js # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/content.js b/content.js index f414c96..91f6361 100644 --- a/content.js +++ b/content.js @@ -9,19 +9,6 @@ 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'); @@ -34,44 +21,37 @@ 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 = '设置别名'; + 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 = '#0078d4'; + 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', 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("别名已删除"); - } + button.addEventListener('click', () => { + // 弹窗显示 ID,方便复制 + prompt("该用户的账号ID为 (请复制):", rawId); }); document.body.appendChild(button); } // 应用别名(异步) - getAlias(id.replace(PERSON_ID_PREFIX, "")).then(alias => { + getAlias(rawId).then(alias => { if (alias && el.textContent !== alias) { el.textContent = alias; } @@ -417,6 +397,25 @@ function init() { 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 秒再扫一次(避免漏掉异步更新) diff --git a/manifest.json b/manifest.json index a010f07..2ccd143 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Teams 别名管理", - "version": "0.1.0", + "version": "0.2.0", "icons": { "16": "icons/teams-alias-16.png", "32": "icons/teams-alias-32.png", @@ -10,11 +10,14 @@ }, "description": "给 Teams 好友设置别名", "permissions": ["storage", "scripting"], - "host_permissions": ["https://teams.live.com/v2*"], + "host_permissions": [ + "https://teams.live.com/v2*", + "https://nocodb.example.com/*" + ], "content_scripts": [ { "matches": ["https://teams.live.com/v2*"], - "js": ["content.js"] + "js": ["env.js", "utils.js", "content.js"] } ], "action": { diff --git a/package.json b/package.json index b93586a..88dd897 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "teams-alias", - "version": "0.1.0", + "version": "0.2.0", "main": "index.js", "scripts": { + "build": "node scripts/generate-env.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/popup.html b/popup.html index 0755758..22e8cc7 100644 --- a/popup.html +++ b/popup.html @@ -55,10 +55,10 @@

Teams 别名管理

+ - - - + + diff --git a/popup.js b/popup.js index db1a793..dfa0d6b 100644 --- a/popup.js +++ b/popup.js @@ -17,48 +17,26 @@ document.getElementById('export').addEventListener('click', async () => { URL.revokeObjectURL(url); }); -// 通用导入处理函数 -function handleFileImport(mode = 'overwrite') { - const fileInput = document.getElementById('fileInput'); +// 手动同步 +document.getElementById('sync').addEventListener('click', async () => { + const btn = document.getElementById('sync'); + const originalText = btn.textContent; + btn.textContent = '同步中...'; + btn.disabled = true; - const handler = async (e) => { - const file = e.target.files[0]; - if (!file) return; + try { + if (typeof fetchAliasesFromDB !== 'function') { + throw new Error("utils.js not loaded properly"); + } - 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')); + const aliases = await fetchAliasesFromDB(); + await chrome.storage.local.set({ aliases, lastSync: Date.now() }); + alert(`同步成功!共获取 ${Object.keys(aliases).length} 条数据。`); + } catch (error) { + alert('同步失败,请检查网络或控制台日志。'); + console.error(error); + } finally { + btn.textContent = originalText; + btn.disabled = false; + } +}); \ No newline at end of file diff --git a/scripts/generate-env.js b/scripts/generate-env.js new file mode 100644 index 0000000..632b59f --- /dev/null +++ b/scripts/generate-env.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const path = require('path'); + +const envPath = path.join(__dirname, '..', '.env'); +const outputPath = path.join(__dirname, '..', 'env.js'); + +try { + let envContent = ''; + if (fs.existsSync(envPath)) { + envContent = fs.readFileSync(envPath, 'utf8'); + } + + const envVars = {}; + envContent.split('\n').forEach(line => { + const match = line.match(/^\s*([\w_]+)\s*=\s*(.*)?\s*$/); + if (match) { + const key = match[1]; + let value = match[2] || ''; + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'" ) && value.endsWith("'" ))) { + value = value.slice(1, -1); + } + envVars[key] = value; + } + }); + + const fileContent = `// Auto-generated by scripts/generate-env.js +const ENV_SECRETS = ${JSON.stringify(envVars, null, 4)}; +`; + + fs.writeFileSync(outputPath, fileContent); + console.log('Successfully generated env.js'); + +} catch (err) { + console.error('Error generating env.js:', err); + process.exit(1); +} diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..0db9747 --- /dev/null +++ b/utils.js @@ -0,0 +1,71 @@ +const NOCODB_CONFIG = { + // Token and URL are loaded from env.js (ENV_SECRETS global variable) + TOKEN: (typeof ENV_SECRETS !== 'undefined' && ENV_SECRETS.NOCODB_TOKEN) ? ENV_SECRETS.NOCODB_TOKEN : "", + LIST_URL: (typeof ENV_SECRETS !== 'undefined' && ENV_SECRETS.NOCODB_LIST_URL) ? ENV_SECRETS.NOCODB_LIST_URL : "" +}; + +/** + * 从 NocoDB 获取所有别名记录并整理为键值对 + * @returns {Promise} { "账号ID": "别名", ... } + */ +async function fetchAliasesFromDB() { + const listUrl = NOCODB_CONFIG.LIST_URL; + const headers = { + "xc-token": NOCODB_CONFIG.TOKEN, + "Content-Type": "application/json" + }; + + if (!listUrl || !NOCODB_CONFIG.TOKEN) { + console.error("Teams Alias: 缺少配置 (URL 或 Token),请检查 .env 文件。"); + throw new Error("Missing configuration"); + } + + let allAliases = {}; + let offset = 0; + const limit = 1000; + + try { + while (true) { + // 构建带参数的 URL + const url = new URL(listUrl); + url.searchParams.append("limit", limit); + url.searchParams.append("fields", "账号ID,_最终输出"); + url.searchParams.append("offset", offset); + + const response = await fetch(url.toString(), { headers }); + + if (!response.ok) { + console.error(`获取数据失败: ${response.status} ${response.statusText}`); + throw new Error("网络请求失败"); + } + + const data = await response.json(); + const rows = data.list || []; + + if (rows.length === 0) { + break; + } + + // 整理数据 + rows.forEach(row => { + if (row["账号ID"] && row["_最终输出"]) { + // key = 账号ID, value = _最终输出 + allAliases[row["账号ID"]] = row["_最终输出"]; + } + }); + + if (rows.length < limit) { + break; // 已获取所有数据 + } + + offset += limit; + } + + console.log(`同步完成,共获取 ${Object.keys(allAliases).length} 条别名记录。`); + return allAliases; + + } catch (error) { + console.error("同步别名数据时出错:", error); + throw error; // 向抛出以便调用者处理 + } +}