| import type { DirectoryConfigParams } from "../channels/plugins/directory-config.js"; |
| import type { ChannelDirectoryEntry } from "../channels/plugins/types.js"; |
| import { resolveSlackAccount } from "./accounts.js"; |
| import { createSlackWebClient } from "./client.js"; |
|
|
| type SlackUser = { |
| id?: string; |
| name?: string; |
| real_name?: string; |
| is_bot?: boolean; |
| is_app_user?: boolean; |
| deleted?: boolean; |
| profile?: { |
| display_name?: string; |
| real_name?: string; |
| email?: string; |
| }; |
| }; |
|
|
| type SlackChannel = { |
| id?: string; |
| name?: string; |
| is_archived?: boolean; |
| is_private?: boolean; |
| }; |
|
|
| type SlackListUsersResponse = { |
| members?: SlackUser[]; |
| response_metadata?: { next_cursor?: string }; |
| }; |
|
|
| type SlackListChannelsResponse = { |
| channels?: SlackChannel[]; |
| response_metadata?: { next_cursor?: string }; |
| }; |
|
|
| function resolveReadToken(params: DirectoryConfigParams): string | undefined { |
| const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); |
| return account.userToken ?? account.botToken?.trim(); |
| } |
|
|
| function normalizeQuery(value?: string | null): string { |
| return value?.trim().toLowerCase() ?? ""; |
| } |
|
|
| function buildUserRank(user: SlackUser): number { |
| let rank = 0; |
| if (!user.deleted) { |
| rank += 2; |
| } |
| if (!user.is_bot && !user.is_app_user) { |
| rank += 1; |
| } |
| return rank; |
| } |
|
|
| function buildChannelRank(channel: SlackChannel): number { |
| return channel.is_archived ? 0 : 1; |
| } |
|
|
| export async function listSlackDirectoryPeersLive( |
| params: DirectoryConfigParams, |
| ): Promise<ChannelDirectoryEntry[]> { |
| const token = resolveReadToken(params); |
| if (!token) { |
| return []; |
| } |
| const client = createSlackWebClient(token); |
| const query = normalizeQuery(params.query); |
| const members: SlackUser[] = []; |
| let cursor: string | undefined; |
|
|
| do { |
| const res = (await client.users.list({ |
| limit: 200, |
| cursor, |
| })) as SlackListUsersResponse; |
| if (Array.isArray(res.members)) { |
| members.push(...res.members); |
| } |
| const next = res.response_metadata?.next_cursor?.trim(); |
| cursor = next ? next : undefined; |
| } while (cursor); |
|
|
| const filtered = members.filter((member) => { |
| const name = member.profile?.display_name || member.profile?.real_name || member.real_name; |
| const handle = member.name; |
| const email = member.profile?.email; |
| const candidates = [name, handle, email] |
| .map((item) => item?.trim().toLowerCase()) |
| .filter(Boolean); |
| if (!query) { |
| return true; |
| } |
| return candidates.some((candidate) => candidate?.includes(query)); |
| }); |
|
|
| const rows = filtered |
| .map((member) => { |
| const id = member.id?.trim(); |
| if (!id) { |
| return null; |
| } |
| const handle = member.name?.trim(); |
| const display = |
| member.profile?.display_name?.trim() || |
| member.profile?.real_name?.trim() || |
| member.real_name?.trim() || |
| handle; |
| return { |
| kind: "user", |
| id: `user:${id}`, |
| name: display || undefined, |
| handle: handle ? `@${handle}` : undefined, |
| rank: buildUserRank(member), |
| raw: member, |
| } satisfies ChannelDirectoryEntry; |
| }) |
| .filter(Boolean) as ChannelDirectoryEntry[]; |
|
|
| if (typeof params.limit === "number" && params.limit > 0) { |
| return rows.slice(0, params.limit); |
| } |
| return rows; |
| } |
|
|
| export async function listSlackDirectoryGroupsLive( |
| params: DirectoryConfigParams, |
| ): Promise<ChannelDirectoryEntry[]> { |
| const token = resolveReadToken(params); |
| if (!token) { |
| return []; |
| } |
| const client = createSlackWebClient(token); |
| const query = normalizeQuery(params.query); |
| const channels: SlackChannel[] = []; |
| let cursor: string | undefined; |
|
|
| do { |
| const res = (await client.conversations.list({ |
| types: "public_channel,private_channel", |
| exclude_archived: false, |
| limit: 1000, |
| cursor, |
| })) as SlackListChannelsResponse; |
| if (Array.isArray(res.channels)) { |
| channels.push(...res.channels); |
| } |
| const next = res.response_metadata?.next_cursor?.trim(); |
| cursor = next ? next : undefined; |
| } while (cursor); |
|
|
| const filtered = channels.filter((channel) => { |
| const name = channel.name?.trim().toLowerCase(); |
| if (!query) { |
| return true; |
| } |
| return Boolean(name && name.includes(query)); |
| }); |
|
|
| const rows = filtered |
| .map((channel) => { |
| const id = channel.id?.trim(); |
| const name = channel.name?.trim(); |
| if (!id || !name) { |
| return null; |
| } |
| return { |
| kind: "group", |
| id: `channel:${id}`, |
| name, |
| handle: `#${name}`, |
| rank: buildChannelRank(channel), |
| raw: channel, |
| } satisfies ChannelDirectoryEntry; |
| }) |
| .filter(Boolean) as ChannelDirectoryEntry[]; |
|
|
| if (typeof params.limit === "number" && params.limit > 0) { |
| return rows.slice(0, params.limit); |
| } |
| return rows; |
| } |
|
|