// Shared by both /invite rotate and the weekly cron job (inviteScheduler.js) // so manual and automatic rotations log identically. maxAge is set to match // the rotation cadence as defense-in-depth: if the scheduled rotation were // ever to silently stop running, the invite still expires on its own instead // of staying live forever. const guildConfig = require('../model/guildConfig') const inviteLog = require('../model/inviteLog') const createLogger = require('../utils/logger') const log = createLogger('invites') const ROTATION_MAX_AGE_SECONDS = 7 * 24 * 60 * 60 // 7 days async function rotate(client, guildId, { triggeredBy, triggeredByTag } = {}) { const channelId = await guildConfig.getInviteChannelId(guildId) if (!channelId) throw new Error('No invite channel configured — set one with /invite channel first.') const channel = await client.channels.fetch(channelId) if (!channel || !channel.isTextBased()) throw new Error('Configured invite channel is missing or not text-based.') const current = await inviteLog.getCurrent(guildId) if (current) { try { await channel.guild.invites.delete(current.invite_code, 'Invite rotation') } catch (err) { log.warn('failed to revoke previous invite (may already be gone)', { message: err.message }) } await inviteLog.markRevoked(current.id) } const invite = await channel.createInvite({ maxAge: ROTATION_MAX_AGE_SECONDS, unique: true, reason: 'Invite rotation' }) await inviteLog.record({ guildId, channelId, inviteCode: invite.code, triggeredBy, triggeredByTag }) log.info('invite rotated', { code: invite.code, triggeredBy: triggeredByTag || 'automatic (scheduled)' }) return invite } module.exports = { rotate }