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.",
|
"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.",
|
"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.",
|
"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": {
|
"errors": {
|
||||||
"webhookInvalid": "Das sieht nicht nach einer Slack-Webhook-Adresse aus.",
|
"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.",
|
"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.",
|
"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.",
|
"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": {
|
"errors": {
|
||||||
"webhookInvalid": "That does not look like a Slack webhook address.",
|
"webhookInvalid": "That does not look like a Slack webhook address.",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '~/data/repositories/settings'
|
} from '~/data/repositories/settings'
|
||||||
import { invalid, isUuid, readField, readFlag, type AdminFormState } from './admin-forms'
|
import { invalid, isUuid, readField, readFlag, type AdminFormState } from './admin-forms'
|
||||||
import { runWeeklyReport } from './report-settings'
|
import { runWeeklyReport } from './report-settings'
|
||||||
import { deleteFromSlack, findMessageTs } from './slack'
|
import { deleteFromSlack, findMessageTs, resolveChannelId } from './slack'
|
||||||
import { reportText } from './weekly-report'
|
import { reportText } from './weekly-report'
|
||||||
import type { Viewer } from './auth-access'
|
import type { Viewer } from './auth-access'
|
||||||
|
|
||||||
@@ -132,12 +132,18 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
|||||||
return invalid('runNoChannel')
|
return invalid('runNoChannel')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolved = await resolveChannelId(token, channel)
|
||||||
|
|
||||||
|
if (!resolved.ok) {
|
||||||
|
return invalid('slackSaid', { detail: resolved.detail })
|
||||||
|
}
|
||||||
|
|
||||||
let messageTs = run.messageTs
|
let messageTs = run.messageTs
|
||||||
|
|
||||||
if (!messageTs) {
|
if (!messageTs) {
|
||||||
const lookup = await findMessageTs({
|
const lookup = await findMessageTs({
|
||||||
token,
|
token,
|
||||||
channel,
|
channel: resolved.id,
|
||||||
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
|
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
|
||||||
around: run.createdAt,
|
around: run.createdAt,
|
||||||
})
|
})
|
||||||
@@ -149,7 +155,7 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
|||||||
messageTs = lookup.messageTs
|
messageTs = lookup.messageTs
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await deleteFromSlack(token, channel, messageTs)
|
const result = await deleteFromSlack(token, resolved.id, messageTs)
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
return invalid('slackSaid', { detail: result.detail })
|
return invalid('slackSaid', { detail: result.detail })
|
||||||
@@ -163,7 +169,7 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
|||||||
action: 'settings.reportDelete',
|
action: 'settings.reportDelete',
|
||||||
entity: 'setting',
|
entity: 'setting',
|
||||||
entityId: run.id,
|
entityId: run.id,
|
||||||
data: { channel, messageTs },
|
data: { channel: resolved.id, messageTs },
|
||||||
})
|
})
|
||||||
|
|
||||||
return { status: 'ok', message: 'messageDeleted' }
|
return { status: 'ok', message: 'messageDeleted' }
|
||||||
|
|||||||
@@ -121,3 +121,52 @@ export async function findMessageTs(args: {
|
|||||||
|
|
||||||
return { ok: false, detail: `no_match_in_${messages.length}_messages` }
|
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