Hide fully authorized contacts from the quick-grant list (4.9.1-ops.11).

Recent contacts only show people who still need DM or group access actions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 17:44:17 +08:00
parent f96157da1e
commit 87e44bd36e
5 changed files with 92 additions and 29 deletions

View file

@ -571,7 +571,7 @@ var React24 = __toESM(require("react"), 1);
// package.json
var package_default = {
name: "dsh-im-ops",
version: "4.9.1-ops.10",
version: "4.9.1-ops.11",
description: "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 \u2014 access/session policies owned here.",
keywords: [
"deepseek-harness",
@ -876,7 +876,8 @@ var EN = Object.freeze({
"\u5DF2\u52A0\u5165\u672C\u7FA4\u6388\u6743\uFF0C\u8BB0\u5F97\u70B9\u4FDD\u5B58\u3002": "Added to this group. Remember to save.",
"\u5DF2\u52A0\u5165\u79C1\u804A\u6388\u6743\uFF0C\u8BB0\u5F97\u70B9\u4FDD\u5B58\u3002": "Added to DM access. Remember to save.",
"\u6682\u65E0\u8054\u7CFB\u4EBA\u3002\u6709\u4EBA\u79C1\u804A\u6216 @ \u673A\u5668\u4EBA\u540E\u4F1A\u51FA\u73B0\u5728\u6B64\u3002": "No contacts yet. They appear here after someone DMs or @mentions the bot.",
"\u4EC5\u8BB0\u5F55\u79C1\u804A\u673A\u5668\u4EBA\uFF0C\u6216\u5728\u7FA4\u91CC @ \u673A\u5668\u4EBA\u7684\u4EBA\u3002\u7535\u8BDD\u662F\u552F\u4E00\u6388\u6743\u952E\uFF0C\u53EF\u4E00\u952E\u52A0\u5165\u79C1\u804A/\u672C\u7FA4\u6388\u6743\u3002": "Only people who DM the bot or @mention it in a group are recorded. Phone is the sole auth key; use one-click to grant DM or group access.",
"\u5F53\u524D\u8054\u7CFB\u4EBA\u5747\u5DF2\u5177\u5907\u5BF9\u5E94\u6743\u9650\uFF0C\u65E0\u9700\u518D\u6388\u6743\u3002": "Everyone in contacts already has the matching access; nothing left to grant.",
"\u4EC5\u8BB0\u5F55\u79C1\u804A\u673A\u5668\u4EBA\uFF0C\u6216\u5728\u7FA4\u91CC @ \u673A\u5668\u4EBA\u7684\u4EBA\u3002\u5DF2\u6709\u5BF9\u5E94\u6743\u9650\u7684\u4EBA\u4F1A\u81EA\u52A8\u9690\u85CF\uFF1B\u7535\u8BDD\u662F\u552F\u4E00\u6388\u6743\u952E\uFF0C\u53EF\u4E00\u952E\u52A0\u5165\u79C1\u804A/\u672C\u7FA4\u6388\u6743\u3002": "Only people who DM the bot or @mention it in a group are recorded. People who already have the matching access are hidden. Phone is the sole auth key; use one-click to grant DM or group access.",
"WhatsApp \u6309\u7535\u8BDD\u53F7\u7801\u6388\u6743\uFF1A\u5168\u5C40\u7BA1\u7406\u5458\u7BA1\u79C1\u804A\uFF1B\u7FA4\u7BA1\u7406\u5458\u53EA\u6279\u672C\u7FA4\u3002\u6210\u5458\u4E0D\u53EF\u8DE8\u7FA4\u3001\u6709\u7FA4\u6743\u4E5F\u4E0D\u81EA\u52A8\u83B7\u5F97\u79C1\u804A\u6743\u3002": "WhatsApp authorizes by phone number: global admins manage DMs; group admins approve only their group. Members do not cross groups, and group access does not grant DMs.",
"\u4FDD\u5B58\u5206\u7EA7\u8BBF\u95EE\u8BBE\u7F6E": "Save graded access settings",
"\u4FDD\u5B58\u8BBF\u95EE\u4E0E\u4F1A\u8BDD\u8BBE\u7F6E": "Save access and session settings",
@ -12614,6 +12615,17 @@ function groupDisplayName(groupJid, groups) {
if (typeof title === "string" && title.trim()) return title.trim();
return "\u672A\u547D\u540D\u7FA4";
}
function contactNeedsGrantAction(contact, draft, knownGroupJids) {
const phone = contact?.phone || "";
if (!phone) return true;
if ((draft.globalAdmins ?? []).includes(phone)) return false;
const alreadyDirect = (draft.directMembers ?? []).some((m) => m.phone === phone);
const pendingGroups = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid)).filter((groupJid) => {
const group = draft.groups?.[groupJid] ?? { admins: [], members: [] };
return !(group.admins ?? []).includes(phone) && !(group.members ?? []).some((m) => m.phone === phone);
});
return !alreadyDirect || pendingGroups.length > 0;
}
function digitsOnly(value) {
return String(value ?? "").replace(/[^\d]/g, "");
}
@ -12885,6 +12897,10 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
}
return [...set].sort();
}, [draft.groups, draft.pending, contacts]);
const actionableContacts = React21.useMemo(
() => contacts.filter((contact) => contactNeedsGrantAction(contact, draft, knownGroupJids)),
[contacts, draft, knownGroupJids]
);
const resolvePending = async (pendingId, action) => {
setFeedback(null);
setSaving(true);
@ -13167,12 +13183,20 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
h2(
"p",
{ className: "dim-accessUsersEmpty" },
"\u4EC5\u8BB0\u5F55\u79C1\u804A\u673A\u5668\u4EBA\uFF0C\u6216\u5728\u7FA4\u91CC @ \u673A\u5668\u4EBA\u7684\u4EBA\u3002\u7535\u8BDD\u662F\u552F\u4E00\u6388\u6743\u952E\uFF0C\u53EF\u4E00\u952E\u52A0\u5165\u79C1\u804A/\u672C\u7FA4\u6388\u6743\u3002"
"\u4EC5\u8BB0\u5F55\u79C1\u804A\u673A\u5668\u4EBA\uFF0C\u6216\u5728\u7FA4\u91CC @ \u673A\u5668\u4EBA\u7684\u4EBA\u3002\u5DF2\u6709\u5BF9\u5E94\u6743\u9650\u7684\u4EBA\u4F1A\u81EA\u52A8\u9690\u85CF\uFF1B\u7535\u8BDD\u662F\u552F\u4E00\u6388\u6743\u952E\uFF0C\u53EF\u4E00\u952E\u52A0\u5165\u79C1\u804A/\u672C\u7FA4\u6388\u6743\u3002"
),
contacts.length === 0 ? h2("div", { className: "dim-accessUsersEmpty" }, "\u6682\u65E0\u8054\u7CFB\u4EBA\u3002\u6709\u4EBA\u79C1\u804A\u6216 @ \u673A\u5668\u4EBA\u540E\u4F1A\u51FA\u73B0\u5728\u6B64\u3002") : h2("ul", { className: "dim-accessUserList" }, contacts.slice(0, 30).map((contact) => {
actionableContacts.length === 0 ? h2(
"div",
{ className: "dim-accessUsersEmpty" },
contacts.length === 0 ? "\u6682\u65E0\u8054\u7CFB\u4EBA\u3002\u6709\u4EBA\u79C1\u804A\u6216 @ \u673A\u5668\u4EBA\u540E\u4F1A\u51FA\u73B0\u5728\u6B64\u3002" : "\u5F53\u524D\u8054\u7CFB\u4EBA\u5747\u5DF2\u5177\u5907\u5BF9\u5E94\u6743\u9650\uFF0C\u65E0\u9700\u518D\u6388\u6743\u3002"
) : h2("ul", { className: "dim-accessUserList" }, actionableContacts.slice(0, 30).map((contact) => {
const phone = contact.phone || "";
const alreadyDirect = phone && draft.directMembers.some((m) => m.phone === phone);
const groupTargets = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid));
const alreadyDirect = Boolean(phone) && ((draft.directMembers ?? []).some((m) => m.phone === phone) || (draft.globalAdmins ?? []).includes(phone));
const groupTargets = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid)).filter((groupJid) => {
if (!phone) return true;
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
return !(group.admins ?? []).includes(phone) && !(group.members ?? []).some((m) => m.phone === phone);
});
return h2(
"li",
{
@ -13186,11 +13210,11 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
h2("span", null, (contact.scenes ?? []).join(" / ")),
phone ? null : h2("span", null, "\u5F85\u8865\u7535\u8BDD")
),
phone ? h2("button", {
phone && !alreadyDirect ? h2("button", {
type: "button",
className: "dim-deliveryButton",
"data-kind": "primary",
disabled: saving || alreadyDirect,
disabled: saving,
onClick: () => {
setDraft((current) => {
if (current.directMembers.some((m) => m.phone === phone)) return current;
@ -13204,16 +13228,15 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
});
setFeedback({ tone: "success", message: "\u5DF2\u52A0\u5165\u79C1\u804A\u6388\u6743\uFF0C\u8BB0\u5F97\u70B9\u4FDD\u5B58\u3002" });
}
}, alreadyDirect ? "\u5DF2\u5728\u79C1\u804A" : "\u52A0\u79C1\u804A") : null,
}, "\u52A0\u79C1\u804A") : null,
...groupTargets.map((groupJid) => {
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
const name2 = groupDisplayName(groupJid, draft.groups);
const already = group.admins.includes(phone) || group.members.some((m) => m.phone === phone);
return h2("button", {
key: `add-${groupJid}`,
type: "button",
className: "dim-deliveryButton",
disabled: saving || !phone || already,
disabled: saving || !phone,
onClick: () => {
setDraft((current) => {
const existing = current.groups[groupJid] ?? { title: "", admins: [], members: [] };
@ -13239,7 +13262,7 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
message: ["\u5DF2\u52A0\u5165\u7FA4\u6388\u6743\uFF1A", name2, "\u3002\u8BB0\u5F97\u70B9\u4FDD\u5B58\u3002"].join("")
});
}
}, [already ? "\u5DF2\u5728\u7FA4\uFF1A" : "\u52A0\u5165\u7FA4\uFF1A", name2].join(""));
}, ["\u52A0\u5165\u7FA4\uFF1A", name2].join(""));
})
);
}))

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{
"name": "dsh-im-ops",
"version": "4.9.1-ops.10",
"version": "4.9.1-ops.11",
"description": "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 — access/session policies owned here.",
"keywords": [
"deepseek-harness",

View file

@ -92,6 +92,30 @@ function groupDisplayName(groupJid, groups) {
return '未命名群';
}
/**
* Keep contacts that still need a grant action (DM and/or a known group).
* Fully authorized people are hidden from the quick-grant list.
* @param {{ phone?: string, groupJids?: string[] }} contact
* @param {object} draft
* @param {string[]} knownGroupJids
*/
function contactNeedsGrantAction(contact, draft, knownGroupJids) {
const phone = contact?.phone || '';
if (!phone) return true;
if ((draft.globalAdmins ?? []).includes(phone)) return false;
const alreadyDirect = (draft.directMembers ?? []).some((m) => m.phone === phone);
const pendingGroups = (contact.groupJids ?? [])
.filter((jid) => knownGroupJids.includes(jid))
.filter((groupJid) => {
const group = draft.groups?.[groupJid] ?? { admins: [], members: [] };
return !(group.admins ?? []).includes(phone)
&& !(group.members ?? []).some((m) => m.phone === phone);
});
return !alreadyDirect || pendingGroups.length > 0;
}
function digitsOnly(value) {
return String(value ?? '').replace(/[^\d]/g, '');
}
@ -355,6 +379,10 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
}
return [...set].sort();
}, [draft.groups, draft.pending, contacts]);
const actionableContacts = React.useMemo(
() => contacts.filter((contact) => contactNeedsGrantAction(contact, draft, knownGroupJids)),
[contacts, draft, knownGroupJids],
);
const resolvePending = async (pendingId, action) => {
setFeedback(null);
@ -617,13 +645,25 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
h('fieldset', { className: 'dim-accessScene', disabled: saving },
h('legend', null, '最近联系人(自动沉淀)'),
h('p', { className: 'dim-accessUsersEmpty' },
'仅记录私聊机器人,或在群里 @ 机器人的人。电话是唯一授权键,可一键加入私聊/本群授权。'),
contacts.length === 0
? h('div', { className: 'dim-accessUsersEmpty' }, '暂无联系人。有人私聊或 @ 机器人后会出现在此。')
: h('ul', { className: 'dim-accessUserList' }, contacts.slice(0, 30).map((contact) => {
'仅记录私聊机器人,或在群里 @ 机器人的人。已有对应权限的人会自动隐藏;电话是唯一授权键,可一键加入私聊/本群授权。'),
actionableContacts.length === 0
? h('div', { className: 'dim-accessUsersEmpty' },
contacts.length === 0
? '暂无联系人。有人私聊或 @ 机器人后会出现在此。'
: '当前联系人均已具备对应权限,无需再授权。')
: h('ul', { className: 'dim-accessUserList' }, actionableContacts.slice(0, 30).map((contact) => {
const phone = contact.phone || '';
const alreadyDirect = phone && draft.directMembers.some((m) => m.phone === phone);
const groupTargets = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid));
const alreadyDirect = Boolean(phone)
&& ((draft.directMembers ?? []).some((m) => m.phone === phone)
|| (draft.globalAdmins ?? []).includes(phone));
const groupTargets = (contact.groupJids ?? [])
.filter((jid) => knownGroupJids.includes(jid))
.filter((groupJid) => {
if (!phone) return true;
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
return !(group.admins ?? []).includes(phone)
&& !(group.members ?? []).some((m) => m.phone === phone);
});
return h('li', {
key: `${contact.phone ?? ''}-${(contact.lids ?? []).join(',')}`,
className: 'dim-accessUserRow',
@ -632,11 +672,11 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
h('strong', null, contactLabel(contact)),
h('span', null, (contact.scenes ?? []).join(' / ')),
phone ? null : h('span', null, '待补电话')),
phone ? h('button', {
phone && !alreadyDirect ? h('button', {
type: 'button',
className: 'dim-deliveryButton',
'data-kind': 'primary',
disabled: saving || alreadyDirect,
disabled: saving,
onClick: () => {
setDraft((current) => {
if (current.directMembers.some((m) => m.phone === phone)) return current;
@ -650,17 +690,15 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
});
setFeedback({ tone: 'success', message: '已加入私聊授权,记得点保存。' });
},
}, alreadyDirect ? '已在私聊' : '加私聊') : null,
}, '加私聊') : null,
...groupTargets.map((groupJid) => {
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
const name = groupDisplayName(groupJid, draft.groups);
const already = group.admins.includes(phone)
|| group.members.some((m) => m.phone === phone);
return h('button', {
key: `add-${groupJid}`,
type: 'button',
className: 'dim-deliveryButton',
disabled: saving || !phone || already,
disabled: saving || !phone,
onClick: () => {
setDraft((current) => {
const existing = current.groups[groupJid] ?? { title: '', admins: [], members: [] };
@ -687,7 +725,7 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
message: ['已加入群授权:', name, '。记得点保存。'].join(''),
});
},
}, [already ? '已在群:' : '加入群:', name].join(''));
}, ['加入群:', name].join(''));
}));
}))),
h('fieldset', { className: 'dim-accessScene', disabled: saving },

View file

@ -52,7 +52,9 @@ const EN = Object.freeze({
"暂无联系人。有人私聊或 @ 机器人后会出现在此。": "No contacts yet. They appear here after someone DMs or @mentions the bot.",
"仅记录私聊机器人,或在群里 @ 机器人的人。电话是唯一授权键,可一键加入私聊/本群授权。": "Only people who DM the bot or @mention it in a group are recorded. Phone is the sole auth key; use one-click to grant DM or group access.",
"当前联系人均已具备对应权限,无需再授权。": "Everyone in contacts already has the matching access; nothing left to grant.",
"仅记录私聊机器人,或在群里 @ 机器人的人。已有对应权限的人会自动隐藏;电话是唯一授权键,可一键加入私聊/本群授权。": "Only people who DM the bot or @mention it in a group are recorded. People who already have the matching access are hidden. Phone is the sole auth key; use one-click to grant DM or group access.",
"WhatsApp 按电话号码授权:全局管理员管私聊;群管理员只批本群。成员不可跨群、有群权也不自动获得私聊权。": "WhatsApp authorizes by phone number: global admins manage DMs; group admins approve only their group. Members do not cross groups, and group access does not grant DMs.",
"保存分级访问设置": "Save graded access settings",