├── commands ├── info │ ├── ping.js │ ├── roleinfo.js │ ├── uptime.js │ ├── channelinfo.js │ ├── serverinfo.js │ ├── userinfo.js │ ├── emojiinfo.js │ └── botinfo.js ├── owner │ ├── guilds.js │ ├── eval.js │ ├── botstatus.js │ └── reload.js ├── admin │ ├── toggle-antilink.js │ ├── toggle-antispam.js │ ├── setlog.js │ ├── toggle-automod.js │ └── status.js ├── general │ ├── avatar.js │ ├── math.js │ ├── translate.js │ └── help.js └── moderation │ ├── unmute.js │ ├── clear.js │ ├── warnings.js │ ├── clearwarnings.js │ ├── mute.js │ ├── kick.js │ ├── ban.js │ ├── unban.js │ ├── warn.js │ └── timeout.js ├── .github ├── FUNDING.yml └── workflows │ ├── FUNDING.yml │ └── node.js.yml ├── Dockerfile ├── .replit ├── models ├── Warn.js └── GuildSettings.js ├── events ├── messageDelete.js ├── deleteMessage.js ├── messageUpdate.js ├── messageCreate.js └── interactionCreate.js ├── toggle-antispam.js ├── package.json ├── utils └── logToChannel.js ├── deploy-commands.js ├── README.md ├── index.js └── LICENSE /commands/info/ping.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /commands/info/roleinfo.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: khanmanan 2 | github: khanmanan 3 | -------------------------------------------------------------------------------- /.github/workflows/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom:['cwkhan.xyz', 'automodbot.com'] 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.15-alpine3.15 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 8080 12 | 13 | CMD ["npm","run","start"] -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | modules = ["nodejs-20"] 2 | run = "node index.js" 3 | 4 | [nix] 5 | channel = "stable-24_05" 6 | packages = ["run"] 7 | 8 | [deployment] 9 | run = ["sh", "-c", "node index.js"] 10 | 11 | [[ports]] 12 | localPort = 3000 13 | externalPort = 80 14 | -------------------------------------------------------------------------------- /models/Warn.js: -------------------------------------------------------------------------------- 1 | const { Schema, model, models } = require('mongoose'); 2 | 3 | const warnSchema = new Schema({ 4 | userId: String, 5 | guildId: String, 6 | warnings: [ 7 | { 8 | modId: String, 9 | reason: String, 10 | date: { type: Date, default: Date.now } 11 | } 12 | ] 13 | }); 14 | 15 | // ✅ Only compile if it doesn't already exist 16 | module.exports = models.Warning || model('Warning', warnSchema); 17 | -------------------------------------------------------------------------------- /models/GuildSettings.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose'); 2 | 3 | const settingsSchema = new Schema({ 4 | guildId: String, 5 | automodEnabled: { type: Boolean, default: false }, 6 | antiLink: { type: Boolean, default: false }, 7 | antiSpam: { type: Boolean, default: false }, 8 | antiGhostPing: { type: Boolean, default: false }, 9 | logChannelId: { type: String, default: null }, 10 | }); 11 | 12 | module.exports = model('GuildSettings', settingsSchema); 13 | -------------------------------------------------------------------------------- /events/messageDelete.js: -------------------------------------------------------------------------------- 1 | const GuildSettings = require('../models/GuildSettings'); 2 | 3 | module.exports = { 4 | name: 'messageDelete', 5 | async execute(message) { 6 | if (message.partial || message.author?.bot || !message.guild) return; 7 | 8 | const settings = await GuildSettings.findOne({ guildId: message.guild.id }); 9 | if (!settings?.automodEnabled) return; 10 | 11 | if (message.mentions.users.size > 0) { 12 | await message.channel.send({ 13 | content: `👻 **Ghost ping detected!**\nUser: <@${message.author.id}>`, 14 | }); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /toggle-antispam.js: -------------------------------------------------------------------------------- 1 | const GuildSettings = require('../models/GuildSettings'); 2 | 3 | module.exports = { 4 | name: 'messageDelete', 5 | async execute(message) { 6 | if (message.partial || message.author?.bot || !message.guild) return; 7 | 8 | const settings = await GuildSettings.findOne({ guildId: message.guild.id }); 9 | if (!settings?.automodEnabled || !settings?.antiGhostPing) return; 10 | 11 | if (message.mentions.users.size > 0) { 12 | await message.channel.send({ 13 | content: `👻 **Ghost ping detected!**\nUser: <@${message.author.id}>`, 14 | }); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automod-bot", 3 | "version": "1.0.0", 4 | "description": "A powerful Discord moderation bot with auto mod, slash commands, and a dashboard", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "deploy": "node deploy-commands.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/Khanmanan/automod-bot.git" 13 | }, 14 | "author": "cwkhan", 15 | "license": "MIT", 16 | "dependencies": { 17 | "discord.js": "^14.13.0", 18 | "dotenv": "^16.3.1", 19 | "mongoose": "^7.6.1", 20 | "moment": "^2.29.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /commands/info/uptime.js: -------------------------------------------------------------------------------- 1 | // /commands/info/uptime.js 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('uptime') 7 | .setDescription('Shows how long the bot has been online.'), 8 | async execute(interaction, client) { 9 | const totalSeconds = Math.floor(client.uptime / 1000); 10 | const days = Math.floor(totalSeconds / 86400); 11 | const hours = Math.floor(totalSeconds / 3600) % 24; 12 | const minutes = Math.floor(totalSeconds / 60) % 60; 13 | const seconds = totalSeconds % 60; 14 | 15 | await interaction.reply(`🕒 Uptime: \`${days}d ${hours}h ${minutes}m ${seconds}s\``); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /utils/logToChannel.js: -------------------------------------------------------------------------------- 1 | // utils/logToChannel.js 2 | const { EmbedBuilder } = require('discord.js'); 3 | const GuildSettings = require('../models/GuildSettings'); 4 | 5 | module.exports = async function logToChannel(guild, embedData) { 6 | const settings = await GuildSettings.findOne({ guildId: guild.id }); 7 | if (!settings?.logChannelId) return; 8 | 9 | const logChannel = guild.channels.cache.get(settings.logChannelId); 10 | if (!logChannel) return; 11 | 12 | const embed = new EmbedBuilder() 13 | .setTitle(embedData.title) 14 | .addFields(...embedData.fields) 15 | .setColor(embedData.color || 'Blue') 16 | .setTimestamp(); 17 | 18 | logChannel.send({ embeds: [embed] }); 19 | }; 20 | -------------------------------------------------------------------------------- /commands/owner/guilds.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('guilds') 6 | .setDescription('Shows the servers the bot is in (Bot Owner only)'), 7 | 8 | async execute(interaction) { 9 | if (interaction.user.id !== '682981714523586606') return interaction.reply({ content: '❌ You are not authorized.', ephemeral: true }); 10 | 11 | const guildList = interaction.client.guilds.cache.map(g => `${g.name} (${g.id})`).join('\n'); 12 | 13 | const embed = new EmbedBuilder() 14 | .setTitle('Bot Guilds') 15 | .setDescription(`\`\`\`\n${guildList}\n\`\`\``) 16 | .setColor('#7289DA'); 17 | 18 | await interaction.reply({ embeds: [embed], ephemeral: true }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm install 29 | -------------------------------------------------------------------------------- /deploy-commands.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const fs = require('fs'); 3 | const { REST, Routes } = require('discord.js'); 4 | 5 | const commands = []; 6 | const ds = './commands'; 7 | 8 | fs.readdirSync(ds).forEach(dir => { 9 | const commandFiles = fs.readdirSync(`${ds}/${dir}`).filter(file => file.endsWith('.js')); 10 | for (const file of commandFiles) { 11 | const command = require(`${ds}/${dir}/${file}`); 12 | if (command.data) { 13 | commands.push(command.data.toJSON()); 14 | } 15 | } 16 | }); 17 | 18 | const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); 19 | 20 | (async () => { 21 | try { 22 | console.log('🔁 Deploying slash commands...'); 23 | 24 | await rest.put( 25 | Routes.applicationCommands(process.env.CLIENT_ID), 26 | { body: commands } 27 | ); 28 | 29 | console.log('✅ Successfully deployed slash commands!'); 30 | } catch (error) { 31 | console.error('❌ Failed to deploy commands:', error); 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /events/deleteMessage.js: -------------------------------------------------------------------------------- 1 | const GuildSettings = require('../models/GuildSettings'); 2 | const { EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | name: 'messageDelete', 6 | async execute(message) { 7 | if (!message.guild || message.partial || message.author?.bot) return; 8 | 9 | const settings = await GuildSettings.findOne({ guildId: message.guild.id }); 10 | if (!settings?.logChannelId) return; 11 | 12 | const logChannel = message.guild.channels.cache.get(settings.logChannelId); 13 | if (!logChannel) return; 14 | 15 | const embed = new EmbedBuilder() 16 | .setTitle('🗑️ Message Deleted') 17 | .addFields( 18 | { name: 'User', value: `${message.author.tag}`, inline: true }, 19 | { name: 'Channel', value: `${message.channel}`, inline: true }, 20 | { name: 'Message', value: message.content || '*[No content]*' } 21 | ) 22 | .setTimestamp() 23 | .setColor('Red'); 24 | 25 | logChannel.send({ embeds: [embed] }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /commands/admin/toggle-antilink.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../../models/GuildSettings'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('antilink') 7 | .setDescription('Enable or disable anti-link protection.') 8 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), 9 | 10 | async execute(interaction) { 11 | const guildId = interaction.guild.id; 12 | let settings = await GuildSettings.findOne({ guildId }) || new GuildSettings({ guildId }); 13 | 14 | settings.antiLink = !settings.antiLink; 15 | await settings.save(); 16 | 17 | const embed = new EmbedBuilder() 18 | .setTitle('Anti-Link Toggled') 19 | .setDescription(`Anti-link protection is now **${settings.antiLink ? 'enabled ✅' : 'disabled ❌'}**.`) 20 | .setColor(settings.antiLink ? 'Green' : 'Red'); 21 | 22 | await interaction.reply({ embeds: [embed], ephemeral: true }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /commands/admin/toggle-antispam.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../../models/GuildSettings'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('antispam') 7 | .setDescription('Enable or disable anti-spam protection.') 8 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), 9 | 10 | async execute(interaction) { 11 | const guildId = interaction.guild.id; 12 | let settings = await GuildSettings.findOne({ guildId }) || new GuildSettings({ guildId }); 13 | 14 | settings.antiSpam = !settings.antiSpam; 15 | await settings.save(); 16 | 17 | const embed = new EmbedBuilder() 18 | .setTitle('Anti-Spam Toggled') 19 | .setDescription(`Anti-spam protection is now **${settings.antiSpam ? 'enabled ✅' : 'disabled ❌'}**.`) 20 | .setColor(settings.antiSpam ? 'Green' : 'Red'); 21 | 22 | await interaction.reply({ embeds: [embed], ephemeral: true }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /commands/general/avatar.js: -------------------------------------------------------------------------------- 1 | // /commands/general/avatar.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('avatar') 7 | .setDescription('Get the avatar of a user.') 8 | .addUserOption(option => 9 | option.setName('user') 10 | .setDescription('Select a user') 11 | .setRequired(false)), 12 | async execute(interaction) { 13 | const user = interaction.options.getUser('user') || interaction.user; 14 | const member = interaction.guild.members.cache.get(user.id); 15 | 16 | const embed = new EmbedBuilder() 17 | .setTitle(`${user.username}'s Avatar`) 18 | .setImage(user.displayAvatarURL({ dynamic: true, size: 1024 })) 19 | .setColor('Random'); 20 | 21 | if (member && member.avatar) { 22 | embed.addFields({ name: 'Server Avatar', value: `[View](${member.displayAvatarURL({ dynamic: true, size: 1024 })})` }); 23 | } 24 | 25 | await interaction.reply({ embeds: [embed] }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /commands/general/math.js: -------------------------------------------------------------------------------- 1 | // /commands/general/math.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | const math = require('mathjs'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('math') 8 | .setDescription('Calculate a math expression.') 9 | .addStringOption(option => 10 | option.setName('expression') 11 | .setDescription('Enter the math expression') 12 | .setRequired(true)), 13 | async execute(interaction) { 14 | const expr = interaction.options.getString('expression'); 15 | 16 | try { 17 | const result = math.evaluate(expr); 18 | 19 | const embed = new EmbedBuilder() 20 | .setTitle('🧮 Math Result') 21 | .addFields( 22 | { name: 'Expression', value: `\`${expr}\`` }, 23 | { name: 'Result', value: `\`${result}\`` } 24 | ) 25 | .setColor('Blue'); 26 | 27 | await interaction.reply({ embeds: [embed] }); 28 | } catch (err) { 29 | await interaction.reply({ content: '❌ Invalid expression.', ephemeral: true }); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /commands/owner/eval.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | const { inspect } = require('util'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('eval') 7 | .setDescription('Evaluates JavaScript code (Bot Owner only)') 8 | .addStringOption(option => 9 | option.setName('code') 10 | .setDescription('JavaScript code to run') 11 | .setRequired(true)), 12 | 13 | async execute(interaction) { 14 | if (interaction.user.id !== '682981714523586606') return interaction.reply({ content: '❌ You are not authorized.', ephemeral: true }); 15 | 16 | const code = interaction.options.getString('code'); 17 | try { 18 | let evaled = await eval(code); 19 | if (typeof evaled !== 'string') evaled = inspect(evaled); 20 | 21 | await interaction.reply({ content: `✅ \`\`\`js\n${evaled}\n\`\`\`` }); 22 | } catch (err) { 23 | await interaction.reply({ content: `❌ \`\`\`js\n${err}\n\`\`\``, ephemeral: true }); 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /commands/admin/setlog.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../../models/GuildSettings'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('setlog') 7 | .setDescription('Set the modlog channel') 8 | .addChannelOption(option => 9 | option.setName('channel').setDescription('Channel for logging').setRequired(true)) 10 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), 11 | 12 | async execute(interaction) { 13 | const channel = interaction.options.getChannel('channel'); 14 | const guildId = interaction.guild.id; 15 | 16 | let settings = await GuildSettings.findOne({ guildId }) || new GuildSettings({ guildId }); 17 | settings.logChannelId = channel.id; 18 | await settings.save(); 19 | 20 | const embed = new EmbedBuilder() 21 | .setTitle('✅ Log Channel Set') 22 | .setDescription(`Logging will now go to ${channel}`) 23 | .setColor('Green'); 24 | 25 | await interaction.reply({ embeds: [embed], ephemeral: true }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /commands/info/channelinfo.js: -------------------------------------------------------------------------------- 1 | // /commands/info/channelinfo.js 2 | const { SlashCommandBuilder, EmbedBuilder, ChannelType } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('channelinfo') 7 | .setDescription('Displays information about a channel.') 8 | .addChannelOption(option => 9 | option.setName('channel') 10 | .setDescription('Channel to get info on') 11 | .setRequired(true)), 12 | async execute(interaction) { 13 | const channel = interaction.options.getChannel('channel'); 14 | 15 | const embed = new EmbedBuilder() 16 | .setTitle(`📺 Channel Info: ${channel.name}`) 17 | .setColor('Blue') 18 | .addFields( 19 | { name: 'ID', value: channel.id, inline: true }, 20 | { name: 'Type', value: ChannelType[channel.type], inline: true }, 21 | { name: 'NSFW', value: channel.nsfw ? 'Yes' : 'No', inline: true }, 22 | { name: 'Created', value: ``, inline: true } 23 | ); 24 | 25 | await interaction.reply({ embeds: [embed] }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /commands/info/serverinfo.js: -------------------------------------------------------------------------------- 1 | // /commands/info/serverinfo.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('serverinfo') 7 | .setDescription('Displays information about this server.'), 8 | async execute(interaction) { 9 | const { guild } = interaction; 10 | 11 | const embed = new EmbedBuilder() 12 | .setTitle(`🌐 Server Info: ${guild.name}`) 13 | .setThumbnail(guild.iconURL({ dynamic: true })) 14 | .setColor('Green') 15 | .addFields( 16 | { name: 'Server ID', value: guild.id, inline: true }, 17 | { name: 'Owner', value: `<@${guild.ownerId}>`, inline: true }, 18 | { name: 'Members', value: `${guild.memberCount}`, inline: true }, 19 | { name: 'Roles', value: `${guild.roles.cache.size}`, inline: true }, 20 | { name: 'Channels', value: `${guild.channels.cache.size}`, inline: true }, 21 | { name: 'Created', value: ``, inline: true } 22 | ); 23 | 24 | await interaction.reply({ embeds: [embed] }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /events/messageUpdate.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../models/GuildSettings'); 3 | 4 | module.exports = { 5 | name: 'messageUpdate', 6 | async execute(oldMessage, newMessage) { 7 | if (!newMessage.guild || newMessage.author?.bot || oldMessage.content === newMessage.content) return; 8 | 9 | const settings = await GuildSettings.findOne({ guildId: newMessage.guild.id }); 10 | if (!settings?.logChannelId) return; 11 | 12 | const logChannel = newMessage.guild.channels.cache.get(settings.logChannelId); 13 | if (!logChannel) return; 14 | 15 | const embed = new EmbedBuilder() 16 | .setTitle('✏️ Message Edited') 17 | .addFields( 18 | { name: 'User', value: `${newMessage.author.tag}`, inline: true }, 19 | { name: 'Channel', value: `${newMessage.channel}`, inline: true }, 20 | { name: 'Before', value: oldMessage.content || '*[No content]*' }, 21 | { name: 'After', value: newMessage.content || '*[No content]*' } 22 | ) 23 | .setTimestamp() 24 | .setColor('Orange'); 25 | 26 | logChannel.send({ embeds: [embed] }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /commands/admin/toggle-automod.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../../models/GuildSettings'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('toggle-automod') 7 | .setDescription('Enable or disable Automod for this server.') 8 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), 9 | 10 | async execute(interaction) { 11 | const guildId = interaction.guild.id; 12 | 13 | let settings = await GuildSettings.findOne({ guildId }); 14 | if (!settings) { 15 | settings = new GuildSettings({ guildId }); 16 | } 17 | 18 | settings.automodEnabled = !settings.automodEnabled; 19 | await settings.save(); 20 | 21 | const status = settings.automodEnabled ? 'enabled ✅' : 'disabled ❌'; 22 | const embed = new EmbedBuilder() 23 | .setTitle('Automod Toggled') 24 | .setDescription(`Automod is now **${status}**.`) 25 | .setColor(settings.automodEnabled ? 'Green' : 'Red') 26 | .setTimestamp(); 27 | 28 | await interaction.reply({ embeds: [embed], ephemeral: true }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /commands/info/userinfo.js: -------------------------------------------------------------------------------- 1 | // /commands/info/userinfo.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('userinfo') 7 | .setDescription('Shows information about a user.') 8 | .addUserOption(option => 9 | option.setName('target') 10 | .setDescription('User to get info on') 11 | .setRequired(false)), 12 | async execute(interaction) { 13 | const user = interaction.options.getUser('target') || interaction.user; 14 | const member = interaction.guild.members.cache.get(user.id); 15 | 16 | const embed = new EmbedBuilder() 17 | .setTitle(`👤 User Info: ${user.tag}`) 18 | .setThumbnail(user.displayAvatarURL({ dynamic: true })) 19 | .setColor('Orange') 20 | .addFields( 21 | { name: 'User ID', value: user.id, inline: true }, 22 | { name: 'Account Created', value: ``, inline: true }, 23 | { name: 'Joined Server', value: ``, inline: true }, 24 | { name: 'Roles', value: `${member.roles.cache.map(r => r).join(', ')}`, inline: false } 25 | ); 26 | 27 | await interaction.reply({ embeds: [embed] }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /commands/moderation/unmute.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('unmute') 6 | .setDescription('Unmute a member') 7 | .addUserOption(option => 8 | option.setName('user') 9 | .setDescription('The member to unmute') 10 | .setRequired(true)) 11 | .setDefaultMemberPermissions(PermissionFlagsBits.MuteMembers), 12 | 13 | async execute(interaction) { 14 | const member = interaction.options.getMember('user'); 15 | 16 | if (!member) return interaction.reply({ content: 'User not found.', ephemeral: true }); 17 | 18 | try { 19 | await member.timeout(null); // Remove timeout 20 | const embed = new EmbedBuilder() 21 | .setTitle('🔊 Member Unmuted') 22 | .setColor('Green') 23 | .addFields( 24 | { name: 'User', value: `${member.user.tag}`, inline: true }, 25 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true } 26 | ) 27 | .setTimestamp(); 28 | 29 | await interaction.reply({ embeds: [embed] }); 30 | } catch (err) { 31 | console.error(err); 32 | interaction.reply({ content: 'Failed to unmute the user.', ephemeral: true }); 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /commands/moderation/clear.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('clear') 6 | .setDescription('Clear messages from a channel') 7 | .addIntegerOption(option => 8 | option.setName('amount') 9 | .setDescription('Number of messages to delete') 10 | .setRequired(true)) 11 | .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), 12 | 13 | async execute(interaction) { 14 | const amount = interaction.options.getInteger('amount'); 15 | 16 | if (amount < 1 || amount > 100) 17 | return interaction.reply({ content: 'Please provide a number between 1 and 100.', ephemeral: true }); 18 | 19 | try { 20 | const messages = await interaction.channel.bulkDelete(amount, true); 21 | const embed = new EmbedBuilder() 22 | .setTitle('🧹 Messages Cleared') 23 | .setDescription(`Successfully deleted ${messages.size} messages.`) 24 | .setColor('Blue') 25 | .setTimestamp(); 26 | 27 | await interaction.reply({ embeds: [embed], ephemeral: true }); 28 | } catch (err) { 29 | console.error(err); 30 | interaction.reply({ content: 'Failed to delete messages.', ephemeral: true }); 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /commands/general/translate.js: -------------------------------------------------------------------------------- 1 | // /commands/general/translate.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | const translate = require('@vitalets/google-translate-api'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('translate') 8 | .setDescription('Translate text to a different language.') 9 | .addStringOption(option => 10 | option.setName('text') 11 | .setDescription('Text to translate') 12 | .setRequired(true)) 13 | .addStringOption(option => 14 | option.setName('to') 15 | .setDescription('Language code (e.g. en, fr, hi)') 16 | .setRequired(true)), 17 | async execute(interaction) { 18 | const text = interaction.options.getString('text'); 19 | const toLang = interaction.options.getString('to'); 20 | 21 | try { 22 | const res = await translate(text, { to: toLang }); 23 | 24 | const embed = new EmbedBuilder() 25 | .setTitle('🌐 Translation') 26 | .addFields( 27 | { name: 'Original', value: text }, 28 | { name: `Translated (${toLang})`, value: res.text } 29 | ) 30 | .setColor('Green'); 31 | 32 | await interaction.reply({ embeds: [embed] }); 33 | } catch (err) { 34 | await interaction.reply({ content: '❌ Failed to translate.', ephemeral: true }); 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /commands/moderation/warnings.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); 2 | const Warn = require('../../models/Warn'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('warnings') 7 | .setDescription('View a user\'s warnings') 8 | .addUserOption(option => option.setName('user').setDescription('User to view warnings').setRequired(true)) 9 | .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), 10 | 11 | async execute(interaction) { 12 | const user = interaction.options.getUser('user'); 13 | const warnings = await Warn.findOne({ userId: user.id, guildId: interaction.guild.id }); 14 | 15 | const embed = new EmbedBuilder() 16 | .setTitle(`📋 Warnings for ${user.tag}`) 17 | .setColor('Yellow') 18 | .setTimestamp(); 19 | 20 | if (!warnings || warnings.warnings.length === 0) { 21 | embed.setDescription('No warnings found.'); 22 | } else { 23 | warnings.warnings.slice(0, 10).forEach((warn, i) => { 24 | embed.addFields({ 25 | name: `⚠️ Warning ${i + 1}`, 26 | value: `**Reason:** ${warn.reason}\n**Moderator:** <@${warn.modId}>\n**Date:** ` 27 | }); 28 | }); 29 | } 30 | 31 | await interaction.reply({ embeds: [embed] }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /commands/moderation/clearwarnings.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const Warn = require('../../models/Warn'); 3 | const logToChannel = require('../../utils/logToChannel'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('clearwarns') 8 | .setDescription('Clear all warnings for a user') 9 | .addUserOption(option => option.setName('user').setDescription('User to clear warnings for').setRequired(true)) 10 | .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), 11 | 12 | async execute(interaction) { 13 | const user = interaction.options.getUser('user'); 14 | const result = await Warn.findOneAndDelete({ userId: user.id, guildId: interaction.guild.id }); 15 | 16 | const embed = new EmbedBuilder() 17 | .setTitle('🧹 Warnings Cleared') 18 | .setDescription(result ? `Cleared all warnings for ${user.tag}` : `No warnings to clear for ${user.tag}`) 19 | .setColor('Green') 20 | .setTimestamp(); 21 | 22 | await interaction.reply({ embeds: [embed] }); 23 | 24 | await logToChannel(interaction.guild, { 25 | title: '🧹 Warnings Cleared', 26 | fields: [ 27 | { name: 'User', value: `<@${user.id}>`, inline: true }, 28 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true } 29 | ], 30 | color: 'Green' 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /commands/admin/status.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 2 | const GuildSettings = require('../../models/GuildSettings'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('status') 7 | .setDescription('View the current Automod toggle status for this server'), 8 | 9 | async execute(interaction) { 10 | const guildId = interaction.guild.id; 11 | 12 | let settings = await GuildSettings.findOne({ guildId }); 13 | if (!settings) { 14 | settings = new GuildSettings({ guildId }); 15 | await settings.save(); 16 | } 17 | 18 | const status = (flag) => flag ? '✅ Enabled' : '❌ Disabled'; 19 | 20 | const embed = new EmbedBuilder() 21 | .setTitle(`⚙️ Automod Status for ${interaction.guild.name}`) 22 | .setColor('Blue') 23 | .addFields( 24 | { name: 'Automod', value: status(settings.automodEnabled), inline: true }, 25 | { name: 'Anti-Link', value: status(settings.antiLink), inline: true }, 26 | { name: 'Anti-Spam', value: status(settings.antiSpam), inline: true }, 27 | { name: 'Anti-GhostPing', value: status(settings.antiGhostPing), inline: true }, 28 | ) 29 | .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL() }) 30 | .setTimestamp(); 31 | 32 | await interaction.reply({ embeds: [embed], ephemeral: true }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /commands/owner/botstatus.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, ActivityType } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('botstatus') 6 | .setDescription('Update bot\'s presence (Bot Owner only)') 7 | .addStringOption(option => 8 | option.setName('activity') 9 | .setDescription('Activity text') 10 | .setRequired(true)) 11 | .addStringOption(option => 12 | option.setName('type') 13 | .setDescription('Type: Playing, Watching, Listening') 14 | .addChoices( 15 | { name: 'Playing', value: 'PLAYING' }, 16 | { name: 'Watching', value: 'WATCHING' }, 17 | { name: 'Listening', value: 'LISTENING' } 18 | ) 19 | .setRequired(true)), 20 | 21 | async execute(interaction) { 22 | if (interaction.user.id !== '682981714523586606') return interaction.reply({ content: '❌ Not authorized.', ephemeral: true }); 23 | 24 | const activity = interaction.options.getString('activity'); 25 | const type = interaction.options.getString('type'); 26 | 27 | interaction.client.user.setPresence({ 28 | activities: [{ name: activity, type: ActivityType[type] }], 29 | status: 'online' 30 | }); 31 | 32 | await interaction.reply({ content: `✅ Updated presence to **${type} ${activity}**`, ephemeral: true }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /commands/info/emojiinfo.js: -------------------------------------------------------------------------------- 1 | // /commands/info/emojiinfo.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('emojiinfo') 7 | .setDescription('Displays information about a custom emoji.') 8 | .addStringOption(option => 9 | option.setName('emoji') 10 | .setDescription('Emoji to get info on (e.g. <:smile:1234567890>)') 11 | .setRequired(true)), 12 | async execute(interaction) { 13 | const input = interaction.options.getString('emoji'); 14 | const match = input.match(//); 15 | 16 | if (!match) { 17 | return interaction.reply({ content: 'Invalid emoji format.', ephemeral: true }); 18 | } 19 | 20 | const emoji = interaction.client.emojis.cache.get(match[1]); 21 | if (!emoji) return interaction.reply({ content: 'Emoji not found in cache.', ephemeral: true }); 22 | 23 | const embed = new EmbedBuilder() 24 | .setTitle(`🧩 Emoji Info: ${emoji.name}`) 25 | .setThumbnail(emoji.url) 26 | .setColor('Yellow') 27 | .addFields( 28 | { name: 'ID', value: emoji.id, inline: true }, 29 | { name: 'Animated', value: emoji.animated ? 'Yes' : 'No', inline: true }, 30 | { name: 'Created', value: ``, inline: true }, 31 | { name: 'URL', value: `[Link](${emoji.url})`, inline: false } 32 | ); 33 | 34 | await interaction.reply({ embeds: [embed] }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /commands/info/botinfo.js: -------------------------------------------------------------------------------- 1 | // /commands/info/botinfo.js 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | const os = require('os'); 4 | const packageJson = require('../../package.json'); 5 | 6 | module.exports = { 7 | data: new SlashCommandBuilder() 8 | .setName('botinfo') 9 | .setDescription('Shows detailed information about the bot.'), 10 | async execute(interaction, client) { 11 | const embed = new EmbedBuilder() 12 | .setTitle('🤖 Bot Information') 13 | .setColor('Blurple') 14 | .addFields( 15 | { name: 'Name', value: client.user.username, inline: true }, 16 | { name: 'Tag', value: client.user.discriminator, inline: true }, 17 | { name: 'Ping', value: `${client.ws.ping}ms`, inline: true }, 18 | { name: 'Uptime', value: ``, inline: true }, 19 | { name: 'Servers', value: `${client.guilds.cache.size}`, inline: true }, 20 | { name: 'Users', value: `${client.users.cache.size}`, inline: true }, 21 | { name: 'Node.js', value: `${process.version}`, inline: true }, 22 | { name: 'Platform', value: `${os.platform()} (${os.arch()})`, inline: true }, 23 | { name: 'RAM Usage', value: `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`, inline: true }, 24 | { name: 'Version', value: `v${packageJson.version}`, inline: true } 25 | ) 26 | .setFooter({ text: `Made by ${packageJson.author}` }); 27 | 28 | await interaction.reply({ embeds: [embed] }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /events/messageCreate.js: -------------------------------------------------------------------------------- 1 | const GuildSettings = require('../models/GuildSettings'); 2 | 3 | const userMessages = new Map(); 4 | const linkRegex = /(https?:\/\/[^\s]+)/g; 5 | 6 | module.exports = { 7 | name: 'messageCreate', 8 | async execute(message) { 9 | if (message.author.bot || !message.guild) return; 10 | 11 | const settings = await GuildSettings.findOne({ guildId: message.guild.id }); 12 | if (!settings?.automodEnabled) return; 13 | 14 | // Anti-Link 15 | if (settings.antiLink && linkRegex.test(message.content)) { 16 | try { 17 | await message.delete(); 18 | await message.channel.send({ 19 | content: `🚫 No links allowed here, <@${message.author.id}>!`, 20 | }); 21 | } catch (err) { 22 | console.error('Error deleting link message:', err); 23 | } 24 | } 25 | 26 | // Anti-Spam 27 | if (settings.antiSpam) { 28 | const now = Date.now(); 29 | const key = `${message.guild.id}-${message.author.id}`; 30 | if (!userMessages.has(key)) userMessages.set(key, []); 31 | const timestamps = userMessages.get(key); 32 | 33 | timestamps.push(now); 34 | const recent = timestamps.filter(ts => now - ts < 5000); 35 | userMessages.set(key, recent); 36 | 37 | if (recent.length >= 5) { 38 | try { 39 | await message.delete(); 40 | await message.channel.send({ 41 | content: `⚠️ Stop spamming, <@${message.author.id}>!`, 42 | }); 43 | } catch (err) { 44 | console.error('Error deleting spam message:', err); 45 | } 46 | } 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /commands/moderation/mute.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('mute') 6 | .setDescription('Mute a member') 7 | .addUserOption(option => 8 | option.setName('user') 9 | .setDescription('The member to mute') 10 | .setRequired(true)) 11 | .addStringOption(option => 12 | option.setName('reason') 13 | .setDescription('Reason for mute') 14 | .setRequired(false)) 15 | .setDefaultMemberPermissions(PermissionFlagsBits.MuteMembers), 16 | 17 | async execute(interaction) { 18 | const member = interaction.options.getMember('user'); 19 | const reason = interaction.options.getString('reason') || 'No reason provided'; 20 | 21 | if (!member) return interaction.reply({ content: 'User not found.', ephemeral: true }); 22 | 23 | try { 24 | await member.timeout(10 * 60 * 1000, reason); // 10 minutes timeout 25 | const embed = new EmbedBuilder() 26 | .setTitle('🔇 Member Muted') 27 | .setColor('Orange') 28 | .addFields( 29 | { name: 'User', value: `${member.user.tag}`, inline: true }, 30 | { name: 'Reason', value: reason, inline: true }, 31 | { name: 'Duration', value: '10 minutes', inline: true }, 32 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true } 33 | ) 34 | .setTimestamp(); 35 | 36 | await interaction.reply({ embeds: [embed] }); 37 | } catch (err) { 38 | console.error(err); 39 | interaction.reply({ content: 'Failed to mute the user.', ephemeral: true }); 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /commands/moderation/kick.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const logToChannel = require('../../utils/logToChannel'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('kick') 7 | .setDescription('Kick a member') 8 | .addUserOption(option => 9 | option.setName('user').setDescription('User to kick').setRequired(true)) 10 | .addStringOption(option => 11 | option.setName('reason').setDescription('Reason').setRequired(false)) 12 | .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), 13 | 14 | async execute(interaction) { 15 | const member = interaction.options.getMember('user'); 16 | const reason = interaction.options.getString('reason') || 'No reason provided'; 17 | 18 | if (!member.kickable) { 19 | return interaction.reply({ content: 'I cannot kick this user.', ephemeral: true }); 20 | } 21 | 22 | await member.kick(reason); 23 | 24 | const embed = new EmbedBuilder() 25 | .setTitle('👢 Member Kicked') 26 | .setDescription(`${member.user.tag} was kicked.`) 27 | .addFields({ name: 'Reason', value: reason }) 28 | .setColor('Orange') 29 | .setTimestamp(); 30 | 31 | await interaction.reply({ embeds: [embed] }); 32 | 33 | await logToChannel(interaction.guild, { 34 | title: '👢 User Kicked', 35 | fields: [ 36 | { name: 'User', value: `<@${member.id}>`, inline: true }, 37 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, 38 | { name: 'Reason', value: reason } 39 | ], 40 | color: 'Orange' 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /commands/moderation/ban.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const logToChannel = require('../../utils/logToChannel'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('ban') 7 | .setDescription('Ban a member') 8 | .addUserOption(option => 9 | option.setName('user').setDescription('User to ban').setRequired(true)) 10 | .addStringOption(option => 11 | option.setName('reason').setDescription('Reason').setRequired(false)) 12 | .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), 13 | 14 | async execute(interaction) { 15 | const user = interaction.options.getUser('user'); 16 | const reason = interaction.options.getString('reason') || 'No reason provided'; 17 | 18 | try { 19 | await interaction.guild.members.ban(user.id, { reason }); 20 | 21 | const embed = new EmbedBuilder() 22 | .setTitle('🔨 Member Banned') 23 | .setDescription(`${user.tag} has been banned.`) 24 | .addFields({ name: 'Reason', value: reason }) 25 | .setColor('Red') 26 | .setTimestamp(); 27 | 28 | await interaction.reply({ embeds: [embed] }); 29 | 30 | await logToChannel(interaction.guild, { 31 | title: '🔨 User Banned', 32 | fields: [ 33 | { name: 'User', value: `<@${user.id}>`, inline: true }, 34 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, 35 | { name: 'Reason', value: reason } 36 | ], 37 | color: 'Red' 38 | }); 39 | } catch (err) { 40 | await interaction.reply({ content: `Failed to ban user: ${err.message}`, ephemeral: true }); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /commands/moderation/unban.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const logToChannel = require('../../utils/logToChannel'); 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('unban') 7 | .setDescription('Unban a user by ID') 8 | .addStringOption(option => 9 | option.setName('user_id').setDescription('User ID to unban').setRequired(true)) 10 | .addStringOption(option => 11 | option.setName('reason').setDescription('Reason').setRequired(false)) 12 | .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), 13 | 14 | async execute(interaction) { 15 | const userId = interaction.options.getString('user_id'); 16 | const reason = interaction.options.getString('reason') || 'No reason provided'; 17 | 18 | try { 19 | await interaction.guild.members.unban(userId, reason); 20 | 21 | const embed = new EmbedBuilder() 22 | .setTitle('🔓 Member Unbanned') 23 | .setDescription(`Unbanned <@${userId}>`) 24 | .addFields({ name: 'Reason', value: reason }) 25 | .setColor('Green') 26 | .setTimestamp(); 27 | 28 | await interaction.reply({ embeds: [embed] }); 29 | 30 | await logToChannel(interaction.guild, { 31 | title: '🔓 User Unbanned', 32 | fields: [ 33 | { name: 'User', value: `<@${userId}>`, inline: true }, 34 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, 35 | { name: 'Reason', value: reason } 36 | ], 37 | color: 'Green' 38 | }); 39 | } catch (err) { 40 | await interaction.reply({ content: `Unban failed: ${err.message}`, ephemeral: true }); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /commands/moderation/warn.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); 2 | const Warn = require('../../models/Warn'); 3 | const logToChannel = require('../../utils/logToChannel'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('warn') 8 | .setDescription('Warn a user') 9 | .addUserOption(option => option.setName('user').setDescription('User to warn').setRequired(true)) 10 | .addStringOption(option => option.setName('reason').setDescription('Reason for the warning').setRequired(true)) 11 | .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), 12 | 13 | async execute(interaction) { 14 | const user = interaction.options.getUser('user'); 15 | const reason = interaction.options.getString('reason'); 16 | 17 | let warningData = await Warn.findOne({ userId: user.id, guildId: interaction.guild.id }); 18 | if (!warningData) { 19 | warningData = new Warn({ 20 | userId: user.id, 21 | guildId: interaction.guild.id, 22 | warnings: [] 23 | }); 24 | } 25 | 26 | warningData.warnings.push({ 27 | modId: interaction.user.id, 28 | reason: reason, 29 | date: new Date() 30 | }); 31 | 32 | await warningData.save(); 33 | 34 | const embed = new EmbedBuilder() 35 | .setTitle('⚠️ Member Warned') 36 | .setDescription(`Warned ${user.tag} for: **${reason}**`) 37 | .setColor('Orange') 38 | .setTimestamp(); 39 | 40 | await interaction.reply({ embeds: [embed] }); 41 | 42 | await logToChannel(interaction.guild, { 43 | title: '⚠️ User Warned', 44 | fields: [ 45 | { name: 'User', value: `<@${user.id}>`, inline: true }, 46 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, 47 | { name: 'Reason', value: reason } 48 | ], 49 | color: 'Orange' 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /commands/owner/reload.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('reload') 8 | .setDescription('Reload a command (Bot Owner only)') 9 | .addStringOption(option => 10 | option.setName('command') 11 | .setDescription('Name of the command to reload') 12 | .setRequired(true)), 13 | 14 | async execute(interaction) { 15 | if (interaction.user.id !== '682981714523586606') return interaction.reply({ content: '❌ You are not authorized.', ephemeral: true }); 16 | 17 | const commandName = interaction.options.getString('command').toLowerCase(); 18 | const command = interaction.client.commands.get(commandName); 19 | 20 | if (!command) { 21 | return interaction.reply({ content: `❌ Command \`${commandName}\` not found.`, ephemeral: true }); 22 | } 23 | 24 | const commandFoldersPath = path.join(__dirname, '../'); 25 | const folder = fs.readdirSync(commandFoldersPath).find(folder => fs.existsSync(`${commandFoldersPath}/${folder}/${commandName}.js`)); 26 | 27 | if (!folder) return interaction.reply({ content: '❌ Command file not found.', ephemeral: true }); 28 | 29 | delete require.cache[require.resolve(`../${folder}/${commandName}.js`)]; 30 | 31 | try { 32 | const newCommand = require(`../${folder}/${commandName}.js`); 33 | interaction.client.commands.set(newCommand.data.name, newCommand); 34 | await interaction.reply({ content: `✅ Successfully reloaded \`${commandName}\`.` }); 35 | } catch (error) { 36 | console.error(error); 37 | await interaction.reply({ content: `❌ Error while reloading \`${commandName}\`: \n\`${error.message}\``, ephemeral: true }); 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /commands/moderation/timeout.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js'); 2 | const ms = require('ms'); 3 | const logToChannel = require('../../utils/logToChannel'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('timeout') 8 | .setDescription('Timeout a member') 9 | .addUserOption(option => 10 | option.setName('user').setDescription('User to timeout').setRequired(true)) 11 | .addStringOption(option => 12 | option.setName('duration').setDescription('Time (e.g., 1h, 30m)').setRequired(true)) 13 | .addStringOption(option => 14 | option.setName('reason').setDescription('Reason').setRequired(false)) 15 | .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), 16 | 17 | async execute(interaction) { 18 | const member = interaction.options.getMember('user'); 19 | const duration = ms(interaction.options.getString('duration')); 20 | const reason = interaction.options.getString('reason') || 'No reason provided'; 21 | 22 | if (!duration || duration < 10000 || duration > 28 * 24 * 60 * 60 * 1000) { 23 | return interaction.reply({ content: 'Duration must be between 10s and 28d.', ephemeral: true }); 24 | } 25 | 26 | try { 27 | await member.timeout(duration, reason); 28 | 29 | const embed = new EmbedBuilder() 30 | .setTitle('⏱️ Member Timed Out') 31 | .addFields( 32 | { name: 'User', value: `<@${member.id}>`, inline: true }, 33 | { name: 'Duration', value: interaction.options.getString('duration'), inline: true }, 34 | { name: 'Reason', value: reason } 35 | ) 36 | .setColor('Blue') 37 | .setTimestamp(); 38 | 39 | await interaction.reply({ embeds: [embed] }); 40 | 41 | await logToChannel(interaction.guild, { 42 | title: '⏱️ User Timed Out', 43 | fields: [ 44 | { name: 'User', value: `<@${member.id}>`, inline: true }, 45 | { name: 'Duration', value: interaction.options.getString('duration'), inline: true }, 46 | { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, 47 | { name: 'Reason', value: reason } 48 | ], 49 | color: 'Blue' 50 | }); 51 | } catch (err) { 52 | await interaction.reply({ content: `Timeout failed: ${err.message}`, ephemeral: true }); 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { Events } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: Events.InteractionCreate, 5 | 6 | async execute(interaction) { 7 | try { 8 | if (interaction.isChatInputCommand()) { 9 | const command = interaction.client.commands.get(interaction.commandName); 10 | 11 | if (!command) { 12 | console.error(`No command matching ${interaction.commandName} was found.`); 13 | return await interaction.reply({ content: '❌ This command is not available.', ephemeral: true }); 14 | } 15 | 16 | await command.execute(interaction); 17 | 18 | } else if (interaction.isButton()) { 19 | // Special case: allow message component collectors (like /help) to handle their own buttons 20 | if (interaction.message?.interaction?.user.id === interaction.user.id) return; 21 | 22 | const button = interaction.client.buttons?.get(interaction.customId); 23 | if (!button) { 24 | console.error(`No button handler matching ${interaction.customId} was found.`); 25 | return await interaction.reply({ content: '❌ This button is not working.', ephemeral: true }); 26 | } 27 | 28 | await button.execute(interaction); 29 | 30 | } else if (interaction.isStringSelectMenu()) { 31 | const menu = interaction.client.selectMenus?.get(interaction.customId); 32 | if (!menu) { 33 | console.error(`No select menu handler matching ${interaction.customId} was found.`); 34 | return await interaction.reply({ content: '❌ This select menu is broken.', ephemeral: true }); 35 | } 36 | 37 | await menu.execute(interaction); 38 | 39 | } else if (interaction.isModalSubmit()) { 40 | const modal = interaction.client.modals?.get(interaction.customId); 41 | if (!modal) { 42 | console.error(`No modal handler matching ${interaction.customId} was found.`); 43 | return await interaction.reply({ content: '❌ This modal is invalid.', ephemeral: true }); 44 | } 45 | 46 | await modal.execute(interaction); 47 | } 48 | } catch (error) { 49 | console.error(`Error handling interaction:`, error); 50 | 51 | try { 52 | if (interaction.replied || interaction.deferred) { 53 | await interaction.followUp({ content: '❌ An unexpected error occurred. Please try again later.', ephemeral: true }); 54 | } else { 55 | await interaction.reply({ content: '❌ An unexpected error occurred. Please try again later.', ephemeral: true }); 56 | } 57 | } catch (err) { 58 | console.error('Failed to send error message:', err); 59 | } 60 | } 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 🤖 Automod Bot 3 | 4 | A powerful moderation Discord bot built with Discord.js v14, Mongoose, designed to keep your server clean, safe, and automated. 5 | 6 | ![Automod Banner](https://dcflipybird.netlify.app/botbanner.png) 7 | 8 | --- 9 | [invite the bot](https://discord.com/oauth2/authorize?client_id=1363943273374154842&permissions=8&scope=bot%20applications.Commands) 10 | 11 | ## GitAds Sponsored 12 | [![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=khanmanan/automod-bot@github)](https://gitads.dev/v1/ad-track?source=khanmanan/automod-bot@github) 13 | 14 | 15 | ## 🚀 Features 16 | 17 | - Slash commands with auto-detection and pagination help menu 18 | - Automod with toggle system per server (anti-link, anti-spam, anti-mention, etc.) 19 | - Database logging (MongoDB + Mongoose) 20 | - Moderation tools: Warn, Kick, Ban, TempBan, Mute, Unmute 21 | - Logging system for actions like message delete/edit 22 | - Web dashboard integration (soon) 23 | - Discord OAuth for authentication (in progress) 24 | 25 | --- 26 | 27 | ## 🛠 Setup 28 | 29 | ### Prerequisites 30 | 31 | - Node.js v18 or higher 32 | - MongoDB URI (local or cloud) 33 | - Discord Bot Token 34 | - Firebase setup (optional) 35 | 36 | ### Installation 37 | 38 | ```bash 39 | git clone https://github.com/Khanmanan/automod-bot.git 40 | cd automod-bot 41 | npm install 42 | ``` 43 | 44 | ### Environment Setup 45 | 46 | Create a `.env` file in the root: 47 | 48 | ```env 49 | TOKEN=your_discord_token 50 | MONGO_URI=your_mongo_connection 51 | CLIENT_ID=your_bot_client_id 52 | GUILD_ID=your_guild_id (for testing commands) 53 | ``` 54 | 55 | ### Running the Bot 56 | 57 | ```bash 58 | node index.js 59 | ``` 60 | 61 | To deploy slash commands globally or per guild: 62 | 63 | ```bash 64 | node deploy.js 65 | ``` 66 | 67 | --- 68 | 69 | ## 🧩 Commands 70 | 71 | All commands are organized in the `/commands` folder and auto-loaded by category. Use `/help` to view all. 72 | 73 | - Admin 74 | - Info 75 | - Moderation 76 | - General 77 | - Owner 78 | 79 | --- 80 | 81 | ## 📌 TODO List 82 | 83 | ### 🔧 Core Features 84 | 85 | - [x] Auto-load slash commands by category 86 | - [x] Help command with pagination and buttons 87 | - [x] Logging system for moderation actions 88 | - [x] Toggle-based automod (on/off per rule) 89 | - [x] Rate-limited buttons on help command 90 | 91 | ### 🌐 Web Dashboard 92 | 93 | - [ ] Discord OAuth2 login 94 | - [ ] Guild selector 95 | - [ ] Toggle Automod settings from UI 96 | - [ ] View mod logs and history 97 | 98 | ### 🧪 Future Ideas 99 | 100 | - [ ] Captcha on join (anti-raid) 101 | - [ ] Dashboard theming (Dark ) 102 | - [ ] Analytics dashboard (number of mutes/bans per day) 103 | - [ ] Multi-language support 104 | 105 | --- 106 |

Bot support server

107 | 108 |
109 | 110 |

111 |
112 | 113 | 114 | 115 | ## 👤 Author 116 | 117 | **Khanmanan** 118 | GitHub: [@Khanmanan](https://github.com/Khanmanan) 119 | Bot Repo: [automod-bot](https://github.com/Khanmanan/automod-bot) 120 | 121 | --- 122 | 123 | ## 📜 License 124 | 125 | MIT © 2025 Khanmanan 126 | -------------------------------------------------------------------------------- /commands/general/help.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | data: new SlashCommandBuilder() 7 | .setName('help') 8 | .setDescription('Show all bot commands!'), 9 | 10 | async execute(interaction, client) { 11 | const categories = []; 12 | const commands = []; 13 | 14 | const commandsPath = path.join(__dirname, '..'); // Adjust if needed 15 | const folders = fs.readdirSync(commandsPath); 16 | 17 | for (const folder of folders) { 18 | const folderPath = path.join(commandsPath, folder); 19 | if (fs.lstatSync(folderPath).isDirectory()) { 20 | const files = fs.readdirSync(folderPath).filter(file => file.endsWith('.js')); 21 | const cmds = []; 22 | 23 | for (const file of files) { 24 | const cmd = require(path.join(folderPath, file)); 25 | cmds.push({ 26 | name: cmd.data?.name || file.replace('.js', ''), 27 | description: cmd.data?.description || "No description provided." 28 | }); 29 | } 30 | 31 | categories.push(folder); 32 | commands.push(cmds); 33 | } 34 | } 35 | 36 | const pages = categories.map((cat, idx) => { 37 | return new EmbedBuilder() 38 | .setTitle("Help Menu") 39 | .setDescription(`📌 **Use the buttons below to navigate!**\n\n__**${cat.toUpperCase()} COMMANDS -**__\n\n` + 40 | commands[idx].map(cmd => `**/${cmd.name}**\n${cmd.description}`).join('\n\n') 41 | ) 42 | .setColor("#2f3136") 43 | .setFooter({ text: `Page 1/${categories.length} • Support: https://cwkbot.fun/discord` }); 44 | }); 45 | 46 | let page = 0; 47 | const getPageEmbed = (pageIndex) => { 48 | return pages[pageIndex].setFooter({ text: `Page ${pageIndex + 1}/${pages.length} • Support: https://cwkbot.fun/discord` }); 49 | }; 50 | 51 | // Buttons 52 | const first = new ButtonBuilder().setCustomId('first').setLabel('⏮ First').setStyle(ButtonStyle.Secondary); 53 | const previous = new ButtonBuilder().setCustomId('previous').setLabel('⬅ Previous').setStyle(ButtonStyle.Primary); 54 | const next = new ButtonBuilder().setCustomId('next').setLabel('Next ➡').setStyle(ButtonStyle.Primary); 55 | const last = new ButtonBuilder().setCustomId('last').setLabel('Last ⏭').setStyle(ButtonStyle.Secondary); 56 | 57 | const row = new ActionRowBuilder().addComponents(first, previous, next, last); 58 | 59 | const msg = await interaction.reply({ embeds: [getPageEmbed(page)], components: [row], ephemeral: true, fetchReply: true }); 60 | 61 | const collector = msg.createMessageComponentCollector({ time: 300_000 }); 62 | 63 | collector.on('collect', async i => { 64 | if (i.user.id !== interaction.user.id) { 65 | return i.reply({ content: "❌ You can't control this menu!", ephemeral: true }); 66 | } 67 | 68 | if (i.customId === 'first') page = 0; 69 | else if (i.customId === 'previous') page = page > 0 ? --page : pages.length - 1; 70 | else if (i.customId === 'next') page = (page + 1) % pages.length; 71 | else if (i.customId === 'last') page = pages.length - 1; 72 | 73 | await i.update({ embeds: [getPageEmbed(page)], components: [row] }); 74 | }); 75 | 76 | collector.on('end', async () => { 77 | msg.edit({ components: [] }).catch(() => {}); 78 | }); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { Client, GatewayIntentBits, Collection, Partials, EmbedBuilder } = require('discord.js'); 3 | const fs = require('fs'); 4 | const mongoose = require('mongoose'); 5 | const path = require('path'); 6 | const express = require('express'); 7 | const deploy = require ('./deploy-commands.js') 8 | const client = new Client({ 9 | intents: [ 10 | GatewayIntentBits.Guilds, 11 | GatewayIntentBits.GuildMessages, 12 | GatewayIntentBits.MessageContent, 13 | GatewayIntentBits.GuildMembers, 14 | GatewayIntentBits.GuildModeration, 15 | ], 16 | partials: [Partials.Message, Partials.Channel, Partials.GuildMember], 17 | }); 18 | 19 | client.commands = new Collection(); 20 | 21 | // Load commands 22 | const commandsPath = path.join(__dirname, 'commands'); 23 | fs.readdirSync(commandsPath).forEach(dir => { 24 | const commandFiles = fs.readdirSync(path.join(commandsPath, dir)).filter(file => file.endsWith('.js')); 25 | for (const file of commandFiles) { 26 | const command = require(path.join(commandsPath, dir, file)); 27 | if (command.data && command.execute) { 28 | client.commands.set(command.data.name, command); 29 | } 30 | } 31 | }); 32 | 33 | // Status event 34 | client.once('ready', () => { 35 | console.log(`✅ Bot ready as ${client.user.tag}`); 36 | client.user.setPresence({ 37 | activities: [{ name: 'your server 👀', type: 3 }], 38 | status: 'online' 39 | }); 40 | }); 41 | 42 | // Clean interaction handler 43 | client.on('interactionCreate', async interaction => { 44 | if (interaction.isChatInputCommand()) { 45 | const command = client.commands.get(interaction.commandName); 46 | if (!command) return; 47 | 48 | try { 49 | await command.execute(interaction, client); 50 | } catch (error) { 51 | console.error(`Error executing command ${interaction.commandName}:`, error); 52 | const errorEmbed = new EmbedBuilder() 53 | .setTitle('❌ Command Error') 54 | .setDescription('There was an error executing this command.') 55 | .setColor('Red'); 56 | 57 | if (interaction.replied || interaction.deferred) { 58 | await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); 59 | } else { 60 | await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); 61 | } 62 | } 63 | } 64 | 65 | // Handle other types of interactions here if needed 66 | // Example: button/menu interactions can go here 67 | }); 68 | 69 | // MongoDB connection 70 | mongoose.connect(process.env.MONGO_URI, { 71 | useNewUrlParser: true, 72 | useUnifiedTopology: true, 73 | }).then(() => { 74 | console.log('✅ Connected to MongoDB'); 75 | client.login(process.env.DISCORD_TOKEN); 76 | }).catch(err => { 77 | console.error('❌ MongoDB connection error:', err); 78 | }); 79 | 80 | // Event loader 81 | const eventsPath = path.join(__dirname, 'events'); 82 | if (fs.existsSync(eventsPath)) { 83 | const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); 84 | 85 | for (const file of eventFiles) { 86 | const event = require(path.join(eventsPath, file)); 87 | if (event.once) { 88 | client.once(event.name, (...args) => event.execute(...args, client)); 89 | } else { 90 | client.on(event.name, (...args) => event.execute(...args, client)); 91 | } 92 | } 93 | } 94 | 95 | // KeepAlive server for Replit 96 | const app = express(); 97 | app.get('/', (req, res) => { 98 | res.send('Bot is alive!'); 99 | }); 100 | const PORT = process.env.PORT || 3000; 101 | app.listen(PORT, () => { 102 | console.log(`[+] KeepAlive server running on port ${PORT}`); 103 | }); 104 | 105 | // Error catcher 106 | process.on('unhandledRejection', error => { 107 | console.error('Unhandled promise rejection:', error); 108 | }); 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | NOTE 15 | 16 | Collaborators and all contributors alongwith the copyright holder have the discretion as to submit takedown requests upon any material that is possibly infringing 17 | the license holder's rights. 18 | 19 | 20 | Statement of Purpose 21 | 22 | The laws of most jurisdictions throughout the world automatically confer 23 | exclusive Copyright and Related Rights (defined below) upon the creator 24 | and subsequent owner(s) (each and all, an "owner") of an original work of 25 | authorship and/or a database (each, a "Work"). 26 | 27 | Certain owners wish to permanently relinquish those rights to a Work for 28 | the purpose of contributing to a commons of creative, cultural and 29 | scientific works ("Commons") that the public can reliably and without fear 30 | of later claims of infringement build upon, modify, incorporate in other 31 | works, reuse and redistribute as freely as possible in any form whatsoever 32 | and for any purposes, including without limitation commercial purposes. 33 | These owners may contribute to the Commons to promote the ideal of a free 34 | culture and the further production of creative, cultural and scientific 35 | works, or to gain reputation or greater distribution for their Work in 36 | part through the use and efforts of others. 37 | 38 | For these and/or other purposes and motivations, and without any 39 | expectation of additional consideration or compensation, the person 40 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 41 | is an owner of Copyright and Related Rights in the Work, voluntarily 42 | elects to apply CC0 to the Work and publicly distribute the Work under its 43 | terms, with knowledge of his or her Copyright and Related Rights in the 44 | Work and the meaning and intended legal effect of CC0 on those rights. 45 | 46 | 1. Copyright and Related Rights. A Work made available under CC0 may be 47 | protected by copyright and related or neighboring rights ("Copyright and 48 | Related Rights"). Copyright and Related Rights include, but are not 49 | limited to, the following: 50 | 51 | i. the right to reproduce, adapt, distribute, perform, display, 52 | communicate, and translate a Work; 53 | ii. moral rights retained by the original author(s) and/or performer(s); 54 | iii. publicity and privacy rights pertaining to a person's image or 55 | likeness depicted in a Work; 56 | iv. rights protecting against unfair competition in regards to a Work, 57 | subject to the limitations in paragraph 4(a), below; 58 | v. rights protecting the extraction, dissemination, use and reuse of data 59 | in a Work; 60 | vi. database rights (such as those arising under Directive 96/9/EC of the 61 | European Parliament and of the Council of 11 March 1996 on the legal 62 | protection of databases, and under any national implementation 63 | thereof, including any amended or successor version of such 64 | directive); and 65 | vii. other similar, equivalent or corresponding rights throughout the 66 | world based on applicable law or treaty, and any national 67 | implementations thereof. 68 | 69 | 2. Waiver. To the greatest extent permitted by, but not in contravention 70 | of, applicable law, Affirmer hereby overtly, fully, permanently, 71 | irrevocably and unconditionally waives, abandons, and surrenders all of 72 | Affirmer's Copyright and Related Rights and associated claims and causes 73 | of action, whether now known or unknown (including existing as well as 74 | future claims and causes of action), in the Work (i) in all territories 75 | worldwide, (ii) for the maximum duration provided by applicable law or 76 | treaty (including future time extensions), (iii) in any current or future 77 | medium and for any number of copies, and (iv) for any purpose whatsoever, 78 | including without limitation commercial, advertising or promotional 79 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 80 | member of the public at large and to the detriment of Affirmer's heirs and 81 | successors, fully intending that such Waiver shall not be subject to 82 | revocation, rescission, cancellation, termination, or any other legal or 83 | equitable action to disrupt the quiet enjoyment of the Work by the public 84 | as contemplated by Affirmer's express Statement of Purpose. 85 | 86 | 3. Public License Fallback. Should any part of the Waiver for any reason 87 | be judged legally invalid or ineffective under applicable law, then the 88 | Waiver shall be preserved to the maximum extent permitted taking into 89 | account Affirmer's express Statement of Purpose. In addition, to the 90 | extent the Waiver is so judged Affirmer hereby grants to each affected 91 | person a royalty-free, non transferable, non sublicensable, non exclusive, 92 | irrevocable and unconditional license to exercise Affirmer's Copyright and 93 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 94 | maximum duration provided by applicable law or treaty (including future 95 | time extensions), (iii) in any current or future medium and for any number 96 | of copies, and (iv) for any purpose whatsoever, including without 97 | limitation commercial, advertising or promotional purposes (the 98 | "License"). The License shall be deemed effective as of the date CC0 was 99 | applied by Affirmer to the Work. Should any part of the License for any 100 | reason be judged legally invalid or ineffective under applicable law, such 101 | partial invalidity or ineffectiveness shall not invalidate the remainder 102 | of the License, and in such case Affirmer hereby affirms that he or she 103 | will not (i) exercise any of his or her remaining Copyright and Related 104 | Rights in the Work or (ii) assert any associated claims and causes of 105 | action with respect to the Work, in either case contrary to Affirmer's 106 | express Statement of Purpose. 107 | 108 | 4. Limitations and Disclaimers. 109 | 110 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 111 | surrendered, licensed or otherwise affected by this document. 112 | b. Affirmer offers the Work as-is and makes no representations or 113 | warranties of any kind concerning the Work, express, implied, 114 | statutory or otherwise, including without limitation warranties of 115 | title, merchantability, fitness for a particular purpose, non 116 | infringement, or the absence of latent or other defects, accuracy, or 117 | the present or absence of errors, whether or not discoverable, all to 118 | the greatest extent permissible under applicable law. 119 | c. Affirmer disclaims responsibility for clearing rights of other persons 120 | that may apply to the Work or any use thereof, including without 121 | limitation any person's Copyright and Related Rights in the Work. 122 | Further, Affirmer disclaims responsibility for obtaining any necessary 123 | consents, permissions or other rights required for any use of the 124 | Work. 125 | d. Affirmer understands and acknowledges that Creative Commons is not a 126 | party to this document and has no duty or obligation with respect to 127 | this CC0 or use of the Work. 128 | --------------------------------------------------------------------------------