From bb5028ee5575590db36cf5cef11a593380d6fdab Mon Sep 17 00:00:00 2001 From: Matthias G Date: Mon, 3 Aug 2026 13:03:32 +0200 Subject: [PATCH] Report what Slack actually answers The lookup swallowed Slack's error and the interface guessed at a cause. It now shows the raw reason, widens the search window to six hours and falls back to matching the message prefix. --- messages/de.json | 3 +- messages/en.json | 3 +- src/components/admin/MessageDeleteButton.tsx | 6 +++- src/lib/admin-settings.ts | 22 ++++++++------ src/lib/slack.ts | 30 ++++++++++++++------ 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/messages/de.json b/messages/de.json index 7cc841c..9869d41 100644 --- a/messages/de.json +++ b/messages/de.json @@ -299,7 +299,8 @@ "runNotSent": "Bei diesem Lauf ging keine Nachricht raus.", "runNoChannel": "Es ist kein Kanal hinterlegt.", "runNotFound": "Im Kanal war keine passende Nachricht zu finden. Braucht der Bot noch das Recht channels:history?", - "channelRequired": "Zum Bot-Token gehört ein Kanal, sonst weiß Logbuch nicht, wohin." + "channelRequired": "Zum Bot-Token gehört ein Kanal, sonst weiß Logbuch nicht, wohin.", + "slackSaid": "Slack sagt: {detail}" }, "errors": { "nameRequired": "Trag einen Namen ein.", diff --git a/messages/en.json b/messages/en.json index 563b1df..70e5592 100644 --- a/messages/en.json +++ b/messages/en.json @@ -299,7 +299,8 @@ "runNotSent": "That run sent no message.", "runNoChannel": "No channel is stored.", "runNotFound": "No matching message found in the channel. Does the bot still need channels:history?", - "channelRequired": "A bot token needs a channel, otherwise Logbuch has no target." + "channelRequired": "A bot token needs a channel, otherwise Logbuch has no target.", + "slackSaid": "Slack says: {detail}" }, "errors": { "nameRequired": "Enter a name.", diff --git a/src/components/admin/MessageDeleteButton.tsx b/src/components/admin/MessageDeleteButton.tsx index 2489792..be2e222 100644 --- a/src/components/admin/MessageDeleteButton.tsx +++ b/src/components/admin/MessageDeleteButton.tsx @@ -30,7 +30,11 @@ export function MessageDeleteButton({ runId, hint }: MessageDeleteButtonProps) { {hint ? {hint} : null} {state.status === 'error' && state.message ? ( - {messages(state.message)} + + {state.message === 'slackSaid' + ? messages('slackSaid', { detail: state.values?.detail ?? '' }) + : messages(state.message)} + ) : null} ) diff --git a/src/lib/admin-settings.ts b/src/lib/admin-settings.ts index 9a6acfa..e8c58b5 100644 --- a/src/lib/admin-settings.ts +++ b/src/lib/admin-settings.ts @@ -132,21 +132,27 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData): return invalid('runNoChannel') } - const messageTs = run.messageTs ?? await findMessageTs({ - token, - channel, - text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }), - around: run.createdAt, - }) + let messageTs = run.messageTs if (!messageTs) { - return invalid('runNotFound') + const lookup = await findMessageTs({ + token, + channel, + text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }), + around: run.createdAt, + }) + + if (!lookup.ok) { + return invalid('slackSaid', { detail: lookup.detail }) + } + + messageTs = lookup.messageTs } const result = await deleteFromSlack(token, channel, messageTs) if (!result.ok) { - return invalid('runDeleteFailed') + return invalid('slackSaid', { detail: result.detail }) } await markReportRunDeleted(run.id) diff --git a/src/lib/slack.ts b/src/lib/slack.ts index 6669545..6cb810c 100644 --- a/src/lib/slack.ts +++ b/src/lib/slack.ts @@ -79,14 +79,18 @@ export async function deleteFromSlack(token: string, channel: string, messageTs: return { ok: true, sent: { channel, messageTs } } } +export type LookupResult = + | { ok: true, messageTs: string } + | { ok: false, detail: string } + export async function findMessageTs(args: { token: string channel: string text: string around: Date window?: number -}): Promise { - const window = args.window ?? 30 * 60 * 1000 +}): Promise { + const window = args.window ?? 6 * 60 * 60 * 1000 const oldest = (args.around.getTime() - window) / 1000 const latest = (args.around.getTime() + window) / 1000 @@ -95,15 +99,25 @@ export async function findMessageTs(args: { oldest: String(oldest), latest: String(latest), inclusive: true, - limit: 100, + limit: 200, }) - if (!ok || !Array.isArray(data.messages)) { - return undefined + if (!ok) { + return { ok: false, detail: String(data.error ?? 'unknown_error') } } - const found = (data.messages as { text?: string, ts?: string }[]) - .find(message => typeof message.ts === 'string' && message.text === args.text) + const messages = Array.isArray(data.messages) ? data.messages as { text?: string, ts?: string }[] : [] + const exact = messages.find(message => typeof message.ts === 'string' && message.text === args.text) - return found?.ts + if (exact?.ts) { + return { ok: true, messageTs: exact.ts } + } + + const loose = messages.find(message => typeof message.ts === 'string' && (message.text ?? '').startsWith('Logbuch,')) + + if (loose?.ts) { + return { ok: true, messageTs: loose.ts } + } + + return { ok: false, detail: `no_match_in_${messages.length}_messages` } }