mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
Sync WhatsApp group subjects into access UI (4.9.1-ops.10).
Refresh missing/forced group titles via groupMetadata and participating catalog so cards show real names instead of bare JIDs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a169b164a8
commit
f96157da1e
13 changed files with 568 additions and 260 deletions
|
|
@ -14,6 +14,7 @@ import { h, localizeText } from './i18n.js';
|
|||
|
||||
export const ACCESS_GRANT_ENDPOINT = 'bot.access-grant.set';
|
||||
export const ACCESS_PENDING_RESOLVE_ENDPOINT = 'bot.access-pending.resolve';
|
||||
export const ACCESS_GROUP_TITLES_REFRESH_ENDPOINT = 'bot.access-group-titles.refresh';
|
||||
export const GROUP_SESSION_SCOPE_ENDPOINT = 'bot.group-session-scope.set';
|
||||
|
||||
function unwrapRpcResult(result) {
|
||||
|
|
@ -88,10 +89,7 @@ function NicknameHint({ contacts, phone }) {
|
|||
function groupDisplayName(groupJid, groups) {
|
||||
const title = groups?.[groupJid]?.title;
|
||||
if (typeof title === 'string' && title.trim()) return title.trim();
|
||||
const raw = String(groupJid ?? '');
|
||||
const id = raw.replace(/@g\.us$/i, '');
|
||||
if (id.length > 14) return `${id.slice(0, 10)}…@g.us`;
|
||||
return raw || '未知群';
|
||||
return '未命名群';
|
||||
}
|
||||
|
||||
function digitsOnly(value) {
|
||||
|
|
@ -300,7 +298,7 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
|
|||
const bot = Array.isArray(value?.bots)
|
||||
? value.bots.find((entry) => entry?.botId === account.botId)
|
||||
: null;
|
||||
const grant = normalizeAccessGrant(bot?.accessGrant);
|
||||
let grant = normalizeAccessGrant(bot?.accessGrant);
|
||||
if (grant) {
|
||||
setDraft(cloneGrant(grant, ownerPhone));
|
||||
onSaved?.(grant);
|
||||
|
|
@ -308,6 +306,30 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
|
|||
if (bot?.groupSessionScope) {
|
||||
setGroupSessionScope(normalizeGroupSessionScope(bot.groupSessionScope));
|
||||
}
|
||||
|
||||
const missingTitles = Object.entries(grant?.groups ?? {})
|
||||
.filter(([, group]) => !(typeof group?.title === 'string' && group.title.trim()))
|
||||
.map(([jid]) => jid);
|
||||
const pendingGroupJids = (grant?.pending ?? [])
|
||||
.map((entry) => entry.groupJid)
|
||||
.filter((jid) => typeof jid === 'string' && jid.endsWith('@g.us'));
|
||||
const toSync = [...new Set([...missingTitles, ...pendingGroupJids])];
|
||||
if (toSync.length > 0) {
|
||||
try {
|
||||
const refreshed = unwrapRpcResult(await rpcCall(ACCESS_GROUP_TITLES_REFRESH_ENDPOINT, {
|
||||
botId: account.botId,
|
||||
groupJids: toSync,
|
||||
}));
|
||||
if (cancelled) return;
|
||||
const next = grantFromSnapshot(refreshed, account.botId);
|
||||
if (next) {
|
||||
setDraft(cloneGrant(next, ownerPhone));
|
||||
onSaved?.(next);
|
||||
}
|
||||
} catch {
|
||||
// Title sync is best-effort while the bot is offline.
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
setFeedback({
|
||||
|
|
@ -485,20 +507,54 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
|
|||
h('div', { className: 'dim-accessGroupHeading' },
|
||||
h('h3', null, groupDisplayName(groupJid, draft.groups)),
|
||||
h('code', { className: 'dim-accessGroupJid' }, groupJid)),
|
||||
h('label', { className: 'dim-accessField' },
|
||||
h('span', null, '群名称'),
|
||||
h('input', {
|
||||
value: group.title ?? '',
|
||||
maxLength: 128,
|
||||
placeholder: '填写便于识别的群名称',
|
||||
onChange: (event) => setDraft((current) => ({
|
||||
...current,
|
||||
groups: {
|
||||
...current.groups,
|
||||
[groupJid]: { ...group, title: event.target.value },
|
||||
},
|
||||
h('div', { className: 'dim-accessGroupTitleRow' },
|
||||
h('label', { className: 'dim-accessField' },
|
||||
h('span', null, '群名称'),
|
||||
h('input', {
|
||||
value: group.title ?? '',
|
||||
maxLength: 128,
|
||||
placeholder: '填写便于识别的群名称',
|
||||
onChange: (event) => setDraft((current) => ({
|
||||
...current,
|
||||
groups: {
|
||||
...current.groups,
|
||||
[groupJid]: { ...group, title: event.target.value },
|
||||
},
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
h('button', {
|
||||
type: 'button',
|
||||
className: 'dim-deliveryButton',
|
||||
disabled: saving,
|
||||
onClick: () => void (async () => {
|
||||
setSaving(true);
|
||||
setFeedback(null);
|
||||
try {
|
||||
const refreshed = unwrapRpcResult(await rpcCall(
|
||||
ACCESS_GROUP_TITLES_REFRESH_ENDPOINT,
|
||||
{ botId: account.botId, groupJids: [groupJid] },
|
||||
));
|
||||
const next = grantFromSnapshot(refreshed, account.botId);
|
||||
if (!next) throw new Error('服务没有返回已保存的访问授权。');
|
||||
setDraft(cloneGrant(next, ownerPhone));
|
||||
onSaved?.(next);
|
||||
const title = next.groups?.[groupJid]?.title;
|
||||
setFeedback({
|
||||
tone: title ? 'success' : 'error',
|
||||
message: title
|
||||
? ['已同步群名:', title].join('')
|
||||
: '未能从 WhatsApp 读取群名,请确认机器人在线且仍在该群。',
|
||||
});
|
||||
} catch (error) {
|
||||
setFeedback({
|
||||
tone: 'error',
|
||||
message: error?.message || '同步群名失败,请稍后重试。',
|
||||
});
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}),
|
||||
}, '同步群名')),
|
||||
h(AdminPhones, {
|
||||
title: '本群管理员',
|
||||
help: '未配置时,本群申请回落给全局管理员审批(仍只授予本群权)。',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
|
|||
setAccessPolicy: 'bot.access-policy.set',
|
||||
setAccessGrant: 'bot.access-grant.set',
|
||||
resolveAccessPending: 'bot.access-pending.resolve',
|
||||
refreshAccessGroupTitles: 'bot.access-group-titles.refresh',
|
||||
setGroupSessionScope: 'bot.group-session-scope.set',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ const EN = Object.freeze({
|
|||
'机器人设置页签': 'Bot settings tabs',
|
||||
'投递设置': 'Delivery settings',
|
||||
'访问设置': 'Access settings',
|
||||
"请提交有效的群名同步请求。": "Submit a valid group-name sync request.",
|
||||
"同步群名失败,请稍后重试。": "Could not sync the group name. Try again later.",
|
||||
"未能从 WhatsApp 读取群名,请确认机器人在线且仍在该群。": "Could not read the group name from WhatsApp. Confirm the bot is online and still in the group.",
|
||||
"已同步群名:": "Synced group name: ",
|
||||
"同步群名": "Sync group name",
|
||||
"未命名群": "Unnamed group",
|
||||
"暂无昵称(私聊/@ 后会自动补齐)": "No nickname yet (filled after DM or @mention)",
|
||||
"群聊 · ": "Group · ",
|
||||
|
||||
|
|
|
|||
|
|
@ -483,6 +483,7 @@ const CSS = String.raw`
|
|||
.dim-accessGroupHeading { min-width: 0; display: grid; gap: 2px; }
|
||||
.dim-accessGroupHeading h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 21px; font-weight: 650; }
|
||||
.dim-accessGroupJid { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 16px; overflow-wrap: anywhere; }
|
||||
.dim-accessGroupTitleRow { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 10px; align-items: end; }
|
||||
.dim-accessNickname { display: block; margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 560; }
|
||||
.dim-accessNicknameEmpty { color: var(--dsw-alias-label-tertiary, #8f959e); font-weight: 400; }
|
||||
.dim-accessUserRow { position: relative; z-index: 0; overflow: visible; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue