diff --git a/src/commands/control/rename-user.ts b/src/commands/control/rename-user.ts new file mode 100644 index 0000000..8810e21 --- /dev/null +++ b/src/commands/control/rename-user.ts @@ -0,0 +1,166 @@ +import { ApplyOptions } from '@sapphire/decorators'; +import { + ChatInputCommand, + Command, + CommandOptions, + CommandOptionsRunTypeEnum, +} from '@sapphire/framework'; +import { + ChatInputCommandInteraction, + Guild, + GuildMember, + Message, +} from 'discord.js'; + +import { BotEvents, GuildSettings } from '#base/config/constants'; +import { EmojiCoin } from '#base/config/emojies'; +import { replyWithError } from '#base/lib/sapphire'; +import { BotEventsService } from '#base/services/events.service'; +import { GuildSettingService } from '#base/services/guild-setting.service'; +import { UserService } from '#base/services/user.service'; + +const OPTIONS = { + USER: 'user', + NEW_NAME: 'new_name', +}; + +@ApplyOptions({ + description: 'Переименовать юзера', + runIn: [CommandOptionsRunTypeEnum.GuildText], + name: 'rn', +}) +export class RenameCommand extends Command { + override registerApplicationCommands(registry: ChatInputCommand.Registry) { + registry.registerChatInputCommand((builder) => + builder + .setName(this.name) + .setDescription(this.description) + .addUserOption((option) => + option + .setName(OPTIONS.USER) + .setDescription('Пользователь, которого нужно переименовать') + .setRequired(true), + ) + .addStringOption((option) => + option + .setName(OPTIONS.NEW_NAME) + .setDescription('Новое имя') + .setRequired(true), + ), + ); + } + + public override async messageRun(message: Message) { + if (message.mentions.users.size > 1) { + return message.reply({ + content: 'Можно переименовать только одного пользователя!', + }); + } + + const target = message.mentions.members.at(0) ?? message.member; + + const nickname = message.content + .replace('!rn ', '') + .replaceAll(/<@(.*?)>/g, ''); + + const { text } = await this.renameUser( + message.guild, + message.member, + target, + nickname, + ); + + await message.reply({ content: text }); + } + + public override async chatInputRun( + interaction: ChatInputCommandInteraction, + ): Promise { + const targetUser = interaction.options.getUser(OPTIONS.USER, true); + const targetMember = await interaction.guild.members.fetch(targetUser.id); + + const newName = interaction.options.getString(OPTIONS.NEW_NAME, true); + + const { error, text } = await this.renameUser( + interaction.guild, + interaction.member as GuildMember, + targetMember, + newName, + ); + + if (error) { + return replyWithError(interaction, text); + } + return interaction.reply(text); + } + + private async renameUser( + guild: Guild, + member: GuildMember, + target: GuildMember, + nickname: string, + ): Promise<{ error: boolean; text: string }> { + const activeRoleId = await GuildSettingService.Instance.get( + guild.id, + GuildSettings.RoleActive, + '', + ); + + if (!activeRoleId) { + return { + error: true, + text: `Такой роли нет, которая требуется ... что?`, + }; + } + + if (!member.roles.cache.has(activeRoleId)) { + return { + error: true, + text: `У вас нет роли <@&${activeRoleId}>`, + }; + } + + const prevNickname = target.nickname || target.user.username; + + if (target.id === this.container.client.id) { + const user = await UserService.Instance.get(guild.id, member.id); + if (user.coins < 10_000) { + return { + error: true, + text: `Чтобы переименовать бота, нужно 10 000 ${EmojiCoin.Top}`, + }; + } + user.coins -= 10_000; + + await UserService.Instance.database.persistAndFlush(user); + } + + const message = await BotEventsService.Instance.getRandom( + guild.id, + BotEvents.MEMBER_SET_NAME, + { + user: `**${prevNickname}**`, + nickname: `**${nickname}**`, + }, + ); + + try { + await target.setNickname( + nickname, + message + '. By ' + member.displayName, + ); + } catch (e) { + this.container.logger.error(e); + + return { + error: true, + text: 'Не удается установить ник', + }; + } + + return { + error: false, + text: message, + }; + } +} diff --git a/src/commands/control/rename-voice.ts b/src/commands/control/rename-voice.ts new file mode 100644 index 0000000..676449f --- /dev/null +++ b/src/commands/control/rename-voice.ts @@ -0,0 +1,98 @@ +import { ApplyOptions } from '@sapphire/decorators'; +import { + ChatInputCommand, + Command, + CommandOptions, + CommandOptionsRunTypeEnum, +} from '@sapphire/framework'; +import { ChannelType, ChatInputCommandInteraction } from 'discord.js'; + +import { GuildSettings } from '#base/config/constants'; +import { replyWithError } from '#base/lib/sapphire'; +import { GuildSettingService } from '#base/services/guild-setting.service'; + +const OPTIONS = { + CHANNEL: 'channel', + NEW_NAME: 'new_name', +}; + +@ApplyOptions({ + description: 'Переименовать голосовой канал', + runIn: [CommandOptionsRunTypeEnum.GuildText], + name: 'rnv', +}) +export class RenameVoiceCommand extends Command { + override registerApplicationCommands(registry: ChatInputCommand.Registry) { + registry.registerChatInputCommand( + (builder) => + builder + .setName(this.name) + .setDescription(this.description) + .addChannelOption((option) => + option + .setName(OPTIONS.CHANNEL) + .setDescription('Голосовой канал, который нужно переименовать') + .addChannelTypes(ChannelType.GuildVoice) + .setRequired(true), + ) + .addStringOption((option) => + option + .setName(OPTIONS.NEW_NAME) + .setDescription('Новое имя') + .setRequired(true), + ), + { idHints: ['1077462157941297172'] }, + ); + } + + public override async chatInputRun( + interaction: ChatInputCommandInteraction, + ): Promise { + const activeRoleId = await GuildSettingService.Instance.get( + interaction.guildId, + GuildSettings.RoleActive, + '', + ); + + if (!activeRoleId) { + return replyWithError( + interaction, + `Такой роли нет, которая требуется ... что?`, + ); + } + + const member = await interaction.guild.members.fetch(interaction.user.id); + if (!member.roles.cache.has(activeRoleId)) { + return replyWithError(interaction, `У вас нет роли <@&${activeRoleId}>`); + } + + const channel = interaction.options.getChannel(OPTIONS.CHANNEL, true); + const newName = interaction.options.getString(OPTIONS.NEW_NAME, true); + + if (channel) { + const voiceChannel = await interaction.guild.channels.fetch(channel.id); + const prevChannelName = channel.name; + + if (voiceChannel.type !== ChannelType.GuildVoice) { + return replyWithError( + interaction, + 'Переименовать возможно только голосовой канал', + ); + } + + try { + await voiceChannel.setName(newName, `${channel}`); + } catch (e) { + this.container.logger.error(e); + + return replyWithError(interaction, '', true); + } + + return interaction.reply({ + content: `Голосовой канал **${prevChannelName}** был переименован в **${newName}**`, + }); + } + + return replyWithError(interaction, 'Канал не был указан'); + } +} diff --git a/src/config/constants.ts b/src/config/constants.ts index 079d5e4..7b1f8d0 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -13,6 +13,7 @@ export enum GuildSettings { EventsMessage = 'events_message', StatsMessage = 'stats_message', SystemChannel = 'system_channel', + RoleActive = 'role_active', } export enum BotEvents { @@ -21,4 +22,5 @@ export enum BotEvents { MEMBER_LEAVE = 'member_leave', MEMBER_BAN = 'member_ban', MEMBER_KICK = 'member_kick', + MEMBER_SET_NAME = 'member_set_name', } diff --git a/src/scheduled-tasks/stats.ts b/src/scheduled-tasks/stats.ts index fcc6328..63f4d0b 100644 --- a/src/scheduled-tasks/stats.ts +++ b/src/scheduled-tasks/stats.ts @@ -14,6 +14,7 @@ import { formatTime, getTimeInfo, pickRandom } from '#lib/utils'; @ApplyOptions({ pattern: '0 15 * * *', name: 'post-stats-task', + timezone: 'Europe/Moscow', }) export class StatsTask extends ScheduledTask { get statsService() { @@ -70,8 +71,8 @@ export class StatsTask extends ScheduledTask { const channel = (await guild.channels.fetch(channel_id)) as TextChannel; const newRegs = await this.statsService.getNewRegs(guild.id, period); - newRegs.length = 15; - const embed = this.buildEmbed(stats, newRegs); + + const embed = this.buildEmbed(stats, newRegs.slice(0, 15)); const title = { [StatsPeriod.Day]: 'Ежедневная статистика', @@ -107,6 +108,8 @@ export class StatsTask extends ScheduledTask { await this.statsService.database.nativeDelete(StatsEntity, { period, }); + + await this.statsService.database.flush(); } private buildEmbed(stats: StatsEntity[], newRegs: UserEntity[]) {