mirror of
https://github.com/UiO-Gaming/name-100-uio-gamers.git
synced 2026-11-05 13:56:53 +01:00
Share member list refreshes, fall back to stale cache and retry on Discord rate limits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c37c4d6d5
commit
6a3487e6da
2 files changed
+75
-87
No files matched your search
+35
-35
@@ -1,50 +1,50 @@
|
||||
import type { DiscordGuildMember } from "@/types";
|
||||
|
||||
export async function verifyUserRole(userId: string): Promise<boolean> {
|
||||
const guildId = process.env.DISCORD_GUILD_ID;
|
||||
const requiredRoleId = process.env.DISCORD_REQUIRED_ROLE_ID;
|
||||
const DISCORD_API_URL = "https://discord.com/api/v10";
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const MAX_RETRY_DELAY_SECONDS = 10;
|
||||
|
||||
if (!guildId || !requiredRoleId) {
|
||||
console.error("Missing DISCORD_GUILD_ID or DISCORD_REQUIRED_ROLE_ID environment variables");
|
||||
return false;
|
||||
export function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`Missing ${name} environment variable`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the bot token from the Discord application
|
||||
const botToken = process.env.DISCORD_BOT_TOKEN;
|
||||
if (!botToken) {
|
||||
console.error("Missing DISCORD_BOT_TOKEN environment variable");
|
||||
return false;
|
||||
}
|
||||
export async function discordFetch(path: string): Promise<Response> {
|
||||
const botToken = requireEnv("DISCORD_BOT_TOKEN");
|
||||
|
||||
// Fetch the user's guild member information
|
||||
const response = await fetch(`https://discord.com/api/v10/guilds/${guildId}/members/${userId}`, {
|
||||
headers: {
|
||||
Authorization: `Bot ${botToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
const response = await fetch(`${DISCORD_API_URL}${path}`, {
|
||||
headers: { Authorization: `Bot ${botToken}` },
|
||||
});
|
||||
|
||||
if (response.status !== 429 || attempt === MAX_ATTEMPTS) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const retryAfter = Math.min(Number(response.headers.get("retry-after")) || 1, MAX_RETRY_DELAY_SECONDS);
|
||||
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyUserRole(userId: string): Promise<boolean> {
|
||||
try {
|
||||
const guildId = requireEnv("DISCORD_GUILD_ID");
|
||||
const requiredRoleId = requireEnv("DISCORD_REQUIRED_ROLE_ID");
|
||||
|
||||
const response = await discordFetch(`/guilds/${guildId}/members/${userId}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
}
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
// User is not a member of the guild
|
||||
console.log(`User ${userId} is not a member of guild ${guildId}`);
|
||||
return false;
|
||||
}
|
||||
if (response.status === 403) {
|
||||
console.error("Bot does not have permission to view guild members");
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Discord API error: ${response.status} ${response.statusText}`);
|
||||
throw new Error(`Failed to fetch guild member ${userId}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const member: DiscordGuildMember = await response.json();
|
||||
|
||||
// Check if the user has the required role
|
||||
const hasRole = member.roles.includes(requiredRoleId);
|
||||
console.log(`User ${userId} has role ${requiredRoleId}: ${hasRole}`);
|
||||
|
||||
return hasRole;
|
||||
return member.roles.includes(requiredRoleId);
|
||||
} catch (error) {
|
||||
console.error("Error verifying user role:", error);
|
||||
return false;
|
||||
|
||||
+40
-52
@@ -1,68 +1,56 @@
|
||||
import { discordFetch, requireEnv } from "@/lib/discord";
|
||||
import type { DiscordGuildMember, Match } from "@/types";
|
||||
|
||||
interface CacheEntry {
|
||||
data: DiscordGuildMember[];
|
||||
timestamp: number;
|
||||
}
|
||||
const CACHE_TTL = 5 * 60 * 1000;
|
||||
const PAGE_SIZE = 1000;
|
||||
|
||||
// In-memory cache for Discord API responses
|
||||
let discordMembersCache: CacheEntry | null = null;
|
||||
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
||||
let membersCache: { members: DiscordGuildMember[]; fetchedAt: number } | null = null;
|
||||
let pendingFetch: Promise<DiscordGuildMember[]> | null = null;
|
||||
|
||||
async function getDiscordMembers(): Promise<DiscordGuildMember[]> {
|
||||
const guildId = process.env.DISCORD_GUILD_ID;
|
||||
const botToken = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
if (!guildId || !botToken) {
|
||||
throw new Error("Missing required Discord environment variables");
|
||||
}
|
||||
|
||||
// Check if cache is valid
|
||||
if (discordMembersCache && Date.now() - discordMembersCache.timestamp < CACHE_TTL) {
|
||||
return discordMembersCache.data;
|
||||
}
|
||||
|
||||
// Fetch fresh data from Discord API with pagination
|
||||
const allMembers: DiscordGuildMember[] = [];
|
||||
let after: string | undefined = undefined;
|
||||
const limit = 1000; // Maximum allowed by Discord API
|
||||
|
||||
do {
|
||||
const url = new URL(`https://discord.com/api/v10/guilds/${guildId}/members`);
|
||||
url.searchParams.set("limit", limit.toString());
|
||||
if (after) {
|
||||
url.searchParams.set("after", after);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bot ${botToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
async function fetchAllMembers(): Promise<DiscordGuildMember[]> {
|
||||
const guildId = requireEnv("DISCORD_GUILD_ID");
|
||||
const members: DiscordGuildMember[] = [];
|
||||
let after = "0";
|
||||
|
||||
for (;;) {
|
||||
const response = await discordFetch(`/guilds/${guildId}/members?limit=${PAGE_SIZE}&after=${after}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch guild members: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const members: DiscordGuildMember[] = await response.json();
|
||||
allMembers.push(...members);
|
||||
const page: DiscordGuildMember[] = await response.json();
|
||||
members.push(...page);
|
||||
|
||||
// Update 'after' for next page (use the last member's user ID)
|
||||
if (members.length === limit) {
|
||||
after = members[members.length - 1].user.id;
|
||||
} else {
|
||||
after = undefined; // No more pages
|
||||
if (page.length < PAGE_SIZE) {
|
||||
return members;
|
||||
}
|
||||
} while (after);
|
||||
after = page[page.length - 1].user.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache
|
||||
discordMembersCache = {
|
||||
data: allMembers,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
async function getDiscordMembers(): Promise<DiscordGuildMember[]> {
|
||||
if (membersCache && Date.now() - membersCache.fetchedAt < CACHE_TTL) {
|
||||
return membersCache.members;
|
||||
}
|
||||
|
||||
return allMembers;
|
||||
pendingFetch ??= fetchAllMembers()
|
||||
.then((members) => {
|
||||
membersCache = { members, fetchedAt: Date.now() };
|
||||
return members;
|
||||
})
|
||||
.finally(() => {
|
||||
pendingFetch = null;
|
||||
});
|
||||
|
||||
try {
|
||||
return await pendingFetch;
|
||||
} catch (error) {
|
||||
if (!membersCache) {
|
||||
throw error;
|
||||
}
|
||||
console.error("Failed to refresh guild members, using stale cache:", error);
|
||||
return membersCache.members;
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayName(member: DiscordGuildMember): string {
|
||||
|
||||
Reference in new issue
Block a user