Added level system and move commands to categories
This commit is contained in:
parent
23460dfdbf
commit
70f8c23bbf
37
.dockerignore
Normal file
37
.dockerignore
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Docker files
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Development config
|
||||||
|
botconfig.dev.json
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/.babelrc
|
/.babelrc
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
/botconfig.json
|
/botconfig.json
|
||||||
|
/botconfig.dev.json
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Use Debian Bookworm as the base image
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# Install Node.js and npm
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
gnupg \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
fonts-liberation fonts-dejavu fontconfig \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json (if available)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npm", "start"]
|
||||||
BIN
assets/level_clean.png
Normal file
BIN
assets/level_clean.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 465 KiB |
BIN
assets/progress_fill.png
Normal file
BIN
assets/progress_fill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 427 B |
92
commands/admin/adjust_xp.js
Normal file
92
commands/admin/adjust_xp.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
const Embed = require("../../functions/embed")
|
||||||
|
const { updateUserXP, getUserXP } = require("../../models/user");
|
||||||
|
const { xpSystem } = require("../../functions/level/xpSystem");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
config: {
|
||||||
|
name: "adjustxp",
|
||||||
|
cooldown: 5000,
|
||||||
|
available: true,
|
||||||
|
usage: true,
|
||||||
|
permissions: [],
|
||||||
|
aliases: [],
|
||||||
|
roles: ['staff'],
|
||||||
|
dm: false,
|
||||||
|
},
|
||||||
|
run: async (client, message, args, db) => {
|
||||||
|
if (!args[0]) return message.reply("You must mention a user to adjust their XP", false);
|
||||||
|
if (message.mentionIds && message.mentionIds.length > 0) {
|
||||||
|
let targetUser;
|
||||||
|
try {
|
||||||
|
const server = await client.servers.fetch(message.server.id);
|
||||||
|
const member = await server.fetchMember(message.mentionIds[0]);
|
||||||
|
|
||||||
|
if (!member || !member.id || !member.id.user) {
|
||||||
|
throw new Error("Invalid member structure");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the user using the user ID from the member object
|
||||||
|
targetUser = await client.users.fetch(member.id.user);
|
||||||
|
|
||||||
|
if (!targetUser || !targetUser.username) {
|
||||||
|
throw new Error("Could not fetch user information");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (fetchError) {
|
||||||
|
console.error("Fetch error details:", fetchError.message);
|
||||||
|
return message.reply({
|
||||||
|
embeds: [new Embed()
|
||||||
|
.setDescription("Could not find the mentioned user.")
|
||||||
|
.setColor(`#FF0000`)]
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[1]) return message.reply("You must specify an amount to adjust the XP by. `!adjust_xp @USER +/-<AMOUNT>`", false);
|
||||||
|
|
||||||
|
// Validate the amount is a valid number
|
||||||
|
const amountStr = args[1].replace("+", "").replace("-", "");
|
||||||
|
const amount = parseInt(amountStr);
|
||||||
|
|
||||||
|
if (isNaN(amount)) {
|
||||||
|
return message.reply({
|
||||||
|
embeds: [new Embed()
|
||||||
|
.setDescription("Invalid amount specified. Please provide a valid number.")
|
||||||
|
.setColor(`#FF0000`)]
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userDoc = await getUserXP(targetUser.id);
|
||||||
|
const currentXP = userDoc.xp || 0;
|
||||||
|
const newXP = args[1].includes("+") ? currentXP + amount : currentXP - amount;
|
||||||
|
|
||||||
|
// Ensure the new XP value is not negative
|
||||||
|
if (newXP < 0) {
|
||||||
|
return message.reply({
|
||||||
|
embeds: [new Embed()
|
||||||
|
.setDescription("Cannot set XP below 0.")
|
||||||
|
.setColor(`#FF0000`)]
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateUserXP(targetUser, newXP);
|
||||||
|
const adjusted_amount = args[1].includes("+") ? `+${amount}` : `-${amount}`;
|
||||||
|
await xpSystem(client, message);
|
||||||
|
return message.reply({
|
||||||
|
embeds: [new Embed()
|
||||||
|
.setDescription(`XP adjusted for ${targetUser.username} by ${adjusted_amount}`)
|
||||||
|
.setColor(`#00FF00`)]
|
||||||
|
}, false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating XP:", error);
|
||||||
|
return message.reply({
|
||||||
|
embeds: [new Embed()
|
||||||
|
.setDescription("An error occurred while updating the XP.")
|
||||||
|
.setColor(`#FF0000`)]
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return message.reply("You must mention a user to adjust their XP", false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
@ -1,10 +1,25 @@
|
|||||||
const axios = require(`axios`);
|
const axios = require(`axios`);
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const botConfig = require(path.join(__dirname, '../botconfig.json'));
|
const botConfig = require(path.join(__dirname, '../../botconfig.json'));
|
||||||
const { generateUniqueId } = require(path.join(__dirname, '../functions/randomStr'));
|
const { generateUniqueId } = require(path.join(__dirname, '../../functions/randomStr'));
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const Invites = require(path.join(__dirname, '../models/registerInvite'));
|
const Invites = require(path.join(__dirname, '../../models/registerInvite'));
|
||||||
const Embed = require(path.join(__dirname, '../functions/embed'));
|
const Embed = require(path.join(__dirname, '../../functions/embed'));
|
||||||
|
const DELAY = '5m';
|
||||||
|
|
||||||
|
// Helper function to parse time string to milliseconds
|
||||||
|
const parseTimeToMs = (timeStr) => {
|
||||||
|
const unit = timeStr.slice(-1).toLowerCase();
|
||||||
|
const value = parseInt(timeStr.slice(0, -1));
|
||||||
|
|
||||||
|
switch(unit) {
|
||||||
|
case 's': return value * 1000; // seconds
|
||||||
|
case 'm': return value * 60 * 1000; // minutes
|
||||||
|
case 'h': return value * 60 * 60 * 1000; // hours
|
||||||
|
case 'd': return value * 24 * 60 * 60 * 1000; // days
|
||||||
|
default: return 300000; // default 5 minutes if invalid format
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create axios instance outside of module.exports
|
// Create axios instance outside of module.exports
|
||||||
const makeRequest = axios.create({
|
const makeRequest = axios.create({
|
||||||
@ -17,6 +32,16 @@ const makeRequest = axios.create({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper function to delete messages after delay
|
||||||
|
const deleteAfterDelay = async (message, delay = parseTimeToMs(DELAY)) => {
|
||||||
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
await message.delete().catch(() => {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
name: `register`,
|
name: `register`,
|
||||||
@ -29,24 +54,41 @@ module.exports = {
|
|||||||
dm: true
|
dm: true
|
||||||
},
|
},
|
||||||
run: async (client, message, args, db) => {
|
run: async (client, message, args, db) => {
|
||||||
|
console.log('[Register Command] Command started with args:', {
|
||||||
|
args: args,
|
||||||
|
channelType: message.channel.type,
|
||||||
|
author: message.author.username,
|
||||||
|
guild: message.server?.name || 'DM'
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
// If not in DM, check for permissions and roles
|
// If not in DM, check for permissions and roles
|
||||||
if (message.channel.type !== 'DirectMessage') {
|
if (message.channel.type !== 'DirectMessage') {
|
||||||
|
console.log('[Register Command] Not in DM, checking permissions');
|
||||||
// Get allowed role IDs from config
|
// Get allowed role IDs from config
|
||||||
const allowedRoleIds = module.exports.config.roles
|
const allowedRoleIds = module.exports.config.roles
|
||||||
.map(roleName => botConfig.roles[0][roleName])
|
.map(roleName => botConfig.roles[0][roleName])
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
console.log('[Register Command] Allowed role IDs:', allowedRoleIds);
|
||||||
|
console.log('[Register Command] User roles:', message.member.roles);
|
||||||
|
console.log('[Register Command] Bot config roles:', botConfig.roles[0]);
|
||||||
|
|
||||||
const hasRequiredRole = message.member.roles.some(roleId => allowedRoleIds.includes(roleId));
|
const hasRequiredRole = message.member.roles.some(roleId => allowedRoleIds.includes(roleId));
|
||||||
|
console.log('[Register Command] Has required role:', hasRequiredRole);
|
||||||
|
console.log('[Register Command] Is owner:', client.config.owners.includes(message.authorId));
|
||||||
|
|
||||||
if (!hasRequiredRole && !client.config.owners.includes(message.authorId)) {
|
if (!hasRequiredRole && !client.config.owners.includes(message.authorId)) {
|
||||||
return message.reply({
|
console.log('[Register Command] User lacks required permissions');
|
||||||
|
const reply = await message.reply({
|
||||||
embeds: [
|
embeds: [
|
||||||
new Embed()
|
new Embed()
|
||||||
.setColor("#FF0000")
|
.setColor("#FF0000")
|
||||||
.setDescription(`You don't have the required roles to use this command.`)
|
.setDescription(`You don't have the required roles to use this command.`)
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
// Delete both messages after 5 minutes
|
||||||
|
deleteAfterDelay(message);
|
||||||
|
deleteAfterDelay(reply);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,41 +122,46 @@ module.exports = {
|
|||||||
|
|
||||||
await registerInvite.save();
|
await registerInvite.save();
|
||||||
|
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `Run this command in a DM to the bot and use this invite code: \`${inviteCode}\`\n
|
content: `Run this command in a DM to the bot and use this invite code: \`${inviteCode}\`\n
|
||||||
**Usage:** \`!register ${inviteCode} <username> <email> <f95zone profile url>\`\n
|
**Usage:** \`!register ${inviteCode} <username> <email> <f95zone profile url>\`\n
|
||||||
**Note: This invite code will expire in 10 minutes.**`
|
**Note: This invite code will expire in 10 minutes.**`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [invite, username, email, profileUrl] = args;
|
const [invite, username, email, profileUrl] = args;
|
||||||
|
|
||||||
if (!invite || !username || !email || !profileUrl) {
|
if (!invite || !username || !email || !profileUrl) {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG3] Please provide a invite code, username, email, and f95zone profile url. \n\n**Usage:** \`${client.prefix}register <invite> <username> <email> <f95zone profile url>\``
|
content: `[REG3] Please provide a invite code, username, email, and f95zone profile url. \n\n**Usage:** \`${client.prefix}register <invite> <username> <email> <f95zone profile url>\``
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DM flow - Use client.database instead of direct model
|
// DM flow - Use client.database instead of direct model
|
||||||
const registerInvite = await Invites.findOne({ invite: invite });
|
const registerInvite = await Invites.findOne({ invite: invite });
|
||||||
|
|
||||||
if (!registerInvite) {
|
if (!registerInvite) {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG1] You don't have an invite code. Please run the command outside of DMs.`
|
content: `[REG1] You don't have an invite code. Please run the command outside of DMs.`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registerInvite.createdAt + 10 * 60 * 1000 < Date.now()) {
|
if (registerInvite.createdAt + 10 * 60 * 1000 < Date.now()) {
|
||||||
await registerInvite.deleteOne({ invite: invite });
|
await registerInvite.deleteOne({ invite: invite });
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG2] Your invite code has expired. Please run the command outside of DMs.`
|
content: `[REG2] Your invite code has expired. Please run the command outside of DMs.`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invite !== registerInvite.invite) {
|
if (invite !== registerInvite.invite) {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG4] Invalid invite code. Please run the command outside of DMs.`
|
content: `[REG4] Invalid invite code. Please run the command outside of DMs.`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let f95zoneId = profileUrl.split('/')[4];
|
let f95zoneId = profileUrl.split('/')[4];
|
||||||
@ -134,40 +181,49 @@ module.exports = {
|
|||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
await registerInvite.deleteOne({ invite: invite });
|
await registerInvite.deleteOne({ invite: invite });
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5] You have been registered. Please check your email for verification.`
|
content: `[REG5] You have been registered. Please check your email for verification.\n
|
||||||
|
**If you do not receive email in 5 minutes. Please check your spam or trash for email.**`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} else if (data.status === 'invalid_email') {
|
} else if (data.status === 'invalid_email') {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5.1] An error occurred during registration. ${data.msg}`
|
content: `[REG5.1] An error occurred during registration. ${data.msg}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} else if (data.status === 'username_exists') {
|
} else if (data.status === 'username_exists') {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5.2] An error occurred during registration. ${data.msg}`
|
content: `[REG5.2] An error occurred during registration. ${data.msg}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} else if (data.status === 'invalid_f95_id') {
|
} else if (data.status === 'invalid_f95_id') {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5.3] An error occurred during registration. ${data.msg}`
|
content: `[REG5.3] An error occurred during registration. ${data.msg}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} else if (data.status === 'email_exists') {
|
} else if (data.status === 'email_exists') {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5.4] An error occurred during registration. ${data.msg}`
|
content: `[REG5.4] An error occurred during registration. ${data.msg}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} else if (data.status === 'error') {
|
} else if (data.status === 'error') {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG5.5] An error occurred during registration. ${data.msg}`
|
content: `[REG5.5] An error occurred during registration. ${data.msg}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (apiError) {
|
} catch (apiError) {
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG6] An error occurred during registration. Please try again later.\n\`${apiError}\``
|
content: `[REG6] An error occurred during registration. Please try again later.\n\`${apiError}\``
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Registration Error:', error);
|
console.error('Registration Error:', error);
|
||||||
return message.reply({
|
const reply = await message.reply({
|
||||||
content: `[REG7] An unexpected error occurred. Please try again later.\n\`${error}\``
|
content: `[REG7] An unexpected error occurred. Please try again later.\n\`${error}\``
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const Reload = require("../functions/reload")
|
const Reload = require("../../functions/reload")
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
name: "reload",
|
name: "reload",
|
||||||
@ -1,6 +1,6 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
const Uploader = require("revolt-uploader");
|
const Uploader = require("revolt-uploader");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -1,12 +1,12 @@
|
|||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
const fetch = require('node-fetch-commonjs')
|
const fetch = require('node-fetch-commonjs')
|
||||||
const config = require('../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
name: "gif",
|
name: "gif",
|
||||||
usage: true,
|
usage: true,
|
||||||
cooldown: 5000,
|
cooldown: 10000,
|
||||||
available: true,
|
available: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const Embed = require("../functions/embed");
|
const Embed = require("../../functions/embed");
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -1,6 +1,6 @@
|
|||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
const CommandsDB = require('../models/commands');
|
const CommandsDB = require('../../models/commands');
|
||||||
const { random } = require('../functions/randomStr');
|
const { random } = require('../../functions/randomStr');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -1,8 +1,8 @@
|
|||||||
const Embed = require(`../functions/embed`)
|
const Embed = require(`../../functions/embed`)
|
||||||
const Polls = require(`../functions/poll`)
|
const Polls = require(`../../functions/poll`)
|
||||||
const dhms = require(`../functions/dhms`);
|
const dhms = require(`../../functions/dhms`);
|
||||||
const PollDB = require("../models/polls");
|
const PollDB = require("../../models/polls");
|
||||||
const SavedPolls = require(`../models/savedPolls`)
|
const SavedPolls = require(`../../models/savedPolls`)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
@ -1,8 +1,8 @@
|
|||||||
const Embed = require(`../functions/embed`)
|
const Embed = require(`../../functions/embed`)
|
||||||
const Polls = require(`../functions/poll`)
|
const Polls = require(`../../functions/poll`)
|
||||||
const dhms = require(`../functions/dhms`);
|
const dhms = require(`../../functions/dhms`);
|
||||||
const PollDB = require("../models/rule7");
|
const PollDB = require("../../models/rule7");
|
||||||
const SavedPolls = require(`../models/savedPolls`)
|
const SavedPolls = require(`../../models/savedPolls`)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const Embed = require("../functions/embed")
|
const Embed = require("../../functions/embed")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
@ -1,7 +1,7 @@
|
|||||||
const Embed = require("../functions/embed");
|
const Embed = require("../../functions/embed");
|
||||||
const Giveaway = require("../models/giveaways");
|
const Giveaway = require("../../models/giveaways");
|
||||||
const SavedPolls = require("../models/savedPolls");
|
const SavedPolls = require("../../models/savedPolls");
|
||||||
const { dependencies } = require("../package.json");
|
const { dependencies } = require("../../package.json");
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {
|
||||||
name: "info",
|
name: "info",
|
||||||
46
commands/utility/level.js
Normal file
46
commands/utility/level.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const { getUserXP } = require("../../models/user");
|
||||||
|
const generateLevelCard = require("../../functions/level/generate_level_card");
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
config: {
|
||||||
|
name: "level",
|
||||||
|
usage: true,
|
||||||
|
cooldown: 5000,
|
||||||
|
available: true,
|
||||||
|
permissions: [],
|
||||||
|
roles: [],
|
||||||
|
dm: false,
|
||||||
|
aliases: ['lvl']
|
||||||
|
},
|
||||||
|
run: async (client, message, args, db) => {
|
||||||
|
try {
|
||||||
|
const user = await getUserXP(message.author.id);
|
||||||
|
const levelCardBuffer = await generateLevelCard(user);
|
||||||
|
|
||||||
|
// Create a temporary file path
|
||||||
|
const tempFilePath = path.join(os.tmpdir(), `level_card_${message.author.id}.png`);
|
||||||
|
|
||||||
|
// Save the buffer to the temporary file
|
||||||
|
await fs.writeFile(tempFilePath, levelCardBuffer);
|
||||||
|
|
||||||
|
// Upload the file
|
||||||
|
const attachment = await client.Uploader.uploadFile(tempFilePath, "level_card.png");
|
||||||
|
|
||||||
|
// Send the message with the attachment
|
||||||
|
await message.channel.sendMessage({
|
||||||
|
attachments: [attachment]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up the temporary file
|
||||||
|
await fs.unlink(tempFilePath).catch(console.error);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in level command:', error);
|
||||||
|
await message.channel.sendMessage({
|
||||||
|
content: "❌ An error occurred while generating your level card. Please try again later."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
42
docker-compose.yml
Normal file
42
docker-compose.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: zonies-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MONGODB_URI=mongodb://mongodb:27017/zonies
|
||||||
|
volumes:
|
||||||
|
- ./assets:/usr/src/app/assets
|
||||||
|
- ./botconfig.json:/usr/src/app/botconfig.json
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '2'
|
||||||
|
memory: 4G
|
||||||
|
reservations:
|
||||||
|
cpus: '1'
|
||||||
|
memory: 2G
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
image: mongo:6.0
|
||||||
|
container_name: zonies-mongodb
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
volumes:
|
||||||
|
- mongodb_data:/data/db
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongodb_data:
|
||||||
@ -5,6 +5,7 @@ const EditCollector = require(path.join(__dirname, "../functions/messageEdit"));
|
|||||||
const CommandDB = require(path.join(__dirname, "../models/commands"));
|
const CommandDB = require(path.join(__dirname, "../models/commands"));
|
||||||
const { isJson } = require(path.join(__dirname, "../functions/randomStr"));
|
const { isJson } = require(path.join(__dirname, "../functions/randomStr"));
|
||||||
const botConfig = require(path.join(__dirname, "../botconfig.json"));
|
const botConfig = require(path.join(__dirname, "../botconfig.json"));
|
||||||
|
const {xpSystem} = require(path.join(__dirname, "../functions/level/xpSystem"));
|
||||||
|
|
||||||
module.exports = async (client, message) => {
|
module.exports = async (client, message) => {
|
||||||
// Early return checks
|
// Early return checks
|
||||||
@ -13,12 +14,18 @@ module.exports = async (client, message) => {
|
|||||||
const isDM = message.channel.type === "DirectMessage";
|
const isDM = message.channel.type === "DirectMessage";
|
||||||
|
|
||||||
// Get guild settings (if not DM)
|
// Get guild settings (if not DM)
|
||||||
const db = isDM ? { prefix: client.botConfig.prefix, language: 'en' } : await client.database.getGuild(message.server.id, true);
|
const db = isDM ? { prefix: client.config.prefix, language: 'en' } : await client.database.getGuild(message.server.id, true);
|
||||||
|
|
||||||
|
if (!isDM) {
|
||||||
|
await xpSystem(client, message);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if message starts with prefix
|
// Check if message starts with prefix
|
||||||
if (!message.content.startsWith(db.prefix)) {
|
if (!message.content.startsWith(db.prefix)) {
|
||||||
|
|
||||||
// Handle bot mention
|
// Handle bot mention
|
||||||
if (message.content && (new RegExp(`^(<@!?${client.user.id}>)`)).test(message.content)) {
|
if (message.content && (new RegExp(`^(<@!?${client.user.id}>)`)).test(message.content)) {
|
||||||
|
console.log('[MessageCreate] Bot was mentioned');
|
||||||
const mention = new Embed()
|
const mention = new Embed()
|
||||||
.setColor("#A52F05")
|
.setColor("#A52F05")
|
||||||
.setTitle(client.user.username)
|
.setTitle(client.user.username)
|
||||||
@ -41,6 +48,7 @@ module.exports = async (client, message) => {
|
|||||||
// Handle custom commands
|
// Handle custom commands
|
||||||
let check = await CommandDB.findOne({ name: cmd }).select("name").lean();
|
let check = await CommandDB.findOne({ name: cmd }).select("name").lean();
|
||||||
if (check) {
|
if (check) {
|
||||||
|
console.log('[MessageCreate] Custom command found:', cmd);
|
||||||
CommandDB.findOne({ name: cmd }).then((data) => {
|
CommandDB.findOne({ name: cmd }).then((data) => {
|
||||||
if (isJson(data.content)) {
|
if (isJson(data.content)) {
|
||||||
let items = JSON.parse(data.content);
|
let items = JSON.parse(data.content);
|
||||||
@ -53,6 +61,7 @@ module.exports = async (client, message) => {
|
|||||||
|
|
||||||
// Command handling
|
// Command handling
|
||||||
let commandfile = client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd));
|
let commandfile = client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd));
|
||||||
|
|
||||||
if (commandfile) {
|
if (commandfile) {
|
||||||
// DM Check - if command doesn't allow DMs and we're in a DM, return
|
// DM Check - if command doesn't allow DMs and we're in a DM, return
|
||||||
if (isDM && !commandfile.config.dm) {
|
if (isDM && !commandfile.config.dm) {
|
||||||
|
|||||||
88
functions/level/generate_level_card.js
Normal file
88
functions/level/generate_level_card.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
const { createCanvas, loadImage } = require('canvas');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the XP required for the next level
|
||||||
|
* @param {number} currentLevel - Current user level
|
||||||
|
* @returns {number} XP required for next level
|
||||||
|
*/
|
||||||
|
function calculateNextLevelXP(currentLevel) {
|
||||||
|
// Using the inverse of the level calculation formula from xpSystem.js
|
||||||
|
// level = 0.47 * sqrt(xp)
|
||||||
|
// Therefore, xp = (level/0.47)^2
|
||||||
|
return Math.ceil(Math.pow((currentLevel + 1) / 0.47, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a level card image for a user
|
||||||
|
* @param {Object} user - User object containing id, username, xp, and level
|
||||||
|
* @returns {Promise<Buffer>} - Returns a buffer containing the generated image
|
||||||
|
*/
|
||||||
|
async function generateLevelCard(user) {
|
||||||
|
// Create canvas with dimensions matching the background image
|
||||||
|
const canvas = createCanvas(1516, 662);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load background image
|
||||||
|
const background = await loadImage(path.join(__dirname, '../../assets/level_clean.png'));
|
||||||
|
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Load progress bar fill image
|
||||||
|
const progressFill = await loadImage(path.join(__dirname, '../../assets/progress_fill.png'));
|
||||||
|
|
||||||
|
// Calculate XP progress using the same formula as xpSystem.js
|
||||||
|
const currentLevelXP = Math.floor(Math.pow(user.level / 0.47, 2));
|
||||||
|
const nextLevelXP = Math.floor(calculateNextLevelXP(user.level));
|
||||||
|
const xpInLevel = user.xp - currentLevelXP;
|
||||||
|
const xpForLevel = nextLevelXP - currentLevelXP;
|
||||||
|
// Clamp progress between 0 and 1
|
||||||
|
const progress = Math.max(0, Math.min(1, xpInLevel / xpForLevel));
|
||||||
|
// Clamp displayed XP to not exceed xpForLevel
|
||||||
|
const displayXP = Math.min(Math.max(0, xpInLevel), xpForLevel);
|
||||||
|
|
||||||
|
// Draw username
|
||||||
|
ctx.font = 'bold 60px Arial';
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText(user.username, 420, 250);
|
||||||
|
|
||||||
|
// Draw level
|
||||||
|
ctx.font = 'bold 48px Arial';
|
||||||
|
ctx.fillStyle = '#FFD700'; // Gold color for level
|
||||||
|
ctx.fillText(`Level ${user.level}`, 420, 330);
|
||||||
|
|
||||||
|
// Progress bar dimensions and position
|
||||||
|
const progressBarX = 472;
|
||||||
|
const progressBarY = 388;
|
||||||
|
const progressBarWidth = 800;
|
||||||
|
const progressBarHeight = 60;
|
||||||
|
|
||||||
|
// Draw progress bar fill
|
||||||
|
ctx.drawImage(
|
||||||
|
progressFill,
|
||||||
|
progressBarX,
|
||||||
|
progressBarY,
|
||||||
|
progressBarWidth * progress,
|
||||||
|
progressBarHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw XP progress text inside the bar, right-aligned
|
||||||
|
ctx.font = 'bold 36px Arial';
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(
|
||||||
|
`${displayXP}/${xpForLevel} XP`,
|
||||||
|
progressBarX + progressBarWidth / 2,
|
||||||
|
progressBarY + progressBarHeight / 2 + 12 // adjust as needed
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert canvas to buffer
|
||||||
|
return canvas.toBuffer('image/png');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating level card:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = generateLevelCard;
|
||||||
84
functions/level/xpSystem.js
Normal file
84
functions/level/xpSystem.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
const Embed = require("../embed");
|
||||||
|
const { User, getUserXP, checkUser, updateUserMessageCount, updateUserXPAndLevel } = require("../../models/user");
|
||||||
|
const { XPSetting, getXPSettings } = require("../../models/xp_setting");
|
||||||
|
const logger = require("../logger");
|
||||||
|
|
||||||
|
const self = module.exports = {
|
||||||
|
xpSystem: async (client, message) => {
|
||||||
|
try {
|
||||||
|
// Check if message is from a server
|
||||||
|
if (!message.server) {
|
||||||
|
return; // Skip XP for DMs
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkUser(message.author);
|
||||||
|
const user = await getUserXP(message.author.id);
|
||||||
|
const settings = await getXPSettings(message.server.id);
|
||||||
|
|
||||||
|
// If no settings exist for this server, create default settings
|
||||||
|
if (!settings) {
|
||||||
|
const defaultSettings = {
|
||||||
|
serverId: message.server.id,
|
||||||
|
messages_per_xp: 3,
|
||||||
|
min_xp_per_gain: 2,
|
||||||
|
max_xp_per_gain: 12,
|
||||||
|
weekend_multiplier: 2,
|
||||||
|
weekend_days: "sat,sun",
|
||||||
|
double_xp_enabled: false,
|
||||||
|
level_up_enabled: true,
|
||||||
|
level_up_channel: '01HF7B18Z864E10XSF22F9RFZQ'
|
||||||
|
};
|
||||||
|
await XPSetting.create(defaultSettings);
|
||||||
|
return; // Skip this message, will work from next message
|
||||||
|
}
|
||||||
|
|
||||||
|
user.message_count++;
|
||||||
|
|
||||||
|
if (user.message_count >= settings.messages_per_xp) {
|
||||||
|
user.message_count = 0;
|
||||||
|
let xpGain = Math.floor(Math.random() * (settings.max_xp_per_gain - settings.min_xp_per_gain + 1)) + settings.min_xp_per_gain;
|
||||||
|
|
||||||
|
if (settings.double_xp_enabled || self.isWeekend(settings.weekend_days)) {
|
||||||
|
xpGain *= settings.weekend_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.xp += xpGain;
|
||||||
|
|
||||||
|
const newLevel = self.calculateLevel(user.xp);
|
||||||
|
if (newLevel > user.level) {
|
||||||
|
user.level = newLevel;
|
||||||
|
if (settings.level_up_enabled) {
|
||||||
|
const channel = client.channels.get(settings.level_up_channel);
|
||||||
|
if (channel) {
|
||||||
|
await self.sendLevelUpMessage(channel, message.author, newLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateUserXPAndLevel(message.author.id, user.xp, user.level, user.message_count);
|
||||||
|
logger.info('XP SYSTEM', `${message.author.username} gained ${xpGain} XP and is now level ${user.level}`);
|
||||||
|
} else {
|
||||||
|
await updateUserMessageCount(message.author.id, user.message_count);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('XP SYSTEM', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isWeekend: (weekendDays) => {
|
||||||
|
const today = new Date().toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase();
|
||||||
|
return weekendDays.split(',').includes(today);
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateLevel: (xp) => Math.floor(0.47 * Math.sqrt(xp)),
|
||||||
|
|
||||||
|
sendLevelUpMessage: async (channel, user, newLevel) => {
|
||||||
|
const embed = {
|
||||||
|
title: 'Level Up!',
|
||||||
|
description: `Congratulations ${user.username}! You've reached level ${newLevel}!`,
|
||||||
|
colour: '#00FF00'
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.sendMessage({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,7 +1,30 @@
|
|||||||
const { readdirSync } = require("fs")
|
const { readdirSync, statSync } = require("fs")
|
||||||
const { join } = require("path")
|
const { join } = require("path")
|
||||||
const color = require("../functions/colorCodes")
|
const color = require("../functions/colorCodes")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively gets all command files from a directory
|
||||||
|
* @param {string} dir - Directory to scan
|
||||||
|
* @returns {string[]} Array of command file paths
|
||||||
|
*/
|
||||||
|
function getCommandFiles(dir) {
|
||||||
|
const files = []
|
||||||
|
const items = readdirSync(dir)
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const path = join(dir, item)
|
||||||
|
const stat = statSync(path)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
files.push(...getCommandFiles(path))
|
||||||
|
} else if (item.endsWith('.js')) {
|
||||||
|
files.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and registers all command files from the commands directory
|
* Loads and registers all command files from the commands directory
|
||||||
* @param {Object} client - The Discord client instance
|
* @param {Object} client - The Discord client instance
|
||||||
@ -10,14 +33,14 @@ const color = require("../functions/colorCodes")
|
|||||||
module.exports = async (client) => {
|
module.exports = async (client) => {
|
||||||
try {
|
try {
|
||||||
const commandsPath = join(__dirname, "..", "commands")
|
const commandsPath = join(__dirname, "..", "commands")
|
||||||
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith(".js"))
|
const commandFiles = getCommandFiles(commandsPath)
|
||||||
|
|
||||||
let loadedCommands = 0
|
let loadedCommands = 0
|
||||||
let failedCommands = 0
|
let failedCommands = 0
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
try {
|
try {
|
||||||
const command = require(join(commandsPath, file))
|
const command = require(file)
|
||||||
|
|
||||||
// Validate command structure
|
// Validate command structure
|
||||||
if (!command.config?.name) {
|
if (!command.config?.name) {
|
||||||
@ -26,6 +49,8 @@ module.exports = async (client) => {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(color("%", `%b[Command_Handler]%7 :: Loading %e${command.config.name}%7 command`))
|
||||||
|
|
||||||
// Register command and aliases
|
// Register command and aliases
|
||||||
client.commands.set(command.config.name, command)
|
client.commands.set(command.config.name, command)
|
||||||
if (command.config.aliases?.length) {
|
if (command.config.aliases?.length) {
|
||||||
|
|||||||
17
index.js
17
index.js
@ -1,12 +1,27 @@
|
|||||||
const { Client } = require("revolt.js");
|
const { Client } = require("revolt.js");
|
||||||
const { Collection } = require('@discordjs/collection');
|
const { Collection } = require('@discordjs/collection');
|
||||||
const { token, mongoDB, api } = require("./botconfig.json");
|
|
||||||
const logger = require('./functions/logger');
|
const logger = require('./functions/logger');
|
||||||
const checkPolls = require('./functions/checkPolls');
|
const checkPolls = require('./functions/checkPolls');
|
||||||
const color = require("./functions/colorCodes");
|
const color = require("./functions/colorCodes");
|
||||||
const Uploader = require("revolt-uploader");
|
const Uploader = require("revolt-uploader");
|
||||||
const TranslationHandler = require('./handlers/translation');
|
const TranslationHandler = require('./handlers/translation');
|
||||||
const DatabaseHandler = require('./handlers/database');
|
const DatabaseHandler = require('./handlers/database');
|
||||||
|
// let config;
|
||||||
|
|
||||||
|
// if (process.env.NODE_ENV === "development") {
|
||||||
|
// config = require("./botconfig.dev.json");
|
||||||
|
// } else {
|
||||||
|
// config = require("./botconfig.json");
|
||||||
|
// }
|
||||||
|
const config = require("./botconfig.json");
|
||||||
|
|
||||||
|
// Validate required configuration
|
||||||
|
if (!config || !config.token || !config.mongoDB || !config.api) {
|
||||||
|
throw new Error('Missing required configuration. Please check your botconfig.json file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token, mongoDB, api } = config;
|
||||||
|
console.log(mongoDB)
|
||||||
|
|
||||||
class Bot {
|
class Bot {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
|
|||||||
BIN
level_card.png
Normal file
BIN
level_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 458 KiB |
@ -2,7 +2,7 @@ const { Schema, model } = require("mongoose");
|
|||||||
|
|
||||||
const guilds = new Schema({
|
const guilds = new Schema({
|
||||||
id: { type: String },
|
id: { type: String },
|
||||||
prefix: { type: String, default: "f!" },
|
prefix: { type: String, default: "!" },
|
||||||
language: { type: String, default: "en_EN" },
|
language: { type: String, default: "en_EN" },
|
||||||
joined: { type: String, default: Date.now() / 1000 | 0 },
|
joined: { type: String, default: Date.now() / 1000 | 0 },
|
||||||
dm: { type: Boolean, default: true },
|
dm: { type: Boolean, default: true },
|
||||||
|
|||||||
118
models/user.js
Normal file
118
models/user.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const { Schema, model } = require("mongoose");
|
||||||
|
|
||||||
|
const user = new Schema({
|
||||||
|
id: { type: String },
|
||||||
|
username: { type: String },
|
||||||
|
xp: { type: Number, default: 0 },
|
||||||
|
level: { type: Number, default: 1 },
|
||||||
|
message_count: { type: Number, default: 0 }
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkUser(user) {
|
||||||
|
const userId = user.id;
|
||||||
|
const username = user.username;
|
||||||
|
|
||||||
|
// Find user by id
|
||||||
|
const existingUser = await User.findOne({ id: userId });
|
||||||
|
|
||||||
|
// If user doesn't exist, create new user
|
||||||
|
if (!existingUser) {
|
||||||
|
await User.create({
|
||||||
|
id: userId,
|
||||||
|
username: username,
|
||||||
|
xp: 0,
|
||||||
|
level: 1,
|
||||||
|
message_count: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserXP(userId) {
|
||||||
|
let userDoc = await User.findOne({ id: userId });
|
||||||
|
|
||||||
|
// If user doesn't exist, create new user
|
||||||
|
if (!userDoc) {
|
||||||
|
userDoc = await User.create({
|
||||||
|
id: userId,
|
||||||
|
xp: 0,
|
||||||
|
level: 1,
|
||||||
|
message_count: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return userDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserXP(user, xp) {
|
||||||
|
const userId = user.id;
|
||||||
|
const userDoc = await User.findOne({ id: userId });
|
||||||
|
userDoc.xp = xp;
|
||||||
|
await userDoc.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserLevel(user, level) {
|
||||||
|
const userId = user.id;
|
||||||
|
const userDoc = await User.findOne({ id: userId });
|
||||||
|
userDoc.level = level;
|
||||||
|
await userDoc.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserXPAndLevel(userId, xp, level, message_count) {
|
||||||
|
const userDoc = await User.findOne({ id: userId });
|
||||||
|
if (!userDoc) {
|
||||||
|
return await User.create({
|
||||||
|
id: userId,
|
||||||
|
xp: xp,
|
||||||
|
level: level,
|
||||||
|
message_count: message_count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
userDoc.xp = xp;
|
||||||
|
userDoc.level = level;
|
||||||
|
userDoc.message_count = message_count;
|
||||||
|
await userDoc.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserMessageCount(userId, message_count) {
|
||||||
|
const userDoc = await User.findOne({ id: userId });
|
||||||
|
if (!userDoc) {
|
||||||
|
return await User.create({
|
||||||
|
id: userId,
|
||||||
|
xp: 0,
|
||||||
|
level: 1,
|
||||||
|
message_count: message_count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
userDoc.message_count = message_count;
|
||||||
|
await userDoc.save();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves the top users sorted by XP
|
||||||
|
* @param {number} limit - Maximum number of users to return (default: 10)
|
||||||
|
* @returns {Promise<Array>} Array of user objects containing id, username, xp, and level
|
||||||
|
*/
|
||||||
|
async function getLeaderboard(limit = 10) {
|
||||||
|
const users = await User.find()
|
||||||
|
.sort({ xp: -1 })
|
||||||
|
.limit(limit)
|
||||||
|
.select('id username xp level');
|
||||||
|
|
||||||
|
return users.map(user => ({
|
||||||
|
user_id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
xp: user.xp,
|
||||||
|
level: user.level
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const User = model("user", user);
|
||||||
|
module.exports = {
|
||||||
|
checkUser,
|
||||||
|
getUserXP,
|
||||||
|
updateUserXP,
|
||||||
|
updateUserLevel,
|
||||||
|
updateUserXPAndLevel,
|
||||||
|
getLeaderboard,
|
||||||
|
updateUserMessageCount,
|
||||||
|
User
|
||||||
|
};
|
||||||
30
models/xp_setting.js
Normal file
30
models/xp_setting.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const { Schema, model } = require("mongoose");
|
||||||
|
|
||||||
|
const xpSetting = new Schema({
|
||||||
|
messages_per_xp: { type: Number },
|
||||||
|
min_xp_per_gain: { type: Number },
|
||||||
|
max_xp_per_gain: { type: Number },
|
||||||
|
weekend_multiplier: { type: Number },
|
||||||
|
weekend_days: { type: String },
|
||||||
|
double_xp_enabled: { type: Boolean },
|
||||||
|
serverId: { type: String },
|
||||||
|
level_up_channel: { type: String },
|
||||||
|
level_up_enabled: { type: Boolean }
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getXPSettings(serverId) {
|
||||||
|
const settings = await XPSetting.findOne({ serverId: serverId });
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateXPSettings(serverId, settings) {
|
||||||
|
await XPSetting.updateOne({ serverId: serverId }, { $set: settings });
|
||||||
|
}
|
||||||
|
|
||||||
|
const XPSetting = model("xpSetting", xpSetting);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getXPSettings,
|
||||||
|
updateXPSettings,
|
||||||
|
XPSetting
|
||||||
|
};
|
||||||
93
package-lock.json
generated
93
package-lock.json
generated
@ -21,6 +21,9 @@
|
|||||||
"revolt.js": "npm:revolt.js-update@^7.0.0-beta.9",
|
"revolt.js": "npm:revolt.js-update@^7.0.0-beta.9",
|
||||||
"screen": "^0.2.10",
|
"screen": "^0.2.10",
|
||||||
"wumpfetch": "^0.3.1"
|
"wumpfetch": "^0.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/collection": {
|
"node_modules/@discordjs/collection": {
|
||||||
@ -385,6 +388,40 @@
|
|||||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-env": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"cross-env": "src/bin/cross-env.js",
|
||||||
|
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-spawn": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||||
@ -674,6 +711,13 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/isexe": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/isomorphic-ws": {
|
"node_modules/isomorphic-ws": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||||
@ -1171,6 +1215,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.8",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||||
@ -1359,6 +1413,29 @@
|
|||||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shiki": {
|
"node_modules/shiki": {
|
||||||
"version": "0.14.4",
|
"version": "0.14.4",
|
||||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz",
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz",
|
||||||
@ -1655,6 +1732,22 @@
|
|||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/node-which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wide-align": {
|
"node_modules/wide-align": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js"
|
"start": "node index.js",
|
||||||
|
"dev": "cross-env NODE_ENV=development node index.js"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -26,5 +27,8 @@
|
|||||||
"Discord": "ainzooalgown",
|
"Discord": "ainzooalgown",
|
||||||
"Revolt": "Ryahn#1337",
|
"Revolt": "Ryahn#1337",
|
||||||
"GitHub": "https://github.com/Ryahn/zonies"
|
"GitHub": "https://github.com/Ryahn/zonies"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
test.js
10
test.js
@ -15,13 +15,11 @@
|
|||||||
// })
|
// })
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const axios = require("axios");
|
const xp = Math.floor(0.47 * Math.sqrt(12345632346));
|
||||||
const query = 'yeet'
|
const level = Math.floor(xp / 100);
|
||||||
const url = ('https://api.urbandictionary.com/v0/define?term=' + query)
|
|
||||||
const response = await axios.get(url);
|
|
||||||
const data = response.data;
|
|
||||||
const def = data.list[0];
|
|
||||||
|
|
||||||
|
console.log(xp);
|
||||||
|
console.log(level);
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
Loading…
x
Reference in New Issue
Block a user