From 6072ab716f842fdcbea065570903bf2af657f9d3 Mon Sep 17 00:00:00 2001 From: artbiit Date: Tue, 7 Apr 2026 16:34:08 +0900 Subject: [PATCH] refactor: remove invite tracking functionality and migrate member join handling to AutoRoleService --- .../migration.sql | 8 +++++ prisma/schema.prisma | 10 ------- src/client/KordClient.ts | 1 - src/events/guildCreate.ts | 2 -- src/events/guildMemberAdd.ts | 4 +-- src/events/ready.ts | 2 -- src/i18n/locales/en.ts | 23 --------------- src/i18n/locales/ko.ts | 23 --------------- src/services/AuditLogService.ts | 2 +- src/services/AutoRoleService.ts | 29 +++++++++++++++++++ src/services/PermissionAuditService.ts | 19 +----------- 11 files changed, 41 insertions(+), 82 deletions(-) create mode 100644 prisma/migrations/20260407073319_remove_invite_role_model/migration.sql diff --git a/prisma/migrations/20260407073319_remove_invite_role_model/migration.sql b/prisma/migrations/20260407073319_remove_invite_role_model/migration.sql new file mode 100644 index 0000000..d5bad3a --- /dev/null +++ b/prisma/migrations/20260407073319_remove_invite_role_model/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the `InviteRole` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +DROP TABLE "InviteRole"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 67fac0f..09d6b3b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,16 +17,6 @@ model GuildConfig { updatedAt DateTime @updatedAt } -model InviteRole { - id String @id @default(uuid()) - guildId String - inviteCode String - roleId String - createdAt DateTime @default(now()) - - @@unique([guildId, inviteCode]) -} - model UserSubscription { userId String @id tier SubscriptionTier @default(FREE) diff --git a/src/client/KordClient.ts b/src/client/KordClient.ts index 931d9a0..02cf2c3 100644 --- a/src/client/KordClient.ts +++ b/src/client/KordClient.ts @@ -16,7 +16,6 @@ export class KordClient extends Client { GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildInvites, GatewayIntentBits.GuildMembers, ], partials: [Partials.Message, Partials.Channel, Partials.GuildMember], diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index 45af01c..b95a92f 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -1,12 +1,10 @@ import { Events, Guild } from 'discord.js'; -import { InviteService } from '../services/InviteService'; import { PresenceService } from '../services/PresenceService'; export default { name: Events.GuildCreate, once: false, async execute(guild: Guild) { - await InviteService.cacheGuildInvites(guild); PresenceService.updatePresence(guild.client); }, }; diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index dec0bd4..e070c88 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -1,10 +1,10 @@ import { Events, GuildMember } from 'discord.js'; -import { InviteService } from '../services/InviteService'; +import { autoRoleService } from '../services/AutoRoleService'; export default { name: Events.GuildMemberAdd, once: false, async execute(member: GuildMember) { - await InviteService.handleMemberAdd(member); + await autoRoleService.handleMemberJoin(member); }, }; diff --git a/src/events/ready.ts b/src/events/ready.ts index 80d4fcd..5f256bc 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,6 @@ import { Events } from 'discord.js'; import { KordClient } from '../client/KordClient'; import { logger } from '../utils/logger'; -import { InviteService } from '../services/InviteService'; import { VoiceService } from '../services/VoiceService'; import { PresenceService } from '../services/PresenceService'; import { EventService } from '../services/EventService'; @@ -14,7 +13,6 @@ export default { once: true, async execute(client: KordClient) { logger.info(`Ready! Logged in as ${client.user?.tag}`); - await InviteService.cacheAllInvites(client); await VoiceService.syncChannels(client); PresenceService.startActivePresence(client); EventService.startReminderLoop(client); diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index c839c72..e40babd 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -211,26 +211,6 @@ export const en: TranslationSchema = { permissionsError: 'Failed to assign role due to low bot hierarchy or missing permissions.', suspendNotice: 'Auto role assignment has been suspended due to insufficient permissions. Please check the bot\'s permissions and role hierarchy.', }, - invite: { - description: 'Manage roles mapped to invite codes.', - listDescription: 'List all invite codes in the server.', - linkDescription: 'Link a role to an existing invite code.', - createDescription: 'Create a new invite code with a mapped role.', - unlinkDescription: 'Unlink a role from an invite code.', - codeOption: 'Invite code string', - roleOption: 'Role to assign', - usesOption: 'Maximum uses', - ageOption: 'Expiration (seconds, 0=unlimited)', - filterOption: 'Lookup filter (all=all, managed=managed only)', - listTitle: 'Invite Code Mappings', - listEmpty: 'No invite codes are currently linked to roles.', - linkSuccess: 'Role {{role}} has been linked to invite code `{{code}}`.', - unlinkSuccess: 'Role link has been removed from invite code `{{code}}`.', - createSuccess: 'New invite code `{{code}}` has been created and linked to role {{role}}.', - expireWarning: 'Invite code `{{code}}` has expired or been deleted. Role link has been removed.', - identifyFail: 'Could not identify the invite code for the joining user.', - identifyFailDesc: 'Due to simultaneous joins, the invite code for {{user}} could not be determined. Only default roles were assigned.', - }, music: { description: 'Play YouTube audio in voice channels.', addDescription: 'Search YouTube or add a video URL to the queue.', @@ -348,8 +328,6 @@ export const en: TranslationSchema = { VOICE_GLOBAL: 'Voice Channels (Global)', VOICE_GENERATOR_CHANNEL: 'Voice Generator Channel', VOICE_GENERATOR_CATEGORY: 'Voice Generator Category', - INVITE_TRACKING: 'Invite Tracking', - INVITE_ROLE_HIERARCHY: 'Invite Role Assignment (Hierarchy)', MIMIC_WEBHOOK: 'Message Mimic (Webhook)', }, }, @@ -408,7 +386,6 @@ export const en: TranslationSchema = { BOOT: 'Boot', VOICE: 'Voice', PERMISSION: 'Permission', - INVITE: 'Invite', }, }, config: { diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index a11b70a..b3cb269 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -211,26 +211,6 @@ export const ko: TranslationSchema = { permissionsError: '봇의 역할 순위가 낮거나 권한이 부족하여 역할을 부여할 수 없습니다.', suspendNotice: '권한 부족으로 인해 자동 역할 부여 기능이 일시 중지되었습니다. 봇의 권한과 역할 순위를 확인해 주세요.', }, - invite: { - description: '초대 코드와 역할을 연동하여 관리합니다.', - listDescription: '서버의 초대 코드 목록을 조회합니다.', - linkDescription: '기존 초대 코드에 역할을 연동합니다.', - createDescription: '역할이 연동된 새로운 초대 코드를 생성합니다.', - unlinkDescription: '초대 코드에 연동된 역할을 해제합니다.', - codeOption: '초대 코드 문자열', - roleOption: '부여할 역할', - usesOption: '최대 사용 횟수', - ageOption: '만료 기간(초, 0=무제한)', - filterOption: '조회 필터 (all=전체, managed=관리 중)', - listTitle: '초대 코드 매핑 목록', - listEmpty: '연동된 초대 코드가 없습니다.', - linkSuccess: '초대 코드 `{{code}}`에 {{role}} 역할이 연동되었습니다.', - unlinkSuccess: '초대 코드 `{{code}}`의 역할 연동이 해제되었습니다.', - createSuccess: '새로운 초대 코드 `{{code}}`가 생성되었으며 {{role}} 역할이 연동되었습니다.', - expireWarning: '초대 코드 `{{code}}`가 만료/삭제되어 역할 연동이 해제되었습니다.', - identifyFail: '참여한 유저의 초대 코드를 식별하지 못했습니다.', - identifyFailDesc: '동시 접속 등의 이유로 {{user}}님의 초대 코드를 확정할 수 없어 기본 역할만 부여되었습니다.', - }, music: { description: 'Play YouTube audio in voice channels.', addDescription: 'Search YouTube or add a video URL to the queue.', @@ -348,8 +328,6 @@ export const ko: TranslationSchema = { VOICE_GLOBAL: '임시 음성 채널 (전역)', VOICE_GENERATOR_CHANNEL: '음성 생성기 채널', VOICE_GENERATOR_CATEGORY: '음성 생성기 카테고리', - INVITE_TRACKING: '초대 추적', - INVITE_ROLE_HIERARCHY: '초대 역할 부여 (계층 검사)', MIMIC_WEBHOOK: '메시지 흉내 (Webhook)', }, }, @@ -408,7 +386,6 @@ export const ko: TranslationSchema = { BOOT: '부팅', VOICE: '음성', PERMISSION: '권한', - INVITE: '초대', }, }, config: { diff --git a/src/services/AuditLogService.ts b/src/services/AuditLogService.ts index 9352bdd..c18444f 100644 --- a/src/services/AuditLogService.ts +++ b/src/services/AuditLogService.ts @@ -3,7 +3,7 @@ import { prisma } from '../database'; import { env } from '../config/env'; export type AuditSeverity = 'INFO' | 'WARN' | 'ERROR'; -export type AuditCategory = 'SYSTEM' | 'BOOT' | 'VOICE' | 'PERMISSION' | 'INVITE' | 'MIMIC'; +export type AuditCategory = 'SYSTEM' | 'BOOT' | 'VOICE' | 'PERMISSION' | 'MIMIC'; export interface AuditLogPayload { category: AuditCategory; diff --git a/src/services/AutoRoleService.ts b/src/services/AutoRoleService.ts index 101cf29..f942a6b 100644 --- a/src/services/AutoRoleService.ts +++ b/src/services/AutoRoleService.ts @@ -38,6 +38,35 @@ export class AutoRoleService { async setEnabled(guildId: string, enabled: boolean) { return this.updateConfig(guildId, { isEnabled: enabled }); } + + /** + * 신규 멤버가 입장했을 때 자동으로 역할을 부여합니다. + */ + async handleMemberJoin(member: GuildMember) { + const config = await this.getConfig(member.guild.id); + if (!config) return; + + const isBot = member.user.bot; + const isEnabled = isBot ? config.botEnabled : config.isEnabled; + const roleIds = isBot ? config.botRoleIds : config.userRoleIds; + + if (!isEnabled || roleIds.length === 0) return; + + try { + await member.roles.add(roleIds, 'Kord Auto-Role'); + logger.info(`[AutoRole] Added roles to ${member.user.tag} in ${member.guild.name}`); + } catch (error) { + logger.error(`[AutoRole] Failed to add roles to ${member.user.tag} in ${member.guild.name}`, error); + + // 권한 문제인 경우 감사 로그에 기록 + await auditLogService.log(member.guild, { + category: 'PERMISSION', + severity: 'WARN', + title: 'Auto-Role Failure', + description: `Failed to assign roles to ${member.user.toString()} automatically. Please check the bot's permission and role hierarchy.` + }).catch(() => {}); + } + } } export const autoRoleService = new AutoRoleService(); diff --git a/src/services/PermissionAuditService.ts b/src/services/PermissionAuditService.ts index 70aed84..694a798 100644 --- a/src/services/PermissionAuditService.ts +++ b/src/services/PermissionAuditService.ts @@ -109,24 +109,7 @@ const FEATURE_DEFINITIONS: FeatureDefinition[] = [ }, }, - // ── 5. 초대 추적 ── - { - featureKey: 'INVITE_TRACKING', - scope: 'guild', - permissions: [PermissionFlagsBits.ManageGuild], - }, - - // ── 6. 역할 자동 부여 (초대 연동) - 계층 검사 ── - { - featureKey: 'INVITE_ROLE_HIERARCHY', - scope: 'hierarchy', - resolveTargetRoleIds: async (guildId) => { - const inviteRoles = await prisma.inviteRole.findMany({ where: { guildId } }); - return inviteRoles.map((ir: { roleId: string }) => ir.roleId); - }, - }, - - // ── 7. 메시지 흉내 (Mimic) ── + // ── 5. 메시지 흉내 (Mimic) ── { featureKey: 'MIMIC_WEBHOOK', scope: 'guild',