first on gitea

This commit is contained in:
st committed 2026-03-21 13:25:24 -05:00
1 parent 27894dac21
commit 94bf054772
22 files changed
+4829 -4657

No files matched your search

+8 -8
View File
@@ -1,8 +1,8 @@
# Discord Bot Token — get yours at https://discord.com/developers/applications # Discord Bot Token — get yours at https://discord.com/developers/applications
BOT_TOKEN=your_bot_token_here BOT_TOKEN=your_bot_token_here
# Command prefix (default: !) # Command prefix (default: !)
PREFIX=! PREFIX=!
# Guild ID for test server (optional — used for dev utilities) # Guild ID for test server (optional — used for dev utilities)
GUILD_ID=your_guild_id_here GUILD_ID=your_guild_id_here
+141 -141
View File
@@ -1,142 +1,142 @@
# Logs # Logs
logs logs
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data # Runtime data
pids pids
*.pid *.pid
*.seed *.seed
*.pid.lock *.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover # Directory for instrumented libs generated by jscoverage/JSCover
lib-cov lib-cov
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
coverage coverage
*.lcov *.lcov
# nyc test coverage # nyc test coverage
.nyc_output .nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt .grunt
# Bower dependency directory (https://bower.io/) # Bower dependency directory (https://bower.io/)
bower_components bower_components
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/ jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/) # Snowpack dependency directory (https://snowpack.dev/)
web_modules/ web_modules/
# TypeScript cache # TypeScript cache
*.tsbuildinfo *.tsbuildinfo
# Optional npm cache directory # Optional npm cache directory
.npm .npm
# Optional eslint cache # Optional eslint cache
.eslintcache .eslintcache
# Optional stylelint cache # Optional stylelint cache
.stylelintcache .stylelintcache
# Optional REPL history # Optional REPL history
.node_repl_history .node_repl_history
# Output of 'npm pack' # Output of 'npm pack'
*.tgz *.tgz
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
# database # database
db.json db.json
# dotenv environment variable files # dotenv environment variable files
.env .env
.env.* .env.*
!.env.example !.env.example
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache
.parcel-cache .parcel-cache
# Next.js build output # Next.js build output
.next .next
out out
# Nuxt.js build / generate output # Nuxt.js build / generate output
.nuxt .nuxt
dist dist
# Gatsby files # Gatsby files
.cache/ .cache/
# Comment in the public line in if your project uses Gatsby and not Next.js # Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support # https://nextjs.org/blog/next-9-1#public-directory-support
# public # public
# vuepress build output # vuepress build output
.vuepress/dist .vuepress/dist
# vuepress v2.x temp and cache directory # vuepress v2.x temp and cache directory
.temp .temp
.cache .cache
# Sveltekit cache directory # Sveltekit cache directory
.svelte-kit/ .svelte-kit/
# vitepress build output # vitepress build output
**/.vitepress/dist **/.vitepress/dist
# vitepress cache directory # vitepress cache directory
**/.vitepress/cache **/.vitepress/cache
# Docusaurus cache and generated files # Docusaurus cache and generated files
.docusaurus .docusaurus
# Serverless directories # Serverless directories
.serverless/ .serverless/
# FuseBox cache # FuseBox cache
.fusebox/ .fusebox/
# DynamoDB Local files # DynamoDB Local files
.dynamodb/ .dynamodb/
# Firebase cache directory # Firebase cache directory
.firebase/ .firebase/
# TernJS port file # TernJS port file
.tern-port .tern-port
# Stores VSCode versions used for testing VSCode extensions # Stores VSCode versions used for testing VSCode extensions
.vscode-test .vscode-test
# yarn v3 # yarn v3
.pnp.* .pnp.*
.yarn/* .yarn/*
!.yarn/patches !.yarn/patches
!.yarn/plugins !.yarn/plugins
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
# Vite logs files # Vite logs files
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
+121 -121
View File
@@ -1,121 +1,121 @@
# twitter (v3) # twitter (v3)
a discord.js v14 moderation (+ more) bot a discord.js v14 moderation (+ more) bot
--- ---
## features ## features
- prefix command system — commands triggered by a configurable prefix (default `!`) - prefix command system — commands triggered by a configurable prefix (default `!`)
- auto-loading — commands and events are loaded automatically from their folders - auto-loading — commands and events are loaded automatically from their folders
- aliases — each command can define one or more aliases - aliases — each command can define one or more aliases
- winston logging — coloured console output + rolling log files (`logs/combined.log`, `logs/error.log`) - 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 - global error handling — unhandled rejections and uncaught exceptions are caught and logged
--- ---
## project structure ## project structure
``` ```
src/ src/
├── index.js # entry point — creates the client, loads handlers ├── index.js # entry point — creates the client, loads handlers
├── commands/ ├── commands/
│ ├── help.js # !help — lists commands or shows command detail │ ├── help.js # !help — lists commands or shows command detail
│ └── ping.js # !ping — latency check │ └── ping.js # !ping — latency check
├── events/ ├── events/
│ ├── ready.js # fires once when the bot is online │ ├── ready.js # fires once when the bot is online
│ └── messageCreate.js # parses every message for prefix commands │ └── messageCreate.js # parses every message for prefix commands
├── handlers/ ├── handlers/
│ ├── commandHandler.js # reads src/commands/ and registers commands │ ├── commandHandler.js # reads src/commands/ and registers commands
│ └── eventHandler.js # reads src/events/ and registers discord events │ └── eventHandler.js # reads src/events/ and registers discord events
└── utils/ └── utils/
└── logger.js # winston logger instance └── logger.js # winston logger instance
logs/ # created automatically at runtime (git-ignored) logs/ # created automatically at runtime (git-ignored)
``` ```
--- ---
## setup ## setup
### 1. clone & install dependencies ### 1. clone & install dependencies
```bash ```bash
git clone https://github.com/yhqe/twitter_v3.git git clone https://github.com/yhqe/twitter_v3.git
cd twitter_v3 cd twitter_v3
npm install npm install
``` ```
### 2. configure environment variables ### 2. configure environment variables
```bash ```bash
cp .env.example .env cp .env.example .env
``` ```
open `.env` and fill in: open `.env` and fill in:
| variable | required | description | | variable | required | description |
|-------------|----------|------------------------------------------------------| |-------------|----------|------------------------------------------------------|
| `BOT_TOKEN` | yes | your bot token from the discord developer portal | | `BOT_TOKEN` | yes | your bot token from the discord developer portal |
| `PREFIX` | no | command prefix (default: `!`) | | `PREFIX` | no | command prefix (default: `!`) |
| `GUILD_ID` | no | test server id (handy for dev stuff) | | `GUILD_ID` | no | test server id (handy for dev stuff) |
### 3. enable the message content intent ### 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. 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 ### 4. start the bot
```bash ```bash
# production # production
npm start npm start
# development (auto-restarts on file changes, node.js 18+) # development (auto-restarts on file changes, node.js 18+)
npm run dev npm run dev
``` ```
--- ---
## adding commands ## adding commands
create a file in `src/commands/`. the handler will load it automatically on next start. create a file in `src/commands/`. the handler will load it automatically on next start.
```js ```js
'use strict'; 'use strict';
module.exports = { module.exports = {
name: 'example', // primary command name (also used as the key) name: 'example', // primary command name (also used as the key)
description: 'An example command.', description: 'An example command.',
aliases: ['ex'], // optional aliases aliases: ['ex'], // optional aliases
usage: 'example <text>', // shown in !help example usage: 'example <text>', // shown in !help example
async execute(client, message, args) { async execute(client, message, args) {
await message.reply(`You said: ${args.join(' ')}`); await message.reply(`You said: ${args.join(' ')}`);
}, },
}; };
``` ```
## adding events ## adding events
create a file in `src/events/`. set `once: true` if it should fire only once (e.g. `ready`). create a file in `src/events/`. set `once: true` if it should fire only once (e.g. `ready`).
```js ```js
'use strict'; 'use strict';
const { Events } = require('discord.js'); const { Events } = require('discord.js');
module.exports = { module.exports = {
name: Events.GuildMemberAdd, name: Events.GuildMemberAdd,
async execute(client, member) { async execute(client, member) {
console.log(`${member.user.tag} joined ${member.guild.name}`); console.log(`${member.user.tag} joined ${member.guild.name}`);
}, },
}; };
``` ```
--- ---
## logging ## 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`). 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`).
+3
View File
@@ -1,5 +1,8 @@
{ {
"autoroles": { "autoroles": {
"1133441692196950101": "1192595134026616894" "1133441692196950101": "1192595134026616894"
},
"autobans": {
"1133441692196950101": "1264634623950848158"
} }
} }
+2467 -2454
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'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
const logger = require('../../utils/logger'); const logger = require('../../utils/logger');
async function getAvailableCapes(username) { async function getAvailableCapes(username) {
const capes =[]; const capes =[];
const mojangReq = await Promise.allSettled([ const mojangReq = await Promise.allSettled([
fetch(`https://api.ashcon.app/mojang/v2/user/${username}`), fetch(`https://api.ashcon.app/mojang/v2/user/${username}`),
]); ]);
if (mojangReq.status === 'fulfilled' && mojangReq.value.ok) { if (mojangReq.status === 'fulfilled' && mojangReq.value.ok) {
const mojangData = await mojangReq.value.json(); const mojangData = await mojangReq.value.json();
if (mojangData.textures && mojangData.textures.cape) { if (mojangData.textures && mojangData.textures.cape) {
capes.push({ capes.push({
source: 'Official (Equipped)', source: 'Official (Equipped)',
url: mojangData.textures.cape.url url: mojangData.textures.cape.url
}); });
} }
} }
return capes; return capes;
} }
module.exports = { module.exports = {
name: 'mc', name: 'mc',
description: `request different information about a minecraft user`, description: `request different information about a minecraft user`,
aliases: ['minecraft'], aliases: ['minecraft'],
usage: 'mc <username>', usage: 'mc <username>',
async execute(client, message, args) { async execute(client, message, args) {
var embed; var embed;
let sent = await message.reply({ let sent = await message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<a:loading_icon:1366587619890302998> fetching...', description: '<a:loading_icon:1366587619890302998> fetching...',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
const username = args[0]; const username = args[0];
if (!username) { if (!username) {
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
description: '<:no:1481811955592466492> please provide a valid minecraft username!', description: '<:no:1481811955592466492> please provide a valid minecraft username!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
return await sent.edit({ content: null, embeds: [embed] }); return await sent.edit({ content: null, embeds: [embed] });
} }
const uuid_res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`); 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 => { const uuid = await uuid_res.json().then(res => res.id).catch(err => {
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
description: '<:no:1481811955592466492> cannot fetch uuid!', description: '<:no:1481811955592466492> cannot fetch uuid!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
return null; return null;
}); });
const res = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`); const res = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`);
const data = await res.json().then(res => res).catch(err => { const data = await res.json().then(res => res).catch(err => {
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
description: '<:no:1481811955592466492> cannot fetch data!', description: '<:no:1481811955592466492> cannot fetch data!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
return null; return null;
}); });
getAvailableCapes(`${data.name}`).then(capes => { getAvailableCapes(`${data.name}`).then(capes => {
logger.debug(`found ${capes.length} cape(s):`); logger.debug(`found ${capes.length} cape(s):`);
logger.debug(capes); logger.debug(capes);
}); });
if (!data) return await sent.edit({ content: null, embeds: [embed] }); 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 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 capes = data.properties[0].value;
const decoded_capes = Buffer.from(capes, 'base64').toString('utf-8'); 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; 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 data for "${username}": ${JSON.stringify(data)}`);
logger.debug(`[mc] fetched cape data for "${username}": ${JSON.stringify(cape_url)}`); logger.debug(`[mc] fetched cape data for "${username}": ${JSON.stringify(cape_url)}`);
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `**[${data.name}](https://namemc.com/profile/${data.name})**`, description: `**[${data.name}](https://namemc.com/profile/${data.name})**`,
fields: [ fields: [
{ name: 'uuid', value: `${data.id}`, inline: true }, { name: 'uuid', value: `${data.id}`, inline: true },
], ],
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
thumbnail: { thumbnail: {
url: skin, url: skin,
height: 1024, height: 1024,
width: 1024, width: 1024,
}, },
}; };
await sent.edit({ content: null, embeds: [embed] }); await sent.edit({ content: null, embeds: [embed] });
}, },
}; };
+423 -386
View File
@@ -1,386 +1,423 @@
'use strict'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const logger = require('../../utils/logger'); const logger = require('../../utils/logger');
async function fetch_avatar(id) { async function fetch_avatar(id) {
const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, { const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
const data = await res.json(); const data = await res.json();
logger.debug(`[fetch_avatar] ${JSON.stringify(data)}`); logger.debug(`[fetch_avatar] ${JSON.stringify(data)}`);
if (data.data && data.data[0] && data.data[0].imageUrl) { if (data.data && data.data[0] && data.data[0].imageUrl) {
logger.debug(`[fetch_avatar] fetched avatar for user id ${id}: ${data.data[0].imageUrl}`); logger.debug(`[fetch_avatar] fetched avatar for user id ${id}: ${data.data[0].imageUrl}`);
return data.data[0].imageUrl; return data.data[0].imageUrl;
} else { } else {
return null; return null;
} }
} }
async function fetch_item_thumb(id) { async function fetch_item_thumb(id) {
const res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${id}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, { const res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${id}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
const data = await res.json(); const data = await res.json();
logger.debug(`[fetch_item_thumb] ${JSON.stringify(data)}`); logger.debug(`[fetch_item_thumb] ${JSON.stringify(data)}`);
if (data.data && data.data[0] && data.data[0].imageUrl) { if (data.data && data.data[0] && data.data[0].imageUrl) {
logger.debug(`[fetch_item_thumb] fetched item thumbnail for item id ${id}: ${data.data[0].imageUrl}`); logger.debug(`[fetch_item_thumb] fetched item thumbnail for item id ${id}: ${data.data[0].imageUrl}`);
return data.data[0].imageUrl; return data.data[0].imageUrl;
} else { } else {
return null; return null;
} }
} }
function to_unix_timestamp(dateString) { function to_unix_timestamp(dateString) {
const unixSeconds = Math.floor(new Date(dateString).getTime() / 1000); const unixSeconds = Math.floor(new Date(dateString).getTime() / 1000);
logger.debug(unixSeconds); logger.debug(unixSeconds);
return unixSeconds; return unixSeconds;
} }
module.exports = { module.exports = {
name: 'rbx', name: 'rbx',
description: `various commands related to roblox`, description: `various commands related to roblox`,
aliases: ['roblox'], aliases: ['roblox'],
usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>', usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>\n.rbx avatar <username>',
async execute(client, message) { async execute(client, message) {
var embed = {}; var embed = {};
const args = message.content.split(' ').slice(1); const args = message.content.split(' ').slice(1);
if (args.length < 2) { if (args.length < 2) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> please provide a subcommand and an argument!', description: '<:no:1481811955592466492> please provide a subcommand and an argument!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const subcommand = args[0].toLowerCase(); const subcommand = args[0].toLowerCase();
const argument = args.slice(1).join(' '); const argument = args.slice(1).join(' ');
let sent = await message.reply({ let sent = await message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<a:loading_icon:1366587619890302998> fetching...', description: '<a:loading_icon:1366587619890302998> fetching...',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
let latency = Date.now() - message.createdTimestamp; let latency = Date.now() - message.createdTimestamp;
let apiLatency = Math.round(client.ws.ping); let apiLatency = Math.round(client.ws.ping);
if (subcommand === 'user' || subcommand === 'u') { if (subcommand === 'user' || subcommand === 'u') {
try { try {
const res = await fetch(`https://users.roproxy.com/v1/usernames/users`, { const res = await fetch(`https://users.roproxy.com/v1/usernames/users`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` }, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` },
body: JSON.stringify({ usernames: [argument], excludeBannedUsers: false }) body: JSON.stringify({ usernames: [argument], excludeBannedUsers: false })
}); });
const data = await res.json(); const data = await res.json();
logger.debug(`[rbx] fetched user data (roblox, standard) for "${argument}": ${JSON.stringify(data)}`); logger.debug(`[rbx] fetched user data (roblox, standard) for "${argument}": ${JSON.stringify(data)}`);
if (!data.data || !data.data[0]) { if (!data.data || !data.data[0]) {
throw new Error('User not found'); throw new Error('User not found');
} }
const extra_res = await fetch(`https://users.roproxy.com/v1/users/${data.data[0].id}`, { const extra_res = await fetch(`https://users.roproxy.com/v1/users/${data.data[0].id}`, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` } headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` }
}); });
const extra_data = await extra_res.json(); const extra_data = await extra_res.json();
logger.debug(`[rbx] fetched user data (roblox, extra) for "${argument}": ${JSON.stringify(extra_data)}`); logger.debug(`[rbx] fetched user data (roblox, extra) for "${argument}": ${JSON.stringify(extra_data)}`);
const r_res = await fetch(`https://api.rolimons.com/players/v1/playerinfo/${data.data[0].id}`, { const r_res = await fetch(`https://api.rolimons.com/players/v1/playerinfo/${data.data[0].id}`, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
let r_data = {}; let r_data = {};
if (r_res.ok) { if (r_res.ok) {
r_data = await r_res.json(); r_data = await r_res.json();
} else { } else {
logger.warn(`[rbx] Failed to fetch rolimons playerinfo for id ${data.data[0].id}: HTTP ${r_res.status}`); logger.warn(`[rbx] Failed to fetch rolimons playerinfo for id ${data.data[0].id}: HTTP ${r_res.status}`);
r_data = {}; r_data = {};
} }
logger.debug(`[rbx] fetched user data (rolimons) for "${argument}": ${JSON.stringify(r_data)}`); logger.debug(`[rbx] fetched user data (rolimons) for "${argument}": ${JSON.stringify(r_data)}`);
const ra_res = await fetch(`https://api.rolimons.com/players/v1/playerassets/${data.data[0].id}`, { const ra_res = await fetch(`https://api.rolimons.com/players/v1/playerassets/${data.data[0].id}`, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
const ra_data = await ra_res.json(); const ra_data = await ra_res.json();
logger.debug(`[rbx] fetched user data (rolimons, assets) for "${argument}": ${JSON.stringify(ra_data)}`); logger.debug(`[rbx] fetched user data (rolimons, assets) for "${argument}": ${JSON.stringify(ra_data)}`);
if (!data.data || !data.data[0]) { if (!data.data || !data.data[0]) {
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> could not find a roblox user with that username!', description: '<:no:1481811955592466492> could not find a roblox user with that username!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
} }
} else { } else {
const user = data.data[0]; const user = data.data[0];
const avatarUrl = await fetch_avatar(user.id); const avatarUrl = await fetch_avatar(user.id);
const hasCheckmark = user.hasVerifiedBadge; const hasCheckmark = user.hasVerifiedBadge;
const hasPremium = r_data.premium; const hasPremium = r_data.premium;
const isOnline = ra_data.isOnline; const isOnline = ra_data.isOnline;
const viewInvRes = await fetch(`https://inventory.roproxy.com/v1/users/${user.id}/can-view-inventory`); const viewInvRes = await fetch(`https://inventory.roproxy.com/v1/users/${user.id}/can-view-inventory`);
const isInvPublic = (await viewInvRes.json()).canView; const isInvPublic = (await viewInvRes.json()).canView;
const formatted_desc = (extra_data.description || "no description") const formatted_desc = (extra_data.description || "no description")
.split('\n') .split('\n')
.map(line => line.trim() ? `-# *${line}*` : '') .map(line => line.trim() ? `-# *${line}*` : '')
.join('\n'); .join('\n');
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `**[${user.displayName}](https://www.roblox.com/users/${user.id}/profile)** [(${user.name})](https://www.rolimons.com/player/${user.id}) ${isOnline ? "<:online:1482657396362055826>" : "<:offline:1482657564650115073>"} ${hasPremium ? "<:premium:1482660040358760602>" : ""} ${hasCheckmark ? "<:Verified:1376786225486364823>" : ""}\n${formatted_desc}`, description: `**[${user.displayName}](https://www.roblox.com/users/${user.id}/profile)** [(${user.name})](https://www.rolimons.com/player/${user.id}) ${isOnline ? "<:online:1482657396362055826>" : "<:offline:1482657564650115073>"} ${hasPremium ? "<:premium:1482660040358760602>" : ""} ${hasCheckmark ? "<:Verified:1376786225486364823>" : ""}\n${formatted_desc}`,
fields: [ fields: [
{ name: 'id', value: String(extra_data.id), inline: true }, { name: 'id', value: String(extra_data.id), inline: true },
{ name: 'rap', value: String(r_data.rap), inline: true }, { name: 'rap', value: String(r_data.rap), inline: true },
{ name: 'value', value: String(r_data.value), inline: true }, { name: 'value', value: String(r_data.value), inline: true },
{ name: 'inventory', value: `${isInvPublic ? 'public' : 'private'}`, inline: true }, { name: 'inventory', value: `${isInvPublic ? 'public' : 'private'}`, inline: true },
{ name: 'terminated', value: String(r_data.terminated), inline: true }, { name: 'terminated', value: String(r_data.terminated), inline: true },
{ name: 'date created', value: `<t:${to_unix_timestamp(String(extra_data.created))}:f>`, inline: true }, { name: 'date created', value: `<t:${to_unix_timestamp(String(extra_data.created))}:f>`, inline: true },
{ name: 'last online', value: `${isOnline ? "now" : "<t:" + r_data.last_online + ":f>"}`, inline: true }, { name: 'last online', value: `${isOnline ? "now" : "<t:" + r_data.last_online + ":f>"}`, inline: true },
], ],
thumbnail: { thumbnail: {
url: avatarUrl, url: avatarUrl,
height: 420, height: 420,
width: 420, width: 420,
}, },
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
} }
} catch (err) { } catch (err) {
logger.error(`[rbx] failed to fetch user data for "${argument}":`, err); logger.error(`[rbx] failed to fetch user data for "${argument}":`, err);
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `<:no:1481811955592466492> could not fetch data for user "${argument}"!`, description: `<:no:1481811955592466492> could not fetch data for user "${argument}"!`,
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
} }
} }
} else if (subcommand === 'item') { } else if (subcommand === 'avatar') {
try { try {
const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, { const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
method: 'GET', method: 'GET',
headers: { headers: { 'Content-Type': 'application/json' }
'Content-Type': 'application/json', });
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
} const data = await res.json();
}); embed = {
const p_res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${argument}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, { color: 0x8aa2b3,
method: 'GET', author: {
headers: { name: message.author.username,
'Content-Type': 'application/json', icon_url: message.author.displayAvatarURL(),
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}` },
} description: `${}`,
}); footer: {
const resale_res = await fetch(`https://economy.roproxy.com/v1/assets/${argument}/resale-data`, { text: `twitter @ ${message.guild.name}`,
method: 'GET', icon_url: client.user.displayAvatarURL(),
headers: { },
'Content-Type': 'application/json', timestamp: new Date(),
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}` };
} } catch (err) {
}); logger.error(`[rbx] failed to fetch avatar for user id "${argument}":`, err);
embed = {
const item = await res.json(); color: 0x8aa2b3,
logger.debug(`[rbx] fetched item data (roblox) for "${argument}": ${JSON.stringify(item)}`); author: {
name: message.author.username,
const resale_data = await resale_res.json(); icon_url: message.author.displayAvatarURL(),
logger.debug(`[rbx] fetched resale data for "${argument}": ${JSON.stringify(resale_data)}`); },
description: `<:no:1481811955592466492> could not fetch avatar data for user "${argument}"!`,
let itemsJson; footer: {
try { text: `twitter @ ${message.guild.name}`,
const itemsPath = path.resolve(process.cwd(), 'items.json'); icon_url: client.user.displayAvatarURL(),
const itemsRaw = fs.readFileSync(itemsPath, 'utf8'); },
itemsJson = JSON.parse(itemsRaw); timestamp: new Date(),
if (itemsJson.items && itemsJson.items[argument]) { };
logger.debug(`[rbx] found item ${argument} in items.json: ${JSON.stringify(itemsJson.items[argument])}`); }
} } else if (subcommand === 'item') {
} catch (e) { try {
logger.warn(`[rbx] could not read items.json: ${e.message}`); const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, {
} method: 'GET',
headers: {
let item_e = itemsJson.items[argument]; 'Content-Type': 'application/json',
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
var hasAcronym = false; }
if (item_e[1] = "") { });
hasAcronym = false; const p_res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${argument}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, {
} else { method: 'GET',
hasAcronym = true; headers: {
} 'Content-Type': 'application/json',
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
const item_thumb = await fetch_item_thumb(item.AssetId); }
const is_offsale = item.IsForSale; });
const resale_res = await fetch(`https://economy.roproxy.com/v1/assets/${argument}/resale-data`, {
const is_limited = item.IsLimited; method: 'GET',
const is_limited_unique = item.IsLimitedUnique; headers: {
'Content-Type': 'application/json',
const is_limited_at_all = is_limited || is_limited_unique; 'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
}
const limited_string = is_limited ? (is_limited_unique ? "true (unique)" : "true") : "false"; });
embed = { const item = await res.json();
color: 0x8aa2b3, logger.debug(`[rbx] fetched item data (roblox) for "${argument}": ${JSON.stringify(item)}`);
author: {
name: message.author.username, const resale_data = await resale_res.json();
icon_url: message.author.displayAvatarURL(), logger.debug(`[rbx] fetched resale data for "${argument}": ${JSON.stringify(resale_data)}`);
},
description: `[${item.Name}](https://roblox.com/catalog/${argument}) ${hasAcronym ? item_e[1] : ""}\n-# *${item.Description}*`, let itemsJson;
fields: [ try {
{ name: "id", value: `${item.AssetId}`, inline: true }, const itemsPath = path.resolve(process.cwd(), 'items.json');
{ name: "price", value: `${is_offsale ? "offsale" : item.PriceInRobux}`, inline: true }, const itemsRaw = fs.readFileSync(itemsPath, 'utf8');
{ name: "limited", value: `${limited_string}`, inline: true }, itemsJson = JSON.parse(itemsRaw);
{ name: "rap", value: `${is_limited_at_all ? resale_data.recentAveragePrice : "0"}`, inline: true }, if (itemsJson.items && itemsJson.items[argument]) {
{ name: "creator", value: `${item.Creator.Name}`, inline: true }, logger.debug(`[rbx] found item ${argument} in items.json: ${JSON.stringify(itemsJson.items[argument])}`);
], }
thumbnail: { } catch (e) {
url: item_thumb, logger.warn(`[rbx] could not read items.json: ${e.message}`);
height: 420, }
width: 420
}, let item_e = itemsJson.items[argument];
footer: {
text: `twitter @ ${message.guild.name}`, var hasAcronym = false;
icon_url: client.user.displayAvatarURL(), if (item_e[1] = "") {
}, hasAcronym = false;
timestamp: new Date(), } else {
}; hasAcronym = true;
} catch (err) { }
logger.error(`[rbx] failed to fetch item data for "${argument}":`, err);
embed = { const item_thumb = await fetch_item_thumb(item.AssetId);
color: 0x8aa2b3, const is_offsale = item.IsForSale;
author: {
name: message.author.username, const is_limited = item.IsLimited;
icon_url: message.author.displayAvatarURL(), const is_limited_unique = item.IsLimitedUnique;
},
description: `<:no:1481811955592466492> could not fetch data for item "${argument}"!`, const is_limited_at_all = is_limited || is_limited_unique;
footer: {
text: `twitter @ ${message.guild.name}`, const limited_string = is_limited ? (is_limited_unique ? "true (unique)" : "true") : "false";
icon_url: client.user.displayAvatarURL(),
}, embed = {
timestamp: new Date(), color: 0x8aa2b3,
} author: {
} name: message.author.username,
} else if (subcommand === 'group') { icon_url: message.author.displayAvatarURL(),
try { },
const res = await fetch(`https://groups.roproxy.com/v1/groups/${argument}`); description: `[${item.Name}](https://roblox.com/catalog/${argument}) ${hasAcronym ? item_e[1] : ""}\n-# *${item.Description}*`,
const p_res = await fetch(`https://thumbnails.roproxy.com/v1/groups/icons?groupIds=${argument}&size=420x420&format=Png&isCircular=false`); fields: [
{ name: "id", value: `${item.AssetId}`, inline: true },
if (!res.ok) throw new Error(); { name: "price", value: `${is_offsale ? "offsale" : item.PriceInRobux}`, inline: true },
if (!p_res.ok) throw new Error(); { name: "limited", value: `${limited_string}`, inline: true },
{ name: "rap", value: `${is_limited_at_all ? resale_data.recentAveragePrice : "0"}`, inline: true },
const group = await res.json(); { name: "creator", value: `${item.Creator.Name}`, inline: true },
const p_json = await p_res.json(); ],
logger.debug(`${JSON.stringify(p_json)}`); thumbnail: {
const thumb = (p_json && p_json.data && p_json.data[0]) ? p_json.data[0].imageUrl : null; url: item_thumb,
height: 420,
embed = { width: 420
color: 0x8aa2b3, },
author: { footer: {
name: message.author.username, text: `twitter @ ${message.guild.name}`,
icon_url: message.author.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
description: `**[${group.name}](https://roblox.com/communities/${argument})**\n-# *${group.description || "no description"}*`, timestamp: new Date(),
thumbnail: { };
url: thumb, } catch (err) {
height: 420, logger.error(`[rbx] failed to fetch item data for "${argument}":`, err);
width: 420 embed = {
}, color: 0x8aa2b3,
fields: [ author: {
{ name: "members", value: `${group.memberCount}`, inline: true }, name: message.author.username,
{ name: "owner", value: `${group.owner.username}`, inline: true }, icon_url: message.author.displayAvatarURL(),
{ name: "shout", value: `${group.shout}`, inline: true }, },
{ name: "open", value: `${group.publicEntryAllowed}`, inline: true }, description: `<:no:1481811955592466492> could not fetch data for item "${argument}"!`,
{ name: "verified", value: `${group.hasVerifiedBadge}`, inline: true }, footer: {
], text: `twitter @ ${message.guild.name}`,
footer: { icon_url: client.user.displayAvatarURL(),
text: `twitter @ ${message.guild.name}`, },
icon_url: client.user.displayAvatarURL(), timestamp: new Date(),
}, }
timestamp: new Date(), }
}; } else if (subcommand === 'group') {
} catch (err) { try {
logger.error(`[rbx] failed to fetch group data for "${argument}":`, err); const res = await fetch(`https://groups.roproxy.com/v1/groups/${argument}`);
embed.title = 'Error'; const p_res = await fetch(`https://thumbnails.roproxy.com/v1/groups/icons?groupIds=${argument}&size=420x420&format=Png&isCircular=false`);
embed.description = 'Failed to fetch group data.';
} if (!res.ok) throw new Error();
} else if (subcommand === 'game') { if (!p_res.ok) throw new Error();
try {
const res = await fetch(`https://games.roproxy.com/v1/games?universeIds=${argument}`); const group = await res.json();
if (!res.ok) throw new Error(); const p_json = await p_res.json();
const data = await res.json(); logger.debug(`${JSON.stringify(p_json)}`);
if (!data.data || !data.data[0]) { const thumb = (p_json && p_json.data && p_json.data[0]) ? p_json.data[0].imageUrl : null;
embed.title = 'Game Not Found';
embed.description = `Could not find a Roblox game with the universe ID **${argument}**.`; embed = {
} else { color: 0x8aa2b3,
const game = data.data[0]; author: {
embed.title = `Roblox Game: ${game.name}`; name: message.author.username,
embed.description = game.description || 'No description.'; icon_url: message.author.displayAvatarURL(),
embed.thumbnail.url = `https://www.roproxy.com/asset-thumbnail/image?assetId=${game.rootPlaceId}&width=420&height=420&format=png`; },
embed.fields = [ description: `**[${group.name}](https://roblox.com/communities/${argument})**\n-# *${group.description || "no description"}*`,
{ name: 'Creator', value: game.creator.name, inline: true }, thumbnail: {
{ name: 'Playing', value: game.playing.toLocaleString(), inline: true }, url: thumb,
{ name: 'Visits', value: game.visits.toLocaleString(), inline: true } height: 420,
]; width: 420
} },
} catch (err) { fields: [
embed.title = 'Error'; { name: "members", value: `${group.memberCount}`, inline: true },
embed.description = 'Failed to fetch game data.'; { name: "owner", value: `${group.owner.username}`, inline: true },
} { name: "shout", value: `${group.shout}`, inline: true },
}/* else { { name: "open", value: `${group.publicEntryAllowed}`, inline: true },
embed.title = 'invalid subcommand'; { name: "verified", value: `${group.hasVerifiedBadge}`, inline: true },
embed.description = 'Valid subcommands: `user`, `item`, `group`, `game`'; ],
}*/ footer: {
text: `twitter @ ${message.guild.name}`,
await sent.edit({ content: null, embeds: [embed] }); icon_url: client.user.displayAvatarURL(),
}, },
}; timestamp: new Date(),
};
} catch (err) {
logger.error(`[rbx] failed to fetch group data for "${argument}":`, err);
embed.title = 'Error';
embed.description = 'Failed to fetch group data.';
}
} else if (subcommand === 'game') {
try {
const res = await fetch(`https://games.roproxy.com/v1/games?universeIds=${argument}`);
if (!res.ok) throw new Error();
const data = await res.json();
if (!data.data || !data.data[0]) {
embed.title = 'Game Not Found';
embed.description = `Could not find a Roblox game with the universe ID **${argument}**.`;
} else {
const game = data.data[0];
embed.title = `Roblox Game: ${game.name}`;
embed.description = game.description || 'No description.';
embed.thumbnail.url = `https://www.roproxy.com/asset-thumbnail/image?assetId=${game.rootPlaceId}&width=420&height=420&format=png`;
embed.fields = [
{ name: 'Creator', value: game.creator.name, inline: true },
{ name: 'Playing', value: game.playing.toLocaleString(), inline: true },
{ name: 'Visits', value: game.visits.toLocaleString(), inline: true }
];
}
} catch (err) {
embed.title = 'Error';
embed.description = 'Failed to fetch game data.';
}
}/* else {
embed.title = 'invalid subcommand';
embed.description = 'Valid subcommands: `user`, `item`, `group`, `game`';
}*/
await sent.edit({ content: null, embeds: [embed] });
},
};
+76 -76
View File
@@ -1,76 +1,76 @@
const { EmbedBuilder } = require('discord.js'); const { EmbedBuilder } = require('discord.js');
'use strict'; 'use strict';
module.exports = { module.exports = {
name: 'help', name: 'help',
description: 'lists all available commands or shows info about a specific one', description: 'lists all available commands or shows info about a specific one',
aliases: ['h', 'commands'], aliases: ['h', 'commands'],
usage: 'help [command]', usage: 'help [command]',
execute(client, message, args) { execute(client, message, args) {
var embed; var embed;
const prefix = process.env.PREFIX || '!'; const prefix = process.env.PREFIX || '!';
// Show info for a specific command // Show info for a specific command
if (args.length > 0) { if (args.length > 0) {
const name = args[0].toLowerCase(); const name = args[0].toLowerCase();
const command = client.commands.get(name); const command = client.commands.get(name);
if (!command) { if (!command) {
return message.reply(`no command found for \`${name}\`.`); return message.reply(`no command found for \`${name}\`.`);
} }
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `**${prefix}${command.name}**\n-# ${command.description || 'no description'}`, description: `**${prefix}${command.name}**\n-# ${command.description || 'no description'}`,
fields: [ fields: [
{ name: 'aliases', value: command.aliases.map((a) => `\`${prefix}${a}\``).join(', ') }, { name: 'aliases', value: command.aliases.map((a) => `\`${prefix}${a}\``).join(', ') },
{ name: 'usage', value: `\`${prefix}${command.usage}\`` } { name: 'usage', value: `\`${prefix}${command.usage}\`` }
], ],
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
return message.reply({ embeds: [embed] }); return message.reply({ embeds: [embed] });
} }
// Build a deduplicated command list (ignore aliases pointing to the same command) // Build a deduplicated command list (ignore aliases pointing to the same command)
const seen = new Set(); const seen = new Set();
const commandList = []; const commandList = [];
for (const command of client.commands.values()) { for (const command of client.commands.values()) {
if (seen.has(command.name)) continue; if (seen.has(command.name)) continue;
seen.add(command.name); seen.add(command.name);
commandList.push(`\`${prefix}${command.name}\` — ${command.description || 'no description'}`); commandList.push(`\`${prefix}${command.name}\` — ${command.description || 'no description'}`);
} }
embed = { embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `-# use ${prefix}help <command> for more details`, description: `-# use ${prefix}help <command> for more details`,
fields: [ fields: [
{ name: 'commands', value: commandList.join('\n') }, { name: 'commands', value: commandList.join('\n') },
], ],
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
return message.reply({ embeds: [embed] }); return message.reply({ embeds: [embed] });
}, },
}; };
+102
View File
@@ -0,0 +1,102 @@
'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`,
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(),
}]
});
}
},
};
+87 -87
View File
@@ -1,88 +1,88 @@
'use strict'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
module.exports = { module.exports = {
name: 'ban', name: 'ban',
description: `ban a user from the current server`, description: `ban a user from the current server`,
aliases: ['banish'], aliases: ['b'],
usage: 'ban <user> [reason] [duration]', usage: 'ban <user> [reason] [duration]',
async execute(client, message, args) { async execute(client, message, args) {
if (!message.member.permissions.has('BanMembers')) { if (!message.member.permissions.has('BanMembers')) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> you do not have permission to use this command!', description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const user = message.mentions.users.first() || client.users.cache.get(args[0]); const user = message.mentions.users.first() || client.users.cache.get(args[0]);
if (!user) { if (!user) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> please specify a user to ban!', description: '<:no:1481811955592466492> please specify a user to ban!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)'; const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
const duration = args[2] ? parseInt(args[2]) : null; const duration = args[2] ? parseInt(args[2]) : null;
try { try {
await message.guild.members.ban(user, { reason }); await message.guild.members.ban(user, { reason });
message.reply({ message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `<:check:1481811973447356496> ${user.username} has been banned!`, description: `<:check:1481811973447356496> ${user.username} has been banned!`,
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
message.reply({ message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> an error occurred while trying to ban the user!', description: '<:no:1481811955592466492> an error occurred while trying to ban the user!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
}, },
}; };
+87 -87
View File
@@ -1,88 +1,88 @@
'use strict'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
module.exports = { module.exports = {
name: 'kick', name: 'kick',
description: `kick a user from the current server`, description: `kick a user from the current server`,
aliases: ['kick'], aliases: ['kick'],
usage: 'kick <user> [reason]', usage: 'kick <user> [reason]',
async execute(client, message, args) { async execute(client, message, args) {
if (!message.member.permissions.has('KickMembers')) { if (!message.member.permissions.has('KickMembers')) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> you do not have permission to use this command!', description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const user = message.mentions.users.first() || client.users.cache.get(args[0]); const user = message.mentions.users.first() || client.users.cache.get(args[0]);
if (!user) { if (!user) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> please specify a user to ban!', description: '<:no:1481811955592466492> please specify a user to ban!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)'; const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
const duration = args[2] ? parseInt(args[2]) : null; const duration = args[2] ? parseInt(args[2]) : null;
try { try {
await message.guild.members.kick(user, { reason }); await message.guild.members.kick(user, { reason });
message.reply({ message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `<:check:1481811973447356496> ${user.username} has been kicked!`, description: `<:check:1481811973447356496> ${user.username} has been kicked!`,
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
message.reply({ message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> an error occurred while trying to ban the user!', description: '<:no:1481811955592466492> an error occurred while trying to ban the user!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
}, },
}; };
+83 -83
View File
@@ -1,84 +1,84 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { time } = require("discord.js"); const { time } = require("discord.js");
module.exports = { module.exports = {
name: 'autorole', name: 'autorole',
description: `assign a role to new members when they join the server`, description: `assign a role to new members when they join the server`,
aliases: ['ar'], aliases: ['ar'],
usage: 'autorole <role>', usage: 'autorole <role>',
async execute(client, message, args) { async execute(client, message, args) {
if (!message.member.permissions.has('Administrator')) { if (!message.member.permissions.has('Administrator')) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> you do not have permission to use this command!', description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]); const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]);
if (!role) { if (!role) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> please provide a valid role!', description: '<:no:1481811955592466492> please provide a valid role!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), 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) // 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'); const dbPath = path.join(__dirname, '..', '..', '..', 'db.json');
let db = {}; let db = {};
if (fs.existsSync(dbPath)) { if (fs.existsSync(dbPath)) {
db = JSON.parse(fs.readFileSync(dbPath, 'utf8')); db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
} }
if (!db.autoroles) { if (!db.autoroles) {
db.autoroles = {}; db.autoroles = {};
} }
db.autoroles[message.guild.id] = role.id; db.autoroles[message.guild.id] = role.id;
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2)); fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
message.reply({ message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `<:check:1481811973447356496> "${role.name}" has been set as the autorole!`, description: `<:check:1481811973447356496> "${role.name}" has been set as the autorole!`,
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}); });
} }
}; };
+43 -43
View File
@@ -1,43 +1,43 @@
'use strict'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
module.exports = { module.exports = {
name: 'ping', name: 'ping',
description: `test the bot's latency to discord's servers and api`, description: `test the bot's latency to discord's servers and api`,
aliases: ['pong'], aliases: ['pong'],
usage: 'ping', usage: 'ping',
async execute(client, message) { async execute(client, message) {
const sent = await message.reply('pinging'); const sent = await message.reply('pinging');
const latency = sent.createdTimestamp - message.createdTimestamp; const latency = sent.createdTimestamp - message.createdTimestamp;
const apiLatency = Math.round(client.ws.ping); const apiLatency = Math.round(client.ws.ping);
const embed = { const embed = {
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: 'fuck you!!!! 🖕', description: 'fuck you!!!! 🖕',
fields: [ fields: [
{ name: '# of cmds loaded', value: `**${client.commands.size}**`, inline: true }, { name: '# of cmds loaded', value: `**${client.commands.size}**`, inline: true },
{ name: 'bot latency', value: `**${latency}ms**`, inline: true }, { name: 'bot latency', value: `**${latency}ms**`, inline: true },
{ name: 'api latency', value: `**${apiLatency}ms**`, inline: true }, { name: 'api latency', value: `**${apiLatency}ms**`, inline: true },
], ],
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
thumbnail: { thumbnail: {
url: client.user.displayAvatarURL(), url: client.user.displayAvatarURL(),
height: 1024, height: 1024,
width: 1024, width: 1024,
}, },
}; };
await sent.edit({ content: null, embeds: [embed] }); await sent.edit({ content: null, embeds: [embed] });
}, },
}; };
+61 -61
View File
@@ -1,61 +1,61 @@
'use strict'; 'use strict';
const { time } = require("discord.js"); const { time } = require("discord.js");
module.exports = { module.exports = {
name: 'purge', name: 'purge',
description: `delete a specified number of messages from the current channel`, description: `delete a specified number of messages from the current channel`,
aliases: ['clear', 'p'], aliases: ['clear', 'p'],
usage: 'purge <number>', usage: 'purge <number>',
async execute(client, message) { async execute(client, message) {
if (!message.member.permissions.has('ManageMessages')) { if (!message.member.permissions.has('ManageMessages')) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
description: '<:no:1481811955592466492> you do not have permission to use this command!', description: '<:no:1481811955592466492> you do not have permission to use this command!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}], }],
}); });
} }
const args = message.content.split(' ').slice(1); const args = message.content.split(' ').slice(1);
const amount = parseInt(args[0]) + 1; const amount = parseInt(args[0]) + 1;
if (isNaN(amount) || amount < 1 || amount > 100) { if (isNaN(amount) || amount < 1 || amount > 100) {
return message.reply({ return message.reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
description: '<:no:1481811955592466492> please provide a number between 1 and 100!', description: '<:no:1481811955592466492> please provide a number between 1 and 100!',
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}], }],
}); });
} }
const deletedMessages = await message.channel.bulkDelete(amount, true); const deletedMessages = await message.channel.bulkDelete(amount, true);
const reply = await message.channel.send({ const reply = await message.channel.send({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: `<:no:1481811955592466492> successfully deleted ${deletedMessages.size - 1} messages!`, description: `<:no:1481811955592466492> successfully deleted ${deletedMessages.size - 1} messages!`,
footer: { footer: {
text: `twitter @ ${message.guild.name}`, text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(), icon_url: client.user.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}], }],
}); });
setTimeout(() => reply.delete().catch(() => { }), 5000); setTimeout(() => reply.delete().catch(() => { }), 5000);
}, },
}; };
+49 -32
View File
@@ -1,33 +1,50 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { Events } = require('discord.js'); const { Events } = require('discord.js');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
module.exports = (client, member) => { module.exports = (client, member) => {
const dbPath = path.join(__dirname, '..', '..', 'db.json'); const dbPath = path.join(__dirname, '..', '..', 'db.json');
const db = JSON.parse(fs.readFileSync(dbPath, 'utf8')); const db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
const roleId = db.autoroles ? db.autoroles[member.guild.id] : null; const roleId = db.autoroles ? db.autoroles[member.guild.id] : null;
logger.info(`member "${member.user.tag}" has joined guild "${member.guild.name}"`); logger.info(`member "${member.user.tag}" has joined guild "${member.guild.name}"`);
// autorole // autorole
{ {
if (!roleId) { if (!roleId) {
logger.info(`no autorole is set up in "${member.guild.name}"`); logger.info(`no autorole is set up in "${member.guild.name}"`);
return; return;
} }
const role = member.guild.roles.cache.get(roleId); const role = member.guild.roles.cache.get(roleId);
if (!role) { if (!role) {
logger.error(`role with id "${roleId}" not found in guild "${member.guild.name}"`); logger.error(`role with id "${roleId}" not found in guild "${member.guild.name}"`);
return; return;
} }
member.roles.add(role) member.roles.add(role)
.then(() => logger.info(`assigned role "${role.name}" to user "${member.user.tag}"!`)) .then(() => logger.info(`assigned role "${role.name}" to user "${member.user.tag}"!`))
.catch(error => logger.error(`failed to assign role "${role.name}" to user "${member.user.tag}": ${error}`)); .catch(error => logger.error(`failed to assign role "${role.name}" to user "${member.user.tag}": ${error}`));
} }
// autoban
{
const autoban = db.autobans ? db.autobans[member.guild.id] : null;
if (!autoban) {
logger.info(`no autoban is set up for ${member.user.tag} in "${member.guild.name}"`);
return;
}
if (member.user.tag === autoban) {
member.ban({ reason: 'autobanned (twitter)' })
.then(() => logger.info(`autobanned user "${member.user.tag}" from guild "${member.guild.name}"`))
.catch(error => logger.error(`failed to autoban user "${member.user.tag}" from guild "${member.guild.name}": ${error}`));
} else {
logger.info(`user "${member.user.tag}" is not on the autoban list for "${member.guild.name}"`);
}
}
}; };
+52 -52
View File
@@ -1,52 +1,52 @@
'use strict'; 'use strict';
const { Events } = require('discord.js'); const { Events } = require('discord.js');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
module.exports = { module.exports = {
name: Events.MessageCreate, name: Events.MessageCreate,
async execute(client, message) { async execute(client, message) {
// Ignore bots and messages without the prefix // Ignore bots and messages without the prefix
if (message.author.bot) return; if (message.author.bot) return;
const prefix = process.env.PREFIX || '!'; const prefix = process.env.PREFIX || '!';
if (!message.content.startsWith(prefix)) return; if (!message.content.startsWith(prefix)) return;
// Parse command name and arguments // Parse command name and arguments
const args = message.content.slice(prefix.length).trim().split(/\s+/); const args = message.content.slice(prefix.length).trim().split(/\s+/);
const commandName = args.shift().toLowerCase(); const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName); const command = client.commands.get(commandName);
if (!command) return; if (!command) return;
logger.info( logger.info(
`[command] "${commandName}" used by ${message.author.tag} (${message.author.id})` + `[command] "${commandName}" used by ${message.author.tag} (${message.author.id})` +
` in ${message.guild ? `#${message.channel.name} (${message.guild.name})` : 'dms'}`, ` in ${message.guild ? `#${message.channel.name} (${message.guild.name})` : 'dms'}`,
); );
try { try {
await command.execute(client, message, args); await command.execute(client, message, args);
} catch (err) { } catch (err) {
logger.error(`[command] error executing "${commandName}":`, err); logger.error(`[command] error executing "${commandName}":`, err);
await message await message
.reply({ .reply({
content: null, embeds: [{ content: null, embeds: [{
color: 0x8aa2b3, color: 0x8aa2b3,
title: '', title: '',
author: { author: {
name: message.author.username, name: message.author.username,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
description: '<:no:1481811955592466492> an error occurred while executing that command!', description: '<:no:1481811955592466492> an error occurred while executing that command!',
footer: { footer: {
text: `${message.author.username} @ twitter`, text: `${message.author.username} @ twitter`,
icon_url: message.author.displayAvatarURL(), icon_url: message.author.displayAvatarURL(),
}, },
timestamp: new Date(), timestamp: new Date(),
}] }]
}) })
.catch(() => null); .catch(() => null);
} }
}, },
}; };
+36 -36
View File
@@ -1,36 +1,36 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { ActivityType, Events } = require('discord.js'); const { ActivityType, Events } = require('discord.js');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
module.exports = { module.exports = {
name: Events.ClientReady, name: Events.ClientReady,
once: true, once: true,
execute(client) { execute(client) {
const db_path = path.join(process.cwd(), 'db.json'); const db_path = path.join(process.cwd(), 'db.json');
if (!fs.existsSync(db_path)) { if (!fs.existsSync(db_path)) {
fs.writeFileSync(db_path, JSON.stringify({}, null, 2)); fs.writeFileSync(db_path, JSON.stringify({}, null, 2));
logger.info('[db] created db.json'); logger.info('[db] created db.json');
} }
const db = JSON.parse(fs.readFileSync(db_path, 'utf8')); const db = JSON.parse(fs.readFileSync(db_path, 'utf8'));
const variables = Object.keys(db); const variables = Object.keys(db);
logger.info(`[db] loaded variables from db.json: ${variables.length > 0 ? variables.join(', ') : 'none'}`); logger.info(`[db] loaded variables from db.json: ${variables.length > 0 ? variables.join(', ') : 'none'}`);
// cache rolimons item list // cache rolimons item list
fetch('https://api.rolimons.com/items/v2/itemdetails') fetch('https://api.rolimons.com/items/v2/itemdetails')
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch rolimons item list'))) .then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch rolimons item list')))
.then(data => { .then(data => {
fs.writeFileSync(path.join(process.cwd(), 'items.json'), JSON.stringify(data, null, 2)); fs.writeFileSync(path.join(process.cwd(), 'items.json'), JSON.stringify(data, null, 2));
}) })
.catch(err => logger.error('[ready] error fetching rolimons item list:', err)); .catch(err => logger.error('[ready] error fetching rolimons item list:', err));
logger.info(`[ready] logged in as ${client.user.tag} (${client.user.id})`); logger.info(`[ready] logged in as ${client.user.tag} (${client.user.id})`);
logger.info(`[ready] serving ${client.guilds.cache.size} guild(s)`); logger.info(`[ready] serving ${client.guilds.cache.size} guild(s)`);
client.user.setActivity(`watching over ${client.guilds.cache.size} servers`, { type: ActivityType.Watching }); client.user.setActivity(`watching over ${client.guilds.cache.size} servers`, { type: ActivityType.Watching });
}, },
}; };
+65 -65
View File
@@ -1,65 +1,65 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
/** /**
* Loads all command files from src/commands and registers them on the client. * Loads all command files from src/commands and registers them on the client.
* Each command file must export: { name, description, aliases?, usage?, execute } * Each command file must export: { name, description, aliases?, usage?, execute }
* *
* @param {import('discord.js').Client} client * @param {import('discord.js').Client} client
*/ */
function loadCommands(client) { function loadCommands(client) {
const commandsPath = path.join(__dirname, '..', 'commands'); const commandsPath = path.join(__dirname, '..', 'commands');
// Recursively get all .js files in commandsPath // Recursively get all .js files in commandsPath
function getAllCommandFiles(dir) { function getAllCommandFiles(dir) {
let results = []; let results = [];
const list = fs.readdirSync(dir, { withFileTypes: true }); const list = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of list) { for (const entry of list) {
const fullPath = path.join(dir, entry.name); const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) { if (entry.isDirectory()) {
results = results.concat(getAllCommandFiles(fullPath)); results = results.concat(getAllCommandFiles(fullPath));
} else if (entry.isFile() && entry.name.endsWith('.js')) { } else if (entry.isFile() && entry.name.endsWith('.js')) {
results.push(path.relative(commandsPath, fullPath)); results.push(path.relative(commandsPath, fullPath));
} }
} }
return results; return results;
} }
const commandFiles = getAllCommandFiles(commandsPath); const commandFiles = getAllCommandFiles(commandsPath);
let loaded = 0; let loaded = 0;
for (const file of commandFiles) { for (const file of commandFiles) {
try { try {
const command = require(path.join(commandsPath, file)); const command = require(path.join(commandsPath, file));
if (!command.name || typeof command.execute !== 'function') { if (!command.name || typeof command.execute !== 'function') {
logger.warn( logger.warn(
`[commandhandler] skipping ${file}: missing "name" or "execute"`, `[commandhandler] skipping ${file}: missing "name" or "execute"`,
); );
continue; continue;
} }
client.commands.set(command.name.toLowerCase(), command); client.commands.set(command.name.toLowerCase(), command);
// Register aliases if provided // Register aliases if provided
if (Array.isArray(command.aliases)) { if (Array.isArray(command.aliases)) {
for (const alias of command.aliases) { for (const alias of command.aliases) {
client.commands.set(alias.toLowerCase(), command); client.commands.set(alias.toLowerCase(), command);
} }
} }
logger.debug(`[commandhandler] loaded command: ${command.name}`); logger.debug(`[commandhandler] loaded command: ${command.name}`);
loaded++; loaded++;
} catch (err) { } catch (err) {
logger.error(`[commandhandler] failed to load ${file}:`, err); logger.error(`[commandhandler] failed to load ${file}:`, err);
} }
} }
logger.info(`[commandhandler] ${loaded} command(s) loaded`); logger.info(`[commandhandler] ${loaded} command(s) loaded`);
} }
module.exports = { loadCommands }; module.exports = { loadCommands };
+53 -53
View File
@@ -1,53 +1,53 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { Events } = require('discord.js'); const { Events } = require('discord.js');
const guildMemberAddEvent = require('../events/guildMemberAdd'); const guildMemberAddEvent = require('../events/guildMemberAdd');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
/** /**
* Loads all event files from src/events and registers them on the client. * Loads all event files from src/events and registers them on the client.
* Each event file must export: { name, once?, execute } * Each event file must export: { name, once?, execute }
* *
* @param {import('discord.js').Client} client * @param {import('discord.js').Client} client
*/ */
function loadEvents(client) { function loadEvents(client) {
const eventsPath = path.join(__dirname, '..', 'events'); const eventsPath = path.join(__dirname, '..', 'events');
const eventFiles = fs const eventFiles = fs
.readdirSync(eventsPath) .readdirSync(eventsPath)
.filter((file) => file.endsWith('.js')); .filter((file) => file.endsWith('.js'));
let loaded = 0; let loaded = 0;
for (const file of eventFiles) { for (const file of eventFiles) {
try { try {
const event = require(path.join(eventsPath, file)); const event = require(path.join(eventsPath, file));
if (!event.name || typeof event.execute !== 'function') { if (!event.name || typeof event.execute !== 'function') {
logger.warn( logger.warn(
`[eventhandler] skipping ${file}: missing "name" or "execute"`, `[eventhandler] skipping ${file}: missing "name" or "execute"`,
); );
continue; continue;
} }
if (event.once) { if (event.once) {
client.once(event.name, (...args) => event.execute(client, ...args)); client.once(event.name, (...args) => event.execute(client, ...args));
} else { } else {
client.on(event.name, (...args) => event.execute(client, ...args)); client.on(event.name, (...args) => event.execute(client, ...args));
} }
logger.debug(`[eventhandler] registered event: ${event.name}`); logger.debug(`[eventhandler] registered event: ${event.name}`);
loaded++; loaded++;
} catch (err) { } catch (err) {
logger.error(`[eventhandler] failed to load ${file}:`, err); logger.error(`[eventhandler] failed to load ${file}:`, err);
} }
} }
client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client)); client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client));
loaded++; loaded++;
logger.info(`[eventhandler] ${loaded} event(s) registered`); logger.info(`[eventhandler] ${loaded} event(s) registered`);
} }
module.exports = { loadEvents }; module.exports = { loadEvents };
+50 -50
View File
@@ -1,50 +1,50 @@
'use strict'; 'use strict';
require('dotenv').config(); require('dotenv').config();
const { Client, Collection, GatewayIntentBits } = require('discord.js'); const { Client, Collection, GatewayIntentBits } = require('discord.js');
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const { loadCommands } = require('./handlers/commandHandler'); const { loadCommands } = require('./handlers/commandHandler');
const { loadEvents } = require('./handlers/eventHandler'); const { loadEvents } = require('./handlers/eventHandler');
// Validate required environment variables // Validate required environment variables
if (!process.env.BOT_TOKEN) { if (!process.env.BOT_TOKEN) {
logger.error( logger.error(
'BOT_TOKEN is not set. Copy .env.example to .env and fill in your token.', 'BOT_TOKEN is not set. Copy .env.example to .env and fill in your token.',
); );
process.exit(1); process.exit(1);
} }
// Create the Discord client with the intents needed for prefix commands // Create the Discord client with the intents needed for prefix commands
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // Required to read message content for prefix commands GatewayIntentBits.MessageContent, // Required to read message content for prefix commands
GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessages,
], ],
}); });
// Attach a Collection to store commands // Attach a Collection to store commands
client.commands = new Collection(); client.commands = new Collection();
// Load commands and events // Load commands and events
loadCommands(client); loadCommands(client);
loadEvents(client); loadEvents(client);
// Global error handling // Global error handling
process.on('unhandledRejection', (err) => { process.on('unhandledRejection', (err) => {
logger.error('[process] unhandled rejection:', err); logger.error('[process] unhandled rejection:', err);
}); });
process.on('uncaughtException', (err) => { process.on('uncaughtException', (err) => {
logger.error('[process] uncaught exception:', err); logger.error('[process] uncaught exception:', err);
process.exit(1); process.exit(1);
}); });
// Connect to Discord // Connect to Discord
client.login(process.env.BOT_TOKEN).catch((err) => { client.login(process.env.BOT_TOKEN).catch((err) => {
logger.error('[login] failed to log in:', err); logger.error('[login] failed to log in:', err);
process.exit(1); process.exit(1);
}); });
+74 -74
View File
@@ -1,74 +1,74 @@
'use strict'; 'use strict';
const { createLogger, format, transports } = require('winston'); const { createLogger, format, transports } = require('winston');
const { consoleFormat } = require("winston-console-format"); const { consoleFormat } = require("winston-console-format");
const { printf } = format; const { printf } = format;
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const logs_dir = path.join(__dirname, '..', '..', 'logs'); const logs_dir = path.join(__dirname, '..', '..', 'logs');
if (!fs.existsSync(logs_dir)) { if (!fs.existsSync(logs_dir)) {
fs.mkdirSync(logs_dir, { recursive: true }); fs.mkdirSync(logs_dir, { recursive: true });
} }
const logger = createLogger({ const logger = createLogger({
level: "silly", level: "silly",
format: format.combine( format: format.combine(
format.timestamp(), format.timestamp(),
format.ms(), format.ms(),
format.errors({ stack: true }), format.errors({ stack: true }),
format.splat(), format.splat(),
format.json({ space: 2 }) format.json({ space: 2 })
), ),
transports: [ transports: [
new transports.Console({ new transports.Console({
format: format.combine( format: format.combine(
format.colorize({ all: true }), format.colorize({ all: true }),
format.padLevels(), format.padLevels(),
consoleFormat({ consoleFormat({
showMeta: true, showMeta: true,
metaStrip: ["timestamp", "service"], metaStrip: ["timestamp", "service"],
inspectOptions: { inspectOptions: {
depth: Infinity, depth: Infinity,
colors: true, colors: true,
maxArrayLength: Infinity, maxArrayLength: Infinity,
breakLength: 120, breakLength: 120,
compact: Infinity, compact: Infinity,
}, },
}) })
), ),
}), }),
new transports.File({ new transports.File({
filename: path.join(logs_dir, 'combined.log'), filename: path.join(logs_dir, 'combined.log'),
format: format.combine( format: format.combine(
format.timestamp(), format.timestamp(),
format.ms(), format.ms(),
format.errors({ stack: true }), format.errors({ stack: true }),
format.splat(), format.splat(),
format.json({ space: 2 }) format.json({ space: 2 })
), ),
}), }),
new transports.File({ new transports.File({
filename: path.join(logs_dir, 'error.log'), filename: path.join(logs_dir, 'error.log'),
level: 'error', level: 'error',
format: format.combine( format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
format.errors({ stack: true }), format.errors({ stack: true }),
printf(({ level, message, timestamp, stack, ...meta }) => { printf(({ level, message, timestamp, stack, ...meta }) => {
let log = `${timestamp} [${level.toUpperCase()}]: ${message}`; let log = `${timestamp} [${level.toUpperCase()}]: ${message}`;
if (stack) { if (stack) {
log += `\ntrace: ${stack}`; log += `\ntrace: ${stack}`;
} }
if (Object.keys(meta).length > 0) { if (Object.keys(meta).length > 0) {
log += `\nmetadata: ${JSON.stringify(meta, null, 2)}`; log += `\nmetadata: ${JSON.stringify(meta, null, 2)}`;
} }
return log; return log;
}) })
), ),
}), }),
], ],
}); });
module.exports = logger; module.exports = logger;