Turn a channel name into an id before touching a message
conversations.history and chat.delete only take ids, so a stored name like logbuch answered channel_not_found. The name is resolved through conversations.list first, and the hint asks for the id.
This commit is contained in:
+1
-1
@@ -757,7 +757,7 @@
|
||||
"includeInternal": "Slack ist intern, deshalb standardmäßig an. Aus heißt nur Kunden- und öffentliche Beiträge.",
|
||||
"sendNow": "Schickt den Stand der letzten sieben Tage sofort in den Kanal.",
|
||||
"botToken": "Aus der Slack-App unter OAuth & Permissions, beginnt mit xoxb. Mit Bot-Token lassen sich Nachrichten später wieder löschen.",
|
||||
"channel": "Name oder Kennung des Kanals, etwa logbuch oder C01234567."
|
||||
"channel": "Am besten die Kanal-ID, etwa C01234567. In Slack: Kanal öffnen, auf den Namen klicken, unten steht die Kanal-ID. Ein Name wie logbuch geht auch, dafür braucht der Bot zusätzlich channels:read."
|
||||
},
|
||||
"errors": {
|
||||
"webhookInvalid": "Das sieht nicht nach einer Slack-Webhook-Adresse aus.",
|
||||
|
||||
+1
-1
@@ -757,7 +757,7 @@
|
||||
"includeInternal": "Slack is internal, so this is on by default. Off means customer and public entries only.",
|
||||
"sendNow": "Sends the last seven days into the channel right away.",
|
||||
"botToken": "From the Slack app under OAuth & Permissions, starts with xoxb. A bot token lets messages be deleted later.",
|
||||
"channel": "Channel name or id, for example logbuch or C01234567."
|
||||
"channel": "Prefer the channel id, for example C01234567. In Slack open the channel, click its name, the id sits at the bottom. A name like logbuch works too, but then the bot also needs channels:read."
|
||||
},
|
||||
"errors": {
|
||||
"webhookInvalid": "That does not look like a Slack webhook address.",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '~/data/repositories/settings'
|
||||
import { invalid, isUuid, readField, readFlag, type AdminFormState } from './admin-forms'
|
||||
import { runWeeklyReport } from './report-settings'
|
||||
import { deleteFromSlack, findMessageTs } from './slack'
|
||||
import { deleteFromSlack, findMessageTs, resolveChannelId } from './slack'
|
||||
import { reportText } from './weekly-report'
|
||||
import type { Viewer } from './auth-access'
|
||||
|
||||
@@ -132,12 +132,18 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
||||
return invalid('runNoChannel')
|
||||
}
|
||||
|
||||
const resolved = await resolveChannelId(token, channel)
|
||||
|
||||
if (!resolved.ok) {
|
||||
return invalid('slackSaid', { detail: resolved.detail })
|
||||
}
|
||||
|
||||
let messageTs = run.messageTs
|
||||
|
||||
if (!messageTs) {
|
||||
const lookup = await findMessageTs({
|
||||
token,
|
||||
channel,
|
||||
channel: resolved.id,
|
||||
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
|
||||
around: run.createdAt,
|
||||
})
|
||||
@@ -149,7 +155,7 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
||||
messageTs = lookup.messageTs
|
||||
}
|
||||
|
||||
const result = await deleteFromSlack(token, channel, messageTs)
|
||||
const result = await deleteFromSlack(token, resolved.id, messageTs)
|
||||
|
||||
if (!result.ok) {
|
||||
return invalid('slackSaid', { detail: result.detail })
|
||||
@@ -163,7 +169,7 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
||||
action: 'settings.reportDelete',
|
||||
entity: 'setting',
|
||||
entityId: run.id,
|
||||
data: { channel, messageTs },
|
||||
data: { channel: resolved.id, messageTs },
|
||||
})
|
||||
|
||||
return { status: 'ok', message: 'messageDeleted' }
|
||||
|
||||
@@ -121,3 +121,52 @@ export async function findMessageTs(args: {
|
||||
|
||||
return { ok: false, detail: `no_match_in_${messages.length}_messages` }
|
||||
}
|
||||
|
||||
export type ChannelResult =
|
||||
| { ok: true, id: string }
|
||||
| { ok: false, detail: string }
|
||||
|
||||
const channelIdPattern = /^[CGD][A-Z0-9]{6,}$/u
|
||||
|
||||
export function looksLikeChannelId(value: string): boolean {
|
||||
return channelIdPattern.test(value)
|
||||
}
|
||||
|
||||
export async function resolveChannelId(token: string, channel: string): Promise<ChannelResult> {
|
||||
const wanted = channel.trim().replace(/^#/u, '')
|
||||
|
||||
if (looksLikeChannelId(wanted)) {
|
||||
return { ok: true, id: wanted }
|
||||
}
|
||||
|
||||
let cursor: string | undefined
|
||||
|
||||
for (let page = 0; page < 5; page += 1) {
|
||||
const { ok, data } = await callApi(token, 'conversations.list', {
|
||||
types: 'public_channel,private_channel',
|
||||
exclude_archived: true,
|
||||
limit: 200,
|
||||
...(cursor ? { cursor } : {}),
|
||||
})
|
||||
|
||||
if (!ok) {
|
||||
return { ok: false, detail: String(data.error ?? 'unknown_error') }
|
||||
}
|
||||
|
||||
const channels = Array.isArray(data.channels) ? data.channels as { id?: string, name?: string }[] : []
|
||||
const found = channels.find(entry => entry.name === wanted)
|
||||
|
||||
if (found?.id) {
|
||||
return { ok: true, id: found.id }
|
||||
}
|
||||
|
||||
const meta = data.response_metadata as { next_cursor?: string } | undefined
|
||||
cursor = meta?.next_cursor
|
||||
|
||||
if (!cursor) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: false, detail: `channel_name_not_found:${wanted}` }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user