Files
GSM/discord.js
Alexander Zielonka 20ba93b26f Add multi-guild Discord OAuth support
- Users can now login via Bacanaks OR Piccadilly Discord server
- Highest role from all servers is used (superadmin > moderator > user)
- Lazy initialization fixes env loading timing issue
- Updated documentation with implementation details and troubleshooting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 00:30:15 +01:00

168 lines
4.3 KiB
JavaScript

// Discord OAuth2 Service
const DISCORD_API = 'https://discord.com/api/v10';
// Lazy initialization - wird erst bei Verwendung geladen (nach dotenv)
let _guildConfigs = null;
function getGuildConfigs() {
if (_guildConfigs === null) {
_guildConfigs = [
{
name: 'Bacanaks',
guildId: process.env.DISCORD_GUILD_ID_1,
adminRoleId: process.env.DISCORD_ADMIN_ROLE_ID_1,
modRoleId: process.env.DISCORD_MOD_ROLE_ID_1
},
{
name: 'Piccadilly',
guildId: process.env.DISCORD_GUILD_ID_2,
adminRoleId: process.env.DISCORD_ADMIN_ROLE_ID_2,
modRoleId: process.env.DISCORD_MOD_ROLE_ID_2
}
].filter(config => config.guildId);
}
return _guildConfigs;
}
export function getDiscordAuthUrl() {
const params = new URLSearchParams({
client_id: process.env.DISCORD_CLIENT_ID,
redirect_uri: process.env.DISCORD_REDIRECT_URI,
response_type: 'code',
scope: 'identify guilds.members.read'
});
return `https://discord.com/oauth2/authorize?${params}`;
}
export async function exchangeCode(code) {
const response = await fetch(`${DISCORD_API}/oauth2/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: process.env.DISCORD_CLIENT_ID,
client_secret: process.env.DISCORD_CLIENT_SECRET,
grant_type: 'authorization_code',
code,
redirect_uri: process.env.DISCORD_REDIRECT_URI
})
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to exchange code: ${error}`);
}
return response.json();
}
export async function getDiscordUser(accessToken) {
const response = await fetch(`${DISCORD_API}/users/@me`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to get Discord user');
}
return response.json();
}
// Prüft einen einzelnen Server
async function fetchGuildMember(guildId, userId) {
const response = await fetch(
`${DISCORD_API}/guilds/${guildId}/members/${userId}`,
{
headers: {
Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}`
}
}
);
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`Failed to get guild member from ${guildId}`);
}
return response.json();
}
// Prüft alle konfigurierten Server und gibt Memberships zurück
export async function getGuildMemberships(userId) {
const configs = getGuildConfigs();
const memberships = [];
for (const config of configs) {
try {
const member = await fetchGuildMember(config.guildId, userId);
if (member) {
memberships.push({
config,
member,
roles: member.roles || []
});
}
} catch (err) {
console.error(`[Discord] Failed to check membership for guild ${config.name}:`, err.message);
}
}
return memberships.length > 0 ? memberships : null;
}
// Legacy-Funktion für Kompatibilität
export async function getGuildMember(userId) {
const memberships = await getGuildMemberships(userId);
if (!memberships || memberships.length === 0) {
return null;
}
return memberships[0].member;
}
// Rollen-Priorität: superadmin > moderator > user
const ROLE_PRIORITY = { superadmin: 3, moderator: 2, user: 1 };
// Bestimmt die höchste Rolle aus allen Server-Memberships
export function getUserRoleFromMemberships(memberships) {
if (!memberships || memberships.length === 0) {
return 'user';
}
let highestRole = 'user';
for (const { config, roles } of memberships) {
let role = 'user';
if (roles.includes(config.adminRoleId)) {
role = 'superadmin';
} else if (roles.includes(config.modRoleId)) {
role = 'moderator';
}
if (ROLE_PRIORITY[role] > ROLE_PRIORITY[highestRole]) {
highestRole = role;
}
}
return highestRole;
}
// Legacy-Funktion für Kompatibilität
export function getUserRole(memberRoles) {
const adminRoleId = process.env.DISCORD_ADMIN_ROLE_ID || process.env.DISCORD_ADMIN_ROLE_ID_1;
const modRoleId = process.env.DISCORD_MOD_ROLE_ID || process.env.DISCORD_MOD_ROLE_ID_1;
if (memberRoles.includes(adminRoleId)) {
return 'superadmin';
}
if (memberRoles.includes(modRoleId)) {
return 'moderator';
}
return 'user';
}