Support configured persona webhooks
This commit is contained in:
parent
c7dbd0e42e
commit
d7366ff168
5 changed files with 157 additions and 7 deletions
|
|
@ -610,9 +610,14 @@ function Invoke-CandidateInterventionDeliveryFromIntervention {
|
|||
$previousVoidStatusDir = $env:VOID_STATUS_DIR
|
||||
try {
|
||||
$env:VOID_STATUS_DIR = $statusDir
|
||||
& node @arguments | Out-Null
|
||||
$sendOutput = & node @arguments 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Candidate intervention delivery failed with exit code $LASTEXITCODE."
|
||||
$sendDetail = [string]($sendOutput -join [Environment]::NewLine)
|
||||
if ([string]::IsNullOrWhiteSpace($sendDetail)) {
|
||||
throw "Candidate intervention delivery failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
throw "Candidate intervention delivery failed with exit code $LASTEXITCODE`: $($sendDetail.Trim())"
|
||||
}
|
||||
} finally {
|
||||
$env:VOID_STATUS_DIR = $previousVoidStatusDir
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ async function main() {
|
|||
content,
|
||||
options.replyToMessageId,
|
||||
persona,
|
||||
env,
|
||||
);
|
||||
writeLastSpeechStatus({
|
||||
sentAt: new Date().toISOString(),
|
||||
|
|
@ -239,13 +240,14 @@ async function postChunkedDiscordMessage(
|
|||
content,
|
||||
replyToMessageId,
|
||||
persona,
|
||||
env,
|
||||
) {
|
||||
const chunks = splitDiscordContent(content);
|
||||
|
||||
for (let index = 0; index < chunks.length; index += 1) {
|
||||
const replyTarget = index === 0 ? replyToMessageId : undefined;
|
||||
if (persona) {
|
||||
await postDiscordWebhookMessage(botToken, channelId, chunks[index], replyTarget, persona);
|
||||
await postDiscordWebhookMessage(botToken, channelId, chunks[index], replyTarget, persona, env);
|
||||
} else {
|
||||
await postDiscordMessage(
|
||||
botToken,
|
||||
|
|
@ -289,9 +291,11 @@ async function postDiscordWebhookMessage(
|
|||
content,
|
||||
replyToMessageId,
|
||||
persona,
|
||||
env,
|
||||
) {
|
||||
const target = await resolveWebhookTarget(botToken, channelId);
|
||||
let webhook = await getCachedPersonaWebhook(target.webhookChannelId);
|
||||
const configuredWebhook = await getConfiguredPersonaWebhook(env, target.webhookChannelId);
|
||||
let webhook = configuredWebhook ?? await getCachedPersonaWebhook(target.webhookChannelId);
|
||||
|
||||
if (!webhook) {
|
||||
webhook = await createPersonaWebhook(botToken, target.webhookChannelId);
|
||||
|
|
@ -311,6 +315,12 @@ async function postDiscordWebhookMessage(
|
|||
throw error;
|
||||
}
|
||||
|
||||
if (configuredWebhook) {
|
||||
throw new Error(
|
||||
`Configured persona webhook for channel ${target.webhookChannelId} is no longer executable: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
clearCachedPersonaWebhook(target.webhookChannelId);
|
||||
const refreshedWebhook = await createPersonaWebhook(botToken, target.webhookChannelId);
|
||||
writeCachedPersonaWebhook(target.webhookChannelId, refreshedWebhook);
|
||||
|
|
@ -379,7 +389,11 @@ async function createPersonaWebhook(botToken, channelId) {
|
|||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create Discord webhook: ${response.status} ${await response.text()}`);
|
||||
const detail = await response.text();
|
||||
throw new Error(
|
||||
`Failed to create Discord webhook for channel ${channelId}: ${response.status} ${detail}. ` +
|
||||
`Grant the bot Manage Webhooks there or configure DISCORD_PERSONA_WEBHOOK_URL_${channelId}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
|
|
@ -397,6 +411,60 @@ async function createPersonaWebhook(botToken, channelId) {
|
|||
};
|
||||
}
|
||||
|
||||
async function getConfiguredPersonaWebhook(env, channelId) {
|
||||
const channelSpecificKey = `DISCORD_PERSONA_WEBHOOK_URL_${channelId}`;
|
||||
const rawUrl = trimOptionalString(env[channelSpecificKey]) ?? trimOptionalString(env.DISCORD_PERSONA_WEBHOOK_URL);
|
||||
|
||||
if (!rawUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const webhook = parseDiscordWebhookUrl(rawUrl, channelId);
|
||||
await assertConfiguredWebhookTargetsChannel(webhook, channelId);
|
||||
return webhook;
|
||||
}
|
||||
|
||||
function parseDiscordWebhookUrl(rawUrl, expectedChannelId) {
|
||||
let url;
|
||||
try {
|
||||
url = new URL(rawUrl);
|
||||
} catch {
|
||||
throw new Error(`Configured persona webhook for channel ${expectedChannelId} is not a valid URL.`);
|
||||
}
|
||||
|
||||
const match = url.pathname.match(/\/api(?:\/v\d+)?\/webhooks\/([^/]+)\/([^/?#]+)/) ??
|
||||
url.pathname.match(/\/webhooks\/([^/]+)\/([^/?#]+)/);
|
||||
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`Configured persona webhook for channel ${expectedChannelId} must look like https://discord.com/api/webhooks/<id>/<token>.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: match[1],
|
||||
token: match[2],
|
||||
channelId: expectedChannelId,
|
||||
name: "configured",
|
||||
configured: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function assertConfiguredWebhookTargetsChannel(webhook, expectedChannelId) {
|
||||
const response = await fetch(`https://discord.com/api/v10/webhooks/${webhook.id}/${webhook.token}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Configured persona webhook lookup failed: ${response.status} ${await response.text()}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (payload.channel_id !== expectedChannelId) {
|
||||
throw new Error(
|
||||
`Configured persona webhook targets channel ${payload.channel_id}, not required channel ${expectedChannelId}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function executePersonaWebhook(
|
||||
webhook,
|
||||
{ threadId, content, replyToMessageId, username, avatarUrl },
|
||||
|
|
|
|||
Loading…
Reference in a new issue