-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(chatInput): add NG word filter functionality
- Loading branch information
Showing
14 changed files
with
380 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { | ||
ApplicationIntegrationType, | ||
AutoModerationActionType, | ||
AutoModerationRuleEventType, | ||
AutoModerationRuleTriggerType, | ||
ChatInputCommandInteraction, | ||
InteractionContextType, | ||
PermissionFlagsBits, | ||
SlashCommandBuilder, | ||
type SlashCommandSubcommandsOnlyBuilder, | ||
} from 'discord.js'; | ||
import { constants } from '../../config/constants.js'; | ||
import { type ChatInputCommand } from '../../core/types/ChatInputCommand.js'; | ||
import { type CommandSetting } from '../../core/types/CommandSetting.js'; | ||
import { GuildSetting } from '../../database/entities/GuildSetting.js'; | ||
import { GuildSettingManager } from '../../utils/GuildSettingManager.js'; | ||
import { userFormat } from '../../utils/userFormat.js'; | ||
type Rule = { | ||
name: string; | ||
value: 'invite-link' | 'token' | 'mention' | 'email'; | ||
}; | ||
export default class NgWord implements ChatInputCommand { | ||
public command: SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder; | ||
public settings: CommandSetting; | ||
get rules(): Rule[] { | ||
return [ | ||
{ name: '招待リンク(おすすめ)', value: 'invite-link' }, | ||
{ name: 'トークン(おすすめ)', value: 'token' }, | ||
{ name: '全員メンション', value: 'mention' }, | ||
{ name: 'メールアドレス', value: 'email' }, | ||
]; | ||
} | ||
getRuleRegex(ruleName: 'invite-link' | 'token' | 'mention' | 'email') { | ||
const data = { | ||
'invite-link': [ | ||
constants.regexs.inviteUrls.dicoall, | ||
constants.regexs.inviteUrls.disboard, | ||
constants.regexs.inviteUrls.discoparty, | ||
constants.regexs.inviteUrls.discord, | ||
constants.regexs.inviteUrls.discordCafe, | ||
constants.regexs.inviteUrls.dissoku, | ||
constants.regexs.inviteUrls.sabach, | ||
], | ||
token: [constants.regexs.discordToken], | ||
mention: [constants.regexs.mention], | ||
email: [constants.regexs.email], | ||
}; | ||
return data[ruleName].map((regex) => `${regex}`); | ||
} | ||
constructor() { | ||
this.command = new SlashCommandBuilder() | ||
.setName('ngword') | ||
.setDescription('NGワード関連コマンド') | ||
.addSubcommand((input) => | ||
input | ||
.setName('add') | ||
.setDescription('AutoModにAquedが提供するルールを追加します') | ||
.addStringOption((input) => | ||
input.setName('rule').setDescription('ルール').addChoices(this.rules).setRequired(true), | ||
), | ||
) | ||
.addSubcommand((input) => | ||
input | ||
.setName('remove') | ||
.setDescription('AutoModからAquedが提供するルールを削除します') | ||
.addStringOption((input) => | ||
input.setName('rule').setDescription('ルール').addChoices(this.rules).setRequired(true), | ||
), | ||
) | ||
.setContexts(InteractionContextType.Guild) | ||
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall) | ||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild); | ||
|
||
this.settings = { enable: true, permissions: [PermissionFlagsBits.ManageGuild] }; | ||
} | ||
isValidValue(value: string): value is Rule['value'] { | ||
return this.rules.some((rule) => rule.value === value); | ||
} | ||
|
||
async run(interaction: ChatInputCommandInteraction) { | ||
const commandName = interaction.options.getSubcommand(); | ||
const ruleName = interaction.options.getString('rule'); | ||
if (!ruleName || !commandName || !interaction.inCachedGuild()) return; | ||
if (!this.isValidValue(ruleName)) { | ||
return await interaction.reply({ content: 'ルールが存在しません。', ephemeral: true }); | ||
} | ||
|
||
const settings = new GuildSettingManager(interaction.guildId); | ||
const setting = (await settings.getSetting()) ?? new GuildSetting(interaction.guildId); | ||
switch (commandName) { | ||
case 'add': { | ||
const rule = await interaction.guild.autoModerationRules.create({ | ||
name: `${ruleName} By Aqued`, | ||
eventType: AutoModerationRuleEventType.MessageSend, | ||
triggerType: AutoModerationRuleTriggerType.Keyword, | ||
triggerMetadata: { | ||
regexPatterns: this.getRuleRegex(ruleName).map((value) => String(value)), | ||
}, | ||
actions: [{ type: AutoModerationActionType.BlockMessage }], | ||
enabled: true, | ||
reason: `${userFormat(interaction.member)}によって作成されました。`, | ||
}); | ||
|
||
const autoMods: string[] = setting.autoMods ?? []; | ||
autoMods.push(rule.id); | ||
await settings.setSetting({ autoMods }); | ||
return await interaction.reply('登録しました!'); | ||
} | ||
|
||
case 'remove': { | ||
if (!setting.autoMods || setting.autoMods.length === 0) { | ||
return await interaction.reply({ content: 'AutoModがAquedによって登録されていません', ephemeral: true }); | ||
} | ||
|
||
let removedCount = 0; | ||
for (const id of setting.autoMods) { | ||
const rule = interaction.guild.autoModerationRules.cache.find( | ||
(value) => value.id === id && value.name === `${ruleName} By Aqued`, | ||
); | ||
|
||
if (rule) { | ||
await rule.delete(`${userFormat(interaction.member)}によって${rule.name}が削除されました`); | ||
const index = setting.autoMods.indexOf(id); | ||
if (index !== -1) { | ||
setting.autoMods.splice(index, 1); | ||
removedCount++; | ||
} | ||
} | ||
} | ||
|
||
if (removedCount > 0) { | ||
await settings.setSetting({ autoMods: setting.autoMods }); | ||
return await interaction.reply(`削除しました (${removedCount} 件のルール)。`); | ||
} else { | ||
return await interaction.reply({ content: '削除するルールが見つかりませんでした。', ephemeral: true }); | ||
} | ||
} | ||
|
||
default: | ||
return; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { ChatInputCommandInteraction, EmbedBuilder, InteractionContextType, SlashCommandBuilder } from 'discord.js'; | ||
import { inspect } from 'util'; | ||
import { Logger } from '../../core/Logger.js'; | ||
import { type ChatInputCommand } from '../../core/types/ChatInputCommand.js'; | ||
import { type CommandSetting } from '../../core/types/CommandSetting.js'; | ||
|
||
export default class Top implements ChatInputCommand { | ||
public command: SlashCommandBuilder; | ||
public settings: CommandSetting; | ||
constructor() { | ||
this.command = new SlashCommandBuilder() | ||
.setName('top') | ||
.setDescription('top!!!') | ||
.setContexts(InteractionContextType.Guild); | ||
this.settings = { enable: true }; | ||
} | ||
async run(interaction: ChatInputCommandInteraction) { | ||
try { | ||
if (!interaction.channel) return; | ||
const messages = await interaction.channel.messages.fetch({ after: '0', limit: 1 }); | ||
const message = messages.first(); | ||
if (message) { | ||
await interaction.reply({ | ||
embeds: [new EmbedBuilder().setDescription(`[**一番上のメッセージへジャンプ!**](${message.url})`)], | ||
}); | ||
} else { | ||
await interaction.reply({ | ||
embeds: [new EmbedBuilder().setAuthor({ name: 'メッセージの取得に失敗' })], | ||
}); | ||
} | ||
} catch (error) { | ||
Logger.error(inspect(error)); | ||
await interaction.reply({ | ||
embeds: [new EmbedBuilder().setAuthor({ name: 'メッセージの取得に失敗' })], | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import 'reflect-metadata'; | ||
import { DataSource } from 'typeorm'; | ||
import { config } from '../config/config.js'; | ||
import { entities } from '../database/entities/index.js'; | ||
|
||
export const dataSource = new DataSource({ | ||
type: 'mysql', | ||
host: config.mysql.host, | ||
username: config.mysql.user, | ||
password: config.mysql.password, | ||
port: config.mysql.port, | ||
database: 'aqued', | ||
synchronize: true, // for "develop" | ||
dropSchema: true, // for "develop" | ||
entities: entities, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; | ||
import { ChatInputCommandInteraction, SlashCommandBuilder, type SlashCommandSubcommandsOnlyBuilder } from 'discord.js'; | ||
import type { CommandSetting } from './CommandSetting.js'; | ||
|
||
export interface ChatInputCommand { | ||
command: SlashCommandBuilder; | ||
command: SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder; | ||
settings: CommandSetting; | ||
run(interaction: ChatInputCommandInteraction): Promise<unknown>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Column, Entity, PrimaryColumn } from 'typeorm'; | ||
|
||
@Entity({ name: 'GUILD_SETTING' }) | ||
export class GuildSetting { | ||
@PrimaryColumn({ name: 'GUILD_ID', type: 'bigint', comment: 'ギルドID' }) | ||
guildId: string; | ||
@Column({ name: 'AUTO_MODS', type: 'simple-array', comment: 'Aquedにより設定されたAutoModのId配列', nullable: true }) | ||
autoMods?: string[]; | ||
|
||
constructor(guildId: string) { | ||
this.guildId = guildId; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { GuildSetting } from './GuildSetting.js'; | ||
|
||
export const entities = [GuildSetting]; |
Oops, something went wrong.