diff --git a/messages/de.json b/messages/de.json
index 0f99089..2a50716 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -295,7 +295,10 @@
"runNotDeletable": "Dieser Lauf ging über den Webhook, dafür gibt es keine Nachrichtenkennung.",
"runNoToken": "Ohne Bot-Token lässt sich in Slack nichts löschen.",
"runDeleteFailed": "Slack hat das Löschen abgelehnt.",
- "tokenInvalid": "Ein Bot-Token beginnt mit xoxb oder xoxp."
+ "tokenInvalid": "Ein Bot-Token beginnt mit xoxb oder xoxp.",
+ "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?"
},
"errors": {
"nameRequired": "Trag einen Namen ein.",
@@ -763,10 +766,11 @@
"messageDelete": "In Slack löschen",
"messageDeleting": "Wird gelöscht",
"messageDeleted": "Gelöscht",
- "messageWebhook": "Über Webhook, nicht löschbar",
+ "messageWebhook": "Über Webhook, Kennung wird gesucht",
"pathBot": "Versand über den Bot-Token. Nachrichten lassen sich hier wieder löschen.",
"pathWebhook": "Versand über den Webhook. Nachrichten lassen sich danach nicht mehr löschen, dafür braucht es einen Bot-Token.",
- "pathNone": "Noch kein Weg hinterlegt, es geht nichts raus."
+ "pathNone": "Noch kein Weg hinterlegt, es geht nichts raus.",
+ "messageNoToken": "Ohne Bot-Token nicht löschbar"
}
},
"filter": {
diff --git a/messages/en.json b/messages/en.json
index b56c918..7d70bbe 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -295,7 +295,10 @@
"runNotDeletable": "That run went through the webhook, so there is no message id.",
"runNoToken": "Without a bot token nothing can be deleted in Slack.",
"runDeleteFailed": "Slack refused the deletion.",
- "tokenInvalid": "A bot token starts with xoxb or xoxp."
+ "tokenInvalid": "A bot token starts with xoxb or xoxp.",
+ "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?"
},
"errors": {
"nameRequired": "Enter a name.",
@@ -763,10 +766,11 @@
"messageDelete": "Delete in Slack",
"messageDeleting": "Deleting",
"messageDeleted": "Deleted",
- "messageWebhook": "Sent by webhook, cannot be deleted",
+ "messageWebhook": "Sent by webhook, id will be looked up",
"pathBot": "Sending through the bot token. Messages can be deleted from here.",
"pathWebhook": "Sending through the webhook. Messages cannot be deleted afterwards, that needs a bot token.",
- "pathNone": "No route stored yet, nothing goes out."
+ "pathNone": "No route stored yet, nothing goes out.",
+ "messageNoToken": "Not deletable without a bot token"
}
},
"filter": {
diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx
index 826bbd1..4d088e5 100644
--- a/src/app/admin/settings/page.tsx
+++ b/src/app/admin/settings/page.tsx
@@ -40,6 +40,7 @@ export default async function AdminSettingsPage() {
const format = await getFormatter()
const config = await readSlackConfig()
const runs = await listReportRuns(historyCount)
+ const canDelete = config.hasBot
return (
@@ -104,9 +105,9 @@ export default async function AdminSettingsPage() {
{run.deletedAt
? t('messageDeleted')
- : run.messageTs
- ?
- : run.sent ? t('messageWebhook') : '-'}
+ : run.sent && canDelete
+ ?
+ : run.sent ? t('messageNoToken') : '-'}
|
))}
diff --git a/src/components/admin/MessageDeleteButton.tsx b/src/components/admin/MessageDeleteButton.tsx
index 56d8622..2489792 100644
--- a/src/components/admin/MessageDeleteButton.tsx
+++ b/src/components/admin/MessageDeleteButton.tsx
@@ -10,9 +10,10 @@ import { emptyAdminFormState } from '~/lib/admin-forms'
export type MessageDeleteButtonProps = {
runId: string
+ hint?: string
}
-export function MessageDeleteButton({ runId }: MessageDeleteButtonProps) {
+export function MessageDeleteButton({ runId, hint }: MessageDeleteButtonProps) {
const t = useTranslations('admin.settings')
const messages = useTranslations('admin.messages')
const [state, formAction] = useActionState(deleteSlackMessage, emptyAdminFormState)
@@ -27,6 +28,7 @@ export function MessageDeleteButton({ runId }: MessageDeleteButtonProps) {
}>
{t('messageDelete')}
+ {hint ?
{hint} : null}
{state.status === 'error' && state.message ? (
{messages(state.message)}
) : null}
diff --git a/src/lib/admin-settings.ts b/src/lib/admin-settings.ts
index aa7dbf1..9ae07fb 100644
--- a/src/lib/admin-settings.ts
+++ b/src/lib/admin-settings.ts
@@ -8,7 +8,8 @@ import {
} from '~/data/repositories/settings'
import { invalid, isUuid, readField, readFlag, type AdminFormState } from './admin-forms'
import { runWeeklyReport } from './report-settings'
-import { deleteFromSlack } from './slack'
+import { deleteFromSlack, findMessageTs } from './slack'
+import { reportText } from './weekly-report'
import type { Viewer } from './auth-access'
function looksLikeWebhook(value: string): boolean {
@@ -108,18 +109,34 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
return invalid('runUnknown')
}
- if (!run.channel || !run.messageTs) {
- return invalid('runNotDeletable')
+ if (!run.sent) {
+ return invalid('runNotSent')
}
- const values = await readSettings(['slack.botToken'])
+ const values = await readSettings(['slack.botToken', 'slack.channel'])
const token = values.get('slack.botToken')?.trim()
+ const channel = run.channel ?? values.get('slack.channel')?.trim()
if (!token) {
return invalid('runNoToken')
}
- const result = await deleteFromSlack(token, run.channel, run.messageTs)
+ if (!channel) {
+ 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,
+ })
+
+ if (!messageTs) {
+ return invalid('runNotFound')
+ }
+
+ const result = await deleteFromSlack(token, channel, messageTs)
if (!result.ok) {
return invalid('runDeleteFailed')
@@ -133,7 +150,7 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
action: 'settings.reportDelete',
entity: 'setting',
entityId: run.id,
- data: { channel: run.channel, messageTs: run.messageTs },
+ data: { channel, messageTs },
})
return { status: 'ok', message: 'messageDeleted' }
diff --git a/src/lib/slack.ts b/src/lib/slack.ts
index 24c4a4a..6669545 100644
--- a/src/lib/slack.ts
+++ b/src/lib/slack.ts
@@ -78,3 +78,32 @@ export async function deleteFromSlack(token: string, channel: string, messageTs:
return { ok: true, sent: { channel, messageTs } }
}
+
+export async function findMessageTs(args: {
+ token: string
+ channel: string
+ text: string
+ around: Date
+ window?: number
+}): Promise
{
+ const window = args.window ?? 30 * 60 * 1000
+ const oldest = (args.around.getTime() - window) / 1000
+ const latest = (args.around.getTime() + window) / 1000
+
+ const { ok, data } = await callApi(args.token, 'conversations.history', {
+ channel: args.channel,
+ oldest: String(oldest),
+ latest: String(latest),
+ inclusive: true,
+ limit: 100,
+ })
+
+ if (!ok || !Array.isArray(data.messages)) {
+ return undefined
+ }
+
+ const found = (data.messages as { text?: string, ts?: string }[])
+ .find(message => typeof message.ts === 'string' && message.text === args.text)
+
+ return found?.ts
+}
diff --git a/src/lib/weekly-report.ts b/src/lib/weekly-report.ts
index f08da33..23e53b8 100644
--- a/src/lib/weekly-report.ts
+++ b/src/lib/weekly-report.ts
@@ -35,6 +35,12 @@ export type SlackMessage = {
const dateFormat = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
+export function reportText(args: { from: Date, to: Date, total: number }): string {
+ const period = `${dateFormat.format(args.from)} bis ${dateFormat.format(args.to)}`
+
+ return `Logbuch, ${period}: ${args.total === 1 ? '1 Eintrag' : `${args.total} Einträge`}`
+}
+
export function windowFor(now: Date, days = reportDays): { from: Date, to: Date } {
const to = new Date(now)
const from = new Date(to.getTime() - days * 24 * 60 * 60 * 1000)
@@ -145,7 +151,7 @@ export function buildMessage(
const period = `${dateFormat.format(report.from)} bis ${dateFormat.format(report.to)}`
const entries = count(report.total, 'Eintrag', 'Einträge')
- const text = `Logbuch, ${period}: ${entries}`
+ const text = reportText(report)
const blocks: unknown[] = [
{ type: 'header', text: { type: 'plain_text', text: 'Logbuch' } },