refactor: remove retroactive autorole functionality and associated database tables

This commit is contained in:
이정수 2026-04-07 15:14:25 +09:00
parent 798d3d589c
commit 579e9a8a61
8 changed files with 13 additions and 183 deletions

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the `AutoRoleExclude` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropTable
DROP TABLE "AutoRoleExclude";
-- DropEnum
DROP TYPE "ExcludeType";

View File

@ -218,20 +218,7 @@ model AutoRoleConfig {
updatedAt DateTime @updatedAt
}
model AutoRoleExclude {
id String @id @default(uuid())
guildId String
targetId String // Role ID or User ID
type ExcludeType
createdAt DateTime @default(now())
@@unique([guildId, targetId, type])
}
enum ExcludeType {
ROLE
USER
}
model RefinementLevelConfig {
level Int @id

View File

@ -66,21 +66,9 @@ export async function generateAutoRoleDashboard(guild: import('discord.js').Guil
const rowBotRole = new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(botSelect);
const rowRetroactive = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('autorole_retroactive')
.setLabel(t(locale, 'commands.autorole.retroactiveBtn'))
.setStyle(ButtonStyle.Secondary)
.setDisabled(!config?.userRoleIds || config.userRoleIds.length === 0),
new ButtonBuilder()
.setCustomId('autorole_exclude')
.setLabel(t(locale, 'commands.autorole.excludeTitle'))
.setStyle(ButtonStyle.Secondary)
);
return {
embeds: [embed],
components: [rowUserRole, rowBotRole, rowRetroactive]
components: [rowUserRole, rowBotRole]
};
}

View File

@ -150,17 +150,7 @@ export default {
// 타임아웃 방지를 위해 즉시 승인
await interaction.deferUpdate();
const guild = interaction.guild!;
if (interaction.customId === 'autorole_retroactive') {
const config = await autoRoleService.getConfig(guild.id);
if (config?.userRoleIds && config.userRoleIds.length > 0) {
await autoRoleService.applyRetroactively(guild, config.userRoleIds, interaction.user.id);
const { generateAutoRoleDashboard } = require('../commands/autorole');
const dashboard = await generateAutoRoleDashboard(guild, locale);
await interaction.editReply({ content: t(locale, 'commands.autorole.retroactiveStarted'), ...dashboard });
}
}
// 나머지 버튼 처리
// 나머지 버튼 처리 (현재 사용 안함)
}, locale);
}
else if (interaction.isRoleSelectMenu() && interaction.customId.startsWith('autorole_select_')) {

View File

@ -208,12 +208,6 @@ export const en: TranslationSchema = {
enabled: 'Enabled',
disabled: 'Disabled',
updateSuccess: 'Auto role settings have been updated.',
retroactiveBtn: 'Apply Retroactively to Entire Server Now',
retroactiveConfirm: 'Do you want to scan all server members and assign roles? This may take time depending on the member count.',
retroactiveStarted: 'Retroactive assignment has started in the background. Check progress in audit logs.',
excludeTitle: 'Retroactive Exclude Settings',
excludeDesc: 'Users with specific IDs or roles will be excluded from retroactive assignment.',
excludeAddBtn: 'Add Exclude Target',
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.',
},

View File

@ -208,12 +208,6 @@ export const ko: TranslationSchema = {
enabled: '활성',
disabled: '비활성',
updateSuccess: '자동 역할 설정이 업데이트되었습니다.',
retroactiveBtn: '지금 서버 전체에 소급 적용',
retroactiveConfirm: '서버의 모든 멤버를 스캔하여 역할을 부여하시겠습니까? 멤버 수에 따라 시간이 걸릴 수 있습니다.',
retroactiveStarted: '백그라운드에서 소급 적용을 시작합니다. 진행 상황은 감사 로그에서 확인하세요.',
excludeTitle: '소급 적용 제외 설정',
excludeDesc: '특정 ID나 역할을 가진 유저는 소급 적용 대상에서 제외됩니다.',
excludeAddBtn: '제외 대상 추가',
permissionsError: '봇의 역할 순위가 낮거나 권한이 부족하여 역할을 부여할 수 없습니다.',
suspendNotice: '권한 부족으로 인해 자동 역할 부여 기능이 일시 중지되었습니다. 봇의 권한과 역할 순위를 확인해 주세요.',
},

View File

@ -162,12 +162,6 @@ export interface TranslationSchema {
enabled: string;
disabled: string;
updateSuccess: string;
retroactiveBtn: string;
retroactiveConfirm: string;
retroactiveStarted: string;
excludeTitle: string;
excludeDesc: string;
excludeAddBtn: string;
permissionsError: string;
suspendNotice: string;
};

View File

@ -38,134 +38,6 @@ export class AutoRoleService {
async setEnabled(guildId: string, enabled: boolean) {
return this.updateConfig(guildId, { isEnabled: enabled });
}
/**
* .
*/
async addExclude(guildId: string, targetId: string, type: 'ROLE' | 'USER') {
return prisma.autoRoleExclude.upsert({
where: {
guildId_targetId_type: {
guildId,
targetId,
type,
},
},
create: {
guildId,
targetId,
type,
},
update: {},
});
}
/**
* .
*/
async removeExclude(guildId: string, targetId: string, type: 'ROLE' | 'USER') {
return prisma.autoRoleExclude.deleteMany({
where: {
guildId,
targetId,
type,
},
});
}
/**
* .
*/
async getExcludes(guildId: string) {
return prisma.autoRoleExclude.findMany({
where: { guildId },
});
}
/**
* .
*/
async isExcluded(member: GuildMember): Promise<boolean> {
const excludes = await this.getExcludes(member.guild.id);
// 유저 ID 체크
if (excludes.some((e: any) => e.type === 'USER' && e.targetId === member.id)) {
return true;
}
// 역할 ID 체크
if (excludes.some((e: any) => e.type === 'ROLE' && member.roles.cache.has(e.targetId))) {
return true;
}
return false;
}
/**
* ( ).
*/
async applyRetroactively(guild: Guild, roleIds: string[], initiatorId: string) {
if (!roleIds || roleIds.length === 0) return;
const roles = roleIds.map(id => guild.roles.cache.get(id)).filter((r): r is import('discord.js').Role => r !== undefined);
if (roles.length === 0) return;
// 봇의 권한 및 순위 확인
const botMember = guild.members.me;
if (!botMember || !botMember.permissions.has(PermissionFlagsBits.ManageRoles)) {
logger.warn(`AutoRole: Cannot apply roles in guild ${guild.id} due to missing ManageRoles permission.`);
return;
}
const validRoles = roles.filter(r => botMember.roles.highest.position > r.position);
if (validRoles.length === 0) {
logger.warn(`AutoRole: Cannot apply roles in guild ${guild.id} due to hierarchy limitations.`);
return;
}
// 모든 멤버 페치 (캐시되지 않았을 수 있음)
const members = await guild.members.fetch();
const targets = members.filter(m => !m.user.bot && validRoles.some(r => !m.roles.cache.has(r.id)));
logger.info(`AutoRole: Starting retroactive application of ${validRoles.length} roles to ${targets.size} members in guild ${guild.id}`);
let successCount = 0;
let failCount = 0;
// 비동기 실행 (응답 대기 안 함)
(async () => {
for (const [, member] of targets) {
try {
if (await this.isExcluded(member)) continue;
await member.roles.add(validRoles);
successCount++;
// Rate Limit 방지를 위한 지연 (1.5초)
await new Promise(resolve => setTimeout(resolve, 1500));
} catch (error: any) {
if (error.code === 10007 || error.code === 10013) {
// Unknown Member / User (이미 나감)
continue;
}
if (error.code === 50013) {
// Missing Permissions (도중 권한 상실)
logger.error(`AutoRole: Permission lost during retroactive assignment in guild ${guild.id}`);
break;
}
failCount++;
logger.error(`AutoRole: Failed to assign role to ${member.user.tag}:`, error);
}
}
await auditLogService.log(guild, {
category: 'SYSTEM',
severity: 'INFO',
title: 'Retroactive Role Assignment Completed',
description: `Retroactive assignment of roles (${validRoles.map(r => `<@&${r.id}>`).join(', ')}) by <@${initiatorId}> has finished.\n- Success: ${successCount}\n- Failed: ${failCount}`,
});
})();
}
}
export const autoRoleService = new AutoRoleService();