WIP: [#1]: Athena Rewrite #1
@@ -1,67 +1,11 @@
|
|||||||
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, Guild, GuildMember, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder, time, TimestampStyles, User } from 'discord.js';
|
import { ChatInputCommandInteraction, Client, Guild, GuildMember, time, TimestampStyles, User } from 'discord.js';
|
||||||
import { Database } from '../shared/Database';
|
import { Database } from '../../shared/Database';
|
||||||
import { AltData, comprehensiveAltLookupFromDiscord, deferInteraction, getIdFromInput } from '../utils';
|
import { AltData, comprehensiveAltLookupFromDiscord, deferInteraction, getIdFromInput } from '../../utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ban',
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('ban')
|
|
||||||
.setDescription('Bans a user.')
|
|
||||||
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
|
|
||||||
.setContexts(InteractionContextType.Guild)
|
|
||||||
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('user')
|
|
||||||
.setDescription('The discord user mention, or ID, to ban.')
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption(option =>
|
|
||||||
option
|
|
||||||
.setName('reason')
|
|
||||||
.setDescription('The reason for the ban')
|
|
||||||
.setRequired(false)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('hours')
|
|
||||||
.setDescription('The duration of the ban, added with other options (0 for permanent).')
|
|
||||||
.setRequired(false)
|
|
||||||
)
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('minutes')
|
|
||||||
.setDescription('The duration of the ban, added with other options (0 for permanent).')
|
|
||||||
.setRequired(false)
|
|
||||||
)
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('seconds')
|
|
||||||
.setDescription('The duration of the ban, added with other options (0 for permanent).')
|
|
||||||
.setRequired(false)
|
|
||||||
)
|
|
||||||
.addNumberOption(option =>
|
|
||||||
option
|
|
||||||
.setName('delete-message-days')
|
|
||||||
.setDescription('How far back to delete messages (in days, default: 0 days).')
|
|
||||||
.setRequired(false)
|
|
||||||
.setMinValue(0)
|
|
||||||
.setMaxValue(7)
|
|
||||||
)
|
|
||||||
.addBooleanOption(option =>
|
|
||||||
option
|
|
||||||
.setName('full-ban')
|
|
||||||
.setDescription('Whether or not to prevent the user from joining on known alts (and ban all existing alts).')
|
|
||||||
.setRequired(false)
|
|
||||||
),
|
|
||||||
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
|
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
|
||||||
await deferInteraction(interaction);
|
await deferInteraction(interaction);
|
||||||
|
|
||||||
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
|
|
||||||
|
|
||||||
if (!interaction.guild.members.me) return interaction.editReply('An error has occurred. Please try again later.');
|
|
||||||
|
|
||||||
const input = interaction.options.getString('user', true);
|
const input = interaction.options.getString('user', true);
|
||||||
|
|
||||||
const idToUse = getIdFromInput(input);
|
const idToUse = getIdFromInput(input);
|
||||||
@@ -134,3 +78,38 @@ async function removeAllAlts(altData: AltData[], guild: Guild, moderator: User,
|
|||||||
await removeAllAlts(data.alts, guild, moderator, fullBan, reason, deleteMessageDays, duration, expiresAt);
|
await removeAllAlts(data.alts, guild, moderator, fullBan, reason, deleteMessageDays, duration, expiresAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// External Imports
|
||||||
|
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
|
||||||
|
@SlashCommand(
|
||||||
|
new CommandBuilder('ban', 'Ban a user.')
|
||||||
|
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||||
|
.setContexts(Constants.InteractionContextType.Guild)
|
||||||
|
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||||
|
.setMemberPermission(Constants.PermissionFlagsBits.BanMembers)
|
||||||
|
.addUserOption('user', 'The user to ban.', true)
|
||||||
|
.addStringOption({ name: 'duration', description: 'The ban duration.', required: false })
|
||||||
|
.addStringOption({ name: 'reason', description: 'The ban reason.', required: false })
|
||||||
|
.addNumberOption({ name: 'cleanup', description: 'Number of days worth of messages to delete.', required: false })
|
||||||
|
)
|
||||||
|
class BanCommand extends Command<CommandClient> {
|
||||||
|
cooldown = 5;
|
||||||
|
userPermissions = [Constants.PermissionFlagsBits.BanMembers];
|
||||||
|
|
||||||
|
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction, fetchedData?: any) {
|
||||||
|
if (!interaction.inGuild() || !interaction.member.highestRole) return;
|
||||||
|
await interaction.defer();
|
||||||
|
|
||||||
|
const user = interaction.getRequiredMember('user');
|
||||||
|
const duration = interaction.getString('duration');
|
||||||
|
const reason = interaction.getString('reason') || 'No reason specified.';
|
||||||
|
const messageDays = interaction.getNumber('cleanup') || 0;
|
||||||
|
|
||||||
|
await context.banGuildMember(interaction.guild.id, user.id, messageDays, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new BanCommand('ban');
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// External Imports
|
||||||
|
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
|
||||||
|
@SlashCommand(
|
||||||
|
new CommandBuilder('hard-ban', 'Permanently bans a user, and all known alternate accounts.')
|
||||||
|
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||||
|
.setContexts(Constants.InteractionContextType.Guild)
|
||||||
|
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||||
|
.setMemberPermission(Constants.PermissionFlagsBits.BanMembers)
|
||||||
|
.addUserOption('user', 'The user to ban.', true)
|
||||||
|
.addStringOption({ name: 'reason', description: 'The ban reason.', required: false })
|
||||||
|
.addNumberOption({ name: 'cleanup', description: 'Number of days worth of messages to delete.', required: false })
|
||||||
|
)
|
||||||
|
class HardBanCommand extends Command<CommandClient> {
|
||||||
|
cooldown = 5;
|
||||||
|
userPermissions = [Constants.PermissionFlagsBits.BanMembers];
|
||||||
|
|
||||||
|
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction, fetchedData?: any) {
|
||||||
|
if (!interaction.inGuild() || !interaction.member.highestRole) return;
|
||||||
|
await interaction.defer();
|
||||||
|
|
||||||
|
const user = interaction.getRequiredMember('user');
|
||||||
|
const duration = interaction.getString('duration');
|
||||||
|
const reason = interaction.getString('reason') || 'No reason specified.';
|
||||||
|
const messageDays = interaction.getNumber('cleanup') || 0;
|
||||||
|
|
||||||
|
// Nix -> Tarrgon: Please implement the hard-ban here :3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new HardBanCommand('hardban');
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, SlashCommandBuilder } from 'discord.js';
|
|
||||||
import { config } from '../config';
|
|
||||||
import { syncName } from '../utils';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'name-sync',
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('name-sync')
|
|
||||||
.setDescription('Sync your discord nickname to your e621 name.')
|
|
||||||
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall)
|
|
||||||
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM)
|
|
||||||
.addIntegerOption(option =>
|
|
||||||
option
|
|
||||||
.setName('id')
|
|
||||||
.setDescription('The id of the e621 user to sync your nickname to.')
|
|
||||||
.setRequired(false)
|
|
||||||
),
|
|
||||||
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
|
|
||||||
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
|
|
||||||
const id = interaction.options.getInteger('id');
|
|
||||||
|
|
||||||
const guild = await interaction.client.guilds.fetch(config.DISCORD_GUILD_ID!);
|
|
||||||
|
|
||||||
if (!guild) {
|
|
||||||
return interaction.editReply('An error has occurred. Please try again later.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const member = await guild.members.fetch(interaction.user.id);
|
|
||||||
|
|
||||||
if (!member || !guild.members.me) {
|
|
||||||
return interaction.editReply('An error has occurred. Please try again later.');
|
|
||||||
}
|
|
||||||
|
|
||||||
await syncName(interaction, member, id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { E621User } from '../../types';
|
||||||
|
import { CreateDefaultEmbed, getE621User, SetSeverity } from '../../utils';
|
||||||
|
|
||||||
|
// External Imports
|
||||||
|
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, Member, SlashCommand } from 'athena-prime';
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
|
||||||
|
async function fetchUser(member: Member, id: number | null): Promise<E621User | null> {
|
||||||
|
const syncedIds = await Database.getE621Ids(member.id);
|
||||||
|
|
||||||
|
if (!id || !syncedIds.includes(id))
|
||||||
|
return await getE621User(syncedIds[0]);
|
||||||
|
else
|
||||||
|
return await getE621User(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SlashCommand(
|
||||||
|
new CommandBuilder('name-sync', 'Sync your Discord username with your e621 username.')
|
||||||
|
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||||
|
.setContexts(Constants.InteractionContextType.Guild)
|
||||||
|
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||||
|
.addNumberOption({ name: 'id', description: 'The ID of the user to sync your name to.', required: false, max_value: 16 })
|
||||||
|
)
|
||||||
|
class NameSyncCommand extends Command<CommandClient> {
|
||||||
|
cooldown = 60;
|
||||||
|
|
||||||
|
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction, data: any) {
|
||||||
|
if (!interaction.inGuild()) return;
|
||||||
|
await interaction.defer();
|
||||||
|
|
||||||
|
const id = interaction.getNumber('id');
|
||||||
|
const user = await fetchUser(interaction.member, id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
await interaction.createMessage({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(context),
|
||||||
|
...SetSeverity('error'),
|
||||||
|
|
||||||
|
description: 'I was unable to get a user entry for your account or provided id. Please try again, or contact an administrator.'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.member.edit({ nick: user.name }, 'Member requested a name-sync.');
|
||||||
|
|
||||||
|
await interaction.createMessage({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(context),
|
||||||
|
...SetSeverity('success'),
|
||||||
|
|
||||||
|
description: `Successfully updated your display name. Hello \`${user.name}\`!`
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new NameSyncCommand('name-sync');
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { APIEmbedField, APIRole, AuditLogEvent, EmbedBuilder, Guild, GuildAuditLogsEntry, RoleFlags, SnowflakeUtil } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
import { formatChanges, formatExtras, formatSnowflake, getTargetType } from '../utils';
|
|
||||||
|
|
||||||
const IGNORED_ACTIONS = [
|
|
||||||
AuditLogEvent.MemberMove,
|
|
||||||
// Handled by automod.
|
|
||||||
AuditLogEvent.AutoModerationFlagToChannel
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function handleAuditLogCreate(entry: GuildAuditLogsEntry, guild: Guild) {
|
|
||||||
if (!await shouldLog(entry, guild)) return;
|
|
||||||
const settings = await Database.getGuildSettings(guild.id);
|
|
||||||
|
|
||||||
if (!settings || !settings.audit_logs_channel_id) return;
|
|
||||||
|
|
||||||
const channel = await guild.channels.fetch(settings.audit_logs_channel_id);
|
|
||||||
|
|
||||||
if (!channel || !channel.isSendable()) return;
|
|
||||||
|
|
||||||
const fields: APIEmbedField[] = [
|
|
||||||
{
|
|
||||||
name: 'Actor',
|
|
||||||
value: `<@${entry.executorId}>`,
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (entry.targetId) {
|
|
||||||
const targetType = getTargetType(entry.action);
|
|
||||||
fields.push({
|
|
||||||
name: 'Target',
|
|
||||||
value: formatSnowflake(entry.targetId, targetType),
|
|
||||||
inline: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.reason) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Reason',
|
|
||||||
value: entry.reason,
|
|
||||||
inline: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.changes && entry.changes.length > 0) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Changes',
|
|
||||||
value: formatChanges(entry),
|
|
||||||
inline: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.extra) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Options',
|
|
||||||
value: formatExtras(entry, guild),
|
|
||||||
inline: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(Object.keys(AuditLogEvent)[Object.values(AuditLogEvent).indexOf(entry.action)])
|
|
||||||
.setTimestamp(Number(SnowflakeUtil.decode(entry.id).timestamp))
|
|
||||||
.addFields(...fields);
|
|
||||||
|
|
||||||
channel.send({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shouldLog(entry: GuildAuditLogsEntry, guild: Guild): Promise<boolean> {
|
|
||||||
if (!entry.executorId) return true;
|
|
||||||
|
|
||||||
if (IGNORED_ACTIONS.some(a => entry.action == a)) return false;
|
|
||||||
|
|
||||||
if (entry.action == AuditLogEvent.MemberRoleUpdate) {
|
|
||||||
return await shouldLogRoleChanges(entry as GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shouldLogRoleChanges(entry: GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild: Guild): Promise<boolean> {
|
|
||||||
for (const change of entry.changes) {
|
|
||||||
// Get role changes from the log.
|
|
||||||
const roles = (await Promise.all((change.new! as Pick<APIRole, 'id' | 'name'>[]).map(c => guild.roles.fetch(c.id))));
|
|
||||||
|
|
||||||
// Check if role is part of onboarding.
|
|
||||||
for (const role of roles) {
|
|
||||||
if (role && !role.flags.has(RoleFlags.InPrompt)) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { ChatInputCommandInteraction, GuildMember, ModalSubmitInteraction, UserContextMenuCommandInteraction } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
import { E621User } from '../types';
|
|
||||||
import { getE621User } from './e621-utils';
|
|
||||||
|
|
||||||
export async function syncName(interaction: ChatInputCommandInteraction | UserContextMenuCommandInteraction | ModalSubmitInteraction, member: GuildMember, id: number | null) {
|
|
||||||
if (member.roles.highest.comparePositionTo(member.guild.members.me!.roles.highest) > 0) {
|
|
||||||
const res = interaction.user.id == member.id ? 'your' : 'their';
|
|
||||||
return interaction.editReply(`I am unable to set ${res} nickname as ${res} role is higher than mine.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableIds = await Database.getE621Ids(interaction.user.id);
|
|
||||||
|
|
||||||
let e621User: E621User | null;
|
|
||||||
|
|
||||||
if (!id || !availableIds.includes(id)) {
|
|
||||||
e621User = await getE621User(availableIds[0]);
|
|
||||||
} else {
|
|
||||||
e621User = await getE621User(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e621User) {
|
|
||||||
return interaction.editReply("Couldn't figure out what your name was. Please contact an administrator.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await member.setNickname(e621User.name);
|
|
||||||
interaction.editReply(`Nickname set to: ${e621User.name}`);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user