- Updated the guilds API to support searching by both guild name and ID, improving user experience. - Increased the number of guilds retrieved from the database from 100 to 200 for better visibility. - Implemented invite creation functionality, allowing owners to generate invites directly from the guild management interface. - Enhanced the UI to display guild names, icons, and member counts, providing a more informative overview. - Updated localization files to reflect new search capabilities and invite-related messages in both English and German.
93 lines
2.8 KiB
TypeScript
93 lines
2.8 KiB
TypeScript
import type { DiscordChannelOption, DiscordRoleOption } from '@nexumi/shared';
|
|
import { DASHBOARD_CHANNEL_TYPES } from './discord-channel-types';
|
|
import { env } from './env';
|
|
import { redis } from './redis';
|
|
|
|
const DISCORD_API_BASE = 'https://discord.com/api/v10';
|
|
const CACHE_TTL_SECONDS = 60;
|
|
|
|
export async function discordBotFetch<T>(
|
|
path: string,
|
|
init?: { method?: string; body?: unknown }
|
|
): Promise<T> {
|
|
const method = init?.method ?? 'GET';
|
|
const response = await fetch(`${DISCORD_API_BASE}${path}`, {
|
|
method,
|
|
headers: {
|
|
Authorization: `Bot ${env.BOT_TOKEN}`,
|
|
...(init?.body !== undefined ? { 'Content-Type': 'application/json' } : {})
|
|
},
|
|
body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
cache: 'no-store'
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`Discord API request to ${path} failed with status ${response.status}`);
|
|
}
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
return (await response.json()) as T;
|
|
}
|
|
|
|
interface DiscordChannelRest {
|
|
id: string;
|
|
name: string;
|
|
type: number;
|
|
parent_id?: string | null;
|
|
position?: number;
|
|
}
|
|
|
|
interface DiscordRoleRest {
|
|
id: string;
|
|
name: string;
|
|
color: number;
|
|
position: number;
|
|
managed?: boolean;
|
|
}
|
|
|
|
export async function listGuildChannelsForDashboard(guildId: string): Promise<DiscordChannelOption[]> {
|
|
const cacheKey = `dashboard:guild:${guildId}:channels:v2`;
|
|
const cached = await redis.get(cacheKey);
|
|
if (cached) {
|
|
return JSON.parse(cached) as DiscordChannelOption[];
|
|
}
|
|
|
|
const channels = await discordBotFetch<DiscordChannelRest[]>(`/guilds/${guildId}/channels`);
|
|
const options = channels
|
|
.filter((channel) => DASHBOARD_CHANNEL_TYPES.has(channel.type))
|
|
.map((channel) => ({
|
|
id: channel.id,
|
|
name: channel.name,
|
|
type: channel.type,
|
|
parentId: channel.parent_id ?? null
|
|
}))
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
await redis.set(cacheKey, JSON.stringify(options), 'EX', CACHE_TTL_SECONDS);
|
|
return options;
|
|
}
|
|
|
|
export async function listGuildRolesForDashboard(guildId: string): Promise<DiscordRoleOption[]> {
|
|
const cacheKey = `dashboard:guild:${guildId}:roles`;
|
|
const cached = await redis.get(cacheKey);
|
|
if (cached) {
|
|
return JSON.parse(cached) as DiscordRoleOption[];
|
|
}
|
|
|
|
const roles = await discordBotFetch<DiscordRoleRest[]>(`/guilds/${guildId}/roles`);
|
|
const options = roles
|
|
.filter((role) => role.name !== '@everyone')
|
|
.map((role) => ({
|
|
id: role.id,
|
|
name: role.name,
|
|
color: role.color,
|
|
position: role.position
|
|
}))
|
|
.sort((a, b) => b.position - a.position);
|
|
|
|
await redis.set(cacheKey, JSON.stringify(options), 'EX', CACHE_TTL_SECONDS);
|
|
return options;
|
|
}
|
|
|
|
export { CHANNEL_TYPES, channelPrefix, type ChannelKind } from './discord-channel-types';
|