This commit is contained in:
st committed 2026-06-28 21:55:39 -05:00
commit 105dd2dc4d
16 files changed
+1393 -34498

No files matched your search

+8 -8
View File
@@ -1,8 +1,8 @@
# Discord Bot Token — get yours at https://discord.com/developers/applications
BOT_TOKEN=your_bot_token_here
# Command prefix (default: !)
PREFIX=!
# Guild ID for test server (optional — used for dev utilities)
GUILD_ID=your_guild_id_here
# Discord Bot Token — get yours at https://discord.com/developers/applications
BOT_TOKEN=your_bot_token_here
# Command prefix (default: !)
PREFIX=!
# Guild ID for test server (optional — used for dev utilities)
GUILD_ID=your_guild_id_here
+3 -1
View File
@@ -141,4 +141,6 @@ dist
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
items.json
db.json
+121 -121
View File
@@ -1,121 +1,121 @@
# twitter (v3)
a discord.js v14 moderation (+ more) bot
---
## features
- prefix command system — commands triggered by a configurable prefix (default `!`)
- auto-loading — commands and events are loaded automatically from their folders
- aliases — each command can define one or more aliases
- winston logging — coloured console output + rolling log files (`logs/combined.log`, `logs/error.log`)
- global error handling — unhandled rejections and uncaught exceptions are caught and logged
---
## project structure
```
src/
├── index.js # entry point — creates the client, loads handlers
├── commands/
│ ├── help.js # !help — lists commands or shows command detail
│ └── ping.js # !ping — latency check
├── events/
│ ├── ready.js # fires once when the bot is online
│ └── messageCreate.js # parses every message for prefix commands
├── handlers/
│ ├── commandHandler.js # reads src/commands/ and registers commands
│ └── eventHandler.js # reads src/events/ and registers discord events
└── utils/
└── logger.js # winston logger instance
logs/ # created automatically at runtime (git-ignored)
```
---
## setup
### 1. clone & install dependencies
```bash
git clone https://github.com/yhqe/twitter_v3.git
cd twitter_v3
npm install
```
### 2. configure environment variables
```bash
cp .env.example .env
```
open `.env` and fill in:
| variable | required | description |
|-------------|----------|------------------------------------------------------|
| `BOT_TOKEN` | yes | your bot token from the discord developer portal |
| `PREFIX` | no | command prefix (default: `!`) |
| `GUILD_ID` | no | test server id (handy for dev stuff) |
### 3. enable the message content intent
go to your application in the [discord developer portal](https://discord.com/developers/applications), open **bot → privileged gateway intents**, and enable **message content intent**. you need this for prefix commands.
### 4. start the bot
```bash
# production
npm start
# development (auto-restarts on file changes, node.js 18+)
npm run dev
```
---
## adding commands
create a file in `src/commands/`. the handler will load it automatically on next start.
```js
'use strict';
module.exports = {
name: 'example', // primary command name (also used as the key)
description: 'An example command.',
aliases: ['ex'], // optional aliases
usage: 'example <text>', // shown in !help example
async execute(client, message, args) {
await message.reply(`You said: ${args.join(' ')}`);
},
};
```
## adding events
create a file in `src/events/`. set `once: true` if it should fire only once (e.g. `ready`).
```js
'use strict';
const { Events } = require('discord.js');
module.exports = {
name: Events.GuildMemberAdd,
async execute(client, member) {
console.log(`${member.user.tag} joined ${member.guild.name}`);
},
};
```
---
## logging
log files are written to the `logs/` directory (git-ignored). the log level defaults to `info` and can be overridden with the `LOG_LEVEL` environment variable (e.g. `LOG_LEVEL=debug npm start`).
# twitter (v3)
a discord.js v14 moderation (+ more) bot
---
## features
- prefix command system — commands triggered by a configurable prefix (default `!`)
- auto-loading — commands and events are loaded automatically from their folders
- aliases — each command can define one or more aliases
- winston logging — coloured console output + rolling log files (`logs/combined.log`, `logs/error.log`)
- global error handling — unhandled rejections and uncaught exceptions are caught and logged
---
## project structure
```
src/
├── index.js # entry point — creates the client, loads handlers
├── commands/
│ ├── help.js # !help — lists commands or shows command detail
│ └── ping.js # !ping — latency check
├── events/
│ ├── ready.js # fires once when the bot is online
│ └── messageCreate.js # parses every message for prefix commands
├── handlers/
│ ├── commandHandler.js # reads src/commands/ and registers commands
│ └── eventHandler.js # reads src/events/ and registers discord events
└── utils/
└── logger.js # winston logger instance
logs/ # created automatically at runtime (git-ignored)
```
---
## setup
### 1. clone & install dependencies
```bash
git clone https://github.com/yhqe/twitter_v3.git
cd twitter_v3
npm install
```
### 2. configure environment variables
```bash
cp .env.example .env
```
open `.env` and fill in:
| variable | required | description |
|-------------|----------|------------------------------------------------------|
| `BOT_TOKEN` | yes | your bot token from the discord developer portal |
| `PREFIX` | no | command prefix (default: `!`) |
| `GUILD_ID` | no | test server id (handy for dev stuff) |
### 3. enable the message content intent
go to your application in the [discord developer portal](https://discord.com/developers/applications), open **bot → privileged gateway intents**, and enable **message content intent**. you need this for prefix commands.
### 4. start the bot
```bash
# production
npm start
# development (auto-restarts on file changes, node.js 18+)
npm run dev
```
---
## adding commands
create a file in `src/commands/`. the handler will load it automatically on next start.
```js
'use strict';
module.exports = {
name: 'example', // primary command name (also used as the key)
description: 'An example command.',
aliases: ['ex'], // optional aliases
usage: 'example <text>', // shown in !help example
async execute(client, message, args) {
await message.reply(`You said: ${args.join(' ')}`);
},
};
```
## adding events
create a file in `src/events/`. set `once: true` if it should fire only once (e.g. `ready`).
```js
'use strict';
const { Events } = require('discord.js');
module.exports = {
name: Events.GuildMemberAdd,
async execute(client, member) {
console.log(`${member.user.tag} joined ${member.guild.name}`);
},
};
```
---
## logging
log files are written to the `logs/` directory (git-ignored). the log level defaults to `info` and can be overridden with the `LOG_LEVEL` environment variable (e.g. `LOG_LEVEL=debug npm start`).
-11
View File
@@ -1,11 +0,0 @@
{
"autoroles": {
"1133441692196950101": "1192595134026616894"
},
"autobans": {
"1133441692196950101": "1264634623950848158"
},
"prefixes": {
"1362160460253761536": "*"
}
}
-33156
View File
File diff suppressed because it is too large. Load diff
+609 -609
View File
File diff suppressed because it is too large. Load diff
+139 -139
View File
@@ -1,139 +1,139 @@
'use strict';
const { time } = require("discord.js");
const logger = require('../../utils/logger');
async function getAvailableCapes(username) {
const capes =[];
const mojangReq = await Promise.allSettled([
fetch(`https://api.ashcon.app/mojang/v2/user/${username}`),
]);
if (mojangReq.status === 'fulfilled' && mojangReq.value.ok) {
const mojangData = await mojangReq.value.json();
if (mojangData.textures && mojangData.textures.cape) {
capes.push({
source: 'Official (Equipped)',
url: mojangData.textures.cape.url
});
}
}
return capes;
}
module.exports = {
name: 'mc',
description: `request different information about a minecraft user`,
aliases: ['minecraft'],
usage: 'mc <username>',
async execute(client, message, args) {
var embed;
let sent = await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> fetching...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const username = args[0];
if (!username) {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please provide a valid minecraft username!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return await sent.edit({ content: null, embeds: [embed] });
}
const uuid_res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`);
const uuid = await uuid_res.json().then(res => res.id).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch uuid!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
const res = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`);
const data = await res.json().then(res => res).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch data!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
getAvailableCapes(`${data.name}`).then(capes => {
logger.debug(`found ${capes.length} cape(s):`);
logger.debug(capes);
});
if (!data) return await sent.edit({ content: null, embeds: [embed] });
const skin = await fetch (`https://minotar.net/helm/${data.name}/420.png`).then(res => res.url).catch(err => null);
const capes = data.properties[0].value;
const decoded_capes = Buffer.from(capes, 'base64').toString('utf-8');
const cape_url = JSON.parse(decoded_capes).textures.CAPE ? JSON.parse(decoded_capes).textures.CAPE.url : null;
logger.debug(`[mc] fetched data for "${username}": ${JSON.stringify(data)}`);
logger.debug(`[mc] fetched cape data for "${username}": ${JSON.stringify(cape_url)}`);
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**[${data.name}](https://namemc.com/profile/${data.name})**`,
fields: [
{ name: 'uuid', value: `${data.id}`, inline: true },
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
thumbnail: {
url: skin,
height: 1024,
width: 1024,
},
};
await sent.edit({ content: null, embeds: [embed] });
},
};
'use strict';
const { time } = require("discord.js");
const logger = require('../../utils/logger');
async function getAvailableCapes(username) {
const capes =[];
const mojangReq = await Promise.allSettled([
fetch(`https://api.ashcon.app/mojang/v2/user/${username}`),
]);
if (mojangReq.status === 'fulfilled' && mojangReq.value.ok) {
const mojangData = await mojangReq.value.json();
if (mojangData.textures && mojangData.textures.cape) {
capes.push({
source: 'Official (Equipped)',
url: mojangData.textures.cape.url
});
}
}
return capes;
}
module.exports = {
name: 'mc',
description: `request different information about a minecraft user`,
aliases: ['minecraft'],
usage: 'mc <username>',
async execute(client, message, args) {
var embed;
let sent = await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> fetching...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const username = args[0];
if (!username) {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please provide a valid minecraft username!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return await sent.edit({ content: null, embeds: [embed] });
}
const uuid_res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`);
const uuid = await uuid_res.json().then(res => res.id).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch uuid!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
const res = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`);
const data = await res.json().then(res => res).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch data!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
getAvailableCapes(`${data.name}`).then(capes => {
logger.debug(`found ${capes.length} cape(s):`);
logger.debug(capes);
});
if (!data) return await sent.edit({ content: null, embeds: [embed] });
const skin = await fetch (`https://minotar.net/helm/${data.name}/420.png`).then(res => res.url).catch(err => null);
const capes = data.properties[0].value;
const decoded_capes = Buffer.from(capes, 'base64').toString('utf-8');
const cape_url = JSON.parse(decoded_capes).textures.CAPE ? JSON.parse(decoded_capes).textures.CAPE.url : null;
logger.debug(`[mc] fetched data for "${username}": ${JSON.stringify(data)}`);
logger.debug(`[mc] fetched cape data for "${username}": ${JSON.stringify(cape_url)}`);
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**[${data.name}](https://namemc.com/profile/${data.name})**`,
fields: [
{ name: 'uuid', value: `${data.id}`, inline: true },
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
thumbnail: {
url: skin,
height: 1024,
width: 1024,
},
};
await sent.edit({ content: null, embeds: [embed] });
},
};
+1 -43
View File
@@ -48,7 +48,7 @@ module.exports = {
name: 'rbx',
description: `various commands related to roblox`,
aliases: ['roblox'],
usage: `rbx user <username>\n${env.PREFIX}rbx item <item id>\n${env.PREFIX}rbx group <group id>\n${env.PREFIX}rbx game <game id>\n${env.PREFIX}rbx avatar <username>`,
usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>',
async execute(client, message) {
var embed = {};
@@ -206,48 +206,6 @@ module.exports = {
timestamp: new Date(),
}
}
} else if (subcommand === 'avatar') {
try {
const user = data.data[0];
const a_res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${user.id}&size=720x720&format=Png&isCircular=false`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const a_data = await a_res.json();
logger.debug(`[rbx] fetched avatar for "${argument}": ${JSON.stringify(a_data)}`);
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**${user.displayName}**'s avatar`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
image: {
url: `${a_data.data[0].imageUrl}`
},
timestamp: new Date(),
};
} catch (err) {
logger.error(`[rbx] failed to fetch avatar for user id "${argument}":`, err);
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:no:1481811955592466492> could not fetch avatar data for user "${argument}"!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
}
} else if (subcommand === 'item') {
try {
const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, {
+102
View File
@@ -4,6 +4,108 @@ const { time } = require("discord.js");
const fs = require('fs');
const path = require('path');
module.exports = {
name: 'autoban',
description: `automatically ban a user if they join the server`,
aliases: ['ab', 'banish'],
usage: 'ban <user> [reason] [duration]',
async execute(client, message, args) {
if (!message.member.permissions.has('BanMembers')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const user = message.mentions.users.first() || await client.users.fetch(args[0]).catch(() => null);
if (!user) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please specify a user to autoban!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const reason = args.slice(1).join(' ') || 'autobanned (twitter)';
const dbPath = path.join(__dirname, '..', '..', '..', 'db.json');
let db = {};
if (fs.existsSync(dbPath)) {
db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
}
if (!db.autobans) {
db.autobans = {};
}
db.autobans[message.guild.id] = user.id;
try {
await fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> ${user.username} has been added to the autoban list!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
} catch (error) {
console.error(error);
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> an error occurred while trying to add the user to the autoban list!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
},
};
'use strict';
const { time } = require("discord.js");
const fs = require('fs');
const path = require('path');
module.exports = {
name: 'autoban',
description: `automatically ban a user if they join the server`,
+2 -2
View File
@@ -5,8 +5,8 @@ const { time } = require("discord.js");
module.exports = {
name: 'ban',
description: `ban a user from the current server`,
aliases: ['b'],
usage: 'ban <user> [reason]',
aliases: ['banish'],
usage: 'ban <user> [reason] [duration]',
async execute(client, message, args) {
if (!message.member.permissions.has('BanMembers')) {
+87 -87
View File
@@ -1,88 +1,88 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'kick',
description: `kick a user from the current server`,
aliases: ['kick'],
usage: 'kick <user> [reason]',
async execute(client, message, args) {
if (!message.member.permissions.has('KickMembers')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const user = message.mentions.users.first() || client.users.cache.get(args[0]);
if (!user) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please specify a user to ban!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
const duration = args[2] ? parseInt(args[2]) : null;
try {
await message.guild.members.kick(user, { reason });
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> ${user.username} has been kicked!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
} catch (error) {
console.error(error);
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> an error occurred while trying to ban the user!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
},
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'kick',
description: `kick a user from the current server`,
aliases: ['kick'],
usage: 'kick <user> [reason]',
async execute(client, message, args) {
if (!message.member.permissions.has('KickMembers')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const user = message.mentions.users.first() || client.users.cache.get(args[0]);
if (!user) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please specify a user to ban!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
const duration = args[2] ? parseInt(args[2]) : null;
try {
await message.guild.members.kick(user, { reason });
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> ${user.username} has been kicked!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
} catch (error) {
console.error(error);
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> an error occurred while trying to ban the user!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
},
};
+83 -83
View File
@@ -1,84 +1,84 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { time } = require("discord.js");
module.exports = {
name: 'autorole',
description: `assign a role to new members when they join the server`,
aliases: ['ar'],
usage: 'autorole <role>',
async execute(client, message, args) {
if (!message.member.permissions.has('Administrator')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]);
if (!role) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please provide a valid role!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
// add the role to a json file for the guild and save it to be used when new members join (if the role is already in the database, update it to the new role)
const dbPath = path.join(__dirname, '..', '..', '..', 'db.json');
let db = {};
if (fs.existsSync(dbPath)) {
db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
}
if (!db.autoroles) {
db.autoroles = {};
}
db.autoroles[message.guild.id] = role.id;
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> "${role.name}" has been set as the autorole!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
'use strict';
const fs = require('fs');
const path = require('path');
const { time } = require("discord.js");
module.exports = {
name: 'autorole',
description: `assign a role to new members when they join the server`,
aliases: ['ar'],
usage: 'autorole <role>',
async execute(client, message, args) {
if (!message.member.permissions.has('Administrator')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]);
if (!role) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please provide a valid role!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
// add the role to a json file for the guild and save it to be used when new members join (if the role is already in the database, update it to the new role)
const dbPath = path.join(__dirname, '..', '..', '..', 'db.json');
let db = {};
if (fs.existsSync(dbPath)) {
db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
}
if (!db.autoroles) {
db.autoroles = {};
}
db.autoroles[message.guild.id] = role.id;
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> "${role.name}" has been set as the autorole!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
};
+61 -61
View File
@@ -1,61 +1,61 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'purge',
description: `delete a specified number of messages from the current channel`,
aliases: ['clear', 'p'],
usage: 'purge <number>',
async execute(client, message) {
if (!message.member.permissions.has('ManageMessages')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
}
const args = message.content.split(' ').slice(1);
const amount = parseInt(args[0]) + 1;
if (isNaN(amount) || amount < 1 || amount > 100) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
description: '<:no:1481811955592466492> please provide a number between 1 and 100!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
}
const deletedMessages = await message.channel.bulkDelete(amount, true);
const reply = await message.channel.send({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:no:1481811955592466492> successfully deleted ${deletedMessages.size - 1} messages!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
setTimeout(() => reply.delete().catch(() => { }), 5000);
},
};
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'purge',
description: `delete a specified number of messages from the current channel`,
aliases: ['clear', 'p'],
usage: 'purge <number>',
async execute(client, message) {
if (!message.member.permissions.has('ManageMessages')) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
}
const args = message.content.split(' ').slice(1);
const amount = parseInt(args[0]) + 1;
if (isNaN(amount) || amount < 1 || amount > 100) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
description: '<:no:1481811955592466492> please provide a number between 1 and 100!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
}
const deletedMessages = await message.channel.bulkDelete(amount, true);
const reply = await message.channel.send({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:no:1481811955592466492> successfully deleted ${deletedMessages.size - 1} messages!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}],
});
setTimeout(() => reply.delete().catch(() => { }), 5000);
},
};
+53 -53
View File
@@ -1,53 +1,53 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { Events } = require('discord.js');
const guildMemberAddEvent = require('../events/guildMemberAdd');
const logger = require('../utils/logger');
/**
* Loads all event files from src/events and registers them on the client.
* Each event file must export: { name, once?, execute }
*
* @param {import('discord.js').Client} client
*/
function loadEvents(client) {
const eventsPath = path.join(__dirname, '..', 'events');
const eventFiles = fs
.readdirSync(eventsPath)
.filter((file) => file.endsWith('.js'));
let loaded = 0;
for (const file of eventFiles) {
try {
const event = require(path.join(eventsPath, file));
if (!event.name || typeof event.execute !== 'function') {
logger.warn(
`[eventhandler] skipping ${file}: missing "name" or "execute"`,
);
continue;
}
if (event.once) {
client.once(event.name, (...args) => event.execute(client, ...args));
} else {
client.on(event.name, (...args) => event.execute(client, ...args));
}
logger.debug(`[eventhandler] registered event: ${event.name}`);
loaded++;
} catch (err) {
logger.error(`[eventhandler] failed to load ${file}:`, err);
}
}
client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client));
loaded++;
logger.info(`[eventhandler] ${loaded} event(s) registered`);
}
module.exports = { loadEvents };
'use strict';
const fs = require('fs');
const path = require('path');
const { Events } = require('discord.js');
const guildMemberAddEvent = require('../events/guildMemberAdd');
const logger = require('../utils/logger');
/**
* Loads all event files from src/events and registers them on the client.
* Each event file must export: { name, once?, execute }
*
* @param {import('discord.js').Client} client
*/
function loadEvents(client) {
const eventsPath = path.join(__dirname, '..', 'events');
const eventFiles = fs
.readdirSync(eventsPath)
.filter((file) => file.endsWith('.js'));
let loaded = 0;
for (const file of eventFiles) {
try {
const event = require(path.join(eventsPath, file));
if (!event.name || typeof event.execute !== 'function') {
logger.warn(
`[eventhandler] skipping ${file}: missing "name" or "execute"`,
);
continue;
}
if (event.once) {
client.once(event.name, (...args) => event.execute(client, ...args));
} else {
client.on(event.name, (...args) => event.execute(client, ...args));
}
logger.debug(`[eventhandler] registered event: ${event.name}`);
loaded++;
} catch (err) {
logger.error(`[eventhandler] failed to load ${file}:`, err);
}
}
client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client));
loaded++;
logger.info(`[eventhandler] ${loaded} event(s) registered`);
}
module.exports = { loadEvents };
+50 -50
View File
@@ -1,50 +1,50 @@
'use strict';
require('dotenv').config();
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const logger = require('./utils/logger');
const { loadCommands } = require('./handlers/commandHandler');
const { loadEvents } = require('./handlers/eventHandler');
// Validate required environment variables
if (!process.env.BOT_TOKEN) {
logger.error(
'BOT_TOKEN is not set. Copy .env.example to .env and fill in your token.',
);
process.exit(1);
}
// Create the Discord client with the intents needed for prefix commands
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // Required to read message content for prefix commands
GatewayIntentBits.DirectMessages,
],
});
// Attach a Collection to store commands
client.commands = new Collection();
// Load commands and events
loadCommands(client);
loadEvents(client);
// Global error handling
process.on('unhandledRejection', (err) => {
logger.error('[process] unhandled rejection:', err);
});
process.on('uncaughtException', (err) => {
logger.error('[process] uncaught exception:', err);
process.exit(1);
});
// Connect to Discord
client.login(process.env.BOT_TOKEN).catch((err) => {
logger.error('[login] failed to log in:', err);
process.exit(1);
});
'use strict';
require('dotenv').config();
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const logger = require('./utils/logger');
const { loadCommands } = require('./handlers/commandHandler');
const { loadEvents } = require('./handlers/eventHandler');
// Validate required environment variables
if (!process.env.BOT_TOKEN) {
logger.error(
'BOT_TOKEN is not set. Copy .env.example to .env and fill in your token.',
);
process.exit(1);
}
// Create the Discord client with the intents needed for prefix commands
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // Required to read message content for prefix commands
GatewayIntentBits.DirectMessages,
],
});
// Attach a Collection to store commands
client.commands = new Collection();
// Load commands and events
loadCommands(client);
loadEvents(client);
// Global error handling
process.on('unhandledRejection', (err) => {
logger.error('[process] unhandled rejection:', err);
});
process.on('uncaughtException', (err) => {
logger.error('[process] uncaught exception:', err);
process.exit(1);
});
// Connect to Discord
client.login(process.env.BOT_TOKEN).catch((err) => {
logger.error('[login] failed to log in:', err);
process.exit(1);
});
+74 -74
View File
@@ -1,74 +1,74 @@
'use strict';
const { createLogger, format, transports } = require('winston');
const { consoleFormat } = require("winston-console-format");
const { printf } = format;
const path = require('path');
const fs = require('fs');
const logs_dir = path.join(__dirname, '..', '..', 'logs');
if (!fs.existsSync(logs_dir)) {
fs.mkdirSync(logs_dir, { recursive: true });
}
const logger = createLogger({
level: "silly",
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
transports: [
new transports.Console({
format: format.combine(
format.colorize({ all: true }),
format.padLevels(),
consoleFormat({
showMeta: true,
metaStrip: ["timestamp", "service"],
inspectOptions: {
depth: Infinity,
colors: true,
maxArrayLength: Infinity,
breakLength: 120,
compact: Infinity,
},
})
),
}),
new transports.File({
filename: path.join(logs_dir, 'combined.log'),
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
}),
new transports.File({
filename: path.join(logs_dir, 'error.log'),
level: 'error',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
format.errors({ stack: true }),
printf(({ level, message, timestamp, stack, ...meta }) => {
let log = `${timestamp} [${level.toUpperCase()}]: ${message}`;
if (stack) {
log += `\ntrace: ${stack}`;
}
if (Object.keys(meta).length > 0) {
log += `\nmetadata: ${JSON.stringify(meta, null, 2)}`;
}
return log;
})
),
}),
],
});
module.exports = logger;
'use strict';
const { createLogger, format, transports } = require('winston');
const { consoleFormat } = require("winston-console-format");
const { printf } = format;
const path = require('path');
const fs = require('fs');
const logs_dir = path.join(__dirname, '..', '..', 'logs');
if (!fs.existsSync(logs_dir)) {
fs.mkdirSync(logs_dir, { recursive: true });
}
const logger = createLogger({
level: "silly",
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
transports: [
new transports.Console({
format: format.combine(
format.colorize({ all: true }),
format.padLevels(),
consoleFormat({
showMeta: true,
metaStrip: ["timestamp", "service"],
inspectOptions: {
depth: Infinity,
colors: true,
maxArrayLength: Infinity,
breakLength: 120,
compact: Infinity,
},
})
),
}),
new transports.File({
filename: path.join(logs_dir, 'combined.log'),
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
}),
new transports.File({
filename: path.join(logs_dir, 'error.log'),
level: 'error',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
format.errors({ stack: true }),
printf(({ level, message, timestamp, stack, ...meta }) => {
let log = `${timestamp} [${level.toUpperCase()}]: ${message}`;
if (stack) {
log += `\ntrace: ${stack}`;
}
if (Object.keys(meta).length > 0) {
log += `\nmetadata: ${JSON.stringify(meta, null, 2)}`;
}
return log;
})
),
}),
],
});
module.exports = logger;