mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-10 09:03:17 +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
|
|
@ -238,6 +238,22 @@ export class WhatsappController {
|
|||
});
|
||||
}
|
||||
|
||||
async refreshAccessGroupTitles(botId, groupJids) {
|
||||
if (this.#closed) throw new Error('WhatsApp controller is closed');
|
||||
return this.#withBotTransition(botId, async () => {
|
||||
if (this.#closed) throw new Error('WhatsApp controller is closed');
|
||||
const runtime = this.#runtimes.get(botId);
|
||||
if (!runtime || typeof runtime.refreshGroupTitles !== 'function') {
|
||||
const error = new Error(t('WhatsApp机器人尚未连接'));
|
||||
error.code = 'bot-not-connected';
|
||||
throw error;
|
||||
}
|
||||
await runtime.refreshGroupTitles(groupJids);
|
||||
this.#touch();
|
||||
return this.status();
|
||||
});
|
||||
}
|
||||
|
||||
async setAccessPolicy(botId, value) {
|
||||
if (this.#closed) throw new Error('WhatsApp controller is closed');
|
||||
const accessPolicy = normalizeWhatsappAccessPolicy(value);
|
||||
|
|
|
|||
|
|
@ -241,18 +241,86 @@ export async function enrichWhatsappInboundIdentities(message, raw, {
|
|||
|
||||
/**
|
||||
* Resolve a WhatsApp group subject for display / access-grant group cards.
|
||||
* @param {{ groupMetadata?: (jid: string) => Promise<{ subject?: string }> }|null|undefined} socket
|
||||
* Tries groupMetadata first, then groupFetchAllParticipating as a linked-device fallback.
|
||||
* @param {object|null|undefined} socket
|
||||
* @param {string} groupJid
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function resolveWhatsappGroupSubject(socket, groupJid) {
|
||||
if (!socket || typeof socket.groupMetadata !== 'function') return '';
|
||||
if (typeof groupJid !== 'string' || !groupJid.endsWith('@g.us')) return '';
|
||||
try {
|
||||
const meta = await socket.groupMetadata(groupJid);
|
||||
const subject = typeof meta?.subject === 'string' ? meta.subject.trim() : '';
|
||||
return subject.slice(0, 128);
|
||||
} catch {
|
||||
if (!socket || typeof groupJid !== 'string' || !groupJid.endsWith('@g.us')) return '';
|
||||
|
||||
const fromMeta = (meta) => {
|
||||
for (const key of ['subject', 'name', 'topic']) {
|
||||
if (typeof meta?.[key] === 'string' && meta[key].trim()) {
|
||||
return meta[key].trim().slice(0, 128);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
if (typeof socket.groupMetadata === 'function') {
|
||||
try {
|
||||
const subject = fromMeta(await socket.groupMetadata(groupJid));
|
||||
if (subject) return subject;
|
||||
} catch {
|
||||
// Fall through to participating catalog.
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof socket.groupFetchAllParticipating === 'function') {
|
||||
try {
|
||||
const catalog = await socket.groupFetchAllParticipating();
|
||||
if (catalog && typeof catalog === 'object') {
|
||||
const direct = fromMeta(catalog[groupJid]);
|
||||
if (direct) return direct;
|
||||
for (const meta of Object.values(catalog)) {
|
||||
if (meta?.id === groupJid || meta?.id === groupJid.replace(/@g\.us$/i, '')) {
|
||||
const subject = fromMeta(meta);
|
||||
if (subject) return subject;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve many group subjects in one participating fetch when possible.
|
||||
* @param {object|null|undefined} socket
|
||||
* @param {string[]} groupJids
|
||||
* @returns {Promise<Record<string, string>>}
|
||||
*/
|
||||
export async function resolveWhatsappGroupSubjects(socket, groupJids) {
|
||||
const jids = [...new Set((Array.isArray(groupJids) ? groupJids : [])
|
||||
.filter((jid) => typeof jid === 'string' && jid.endsWith('@g.us')))];
|
||||
/** @type {Record<string, string>} */
|
||||
const titles = {};
|
||||
if (!socket || jids.length === 0) return titles;
|
||||
|
||||
let catalog = null;
|
||||
if (typeof socket.groupFetchAllParticipating === 'function') {
|
||||
try {
|
||||
catalog = await socket.groupFetchAllParticipating();
|
||||
} catch {
|
||||
catalog = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (const jid of jids) {
|
||||
let title = '';
|
||||
if (catalog && typeof catalog === 'object') {
|
||||
const meta = catalog[jid]
|
||||
?? Object.values(catalog).find((entry) => entry?.id === jid);
|
||||
if (typeof meta?.subject === 'string' && meta.subject.trim()) {
|
||||
title = meta.subject.trim().slice(0, 128);
|
||||
}
|
||||
}
|
||||
if (!title) title = await resolveWhatsappGroupSubject(socket, jid);
|
||||
if (title) titles[jid] = title;
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import {
|
|||
normalizeMessageContent,
|
||||
} from '@whiskeysockets/baileys';
|
||||
|
||||
import { emptyAccessGrant, ensureGroupBucket } from '../shared/access-grant.mjs';
|
||||
import { splitMessageText } from '../shared/editable-message-stream.mjs';
|
||||
import { t } from '../shared/i18n.mjs';
|
||||
import { ImagePromptError } from '../shared/image-prompt.mjs';
|
||||
import { trackOutboundArtifactProviderPromise } from '../shared/semantic/artifact.mjs';
|
||||
import { createWhatsappBridgeStatus, WhatsappHarnessBridge } from './whatsapp-bridge.mjs';
|
||||
import { enrichWhatsappInboundIdentities, resolveWhatsappGroupSubject } from './whatsapp-identity.mjs';
|
||||
import { enrichWhatsappInboundIdentities, resolveWhatsappGroupSubject, resolveWhatsappGroupSubjects } from './whatsapp-identity.mjs';
|
||||
import {
|
||||
gateWhatsappInbound,
|
||||
loadWhatsappAccessGrant,
|
||||
|
|
@ -859,6 +860,47 @@ export class WhatsappRuntime {
|
|||
return { sent: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync WhatsApp group subjects into accessGrant.groups[*].title.
|
||||
* @param {string[]|null|undefined} groupJids
|
||||
*/
|
||||
async refreshGroupTitles(groupJids) {
|
||||
if (!this.#workspaces || !this.#botId) {
|
||||
const error = new Error('Access grant store is unavailable');
|
||||
error.code = 'access-grant-unavailable';
|
||||
throw error;
|
||||
}
|
||||
const socket = this.#session?.socket;
|
||||
if (!this.#status.ready || !socket) {
|
||||
const error = new Error(t('WhatsApp机器人尚未连接'));
|
||||
error.code = 'bot-not-connected';
|
||||
throw error;
|
||||
}
|
||||
let grant = this.#workspaces.accessGrantFor(this.#botId) ?? emptyAccessGrant();
|
||||
const targets = Array.isArray(groupJids) && groupJids.length > 0
|
||||
? groupJids
|
||||
: Object.keys(grant.groups ?? {});
|
||||
const missing = targets.filter((jid) => {
|
||||
const title = grant.groups?.[jid]?.title;
|
||||
return typeof jid === 'string' && jid.endsWith('@g.us')
|
||||
&& !(typeof title === 'string' && title.trim());
|
||||
});
|
||||
// Always allow forced refresh of explicit jids even when title already set.
|
||||
const forced = Array.isArray(groupJids) && groupJids.length > 0
|
||||
? targets.filter((jid) => typeof jid === 'string' && jid.endsWith('@g.us'))
|
||||
: missing;
|
||||
const titles = await resolveWhatsappGroupSubjects(socket, forced);
|
||||
let changed = false;
|
||||
for (const [jid, title] of Object.entries(titles)) {
|
||||
const previousTitle = grant.groups?.[jid]?.title ?? '';
|
||||
if (previousTitle === title) continue;
|
||||
grant = ensureGroupBucket(grant, jid, { title });
|
||||
changed = true;
|
||||
}
|
||||
if (changed) await this.#workspaces.setAccessGrant(this.#botId, grant);
|
||||
return grant;
|
||||
}
|
||||
|
||||
async sendProactiveText(target, text, { signal } = {}) {
|
||||
const jid = typeof target?.route?.jid === 'string' ? target.route.jid.trim() : '';
|
||||
const validUser = target?.kind === 'user'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue