// Member join handling: record the join event (with best-effort invite // attribution, Phase 6b) then apply the configured auto-role. Requires the // Server Members privileged intent (already enabled per the Phase 1 setup notes) // and, for invite attribution, the GuildInvites intent. const guildConfig = require('../model/guildConfig') const memberEvents = require('../model/memberEvents') const inviteTracker = require('./inviteTracker') const createLogger = require('../utils/logger') const log = createLogger('members') async function handleGuildMemberAdd(member) { // Attribute the invite first (diffs the invite-use cache), then record the join. // Both are best-effort — a failure here must never block the auto-role below. let invite = { code: null, inviterId: null, inviterTag: null } try { invite = await inviteTracker.attribute(member) } catch (err) { log.warn('invite attribution threw', { userId: member.id, message: err.message }) } try { await memberEvents.record({ guildId: member.guild.id, eventType: 'join', discordUserId: member.id, username: member.user?.tag, inviteCode: invite.code, inviterId: invite.inviterId, inviterTag: invite.inviterTag, }) } catch (err) { log.warn('member join record failed', { userId: member.id, message: err.message }) } try { const roleId = await guildConfig.getAutoRoleId(member.guild.id) if (!roleId) return await member.roles.add(roleId) log.info('auto-role assigned', { userId: member.id, roleId }) } catch (err) { log.warn('auto-role assignment failed', { userId: member.id, message: err.message }) } } module.exports = { handleGuildMemberAdd }