Fix WhatsApp access settings showing empty grant state (4.9.1-ops.6).

Client normalizeBot dropped accessGrant/groupSessionScope, so pending, contacts, and group cards never appeared. Also record contacts only on DM/@, auto-create group buckets on @, and add one-click grant buttons.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 17:05:16 +08:00
parent a0c49707a1
commit 93672dfd91
8 changed files with 685 additions and 444 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.5",
version: "4.9.1-ops.6",
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",
@ -852,6 +852,14 @@ var EN = Object.freeze({
"\u673A\u5668\u4EBA\u8BBE\u7F6E\u9875\u7B7E": "Bot settings tabs",
"\u6295\u9012\u8BBE\u7F6E": "Delivery settings",
"\u8BBF\u95EE\u8BBE\u7F6E": "Access settings",
"\u52A0\u672C\u7FA4": "Add to group",
"\u5DF2\u5728\u672C\u7FA4": "Already in group",
"\u52A0\u79C1\u804A": "Add to DMs",
"\u5DF2\u5728\u79C1\u804A": "Already in DMs",
"\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.",
"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",
@ -11067,6 +11075,207 @@ function installWeixinStyles() {
return () => style.remove();
}
// src/channels/shared/access-grant.mjs
var ACCESS_GRANT_VERSION = 1;
var ACCESS_PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
var ACCESS_GRANT_PHONE_MAX_LENGTH = 32;
var ACCESS_GRANT_GROUP_JID_PATTERN = /^\d{5,32}@g\.us$/;
var CONTROL_CHARACTERS2 = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
function invalidGrant(message) {
const error = new TypeError(message);
error.code = "access-grant-invalid";
throw error;
}
function normalizeAccessPhone(value) {
if (typeof value === "number" && Number.isFinite(value)) value = String(value);
if (typeof value === "bigint") value = String(value);
if (typeof value !== "string") return null;
let raw = value.trim();
if (!raw || CONTROL_CHARACTERS2.test(raw)) return null;
const at = raw.indexOf("@");
if (at > 0) {
const user = raw.slice(0, at).split(":")[0];
const server = raw.slice(at + 1).toLowerCase();
if (!["s.whatsapp.net", "c.us", "hosted"].includes(server)) return null;
raw = user;
}
const digits = raw.replace(/[^\d]/g, "");
if (digits.length < 5 || digits.length > ACCESS_GRANT_PHONE_MAX_LENGTH) return null;
return digits;
}
function validateAccessPhone(value) {
const phone = normalizeAccessPhone(value);
if (!phone) invalidGrant("\u7535\u8BDD\u53F7\u7801\u65E0\u6548\u3002");
return phone;
}
function validateGroupJid(value) {
if (typeof value !== "string" || !ACCESS_GRANT_GROUP_JID_PATTERN.test(value.trim())) {
invalidGrant("\u7FA4 JID \u65E0\u6548\u3002");
}
return value.trim();
}
function validateMember(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u6210\u5458\u6761\u76EE\u65E0\u6548\u3002");
}
const phone = validateAccessPhone(input.phone ?? input.id);
const canExecuteCommands = input.canExecuteCommands === void 0 ? true : Boolean(input.canExecuteCommands);
return Object.freeze({ phone, canExecuteCommands });
}
function validatePhoneList(input, { emptyMessage, label }) {
if (!Array.isArray(input)) invalidGrant(`${label}\u5FC5\u987B\u662F\u6570\u7EC4\u3002`);
const phones = input.map((entry) => typeof entry === "string" || typeof entry === "number" || typeof entry === "bigint" ? validateAccessPhone(entry) : validateAccessPhone(entry?.phone ?? entry?.id));
if (new Set(phones).size !== phones.length) invalidGrant(`${label}\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002`);
if (phones.length === 0 && emptyMessage) invalidGrant(emptyMessage);
return Object.freeze(phones);
}
function validateGroupGrant(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u7FA4\u6388\u6743\u6761\u76EE\u65E0\u6548\u3002");
}
const admins = validatePhoneList(input.admins ?? [], { label: "\u7FA4\u7BA1\u7406\u5458" });
const members = Object.freeze((Array.isArray(input.members) ? input.members : []).map(validateMember));
if (new Set(members.map((m) => m.phone)).size !== members.length) {
invalidGrant("\u7FA4\u6210\u5458\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002");
}
const title = typeof input.title === "string" ? input.title.trim().slice(0, 128) : "";
return Object.freeze({
...title ? { title } : {},
admins,
members
});
}
function validatePending(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u5F85\u6279\u6761\u76EE\u65E0\u6548\u3002");
}
const id5 = typeof input.id === "string" ? input.id.trim() : "";
if (!/^[A-Za-z0-9_-]{6,64}$/.test(id5)) invalidGrant("\u5F85\u6279\u7F16\u53F7\u65E0\u6548\u3002");
const kind = input.kind === "group" ? "group" : input.kind === "direct" ? "direct" : null;
if (!kind) invalidGrant("\u5F85\u6279\u573A\u666F\u65E0\u6548\u3002");
const phone = input.phone ? validateAccessPhone(input.phone) : "";
const lid = typeof input.lid === "string" ? input.lid.trim().slice(0, 128) : "";
if (!phone && !lid) invalidGrant("\u5F85\u6279\u7F3A\u5C11\u7535\u8BDD\u6216 LID\u3002");
const groupJid = kind === "group" ? validateGroupJid(input.groupJid) : void 0;
const pushName = typeof input.pushName === "string" ? input.pushName.trim().slice(0, 128) : "";
const requestText = typeof input.requestText === "string" ? input.requestText.trim().slice(0, 500) : "";
const createdAt = typeof input.createdAt === "string" && input.createdAt ? input.createdAt : (/* @__PURE__ */ new Date()).toISOString();
const unresolved = input.unresolved === true || !phone;
const notifyRefs = Array.isArray(input.notifyRefs) ? Object.freeze(input.notifyRefs.map((ref) => Object.freeze({
adminPhone: validateAccessPhone(ref.adminPhone),
providerMessageId: String(ref.providerMessageId ?? "").trim().slice(0, 128)
})).filter((ref) => ref.providerMessageId)) : Object.freeze([]);
const resolvedByPhone = input.resolvedByPhone ? validateAccessPhone(input.resolvedByPhone) : void 0;
const resolvedAt = typeof input.resolvedAt === "string" ? input.resolvedAt : void 0;
const status = ["pending", "approved", "denied", "expired"].includes(input.status) ? input.status : "pending";
return Object.freeze({
id: id5,
kind,
...groupJid ? { groupJid } : {},
phone: phone || "",
...lid ? { lid } : {},
...pushName ? { pushName } : {},
...requestText ? { requestText } : {},
createdAt,
unresolved,
notifyRefs,
status,
...resolvedByPhone ? { resolvedByPhone } : {},
...resolvedAt ? { resolvedAt } : {}
});
}
function validateContact(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u8054\u7CFB\u4EBA\u6761\u76EE\u65E0\u6548\u3002");
}
const phone = input.phone ? validateAccessPhone(input.phone) : void 0;
const lids = Object.freeze([...Array.isArray(input.lids) ? input.lids : []].map((lid) => String(lid ?? "").trim()).filter((lid) => lid && lid.length <= 128).slice(0, 8));
const pushName = typeof input.pushName === "string" ? input.pushName.trim().slice(0, 128) : "";
const lastSeenAt = typeof input.lastSeenAt === "string" && input.lastSeenAt ? input.lastSeenAt : (/* @__PURE__ */ new Date()).toISOString();
const scenes = Object.freeze([...Array.isArray(input.scenes) ? input.scenes : []].filter((scene) => scene === "direct" || scene === "group").slice(0, 2));
const groupJids = Object.freeze([...Array.isArray(input.groupJids) ? input.groupJids : []].map((jid) => String(jid ?? "").trim()).filter((jid) => ACCESS_GRANT_GROUP_JID_PATTERN.test(jid)).slice(0, 32));
if (!phone && lids.length === 0) invalidGrant("\u8054\u7CFB\u4EBA\u7F3A\u5C11\u7535\u8BDD\u6216 LID\u3002");
return Object.freeze({
...phone ? { phone } : {},
lids,
...pushName ? { pushName } : {},
lastSeenAt,
scenes: scenes.length > 0 ? scenes : Object.freeze(["direct"]),
...groupJids.length > 0 ? { groupJids } : {}
});
}
function validateAccessGrant(input, { requireGlobalAdmin = true } = {}) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u8BBF\u95EE\u6388\u6743\u6587\u6863\u65E0\u6548\u3002");
}
const version = input.version === void 0 ? ACCESS_GRANT_VERSION : Number(input.version);
if (version !== ACCESS_GRANT_VERSION) invalidGrant("\u8BBF\u95EE\u6388\u6743\u7248\u672C\u4E0D\u652F\u6301\u3002");
const globalAdmins = validatePhoneList(input.globalAdmins ?? [], {
label: "\u5168\u5C40\u7BA1\u7406\u5458",
emptyMessage: requireGlobalAdmin ? "\u81F3\u5C11\u4FDD\u7559\u4E00\u4F4D\u5168\u5C40\u7BA1\u7406\u5458\u3002" : void 0
});
if (requireGlobalAdmin && globalAdmins.length === 0) {
invalidGrant("\u81F3\u5C11\u4FDD\u7559\u4E00\u4F4D\u5168\u5C40\u7BA1\u7406\u5458\u3002");
}
const directMembers = Object.freeze((Array.isArray(input.directMembers) ? input.directMembers : []).map(validateMember));
if (new Set(directMembers.map((m) => m.phone)).size !== directMembers.length) {
invalidGrant("\u79C1\u804A\u6388\u6743\u7528\u6237\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002");
}
const groups = /* @__PURE__ */ Object.create(null);
if (input.groups && typeof input.groups === "object" && !Array.isArray(input.groups)) {
for (const [groupJid, grant] of Object.entries(input.groups)) {
groups[validateGroupJid(groupJid)] = validateGroupGrant(grant);
}
}
const pending = Object.freeze((Array.isArray(input.pending) ? input.pending : []).map(validatePending).slice(0, 200));
const contacts = Object.freeze((Array.isArray(input.contacts) ? input.contacts : []).map(validateContact).slice(0, 500));
return Object.freeze({
version: ACCESS_GRANT_VERSION,
globalAdmins,
directMembers,
groups: Object.freeze(groups),
pending,
contacts
});
}
function normalizeAccessGrant(input) {
try {
return validateAccessGrant(input, { requireGlobalAdmin: false });
} catch {
return null;
}
}
var ACCESS_GRANT_COPY = Object.freeze({
pendingAckDirect: "\u5DF2\u63D0\u4EA4\u79C1\u804A\u8BBF\u95EE\u7533\u8BF7\uFF0C\u8BF7\u7B49\u5F85\u5168\u5C40\u7BA1\u7406\u5458\u5BA1\u6279\u3002",
pendingAckGroup: "\u5DF2\u63D0\u4EA4\u672C\u7FA4\u8BBF\u95EE\u7533\u8BF7\uFF0C\u8BF7\u7B49\u5F85\u7BA1\u7406\u5458\u5BA1\u6279\u3002",
pendingUnresolved: "\u5DF2\u8BB0\u5F55\u8BBF\u95EE\u7533\u8BF7\uFF0C\u4F46\u5C1A\u672A\u89E3\u6790\u5230\u7535\u8BDD\u53F7\u7801\uFF1B\u8BF7\u7BA1\u7406\u5458\u5728\u8BBE\u7F6E\u4E2D\u8865\u5168\u540E\u518D\u6279\u51C6\u3002",
notifyTitle: "[dsh-im-ops] \u8BBF\u95EE\u7533\u8BF7",
approvedDirect: "\u79C1\u804A\u8BBF\u95EE\u5DF2\u6279\u51C6\uFF0C\u73B0\u5728\u53EF\u4EE5\u76F4\u63A5\u5BF9\u8BDD\u3002",
approvedGroup: "\u672C\u7FA4\u8BBF\u95EE\u5DF2\u6279\u51C6\uFF0C\u8BF7\u5728\u672C\u7FA4 @ \u673A\u5668\u4EBA\u7EE7\u7EED\u3002",
denied: "\u8BBF\u95EE\u7533\u8BF7\u672A\u901A\u8FC7\u3002",
adminApproved: "\u5DF2\u6279\u51C6\u8BE5\u8BBF\u95EE\u7533\u8BF7\u3002",
adminDenied: "\u5DF2\u62D2\u7EDD\u8BE5\u8BBF\u95EE\u7533\u8BF7\u3002"
});
// src/channels/shared/session-scope.mjs
var GROUP_SESSION_SCOPES = Object.freeze(["chat", "user_in_chat"]);
var DEFAULT_GROUP_SESSION_SCOPE = "user_in_chat";
function normalizeGroupSessionScope(value) {
const scope = String(value ?? "").trim().toLowerCase();
if (scope === "chat" || scope === "shared" || scope === "shared_chat") return "chat";
if (scope === "user_in_chat" || scope === "user" || scope === "per_user" || scope === "member") {
return "user_in_chat";
}
return DEFAULT_GROUP_SESSION_SCOPE;
}
function validateGroupSessionScope(value) {
const scope = String(value ?? "").trim().toLowerCase();
if (scope === "chat" || scope === "user_in_chat") return scope;
const error = new TypeError(`Invalid group session scope: ${String(value)}`);
error.code = "invalid-group-session-scope";
throw error;
}
// plugin-src/client/channels/whatsapp/api.js
var WHATSAPP_RPC_CHANNEL = "/whatsapp";
var WHATSAPP_ENDPOINTS = Object.freeze({
@ -11077,6 +11286,9 @@ var WHATSAPP_ENDPOINTS = Object.freeze({
reconnectBot: "bot.reconnect",
deleteBot: "bot.delete",
setAccessPolicy: "bot.access-policy.set",
setAccessGrant: "bot.access-grant.set",
resolveAccessPending: "bot.access-pending.resolve",
setGroupSessionScope: "bot.group-session-scope.set",
setWorkspace: "bot.workspace.set",
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
setContextEnhancement: "bot.context-enhancement.set"
@ -11147,6 +11359,8 @@ function normalizeBot6(value) {
agentPreset: normalizeAgentPresetId(value.agentPreset),
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
...Object.hasOwn(value, "accessPolicy") ? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) } : {},
...Object.hasOwn(value, "accessGrant") ? { accessGrant: normalizeAccessGrant(value.accessGrant) } : {},
...Object.hasOwn(value, "groupSessionScope") ? { groupSessionScope: normalizeGroupSessionScope(value.groupSessionScope) } : {},
bot: {
name: text5(value.bot?.name, "WhatsApp\u673A\u5668\u4EBA", 100),
idMasked: text5(value.bot?.idMasked, "WhatsApp\u8D26\u53F7", 140)
@ -11840,27 +12054,6 @@ var React22 = __toESM(require("react"), 1);
// plugin-src/client/access-policy-settings.js
var React20 = __toESM(require("react"), 1);
// src/channels/shared/session-scope.mjs
var GROUP_SESSION_SCOPES = Object.freeze(["chat", "user_in_chat"]);
var DEFAULT_GROUP_SESSION_SCOPE = "user_in_chat";
function normalizeGroupSessionScope(value) {
const scope = String(value ?? "").trim().toLowerCase();
if (scope === "chat" || scope === "shared" || scope === "shared_chat") return "chat";
if (scope === "user_in_chat" || scope === "user" || scope === "per_user" || scope === "member") {
return "user_in_chat";
}
return DEFAULT_GROUP_SESSION_SCOPE;
}
function validateGroupSessionScope(value) {
const scope = String(value ?? "").trim().toLowerCase();
if (scope === "chat" || scope === "user_in_chat") return scope;
const error = new TypeError(`Invalid group session scope: ${String(value)}`);
error.code = "invalid-group-session-scope";
throw error;
}
// plugin-src/client/access-policy-settings.js
var ACCESS_POLICY_ENDPOINT = "bot.access-policy.set";
var GROUP_SESSION_SCOPE_ENDPOINT = "bot.group-session-scope.set";
var ACCESS_CHANNEL_DEFINITIONS = Object.freeze({
@ -12323,190 +12516,6 @@ function AccessPolicySettingsPage({ channel: channel4, account, rpcCall, onSaved
// plugin-src/client/access-grant-settings.js
var React21 = __toESM(require("react"), 1);
// src/channels/shared/access-grant.mjs
var ACCESS_GRANT_VERSION = 1;
var ACCESS_PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
var ACCESS_GRANT_PHONE_MAX_LENGTH = 32;
var ACCESS_GRANT_GROUP_JID_PATTERN = /^\d{5,32}@g\.us$/;
var CONTROL_CHARACTERS2 = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
function invalidGrant(message) {
const error = new TypeError(message);
error.code = "access-grant-invalid";
throw error;
}
function normalizeAccessPhone(value) {
if (typeof value === "number" && Number.isFinite(value)) value = String(value);
if (typeof value === "bigint") value = String(value);
if (typeof value !== "string") return null;
let raw = value.trim();
if (!raw || CONTROL_CHARACTERS2.test(raw)) return null;
const at = raw.indexOf("@");
if (at > 0) {
const user = raw.slice(0, at).split(":")[0];
const server = raw.slice(at + 1).toLowerCase();
if (!["s.whatsapp.net", "c.us", "hosted"].includes(server)) return null;
raw = user;
}
const digits = raw.replace(/[^\d]/g, "");
if (digits.length < 5 || digits.length > ACCESS_GRANT_PHONE_MAX_LENGTH) return null;
return digits;
}
function validateAccessPhone(value) {
const phone = normalizeAccessPhone(value);
if (!phone) invalidGrant("\u7535\u8BDD\u53F7\u7801\u65E0\u6548\u3002");
return phone;
}
function validateGroupJid(value) {
if (typeof value !== "string" || !ACCESS_GRANT_GROUP_JID_PATTERN.test(value.trim())) {
invalidGrant("\u7FA4 JID \u65E0\u6548\u3002");
}
return value.trim();
}
function validateMember(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u6210\u5458\u6761\u76EE\u65E0\u6548\u3002");
}
const phone = validateAccessPhone(input.phone ?? input.id);
const canExecuteCommands = input.canExecuteCommands === void 0 ? true : Boolean(input.canExecuteCommands);
return Object.freeze({ phone, canExecuteCommands });
}
function validatePhoneList(input, { emptyMessage, label }) {
if (!Array.isArray(input)) invalidGrant(`${label}\u5FC5\u987B\u662F\u6570\u7EC4\u3002`);
const phones = input.map((entry) => typeof entry === "string" || typeof entry === "number" || typeof entry === "bigint" ? validateAccessPhone(entry) : validateAccessPhone(entry?.phone ?? entry?.id));
if (new Set(phones).size !== phones.length) invalidGrant(`${label}\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002`);
if (phones.length === 0 && emptyMessage) invalidGrant(emptyMessage);
return Object.freeze(phones);
}
function validateGroupGrant(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u7FA4\u6388\u6743\u6761\u76EE\u65E0\u6548\u3002");
}
const admins = validatePhoneList(input.admins ?? [], { label: "\u7FA4\u7BA1\u7406\u5458" });
const members = Object.freeze((Array.isArray(input.members) ? input.members : []).map(validateMember));
if (new Set(members.map((m) => m.phone)).size !== members.length) {
invalidGrant("\u7FA4\u6210\u5458\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002");
}
const title = typeof input.title === "string" ? input.title.trim().slice(0, 128) : "";
return Object.freeze({
...title ? { title } : {},
admins,
members
});
}
function validatePending(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u5F85\u6279\u6761\u76EE\u65E0\u6548\u3002");
}
const id5 = typeof input.id === "string" ? input.id.trim() : "";
if (!/^[A-Za-z0-9_-]{6,64}$/.test(id5)) invalidGrant("\u5F85\u6279\u7F16\u53F7\u65E0\u6548\u3002");
const kind = input.kind === "group" ? "group" : input.kind === "direct" ? "direct" : null;
if (!kind) invalidGrant("\u5F85\u6279\u573A\u666F\u65E0\u6548\u3002");
const phone = input.phone ? validateAccessPhone(input.phone) : "";
const lid = typeof input.lid === "string" ? input.lid.trim().slice(0, 128) : "";
if (!phone && !lid) invalidGrant("\u5F85\u6279\u7F3A\u5C11\u7535\u8BDD\u6216 LID\u3002");
const groupJid = kind === "group" ? validateGroupJid(input.groupJid) : void 0;
const pushName = typeof input.pushName === "string" ? input.pushName.trim().slice(0, 128) : "";
const requestText = typeof input.requestText === "string" ? input.requestText.trim().slice(0, 500) : "";
const createdAt = typeof input.createdAt === "string" && input.createdAt ? input.createdAt : (/* @__PURE__ */ new Date()).toISOString();
const unresolved = input.unresolved === true || !phone;
const notifyRefs = Array.isArray(input.notifyRefs) ? Object.freeze(input.notifyRefs.map((ref) => Object.freeze({
adminPhone: validateAccessPhone(ref.adminPhone),
providerMessageId: String(ref.providerMessageId ?? "").trim().slice(0, 128)
})).filter((ref) => ref.providerMessageId)) : Object.freeze([]);
const resolvedByPhone = input.resolvedByPhone ? validateAccessPhone(input.resolvedByPhone) : void 0;
const resolvedAt = typeof input.resolvedAt === "string" ? input.resolvedAt : void 0;
const status = ["pending", "approved", "denied", "expired"].includes(input.status) ? input.status : "pending";
return Object.freeze({
id: id5,
kind,
...groupJid ? { groupJid } : {},
phone: phone || "",
...lid ? { lid } : {},
...pushName ? { pushName } : {},
...requestText ? { requestText } : {},
createdAt,
unresolved,
notifyRefs,
status,
...resolvedByPhone ? { resolvedByPhone } : {},
...resolvedAt ? { resolvedAt } : {}
});
}
function validateContact(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u8054\u7CFB\u4EBA\u6761\u76EE\u65E0\u6548\u3002");
}
const phone = input.phone ? validateAccessPhone(input.phone) : void 0;
const lids = Object.freeze([...Array.isArray(input.lids) ? input.lids : []].map((lid) => String(lid ?? "").trim()).filter((lid) => lid && lid.length <= 128).slice(0, 8));
const pushName = typeof input.pushName === "string" ? input.pushName.trim().slice(0, 128) : "";
const lastSeenAt = typeof input.lastSeenAt === "string" && input.lastSeenAt ? input.lastSeenAt : (/* @__PURE__ */ new Date()).toISOString();
const scenes = Object.freeze([...Array.isArray(input.scenes) ? input.scenes : []].filter((scene) => scene === "direct" || scene === "group").slice(0, 2));
const groupJids = Object.freeze([...Array.isArray(input.groupJids) ? input.groupJids : []].map((jid) => String(jid ?? "").trim()).filter((jid) => ACCESS_GRANT_GROUP_JID_PATTERN.test(jid)).slice(0, 32));
if (!phone && lids.length === 0) invalidGrant("\u8054\u7CFB\u4EBA\u7F3A\u5C11\u7535\u8BDD\u6216 LID\u3002");
return Object.freeze({
...phone ? { phone } : {},
lids,
...pushName ? { pushName } : {},
lastSeenAt,
scenes: scenes.length > 0 ? scenes : Object.freeze(["direct"]),
...groupJids.length > 0 ? { groupJids } : {}
});
}
function validateAccessGrant(input, { requireGlobalAdmin = true } = {}) {
if (!input || typeof input !== "object" || Array.isArray(input)) {
invalidGrant("\u8BBF\u95EE\u6388\u6743\u6587\u6863\u65E0\u6548\u3002");
}
const version = input.version === void 0 ? ACCESS_GRANT_VERSION : Number(input.version);
if (version !== ACCESS_GRANT_VERSION) invalidGrant("\u8BBF\u95EE\u6388\u6743\u7248\u672C\u4E0D\u652F\u6301\u3002");
const globalAdmins = validatePhoneList(input.globalAdmins ?? [], {
label: "\u5168\u5C40\u7BA1\u7406\u5458",
emptyMessage: requireGlobalAdmin ? "\u81F3\u5C11\u4FDD\u7559\u4E00\u4F4D\u5168\u5C40\u7BA1\u7406\u5458\u3002" : void 0
});
if (requireGlobalAdmin && globalAdmins.length === 0) {
invalidGrant("\u81F3\u5C11\u4FDD\u7559\u4E00\u4F4D\u5168\u5C40\u7BA1\u7406\u5458\u3002");
}
const directMembers = Object.freeze((Array.isArray(input.directMembers) ? input.directMembers : []).map(validateMember));
if (new Set(directMembers.map((m) => m.phone)).size !== directMembers.length) {
invalidGrant("\u79C1\u804A\u6388\u6743\u7528\u6237\u4E0D\u80FD\u5305\u542B\u91CD\u590D\u7535\u8BDD\u3002");
}
const groups = /* @__PURE__ */ Object.create(null);
if (input.groups && typeof input.groups === "object" && !Array.isArray(input.groups)) {
for (const [groupJid, grant] of Object.entries(input.groups)) {
groups[validateGroupJid(groupJid)] = validateGroupGrant(grant);
}
}
const pending = Object.freeze((Array.isArray(input.pending) ? input.pending : []).map(validatePending).slice(0, 200));
const contacts = Object.freeze((Array.isArray(input.contacts) ? input.contacts : []).map(validateContact).slice(0, 500));
return Object.freeze({
version: ACCESS_GRANT_VERSION,
globalAdmins,
directMembers,
groups: Object.freeze(groups),
pending,
contacts
});
}
function normalizeAccessGrant(input) {
try {
return validateAccessGrant(input, { requireGlobalAdmin: false });
} catch {
return null;
}
}
var ACCESS_GRANT_COPY = Object.freeze({
pendingAckDirect: "\u5DF2\u63D0\u4EA4\u79C1\u804A\u8BBF\u95EE\u7533\u8BF7\uFF0C\u8BF7\u7B49\u5F85\u5168\u5C40\u7BA1\u7406\u5458\u5BA1\u6279\u3002",
pendingAckGroup: "\u5DF2\u63D0\u4EA4\u672C\u7FA4\u8BBF\u95EE\u7533\u8BF7\uFF0C\u8BF7\u7B49\u5F85\u7BA1\u7406\u5458\u5BA1\u6279\u3002",
pendingUnresolved: "\u5DF2\u8BB0\u5F55\u8BBF\u95EE\u7533\u8BF7\uFF0C\u4F46\u5C1A\u672A\u89E3\u6790\u5230\u7535\u8BDD\u53F7\u7801\uFF1B\u8BF7\u7BA1\u7406\u5458\u5728\u8BBE\u7F6E\u4E2D\u8865\u5168\u540E\u518D\u6279\u51C6\u3002",
notifyTitle: "[dsh-im-ops] \u8BBF\u95EE\u7533\u8BF7",
approvedDirect: "\u79C1\u804A\u8BBF\u95EE\u5DF2\u6279\u51C6\uFF0C\u73B0\u5728\u53EF\u4EE5\u76F4\u63A5\u5BF9\u8BDD\u3002",
approvedGroup: "\u672C\u7FA4\u8BBF\u95EE\u5DF2\u6279\u51C6\uFF0C\u8BF7\u5728\u672C\u7FA4 @ \u673A\u5668\u4EBA\u7EE7\u7EED\u3002",
denied: "\u8BBF\u95EE\u7533\u8BF7\u672A\u901A\u8FC7\u3002",
adminApproved: "\u5DF2\u6279\u51C6\u8BE5\u8BBF\u95EE\u7533\u8BF7\u3002",
adminDenied: "\u5DF2\u62D2\u7EDD\u8BE5\u8BBF\u95EE\u7533\u8BF7\u3002"
});
// plugin-src/client/access-grant-settings.js
var ACCESS_GRANT_ENDPOINT = "bot.access-grant.set";
var ACCESS_PENDING_RESOLVE_ENDPOINT = "bot.access-pending.resolve";
var GROUP_SESSION_SCOPE_ENDPOINT2 = "bot.group-session-scope.set";
@ -12978,16 +12987,81 @@ function AccessGrantSettingsPage({ channel: channel4, account, rpcCall, onSaved
"fieldset",
{ className: "dim-accessScene", disabled: saving },
h2("legend", null, "\u6700\u8FD1\u8054\u7CFB\u4EBA\uFF08\u81EA\u52A8\u6C89\u6DC0\uFF09"),
contacts.length === 0 ? h2("div", { className: "dim-accessUsersEmpty" }, "\u6682\u65E0\u8054\u7CFB\u4EBA\u3002\u79C1\u804A\u6216\u7FA4\u5185\u89E6\u53D1\u540E\u4F1A\u51FA\u73B0\u5728\u6B64\uFF0C\u4FBF\u4E8E\u8865\u9F50\u7535\u8BDD\u4E0E\u6635\u79F0\u3002") : h2("ul", { className: "dim-accessUserList" }, contacts.slice(0, 30).map((contact) => h2(
"li",
{
key: `${contact.phone ?? ""}-${(contact.lids ?? []).join(",")}`,
className: "dim-accessUserRow"
},
h2("span", null, contactLabel(contact)),
h2("span", null, (contact.scenes ?? []).join("/")),
contact.phone ? null : h2("span", null, "\u5F85\u8865\u7535\u8BDD")
)))
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"
),
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) => {
const phone = contact.phone || "";
const alreadyDirect = phone && draft.directMembers.some((m) => m.phone === phone);
const groupTargets = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid));
return h2(
"li",
{
key: `${contact.phone ?? ""}-${(contact.lids ?? []).join(",")}`,
className: "dim-accessUserRow"
},
h2(
"div",
{ className: "dim-accessField" },
h2("strong", null, contactLabel(contact)),
h2("span", null, (contact.scenes ?? []).join(" / ")),
phone ? null : h2("span", null, "\u5F85\u8865\u7535\u8BDD")
),
phone ? h2("button", {
type: "button",
className: "dim-deliveryButton",
"data-kind": "primary",
disabled: saving || alreadyDirect,
onClick: () => {
setDraft((current) => {
if (current.directMembers.some((m) => m.phone === phone)) return current;
return {
...current,
directMembers: [
...current.directMembers,
{ phone, canExecuteCommands: true }
]
};
});
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,
...groupTargets.map((groupJid) => {
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
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,
onClick: () => {
setDraft((current) => {
const existing = current.groups[groupJid] ?? { title: "", admins: [], members: [] };
if (existing.admins.includes(phone) || existing.members.some((m) => m.phone === phone)) {
return current;
}
return {
...current,
groups: {
...current.groups,
[groupJid]: {
...existing,
members: [
...existing.members,
{ phone, canExecuteCommands: true }
]
}
}
};
});
setFeedback({ tone: "success", message: "\u5DF2\u52A0\u5165\u672C\u7FA4\u6388\u6743\uFF0C\u8BB0\u5F97\u70B9\u4FDD\u5B58\u3002" });
}
}, already ? "\u5DF2\u5728\u672C\u7FA4" : "\u52A0\u672C\u7FA4");
})
);
}))
),
h2(
"fieldset",