mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 18:01:46 +00:00
* refactor: move Discord channel implementation to extensions/discord/src/ Move all Discord source files from src/discord/ to extensions/discord/src/, following the extension migration pattern. Source files in src/discord/ are replaced with re-export shims. Channel-plugin files from src/channels/plugins/*/discord* are similarly moved and shimmed. - Copy all .ts source files preserving subdirectory structure (monitor/, voice/) - Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues) - Fix all relative imports to use correct paths from new location - Create re-export shims at original locations for backward compatibility - Delete test files from shim locations (tests live in extension now) - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate extension files outside src/ - Update write-plugin-sdk-entry-dts.ts to match new declaration output paths * fix: add importOriginal to thread-bindings session-meta mock for extensions test * style: fix formatting in thread-bindings lifecycle test
158 lines
4.9 KiB
TypeScript
158 lines
4.9 KiB
TypeScript
import type { DirectoryConfigParams } from "../../../src/channels/plugins/directory-config.js";
|
|
import {
|
|
buildMessagingTarget,
|
|
parseMentionPrefixOrAtUserTarget,
|
|
requireTargetKind,
|
|
type MessagingTarget,
|
|
type MessagingTargetKind,
|
|
type MessagingTargetParseOptions,
|
|
} from "../../../src/channels/targets.js";
|
|
import { rememberDiscordDirectoryUser } from "./directory-cache.js";
|
|
import { listDiscordDirectoryPeersLive } from "./directory-live.js";
|
|
|
|
export type DiscordTargetKind = MessagingTargetKind;
|
|
|
|
export type DiscordTarget = MessagingTarget;
|
|
|
|
type DiscordTargetParseOptions = MessagingTargetParseOptions;
|
|
|
|
export function parseDiscordTarget(
|
|
raw: string,
|
|
options: DiscordTargetParseOptions = {},
|
|
): DiscordTarget | undefined {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
const userTarget = parseMentionPrefixOrAtUserTarget({
|
|
raw: trimmed,
|
|
mentionPattern: /^<@!?(\d+)>$/,
|
|
prefixes: [
|
|
{ prefix: "user:", kind: "user" },
|
|
{ prefix: "channel:", kind: "channel" },
|
|
{ prefix: "discord:", kind: "user" },
|
|
],
|
|
atUserPattern: /^\d+$/,
|
|
atUserErrorMessage: "Discord DMs require a user id (use user:<id> or a <@id> mention)",
|
|
});
|
|
if (userTarget) {
|
|
return userTarget;
|
|
}
|
|
if (/^\d+$/.test(trimmed)) {
|
|
if (options.defaultKind) {
|
|
return buildMessagingTarget(options.defaultKind, trimmed, trimmed);
|
|
}
|
|
throw new Error(
|
|
options.ambiguousMessage ??
|
|
`Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
|
|
);
|
|
}
|
|
return buildMessagingTarget("channel", trimmed, trimmed);
|
|
}
|
|
|
|
export function resolveDiscordChannelId(raw: string): string {
|
|
const target = parseDiscordTarget(raw, { defaultKind: "channel" });
|
|
return requireTargetKind({ platform: "Discord", target, kind: "channel" });
|
|
}
|
|
|
|
/**
|
|
* Resolve a Discord username to user ID using the directory lookup.
|
|
* This enables sending DMs by username instead of requiring explicit user IDs.
|
|
*
|
|
* @param raw - The username or raw target string (e.g., "john.doe")
|
|
* @param options - Directory configuration params (cfg, accountId, limit)
|
|
* @param parseOptions - Messaging target parsing options (defaults, ambiguity message)
|
|
* @returns Parsed MessagingTarget with user ID, or undefined if not found
|
|
*/
|
|
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;
|
|
|
|
// 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 ?? parseDiscordTarget(trimmed, parseOptions);
|
|
}
|
|
|
|
// Try to resolve as a username via directory lookup
|
|
try {
|
|
const directoryEntries = await listDiscordDirectoryPeersLive({
|
|
...options,
|
|
query: trimmed,
|
|
limit: 1,
|
|
});
|
|
|
|
const match = directoryEntries[0];
|
|
if (match && match.kind === "user") {
|
|
// Extract user ID from the directory entry (format: "user:<id>")
|
|
const userId = match.id.replace(/^user:/, "");
|
|
rememberDiscordDirectoryUser({
|
|
accountId: options.accountId,
|
|
userId,
|
|
handles: [trimmed, match.name, match.handle],
|
|
});
|
|
return buildMessagingTarget("user", userId, trimmed);
|
|
}
|
|
} catch {
|
|
// Directory lookup failed - fall through to parse as-is
|
|
// This preserves existing behavior for channel names
|
|
}
|
|
|
|
// Fallback to original parsing (for channels, etc.)
|
|
return parseDiscordTarget(trimmed, parseOptions);
|
|
}
|
|
|
|
function safeParseDiscordTarget(
|
|
input: string,
|
|
options: DiscordTargetParseOptions,
|
|
): MessagingTarget | undefined {
|
|
try {
|
|
return parseDiscordTarget(input, options);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Check if a string looks like a Discord username (not a mention, prefix, or ID).
|
|
* Usernames typically don't start with special characters except underscore.
|
|
*/
|
|
function isLikelyUsername(input: string): boolean {
|
|
// Skip if it's already a known format
|
|
if (/^(user:|channel:|discord:|@|<@!?)|[\d]+$/.test(input)) {
|
|
return false;
|
|
}
|
|
// Likely a username if it doesn't match known patterns
|
|
return true;
|
|
}
|