Discord: resolve username targets for outbound sends
This commit is contained in:
parent
9daa846457
commit
e402a55003
@ -49,7 +49,7 @@ export async function sendMessageDiscord(
|
|||||||
const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId);
|
const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId);
|
||||||
const textWithTables = convertMarkdownTables(text ?? "", tableMode);
|
const textWithTables = convertMarkdownTables(text ?? "", tableMode);
|
||||||
const { token, rest, request } = createDiscordClient(opts, cfg);
|
const { token, rest, request } = createDiscordClient(opts, cfg);
|
||||||
const recipient = parseRecipient(to);
|
const recipient = await parseRecipient(to, { cfg, accountId: accountInfo.accountId });
|
||||||
const { channelId } = await resolveChannelId(rest, recipient, request);
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
||||||
let result: { id: string; channel_id: string } | { id: string | null; channel_id: string };
|
let result: { id: string; channel_id: string } | { id: string | null; channel_id: string };
|
||||||
try {
|
try {
|
||||||
@ -104,7 +104,7 @@ export async function sendStickerDiscord(
|
|||||||
): Promise<DiscordSendResult> {
|
): Promise<DiscordSendResult> {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const { rest, request } = createDiscordClient(opts, cfg);
|
const { rest, request } = createDiscordClient(opts, cfg);
|
||||||
const recipient = parseRecipient(to);
|
const recipient = await parseRecipient(to, { cfg, accountId: opts.accountId });
|
||||||
const { channelId } = await resolveChannelId(rest, recipient, request);
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
||||||
const content = opts.content?.trim();
|
const content = opts.content?.trim();
|
||||||
const stickers = normalizeStickerIds(stickerIds);
|
const stickers = normalizeStickerIds(stickerIds);
|
||||||
@ -131,7 +131,7 @@ export async function sendPollDiscord(
|
|||||||
): Promise<DiscordSendResult> {
|
): Promise<DiscordSendResult> {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const { rest, request } = createDiscordClient(opts, cfg);
|
const { rest, request } = createDiscordClient(opts, cfg);
|
||||||
const recipient = parseRecipient(to);
|
const recipient = await parseRecipient(to, { cfg, accountId: opts.accountId });
|
||||||
const { channelId } = await resolveChannelId(rest, recipient, request);
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
||||||
const content = opts.content?.trim();
|
const content = opts.content?.trim();
|
||||||
const payload = normalizeDiscordPollInput(poll);
|
const payload = normalizeDiscordPollInput(poll);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { PollLayoutType } from "discord-api-types/payloads/v10";
|
|||||||
import type { RESTAPIPoll } from "discord-api-types/rest/v10";
|
import type { RESTAPIPoll } from "discord-api-types/rest/v10";
|
||||||
import { Routes } from "discord-api-types/v10";
|
import { Routes } from "discord-api-types/v10";
|
||||||
|
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig, type ClawdbotConfig } from "../config/config.js";
|
||||||
import type { RetryConfig } from "../infra/retry.js";
|
import type { RetryConfig } from "../infra/retry.js";
|
||||||
import { createDiscordRetryRunner, type RetryRunner } from "../infra/retry-policy.js";
|
import { createDiscordRetryRunner, type RetryRunner } from "../infra/retry-policy.js";
|
||||||
import { normalizePollDurationHours, normalizePollInput, type PollInput } from "../polls.js";
|
import { normalizePollDurationHours, normalizePollInput, type PollInput } from "../polls.js";
|
||||||
@ -15,6 +15,8 @@ import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.perm
|
|||||||
import { DiscordSendError } from "./send.types.js";
|
import { DiscordSendError } from "./send.types.js";
|
||||||
import { parseDiscordTarget } from "./targets.js";
|
import { parseDiscordTarget } from "./targets.js";
|
||||||
import { normalizeDiscordToken } from "./token.js";
|
import { normalizeDiscordToken } from "./token.js";
|
||||||
|
import { listDiscordDirectoryPeersLive } from "./directory-live.js";
|
||||||
|
import { looksLikeDiscordTargetId } from "../channels/plugins/normalize/discord.js";
|
||||||
|
|
||||||
const DISCORD_TEXT_LIMIT = 2000;
|
const DISCORD_TEXT_LIMIT = 2000;
|
||||||
const DISCORD_MAX_STICKERS = 3;
|
const DISCORD_MAX_STICKERS = 3;
|
||||||
@ -91,14 +93,68 @@ function normalizeReactionEmoji(raw: string) {
|
|||||||
return encodeURIComponent(identifier);
|
return encodeURIComponent(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRecipient(raw: string): DiscordRecipient {
|
async function parseRecipient(
|
||||||
|
raw: string,
|
||||||
|
params: { cfg: ClawdbotConfig; accountId?: string },
|
||||||
|
): Promise<DiscordRecipient> {
|
||||||
const target = parseDiscordTarget(raw, {
|
const target = parseDiscordTarget(raw, {
|
||||||
ambiguousMessage: `Ambiguous Discord recipient "${raw.trim()}". Use "user:${raw.trim()}" for DMs or "channel:${raw.trim()}" for channel messages.`,
|
ambiguousMessage: `Ambiguous Discord recipient "${raw.trim()}". Use "user:${raw.trim()}" for DMs or "channel:${raw.trim()}" for channel messages.`,
|
||||||
});
|
});
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error("Recipient is required for Discord sends");
|
throw new Error("Recipient is required for Discord sends");
|
||||||
}
|
}
|
||||||
return { kind: target.kind, id: target.id };
|
if (target.kind === "user") {
|
||||||
|
return { kind: target.kind, id: target.id };
|
||||||
|
}
|
||||||
|
if (looksLikeDiscordTargetId(raw) || /^\d+$/.test(target.id)) {
|
||||||
|
return { kind: target.kind, id: target.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = raw.trim();
|
||||||
|
if (!query) {
|
||||||
|
throw new Error("Recipient is required for Discord sends");
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = await listDiscordDirectoryPeersLive({
|
||||||
|
cfg: params.cfg,
|
||||||
|
accountId: params.accountId,
|
||||||
|
query,
|
||||||
|
limit: 5,
|
||||||
|
});
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Discord recipient "${raw.trim()}" did not match a user. Use "user:<id>" or "<@id>" for DMs.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
const exactMatches = candidates.filter((entry) => {
|
||||||
|
const name = entry.name?.trim().toLowerCase();
|
||||||
|
const handle = entry.handle?.trim().toLowerCase().replace(/^@/, "");
|
||||||
|
const id = entry.id?.trim().toLowerCase().replace(/^user:/, "");
|
||||||
|
return name === normalizedQuery || handle === normalizedQuery || id === normalizedQuery;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected =
|
||||||
|
exactMatches.length === 1
|
||||||
|
? exactMatches[0]
|
||||||
|
: candidates.length === 1
|
||||||
|
? candidates[0]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
throw new Error(
|
||||||
|
`Multiple Discord users matched "${raw.trim()}". Use "user:<id>" or "<@id>" to disambiguate.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedId = selected.id.replace(/^user:/i, "").trim();
|
||||||
|
if (!resolvedId) {
|
||||||
|
throw new Error(
|
||||||
|
`Discord recipient "${raw.trim()}" did not match a valid user id. Use "user:<id>" or "<@id>".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { kind: "user", id: resolvedId };
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeStickerIds(raw: string[]) {
|
function normalizeStickerIds(raw: string[]) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user