LOADING JASON DISCORD HOST V1
LOADING JASON DISCORD HOST V1
Jason Discord Host V1
Moderation Bot covers the actions most servers need on day one: kick, ban, unban, timeout, and bulk delete. It refuses to target higher roles and can mirror actions to a log channel.
Enable: Server Members Intent.
require("dotenv").config();
const {
Client,
GatewayIntentBits,
EmbedBuilder,
SlashCommandBuilder,
PermissionFlagsBits,
Events,
MessageFlags,
} = require("discord.js");
const TOKEN = process.env.DISCORD_TOKEN;
const GUILD_ID = process.env.GUILD_ID?.trim();
const MOD_LOG_CHANNEL_ID = process.env.MOD_LOG_CHANNEL_ID?.trim();
if (!TOKEN) {
console.error("Missing DISCORD_TOKEN. Copy .env.example to .env and add your bot token.");
process.exit(1);
}
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages],
});
const commands = [
new SlashCommandBuilder().setName("ping").setDescription("Check that Moderation Bot is online."),
new SlashCommandBuilder()
.setName("kick")
.setDescription("Kick a member from the server.")
.addUserOption((o) => o.setName("user").setDescription("Member to kick").setRequired(true))
.addStringOption((o) => o.setName("reason").setDescription("Reason"))
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
new SlashCommandBuilder()
.setName("ban")
.setDescription("Ban a member from the server.")
.addUserOption((o) => o.setName("user").setDescription("Member to ban").setRequired(true))
.addStringOption((o) => o.setName("reason").setDescription("Reason"))
.addIntegerOption((o) =>
o.setName("delete_days").setDescription("Delete recent messages (0-7 days)").setMinValue(0).setMaxValue(7),
)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
new SlashCommandBuilder()
.setName("unban")
.setDescription("Remove a ban by user ID.")
.addStringOption((o) => o.setName("user_id").setDescription("Discord user ID").setRequired(true))
.addStringOption((o) => o.setName("reason").setDescription("Reason"))
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
new SlashCommandBuilder()
.setName("timeout")
.setDescription("Timeout a member (mute in text and voice).")
.addUserOption((o) => o.setName("user").setDescription("Member to timeout").setRequired(true))
.addIntegerOption((o) =>
o.setName("minutes").setDescription("Duration in minutes (1-40320)").setRequired(true).setMinValue(1).setMaxValue(40320),
)
.addStringOption((o) => o.setName("reason").setDescription("Reason"))
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
new SlashCommandBuilder()
.setName("untimeout")
.setDescription("Clear an active timeout.")
.addUserOption((o) => o.setName("user").setDescription("Member to unmute").setRequired(true))
.addStringOption((o) => o.setName("reason").setDescription("Reason"))
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
new SlashCommandBuilder()
.setName("purge")
.setDescription("Bulk delete recent messages in this channel (max 100, newer than 14 days).")
.addIntegerOption((o) =>
o.setName("count").setDescription("How many messages (1-100)").setRequired(true).setMinValue(1).setMaxValue(100),
)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
].map((c) => c.toJSON());
function reasonOf(interaction) {
return interaction.options.getString("reason") || `Action by ${interaction.user.tag} via Moderation Bot`;
}
function canModerate(moderator, target, botMember) {
if (!target.moderatable) return "I cannot moderate that member. Move my role higher or pick someone else.";
if (target.id === moderator.id) return "You cannot run this command on yourself.";
if (target.id === botMember.id) return "I will not moderate myself.";
if (
moderator.id !== moderator.guild.ownerId &&
moderator.roles.highest.comparePositionTo(target.roles.highest) <= 0
) {
return "That member has an equal or higher role than you.";
}
return null;
}
async function logAction(guild, title, fields) {
if (!MOD_LOG_CHANNEL_ID) return;
const channel = await guild.channels.fetch(MOD_LOG_CHANNEL_ID).catch(() => null);
if (!channel?.isTextBased()) return;
const embed = new EmbedBuilder()
.setColor(0xff2bd6)
.setTitle(title)
.addFields(fields)
.setFooter({ text: "Jason Discord Host V1 · Moderation Bot" })
.setTimestamp();
await channel.send({ embeds: [embed] }).catch(() => null);
}
async function registerCommands() {
if (GUILD_ID) {
const guild = await client.guilds.fetch(GUILD_ID);
await guild.commands.set(commands);
console.log(`Registered guild commands in ${guild.name}`);
return;
}
await client.application.commands.set(commands);
console.log("Registered global slash commands.");
}
client.once(Events.ClientReady, async () => {
console.log(`Moderation Bot online as ${client.user.tag}`);
try {
await registerCommands();
} catch (error) {
console.error("Failed to register commands:", error);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
try {
if (interaction.commandName === "ping") {
await interaction.reply({ content: `Pong — ${client.ws.ping}ms`, flags: MessageFlags.Ephemeral });
return;
}
if (!interaction.inGuild()) {
await interaction.reply({ content: "Run moderation commands inside a server.", flags: MessageFlags.Ephemeral });
return;
}
const reason = reasonOf(interaction);
if (interaction.commandName === "kick") {
const target = await interaction.guild.members.fetch(interaction.options.getUser("user", true).id);
const block = canModerate(interaction.member, target, interaction.guild.members.me);
if (block) {
await interaction.reply({ content: block, flags: MessageFlags.Ephemeral });
return;
}
await target.kick(reason);
await interaction.reply({ content: `Kicked **${target.user.tag}**. Reason: ${reason}` });
await logAction(interaction.guild, "Member kicked", [
{ name: "User", value: `${target.user.tag} (${target.id})` },
{ name: "Moderator", value: `${interaction.user.tag}` },
{ name: "Reason", value: reason },
]);
return;
}
if (interaction.commandName === "ban") {
const user = interaction.options.getUser("user", true);
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
if (member) {
const block = canModerate(interaction.member, member, interaction.guild.members.me);
if (block) {
await interaction.reply({ content: block, flags: MessageFlags.Ephemeral });
return;
}
}
const deleteDays = interaction.options.getInteger("delete_days") ?? 0;
await interaction.guild.members.ban(user.id, { reason, deleteMessageSeconds: deleteDays * 24 * 60 * 60 });
await interaction.reply({ content: `Banned **${user.tag}**. Reason: ${reason}` });
await logAction(interaction.guild, "Member banned", [
{ name: "User", value: `${user.tag} (${user.id})` },
{ name: "Moderator", value: `${interaction.user.tag}` },
{ name: "Reason", value: reason },
]);
return;
}
if (interaction.commandName === "unban") {
const userId = interaction.options.getString("user_id", true).trim();
await interaction.guild.members.unban(userId, reason);
await interaction.reply({ content: `Unbanned \`${userId}\`.` });
await logAction(interaction.guild, "Ban removed", [
{ name: "User ID", value: userId },
{ name: "Moderator", value: `${interaction.user.tag}` },
{ name: "Reason", value: reason },
]);
return;
}
if (interaction.commandName === "timeout") {
const target = await interaction.guild.members.fetch(interaction.options.getUser("user", true).id);
const block = canModerate(interaction.member, target, interaction.guild.members.me);
if (block) {
await interaction.reply({ content: block, flags: MessageFlags.Ephemeral });
return;
}
const minutes = interaction.options.getInteger("minutes", true);
await target.timeout(minutes * 60 * 1000, reason);
await interaction.reply({ content: `Timed out **${target.user.tag}** for ${minutes} minute(s).` });
await logAction(interaction.guild, "Member timed out", [
{ name: "User", value: `${target.user.tag}` },
{ name: "Duration", value: `${minutes} minutes` },
{ name: "Moderator", value: `${interaction.user.tag}` },
{ name: "Reason", value: reason },
]);
return;
}
if (interaction.commandName === "untimeout") {
const target = await interaction.guild.members.fetch(interaction.options.getUser("user", true).id);
await target.timeout(null, reason);
await interaction.reply({ content: `Cleared timeout for **${target.user.tag}**.` });
return;
}
if (interaction.commandName === "purge") {
if (!interaction.channel || !interaction.channel.isTextBased() || interaction.channel.isDMBased()) {
await interaction.reply({ content: "Purge only works in server text channels.", flags: MessageFlags.Ephemeral });
return;
}
const count = interaction.options.getInteger("count", true);
const deleted = await interaction.channel.bulkDelete(count, true);
await interaction.reply({
content: `Deleted **${deleted.size}** message(s). Messages older than 14 days cannot be bulk-deleted.`,
flags: MessageFlags.Ephemeral,
});
}
} catch (error) {
console.error(error);
const message = error?.rawError?.message || error.message || "Something went wrong.";
if (interaction.deferred || interaction.replied) {
await interaction.followUp({ content: `Failed: ${message}`, flags: MessageFlags.Ephemeral }).catch(() => null);
} else {
await interaction.reply({ content: `Failed: ${message}`, flags: MessageFlags.Ephemeral }).catch(() => null);
}
}
});
client.login(TOKEN).catch((error) => {
console.error("Login failed. Check DISCORD_TOKEN.", error);
process.exit(1);
});