Spaces:
Running
Running
| import type { WebClient } from "@slack/web-api"; | |
| import { createSlackWebClient } from "./client.js"; | |
| export type SlackChannelLookup = { | |
| id: string; | |
| name: string; | |
| archived: boolean; | |
| isPrivate: boolean; | |
| }; | |
| export type SlackChannelResolution = { | |
| input: string; | |
| resolved: boolean; | |
| id?: string; | |
| name?: string; | |
| archived?: boolean; | |
| }; | |
| type SlackListResponse = { | |
| channels?: Array<{ | |
| id?: string; | |
| name?: string; | |
| is_archived?: boolean; | |
| is_private?: boolean; | |
| }>; | |
| response_metadata?: { next_cursor?: string }; | |
| }; | |
| function parseSlackChannelMention(raw: string): { id?: string; name?: string } { | |
| const trimmed = raw.trim(); | |
| if (!trimmed) { | |
| return {}; | |
| } | |
| const mention = trimmed.match(/^<#([A-Z0-9]+)(?:\|([^>]+))?>$/i); | |
| if (mention) { | |
| const id = mention[1]?.toUpperCase(); | |
| const name = mention[2]?.trim(); | |
| return { id, name }; | |
| } | |
| const prefixed = trimmed.replace(/^(slack:|channel:)/i, ""); | |
| if (/^[CG][A-Z0-9]+$/i.test(prefixed)) { | |
| return { id: prefixed.toUpperCase() }; | |
| } | |
| const name = prefixed.replace(/^#/, "").trim(); | |
| return name ? { name } : {}; | |
| } | |
| async function listSlackChannels(client: WebClient): Promise<SlackChannelLookup[]> { | |
| const channels: SlackChannelLookup[] = []; | |
| let cursor: string | undefined; | |
| do { | |
| const res = (await client.conversations.list({ | |
| types: "public_channel,private_channel", | |
| exclude_archived: false, | |
| limit: 1000, | |
| cursor, | |
| })) as SlackListResponse; | |
| for (const channel of res.channels ?? []) { | |
| const id = channel.id?.trim(); | |
| const name = channel.name?.trim(); | |
| if (!id || !name) { | |
| continue; | |
| } | |
| channels.push({ | |
| id, | |
| name, | |
| archived: Boolean(channel.is_archived), | |
| isPrivate: Boolean(channel.is_private), | |
| }); | |
| } | |
| const next = res.response_metadata?.next_cursor?.trim(); | |
| cursor = next ? next : undefined; | |
| } while (cursor); | |
| return channels; | |
| } | |
| function resolveByName( | |
| name: string, | |
| channels: SlackChannelLookup[], | |
| ): SlackChannelLookup | undefined { | |
| const target = name.trim().toLowerCase(); | |
| if (!target) { | |
| return undefined; | |
| } | |
| const matches = channels.filter((channel) => channel.name.toLowerCase() === target); | |
| if (matches.length === 0) { | |
| return undefined; | |
| } | |
| const active = matches.find((channel) => !channel.archived); | |
| return active ?? matches[0]; | |
| } | |
| export async function resolveSlackChannelAllowlist(params: { | |
| token: string; | |
| entries: string[]; | |
| client?: WebClient; | |
| }): Promise<SlackChannelResolution[]> { | |
| const client = params.client ?? createSlackWebClient(params.token); | |
| const channels = await listSlackChannels(client); | |
| const results: SlackChannelResolution[] = []; | |
| for (const input of params.entries) { | |
| const parsed = parseSlackChannelMention(input); | |
| if (parsed.id) { | |
| const match = channels.find((channel) => channel.id === parsed.id); | |
| results.push({ | |
| input, | |
| resolved: true, | |
| id: parsed.id, | |
| name: match?.name ?? parsed.name, | |
| archived: match?.archived, | |
| }); | |
| continue; | |
| } | |
| if (parsed.name) { | |
| const match = resolveByName(parsed.name, channels); | |
| if (match) { | |
| results.push({ | |
| input, | |
| resolved: true, | |
| id: match.id, | |
| name: match.name, | |
| archived: match.archived, | |
| }); | |
| continue; | |
| } | |
| } | |
| results.push({ input, resolved: false }); | |
| } | |
| return results; | |
| } | |