refactor: remove invite tracking functionality and migrate member join handling to AutoRoleService
This commit is contained in:
parent
c4f5e8d53c
commit
6072ab716f
|
|
@ -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";
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue