feat: add realtime Discord leaderboards
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
export type DiscordVoiceChannel = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 2 | 13;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type DiscordChannelPayload = {
|
||||
id?: unknown;
|
||||
name?: unknown;
|
||||
type?: unknown;
|
||||
position?: unknown;
|
||||
};
|
||||
|
||||
const CHANNEL_CACHE_TTL_MS = 5 * 60_000;
|
||||
const channelCache: {
|
||||
data: DiscordVoiceChannel[] | null;
|
||||
expiresAt: number;
|
||||
} = {
|
||||
data: null,
|
||||
expiresAt: 0,
|
||||
};
|
||||
|
||||
export function parseCurrentVoiceChannels(payload: unknown): DiscordVoiceChannel[] {
|
||||
if (!Array.isArray(payload)) {
|
||||
throw new Error("Discord returned an invalid channel list.");
|
||||
}
|
||||
|
||||
return payload
|
||||
.filter((channel): channel is DiscordChannelPayload => {
|
||||
if (!channel || typeof channel !== "object") return false;
|
||||
const type = (channel as DiscordChannelPayload).type;
|
||||
return type === 2 || type === 13;
|
||||
})
|
||||
.filter((channel) => (
|
||||
typeof channel.id === "string"
|
||||
&& typeof channel.name === "string"
|
||||
))
|
||||
.map((channel) => ({
|
||||
id: channel.id as string,
|
||||
name: channel.name as string,
|
||||
type: channel.type as 2 | 13,
|
||||
position: typeof channel.position === "number" ? channel.position : 0,
|
||||
}))
|
||||
.sort((a, b) => a.position - b.position || a.name.localeCompare(b.name, "th"));
|
||||
}
|
||||
|
||||
export async function getCurrentDiscordVoiceChannels() {
|
||||
if (channelCache.data && Date.now() < channelCache.expiresAt) {
|
||||
return channelCache.data;
|
||||
}
|
||||
|
||||
const token = process.env.DISCORD_BOT_TOKEN;
|
||||
const guildId = process.env.DISCORD_GUILD_ID;
|
||||
if (!token || !guildId) {
|
||||
throw new Error("Discord channel access is not configured.");
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`https://discord.com/api/v10/guilds/${guildId}/channels`,
|
||||
{
|
||||
headers: { Authorization: `Bot ${token}` },
|
||||
cache: "no-store",
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Discord channel request failed with status ${response.status}.`);
|
||||
}
|
||||
|
||||
const channels = parseCurrentVoiceChannels(await response.json());
|
||||
channelCache.data = channels;
|
||||
channelCache.expiresAt = Date.now() + CHANNEL_CACHE_TTL_MS;
|
||||
return channels;
|
||||
}
|
||||
Reference in New Issue
Block a user