mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-09 22:52:52 +00:00
* refactor: use channel target resolution apis * refactor: satisfy delivery lint * refactor: remove unused target parsing shim * fix: preserve routed cron topic targets
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { buildMessagingTarget, type MessagingTarget } from "openclaw/plugin-sdk/channel-targets";
|
|
import type { DirectoryConfigParams } from "openclaw/plugin-sdk/directory-runtime";
|
|
import { resolveDiscordAccount, resolveDiscordAccountAllowFrom } from "./accounts.js";
|
|
import { rememberDiscordDirectoryUser } from "./directory-cache.js";
|
|
import { listDiscordDirectoryPeersLive } from "./directory-live.js";
|
|
import { allowFromContainsDiscordUserId } from "./normalize.js";
|
|
import { parseDiscordSendTarget } from "./send-target-parsing.js";
|
|
import { type DiscordTargetParseOptions } from "./target-parsing.js";
|
|
|
|
/**
|
|
* Resolve a Discord username to user ID using the directory lookup.
|
|
* This enables sending DMs by username instead of requiring explicit user IDs.
|
|
*/
|
|
export async function resolveDiscordTarget(
|
|
raw: string,
|
|
options: DirectoryConfigParams,
|
|
parseOptions: DiscordTargetParseOptions = {},
|
|
): Promise<MessagingTarget | undefined> {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
|
|
const likelyUsername = isLikelyUsername(trimmed);
|
|
const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername;
|
|
|
|
if (
|
|
/^\d+$/.test(trimmed) &&
|
|
parseOptions.defaultKind !== "user" &&
|
|
isConfiguredAllowedDiscordDmUser(trimmed, options)
|
|
) {
|
|
return buildMessagingTarget("user", trimmed, trimmed);
|
|
}
|
|
|
|
// Parse directly if it's already a known format. Use a safe parse so ambiguous
|
|
// numeric targets don't throw when we still want to attempt username lookup.
|
|
const directParse = safeParseDiscordTarget(trimmed, parseOptions);
|
|
if (directParse && directParse.kind !== "channel" && !likelyUsername) {
|
|
return directParse;
|
|
}
|
|
|
|
if (!shouldLookup) {
|
|
return directParse ?? parseDiscordSendTarget(trimmed, parseOptions);
|
|
}
|
|
|
|
try {
|
|
const directoryEntries = await listDiscordDirectoryPeersLive({
|
|
...options,
|
|
query: trimmed,
|
|
limit: 1,
|
|
});
|
|
|
|
const match = directoryEntries[0];
|
|
if (match && match.kind === "user") {
|
|
const userId = match.id.replace(/^user:/, "");
|
|
const resolvedAccountId = resolveDiscordAccount({
|
|
cfg: options.cfg,
|
|
accountId: options.accountId,
|
|
}).accountId;
|
|
rememberDiscordDirectoryUser({
|
|
accountId: resolvedAccountId,
|
|
userId,
|
|
handles: [trimmed, match.name, match.handle],
|
|
});
|
|
return buildMessagingTarget("user", userId, trimmed);
|
|
}
|
|
} catch {
|
|
// Preserve legacy fallback behavior for channel names and direct ids.
|
|
}
|
|
|
|
return parseDiscordSendTarget(trimmed, parseOptions);
|
|
}
|
|
|
|
export async function parseAndResolveDiscordTarget(
|
|
raw: string,
|
|
options: DirectoryConfigParams,
|
|
parseOptions: DiscordTargetParseOptions = {},
|
|
): Promise<MessagingTarget> {
|
|
const resolved =
|
|
(await resolveDiscordTarget(raw, options, parseOptions)) ??
|
|
parseDiscordSendTarget(raw, parseOptions);
|
|
if (!resolved) {
|
|
throw new Error("Recipient is required for Discord sends");
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
function safeParseDiscordTarget(
|
|
input: string,
|
|
options: DiscordTargetParseOptions,
|
|
): MessagingTarget | undefined {
|
|
try {
|
|
return parseDiscordSendTarget(input, options);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function isConfiguredAllowedDiscordDmUser(input: string, options: DirectoryConfigParams): boolean {
|
|
const allowFrom =
|
|
resolveDiscordAccountAllowFrom({
|
|
cfg: options.cfg,
|
|
accountId: options.accountId,
|
|
}) ?? [];
|
|
return allowFromContainsDiscordUserId(allowFrom, input);
|
|
}
|
|
|
|
function isExplicitUserLookup(input: string, options: DiscordTargetParseOptions): boolean {
|
|
if (/^<@!?(\d+)>$/.test(input)) {
|
|
return true;
|
|
}
|
|
if (/^(user:|discord:)/.test(input)) {
|
|
return true;
|
|
}
|
|
if (input.startsWith("@")) {
|
|
return true;
|
|
}
|
|
if (/^\d+$/.test(input)) {
|
|
return options.defaultKind === "user";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isLikelyUsername(input: string): boolean {
|
|
if (/^(user:|channel:|discord:|@|<@!?)|[\d]+$/.test(input)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|