1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-09-29 16:30:59 -04:00

Major changes to the bot.

This commit is contained in:
AnxietyisReal 2023-02-25 11:55:11 +11:00
parent 3d6ccbeadf
commit 8283544f07
22 changed files with 587 additions and 605 deletions

View File

@ -30,13 +30,14 @@
"ms": "2.1.3", "ms": "2.1.3",
"sequelize": "6.28.2", "sequelize": "6.28.2",
"sqlite3": "5.1.4", "sqlite3": "5.1.4",
"systeminformation": "5.17.9", "mongoose": "6.10.0",
"systeminformation": "5.17.10",
"@octokit/rest": "19.0.7", "@octokit/rest": "19.0.7",
"typescript": "4.9.5", "typescript": "4.9.5",
"xml-js": "1.6.11" "xml-js": "1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.14.0", "@types/node": "18.14.1",
"ts-node": "10.9.1" "ts-node": "10.9.1"
} }
} }

View File

@ -1,8 +1,13 @@
import Discord, { Client, WebhookClient, GatewayIntentBits, Partials } from 'discord.js'; import Discord, { Client, WebhookClient, GatewayIntentBits, Partials } from 'discord.js';
import fs from 'node:fs'; import fs from 'node:fs';
import { exec } from 'node:child_process';
import timeNames from './timeNames'; import timeNames from './timeNames';
import { Punishment, formatTimeOpt, Tokens, Config, repeatedMessages } from './typings/interfaces'; import mongoose from 'mongoose';
import { bannedWords, bonkCount, userLevels, punishments } from './schoolClassroom'; import { formatTimeOpt, Tokens, Config, repeatedMessages } from './typings/interfaces';
import bannedWords from './models/bannedWords';
import userLevels from './models/userLevels';
import punishments from './models/punishments';
import bonkCount from './models/bonkCount';
import MPDB from './models/MPServer'; import MPDB from './models/MPServer';
import axios from 'axios'; import axios from 'axios';
import moment from 'moment'; import moment from 'moment';
@ -82,11 +87,16 @@ export default class TClient extends Client {
} }
async init(){ async init(){
MPDB.sync(); MPDB.sync();
this.login(this.tokens.token_main); mongoose.set('strictQuery', true);
this.punishments.initLoad(); await mongoose.connect(this.tokens.mongodb_uri, {
this.bannedWords.initLoad(); autoIndex: true,
this.bonkCount.initLoad(); serverSelectionTimeoutMS: 5000,
this.userLevels.initLoad().intervalSave(30000).disableSaveNotifs(); socketTimeoutMS: 30000,
family: 4,
keepAlive: true,
waitQueueTimeoutMS: 50000
}).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(()=>{console.log(this.logTime(), 'Failed to connect to MongoDB'); exec('pm2 stop Daggerbot')})
await this.login(this.tokens.main);
const commandFiles = fs.readdirSync('src/commands').filter(file=>file.endsWith('.ts')); const commandFiles = fs.readdirSync('src/commands').filter(file=>file.endsWith('.ts'));
for (const file of commandFiles){ for (const file of commandFiles){
const command = require(`./commands/${file}`); const command = require(`./commands/${file}`);
@ -98,12 +108,6 @@ export default class TClient extends Client {
this.on(file.replace('.ts', ''), async(...args)=>eventFile.default.run(this,...args)); this.on(file.replace('.ts', ''), async(...args)=>eventFile.default.run(this,...args));
}); });
} }
formatPunishmentType(punishment: Punishment, client: TClient, cancels?: Punishment){
if (punishment.type == 'removeOtherPunishment'){
cancels ||= this.punishments._content.find((x: Punishment)=>x.id === punishment.cancels)
return cancels.type[0].toUpperCase()+cancels.type.slice(1)+' Removed';
} else return punishment.type[0].toUpperCase()+punishment.type.slice(1);
}
formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){ formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){
let achievedAccuracy = 0; let achievedAccuracy = 0;
let text:any = ''; let text:any = '';
@ -134,6 +138,9 @@ export default class TClient extends Client {
youNeedRole(interaction: Discord.CommandInteraction, role:string){ youNeedRole(interaction: Discord.CommandInteraction, role:string){
return interaction.reply(`This command is restricted to <@&${this.config.mainServer.roles[role]}>`) return interaction.reply(`This command is restricted to <@&${this.config.mainServer.roles[role]}>`)
} }
logTime(){
return `[${this.moment().format('DD/MM/YY HH:mm:ss')}]`
}
alignText(text: string, length: number, alignment: string, emptyChar = ' '){ alignText(text: string, length: number, alignment: string, emptyChar = ' '){
if (alignment == 'right'){ if (alignment == 'right'){
text = emptyChar.repeat(length - text.length)+text; text = emptyChar.repeat(length - text.length)+text;
@ -158,14 +165,6 @@ export default class TClient extends Client {
await interaction.deferReply(); await interaction.deferReply();
await client.punishments.addPunishment(type, { time, interaction }, interaction.user.id, reason, User, GuildMember); await client.punishments.addPunishment(type, { time, interaction }, interaction.user.id, reason, User, GuildMember);
};
async unPunish(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!client.isStaff(interaction.member as Discord.GuildMember)) return this.youNeedRole(interaction, 'dcmod');
const punishment = this.punishments._content.find((x:Punishment)=>x.id === interaction.options.getInteger('case_id'));
if (!punishment) return interaction.reply({content: 'Invalid Case #', ephemeral: true});
const reason = interaction.options.getString('reason') ?? 'Reason unspecified';
const unpunishResult = await this.punishments.removePunishment(punishment.id, interaction.user.id, reason);
interaction.reply(unpunishResult)
} }
async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){ async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){
let Data:any; let Data:any;
@ -177,7 +176,7 @@ export default class TClient extends Client {
}) })
} catch(err){ } catch(err){
error = true; error = true;
console.log(`[${this.moment().format('DD/MM/YY HH:mm:ss')}]`, `${YTChannelName} YT fail`) console.log(this.logTime(), `${YTChannelName} YT fail`)
} }
if (!Data) return; if (!Data) return;
@ -192,5 +191,5 @@ export default class TClient extends Client {
} }
} }
export class WClient extends WebhookClient {tokens: Tokens; constructor(){super({url: tokens.webhook_url})}} export class WClient extends WebhookClient {tokens: Tokens; constructor(){super({url: tokens.webhook_url_test})}}
// hi tae, ik you went to look for secret hello msgs in here too. // hi tae, ik you went to look for secret hello msgs in here too.

View File

@ -3,33 +3,34 @@ import TClient from 'src/client';
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!client.isStaff(interaction.member) && !client.config.eval.whitelist.includes(interaction.member.id)) return client.youNeedRole(interaction, 'admin') if (!client.isStaff(interaction.member) && !client.config.eval.whitelist.includes(interaction.member.id)) return client.youNeedRole(interaction, 'admin')
const word = interaction.options.getString('word'); const word = interaction.options.getString('word', true);
const wordExists = await client.bannedWords._content.findById(word);
({ ({
add: ()=>{ add: async()=>{
if (client.bannedWords._content.includes(word)) return interaction.reply({content: `\`${word}\` is already added.`, ephemeral: true}); if (wordExists) return interaction.reply({content: `\`${word}\` is already added.`, ephemeral: true});
client.bannedWords.addData(word).forceSave(); await client.bannedWords._content.create({_id:word}).then(a=>a.save());
interaction.reply(`Successfully added \`${word}\` to the list.`) interaction.reply(`Successfully added \`${word}\` to the database.`)
}, },
remove: ()=>{ remove: async()=>{
if (client.bannedWords._content.includes(word) == false) return interaction.reply({content: `\`${word}\` doesn't exist on the list.`, ephemeral: true}); if (!wordExists) return interaction.reply({content: `\`${word}\` doesn't exist on the list.`, ephemeral: true});
client.bannedWords.removeData(word, 0, 0).forceSave(); await client.bannedWords._content.findOneAndDelete({_id:word});
interaction.reply(`Successfully removed \`${word}\` from the list.`) interaction.reply(`Successfully removed \`${word}\` from the database.`)
}, },
view: ()=>interaction.reply({content: 'Here is a complete list of banned words!\n*You can open it with a web browser, e.g Chrome/Firefox/Safari, or you can use Visual Studio Code/Notepad++*', files: ['src/database/bannedWords.json'], ephemeral: true}) //view: ()=>interaction.reply({content: 'Here is a complete list of banned words!\n*You can open it with a web browser, e.g Chrome/Firefox/Safari, or you can use Visual Studio Code/Notepad++*', files: ['src/database/bannedWords.json'], ephemeral: true})
} as any)[interaction.options.getSubcommand()](); } as any)[interaction.options.getSubcommand()]();
}, },
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('bannedwords') .setName('bannedwords')
.setDescription('description placeholder') .setDescription('description placeholder')
.addSubcommand((opt)=>opt /*.addSubcommand((opt)=>opt
.setName('view') .setName('view')
.setDescription('View the list of currently banned words.')) .setDescription('View the list of currently banned words.'))
.addSubcommand((opt)=>opt */.addSubcommand((opt)=>opt
.setName('add') .setName('add')
.setDescription('What word do you want to add?') .setDescription('What word do you want to add?')
.addStringOption((optt)=>optt .addStringOption((optt)=>optt
.setName('word') .setName('word')
.setDescription('Add the specific word to automod\'s bannedWords list.') .setDescription('Add the specific word to automod\'s bannedWords database.')
.setRequired(true))) .setRequired(true)))
.addSubcommand((opt)=>opt .addSubcommand((opt)=>opt
.setName('remove') .setName('remove')

View File

@ -8,11 +8,11 @@ export default {
const adminPerm = member.permissions.has('Administrator'); const adminPerm = member.permissions.has('Administrator');
if (adminPerm) return interaction.reply('You cannot bonk an admin!'); if (adminPerm) return interaction.reply('You cannot bonk an admin!');
client.bonkCount._incrementUser(member.id).forceSave(); await client.bonkCount._incrementUser(member.id);
interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor) interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor)
.setDescription(`> <@${member.id}> has been bonked!\n${reason?.length == null ? '' : `> Reason: **${reason}**`}`) .setDescription(`> <@${member.id}> has been bonked!\n${reason?.length == null ? '' : `> Reason: **${reason}**`}`)
.setImage('https://media.tenor.com/7tRddlNUNNcAAAAd/hammer-on-head-minions.gif') .setImage('https://media.tenor.com/7tRddlNUNNcAAAAd/hammer-on-head-minions.gif')
.setFooter({text: `Bonk count for ${member.user.tag}: ${client.bonkCount.getUser(member.id).toLocaleString('en-US')}`}) .setFooter({text: `Bonk count for ${member.user.tag}: ${await client.bonkCount._content.findById(member.id).then(b=>b.value.toLocaleString('en-US'))}`})
]}) ]})
}, },
data: new SlashCommandBuilder() data: new SlashCommandBuilder()

View File

@ -1,6 +1,5 @@
import Discord,{SlashCommandBuilder} from "discord.js"; import Discord,{SlashCommandBuilder} from "discord.js";
import TClient from 'src/client'; import TClient from 'src/client';
import { Punishment } from "src/typings/interfaces";
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod'); if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod');
@ -8,16 +7,15 @@ export default {
({ ({
update: async()=>{ update: async()=>{
const reason = interaction.options.getString('reason'); const reason = interaction.options.getString('reason');
client.punishments._content.find((x:Punishment)=>x.id==caseId).reason = reason; await client.punishments._content.findByIdAndUpdate(caseId, {reason});
client.punishments.forceSave();
await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]}) await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]})
}, },
view: ()=>{ view: async()=>{
const punishment = client.punishments._content.find((x:Punishment)=>x.id==caseId); const punishment = await client.punishments._content.findById(caseId);
if (!punishment) return interaction.reply('Invalid case #'); if (!punishment) return interaction.reply('Invalid Case #');
const cancelledBy = punishment.expired ? client.punishments._content.find((x:Punishment)=>x.cancels==punishment.id) : null; const cancelledBy = punishment.expired ? await client.punishments._content.findOne({cancels:punishment.id}) : null;
const cancels = punishment.cancels ? client.punishments._content.find((x:Punishment)=>x.id==punishment.cancels) : null; const cancels = punishment.cancels ? await client.punishments._content.findOne({_id:punishment.cancels}) : null;
const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(punishment.time).setTitle(`${client.formatPunishmentType(punishment, client, cancels)} | Case #${punishment.id}`).addFields( const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(punishment.time).setTitle(`${punishment.type[0].toUpperCase()+punishment.type.slice(1)} | Case #${punishment.id}`).addFields(
{name: '🔹 User', value: `<@${punishment.member}> \`${punishment.member}\``, inline: true}, {name: '🔹 User', value: `<@${punishment.member}> \`${punishment.member}\``, inline: true},
{name: '🔹 Moderator', value: `<@${punishment.moderator}> \`${punishment.moderator}\``, inline: true}, {name: '🔹 Moderator', value: `<@${punishment.moderator}> \`${punishment.moderator}\``, inline: true},
{name: '\u200b', value: '\u200b', inline: true}, {name: '\u200b', value: '\u200b', inline: true},
@ -27,17 +25,17 @@ export default {
if (punishment.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites case #${cancels.id} with reason \`${cancels.reason}\``}) if (punishment.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites case #${cancels.id} with reason \`${cancels.reason}\``})
interaction.reply({embeds: [embed]}); interaction.reply({embeds: [embed]});
}, },
member: ()=>{ member: async()=>{
// if caseid is user id, show their punishment history sorted by most recent. // if caseid is user id, show their punishment history sorted by most recent.
const user = (interaction.options.getUser('user') as Discord.User); const user = (interaction.options.getUser('user') as Discord.User);
if (user.bot) return interaction.reply(`<@${user.id}>'s punishment history cannot be viewed.`) if (user.bot) return interaction.reply(`<@${user.id}>'s punishment history cannot be viewed.`)
const punishment = client.punishments._content.find((x:Punishment)=>x.member===user.id); const punishments = await client.punishments._content.find({});
if (!punishment) return interaction.reply(`<@${user.id}> has a clean record.`) if (!punishments) return interaction.reply(`<@${user.id}> has a clean record.`)
const cancels = punishment.cancels ? client.punishments._content.find((x:Punishment)=>x.id==punishment.cancels) : null; const userPunishmentData = await client.punishments._content.find({'member':user.id});
const userPunishment = client.punishments._content.filter((x:Punishment)=>x.member==user.id).sort((a:Punishment,b:Punishment)=>a.time-b.time).map((punishment:Punishment)=>{ const userPunishment = userPunishmentData.sort((a,b)=>a.time-b.time).map((punishment)=>{
return { return {
name: `${client.formatPunishmentType(punishment, client, cancels)} | Case #${punishment.id}`, name: `${punishment.type[0].toUpperCase()+punishment.type.slice(1)} | Case #${punishment.id}`,
value: `Reason: \`${punishment.reason}\`\n${punishment.duration ? `Duration: ${client.formatTime(punishment.duration, 3)}\n` : ''}Moderator: <@${punishment.moderator}>${punishment.expired ? `\nOverwritten by case #${client.punishments._content.find((x:Punishment)=>x.cancels==punishment.id).id}` : ''}${punishment.cancels ? `\nOverwrites case #${punishment.cancels}` : ''}` value: `Reason: \`${punishment.reason}\`\n${punishment.duration ? `Duration: ${client.formatTime(punishment.duration, 3)}\n` : ''}Moderator: <@${punishment.moderator}>${punishment.expired ? `\nOverwritten by Case #${punishments.find(x=>x.cancels===punishment._id)?._id}` : ''}${punishment.cancels ? `\nOverwrites case #${punishment.cancels}` : ''}`
} }
}); });
// if caseid is not a punishment nor a user, failed // if caseid is not a punishment nor a user, failed

View File

@ -2,7 +2,7 @@ import Discord,{SlashCommandBuilder} from 'discord.js';
import {Octokit} from '@octokit/rest'; import {Octokit} from '@octokit/rest';
import {exec} from 'node:child_process'; import {exec} from 'node:child_process';
import {readFileSync} from 'node:fs'; import {readFileSync} from 'node:fs';
import * as util from 'node:util'; import util from 'node:util';
import TClient from '../client'; import TClient from '../client';
import path from 'node:path'; import path from 'node:path';
const removeUsername = (text: string)=>{ const removeUsername = (text: string)=>{
@ -46,11 +46,11 @@ export default {
} }
if (error) return; if (error) return;
if (typeof output == 'object') { if (typeof output == 'object') {
output = 'js\n'+util.formatWithOptions({depth: 1}, '%O', output) output = 'js\n'+util.formatWithOptions({depth: 1, colors: true}, '%O', output)
} else { } else {
output = '\n' + String(output); output = '\n' + String(output);
} }
[client.tokens.token_main,client.tokens.token_beta,client.tokens.token_toast,client.tokens.token_tae,client.tokens.webhook_url].forEach((x)=>{ [client.tokens.main,client.tokens.beta,client.tokens.toast,client.tokens.tae,client.tokens.webhook_url,client.tokens.webhook_url_test,client.tokens.mongodb_uri,client.tokens.mongodb_uri_dev].forEach((x)=>{
const regexp = new RegExp(x as string,'g'); const regexp = new RegExp(x as string,'g');
output = output.replace(regexp, ':noblank: No token?'); output = output.replace(regexp, ':noblank: No token?');
}) })
@ -91,12 +91,10 @@ export default {
const name = interaction.options.getString('name'); const name = interaction.options.getString('name');
const url = interaction.options.getString('url'); const url = interaction.options.getString('url');
const currentActivities = client.config.botPresence.activities as Discord.ActivitiesOptions[]; const currentActivities = client.config.botPresence.activities as Discord.ActivitiesOptions[];
if (status) client.config.botPresence.status = status; if (status) client.config.botPresence.status = status;
if (type) currentActivities[0].type = type; if (type) currentActivities[0].type = type;
if (name) currentActivities[0].name = name; if (name) currentActivities[0].name = name;
if (url) currentActivities[0].url = url; if (url) currentActivities[0].url = url;
client.user.setPresence(client.config.botPresence); client.user.setPresence(client.config.botPresence);
interaction.reply([ interaction.reply([
'Presence updated:', 'Presence updated:',
@ -115,7 +113,6 @@ export default {
(client.channels.resolve(client.config.mainServer.channels.console) as Discord.TextChannel).send({content: `Uploaded the current console dump as of <t:${Math.round(Date.now()/1000)}:R>`, files: [`${process.env.pm2_home}/logs/Daggerbot-out-0.log`, `${process.env.pm2_home}/logs/Daggerbot-error-0.log`]}).then(()=>interaction.editReply('It has been uploaded to dev server.')).catch((e:Error)=>interaction.editReply(`\`${e.message}\``)) (client.channels.resolve(client.config.mainServer.channels.console) as Discord.TextChannel).send({content: `Uploaded the current console dump as of <t:${Math.round(Date.now()/1000)}:R>`, files: [`${process.env.pm2_home}/logs/Daggerbot-out-0.log`, `${process.env.pm2_home}/logs/Daggerbot-error-0.log`]}).then(()=>interaction.editReply('It has been uploaded to dev server.')).catch((e:Error)=>interaction.editReply(`\`${e.message}\``))
}, },
restart: ()=>{ restart: ()=>{
client.userLevels.forceSave();
interaction.reply(`Uptime before restarting: **${client.formatTime(client.uptime as number, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot')) interaction.reply(`Uptime before restarting: **${client.formatTime(client.uptime as number, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot'))
} }
} as any)[interaction.options.getSubcommand()](); } as any)[interaction.options.getSubcommand()]();

View File

@ -3,7 +3,6 @@ import TClient from 'src/client';
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
({ ({
srp: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('When will SRP (Survival Roleplay) return?').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1074260301756780544/image.png')]}),
dlskin: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Daggerwin Logistics hex code').setDescription('The main color will be Onyx (`#353839`) with red bumpers.').setImage('https://cdn.discordapp.com/attachments/801965516947324969/806871878736019456/image0.png')]}), dlskin: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Daggerwin Logistics hex code').setDescription('The main color will be Onyx (`#353839`) with red bumpers.').setImage('https://cdn.discordapp.com/attachments/801965516947324969/806871878736019456/image0.png')]}),
vtcR: ()=>interaction.reply(`You can get the <@&${client.config.mainServer.roles.vtcmember}> role from <#802283932430106624> by reacting <@282859044593598464>'s message with :truck:\n*VTC skin can also be found in <#801975222609641472> as well.*`), vtcR: ()=>interaction.reply(`You can get the <@&${client.config.mainServer.roles.vtcmember}> role from <#802283932430106624> by reacting <@282859044593598464>'s message with :truck:\n*VTC skin can also be found in <#801975222609641472> as well.*`),
mpR: ()=>interaction.reply(`You can get the <@&${client.config.mainServer.roles.mpplayer}> role from <#802283932430106624> by reacting <@282859044593598464>'s message with :tractor:`), mpR: ()=>interaction.reply(`You can get the <@&${client.config.mainServer.roles.mpplayer}> role from <#802283932430106624> by reacting <@282859044593598464>'s message with :tractor:`),
@ -20,7 +19,6 @@ export default {
.setDescription('What question do you want answered?') .setDescription('What question do you want answered?')
.setRequired(true) .setRequired(true)
.addChoices( .addChoices(
{ name: 'Survival Roleplay', value: 'srp' },
{ name: 'Daggerwin Logistics hex code', value: 'dlskin' }, { name: 'Daggerwin Logistics hex code', value: 'dlskin' },
{ name: 'Scams in YT comments', value: 'ytscam' }, { name: 'Scams in YT comments', value: 'ytscam' },
{ name: 'VTC Role', value: 'vtcR' }, { name: 'VTC Role', value: 'vtcR' },

View File

@ -24,7 +24,7 @@ async function MPdata(client:TClient, interaction:Discord.ChatInputCommandIntera
// Blame Nawdic & RedRover92 // Blame Nawdic & RedRover92
embed.setTitle('Host is not responding.'); embed.setTitle('Host is not responding.');
embed.setColor(client.config.embedColorRed); embed.setColor(client.config.embedColorRed);
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] dag mp fail to fetch, host is not responding.`); console.log(client.logTime, 'dag mp fail to fetch, host is not responding.');
return interaction.reply('Server didn\'t respond in time.'); return interaction.reply('Server didn\'t respond in time.');
} }
return FSserver return FSserver

View File

@ -1,5 +1,4 @@
import Discord,{SlashCommandBuilder} from 'discord.js'; import Discord,{SlashCommandBuilder} from 'discord.js';
import {UserLevels} from 'src/typings/interfaces';
import TClient from 'src/client'; import TClient from 'src/client';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
@ -7,31 +6,32 @@ import canvas from 'canvas';
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (interaction.guildId !== client.config.mainServer.id) return interaction.reply({content: 'This command doesn\'t work in this server.', ephemeral: true}); if (interaction.guildId !== client.config.mainServer.id) return interaction.reply({content: 'This command doesn\'t work in this server.', ephemeral: true});
const allData = await client.userLevels._content.find({});
({ ({
view: ()=>{ view: async()=>{
// fetch user or user interaction sender // fetch user or user interaction sender
const member = interaction.options.getMember("member") ?? interaction.member as Discord.GuildMember; const member = interaction.options.getMember("member") ?? interaction.member as Discord.GuildMember;
if (member.user.bot) return interaction.reply('Bots don\'t level up, try viewing non-bots instead.') if (member.user.bot) return interaction.reply('Bots don\'t level up, try viewing non-bots instead.')
// information about users progress on level roles // information about users progress on level roles
const information = client.userLevels._content[member.user.id]; const userData = await client.userLevels._content.findById(member.user.id);
const pronounBool = (you: string, they: string) => { // takes 2 words and chooses which to use based on if user did this command on themself const pronounBool = (you: string, they: string) => { // takes 2 words and chooses which to use based on if user did this command on themself
if (interaction.user.id === member.user.id) return you || true; if (interaction.user.id === member.user.id) return you || true;
else return they || false; else return they || false;
}; };
if (!information) return interaction.reply(`${pronounBool('You', 'They')} currently don't have a level, send some messages to level up.`) if (!userData) return interaction.reply(`${pronounBool('You', 'They')} currently don't have a level, send some messages to level up.`)
const index = Object.entries<UserLevels>(client.userLevels._content).sort((a, b) => b[1].messages - a[1].messages).map(x => x[0]).indexOf(member.id) + 1; const index = allData.sort((a, b) => b.messages - a.messages).map(x => x._id).indexOf(member.id) + 1;
const memberDifference = information.messages - client.userLevels.algorithm(information.level); const memberDifference = userData.messages - client.userLevels.algorithm(userData.level);
const levelDifference = client.userLevels.algorithm(information.level+1) - client.userLevels.algorithm(information.level); const levelDifference = client.userLevels.algorithm(userData.level+1) - client.userLevels.algorithm(userData.level);
interaction.reply({embeds: [new client.embed().setColor(member.displayColor).setTitle(`Level: **${information.level}**\nRank: **${index ? '#' + index : 'last'}**\nProgress: **${information.messages - client.userLevels.algorithm(information.level)}/${client.userLevels.algorithm(information.level+1) - client.userLevels.algorithm(information.level)} (${(memberDifference/levelDifference*100).toFixed(2)}%)**\nTotal: **${information.messages}**`).setThumbnail(member.user.avatarURL({ extension: 'png', size: 256}) || member.user.defaultAvatarURL)]}) interaction.reply({embeds: [new client.embed().setColor(member.displayColor).setTitle(`Level: **${userData.level}**\nRank: **${index ? '#' + index : 'last'}**\nProgress: **${memberDifference}/${levelDifference} (${(memberDifference/levelDifference*100).toFixed(2)}%)**\nTotal: **${userData.messages}**`).setThumbnail(member.user.avatarURL({ extension: 'png', size: 256}) || member.user.defaultAvatarURL)]})
}, },
leaderboard: ()=>{ leaderboard: ()=>{
const messageCountsTotal = Object.values<UserLevels>(client.userLevels._content).reduce((a, b) => a + b.messages, 0); const messageCountsTotal = allData.reduce((a, b) => a + b.messages, 0);
const timeActive = Math.floor((Date.now() - client.config.LRSstart)/1000/60/60/24); const timeActive = Math.floor((Date.now() - client.config.LRSstart)/1000/60/60/24);
const dailyMsgsPath = path.join(__dirname, '../database/dailyMsgs.json'); const dailyMsgsPath = path.join(__dirname, '../database/dailyMsgs.json');
const data = JSON.parse(fs.readFileSync(dailyMsgsPath, {encoding: 'utf8'})).map((x: Array<number>, i: number, a: any) => { const data = JSON.parse(fs.readFileSync(dailyMsgsPath, 'utf8')).map((x: Array<number>, i: number, a: any) => {
const yesterday = a[i - 1] || []; const yesterday = a[i - 1] || [];
return x[1] - (yesterday[1] || x[1]); return x[1] - (yesterday[1] || x[1]);
}).slice(1).slice(-60); }).slice(1).slice(-60);
@ -147,12 +147,15 @@ export default {
const ty = graphOrigin[1] + graphSize[1] + (textSize); const ty = graphOrigin[1] + graphSize[1] + (textSize);
ctx.fillText('time ->', tx, ty); ctx.fillText('time ->', tx, ty);
const yeahok = new client.attachmentBuilder(img.toBuffer(), {name: 'dailymsgs.png'}) const topUsers = allData.sort((a,b)=>b.messages - a.messages).slice(0,10).map((x,i)=>`\`${i+1}.\` <@${x._id}>: ${x.messages.toLocaleString('en-US')}`).join('\n');
const graphImage = new client.attachmentBuilder(img.toBuffer(), {name: 'dailymsgs.png'})
const embed = new client.embed().setTitle('Ranking leaderboard') const embed = new client.embed().setTitle('Ranking leaderboard')
.setDescription(`Level System was created **${timeActive}** days ago. Since then, a total of **${messageCountsTotal.toLocaleString('en-US')}** messages have been sent in this server.\nGraph updates daily @ <t:${Math.round((client.config.LRSstart+3600000)/1000)}:t>`) .setDescription(`Level System was created **${timeActive}** days ago. Since then, a total of **${messageCountsTotal.toLocaleString('en-US')}** messages have been sent in this server.`)
.addFields({name: 'Top users by messages sent:', value: Object.entries<UserLevels>(client.userLevels._content).sort((a, b) => b[1].messages - a[1].messages).slice(0, 10).map((x, i) => `\`${i + 1}.\` <@${x[0]}>: ${x[1].messages.toLocaleString('en-US')}`).join('\n')}) .addFields({name: 'Top users by messages sent:', value: topUsers})
.setImage('attachment://dailymsgs.png').setColor(client.config.embedColor) .setImage('attachment://dailymsgs.png').setColor(client.config.embedColor)
interaction.reply({embeds: [embed], files: [yeahok]}) .setFooter({text: 'Graph updates daily.'})
interaction.reply({embeds: [embed], files: [graphImage]})
} }
} as any)[interaction.options.getSubcommand()](); } as any)[interaction.options.getSubcommand()]();
}, },

View File

@ -2,7 +2,12 @@ import Discord,{SlashCommandBuilder} from 'discord.js';
import TClient from 'src/client'; import TClient from 'src/client';
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.unPunish(client, interaction) if (!client.isStaff(interaction.member as Discord.GuildMember)) return client.youNeedRole(interaction, 'dcmod');
const punishment = (await client.punishments._content.find({})).find(x=>x._id === interaction.options.getInteger('case_id', true));
if (!punishment) return interaction.reply({content: 'Invalid Case ID', ephemeral: true});
if (punishment.expired) return interaction.reply('This case has been overwritten by another case.');
const reason = interaction.options.getString('reason') ?? 'Reason unspecified';
await client.punishments.removePunishment(punishment.id, interaction.user.id, reason, interaction);
}, },
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('unpunish') .setName('unpunish')

View File

@ -4,12 +4,14 @@ export default {
async run(client:TClient, member:Discord.GuildMember){ async run(client:TClient, member:Discord.GuildMember){
if (!client.config.botSwitches.logs) return; if (!client.config.botSwitches.logs) return;
if (!member.joinedTimestamp || member.guild?.id != client.config.mainServer.id) return; if (!member.joinedTimestamp || member.guild?.id != client.config.mainServer.id) return;
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColorRed).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048}) as string).setTitle(`Member Left: ${member.user.tag}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).addFields( const levelData = await client.userLevels._content.findById(member.id);
const embed = new client.embed().setColor(client.config.embedColorRed).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048}) as string).setTitle(`Member Left: ${member.user.tag}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).addFields(
{name: '🔹 Account Creation Date', value: `<t:${Math.round(member.user.createdTimestamp/1000)}>\n<t:${Math.round(member.user.createdTimestamp/1000)}:R>`}, {name: '🔹 Account Creation Date', value: `<t:${Math.round(member.user.createdTimestamp/1000)}>\n<t:${Math.round(member.user.createdTimestamp/1000)}:R>`},
{name: '🔹 Server Join Date', value: `<t:${Math.round(member.joinedTimestamp/1000)}>\n<t:${Math.round(member.joinedTimestamp/1000)}:R>`}, {name: '🔹 Server Join Date', value: `<t:${Math.round(member.joinedTimestamp/1000)}>\n<t:${Math.round(member.joinedTimestamp/1000)}:R>`},
{name: `🔹 Roles: ${member.roles.cache.size - 1}`, value: `${member.roles.cache.size > 1 ? member.roles.cache.filter((x)=>x.id !== member.guild.roles.everyone.id).sort((a,b)=>b.position - a.position).map(x=>x).join(member.roles.cache.size > 4 ? ' ' : '\n').slice(0,1024) : 'No roles'}`, inline: true}, {name: `🔹 Roles: ${member.roles.cache.size - 1}`, value: `${member.roles.cache.size > 1 ? member.roles.cache.filter((x)=>x.id !== member.guild.roles.everyone.id).sort((a,b)=>b.position - a.position).map(x=>x).join(member.roles.cache.size > 4 ? ' ' : '\n').slice(0,1024) : 'No roles'}`, inline: true}
{name: '🔹 Level messages', value: `${client.userLevels._content[member.user.id]?.messages.toLocaleString('en-US') || 0}`, inline: true} );
)]}); if (levelData && levelData.messages > 1) embed.addFields({name: '🔹 Total messages', value: levelData.messages.toLocaleString('en-US'), inline: true});
delete client.userLevels._content[member.user.id]; (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[embed]});
await client.userLevels._content.findByIdAndDelete(member.id)
} }
} }

View File

@ -5,7 +5,7 @@ export default {
if (!interaction.inGuild() || !interaction.inCachedGuild()) return; if (!interaction.inGuild() || !interaction.inCachedGuild()) return;
if (interaction.isChatInputCommand()){ if (interaction.isChatInputCommand()){
const commandFile = client.commands.get(interaction.commandName); const commandFile = client.commands.get(interaction.commandName);
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] ${interaction.user.tag} used /${interaction.commandName} ${interaction.options.getSubcommand(false) ?? ''} in #${interaction.channel.name}`); console.log(client.logTime(), `${interaction.user.tag} used /${interaction.commandName} ${interaction.options.getSubcommand(false) ?? ''} in #${interaction.channel.name}`);
if (!client.config.botSwitches.commands && !client.config.eval.whitelist.includes(interaction.user.id)) return interaction.reply({content: 'Bot is currently being run in development mode.', ephemeral: true}); if (!client.config.botSwitches.commands && !client.config.eval.whitelist.includes(interaction.user.id)) return interaction.reply({content: 'Bot is currently being run in development mode.', ephemeral: true});
if (commandFile){ if (commandFile){
try{ try{

View File

@ -14,7 +14,7 @@ export default {
// Arrary of channel ids for automod to be disabled in // Arrary of channel ids for automod to be disabled in
] ]
if (client.bannedWords._content.some((x)=>msgarr.includes(x)) && !message.member.roles.cache.has(client.config.mainServer.roles.dcmod) && message.guildId == client.config.mainServer.id && !Whitelist.includes(message.channelId) && client.config.botSwitches.automod){ if (await client.bannedWords._content.findOne({_id:msgarr}) && !message.member.roles.cache.has(client.config.mainServer.roles.dcmod) && message.guildId == client.config.mainServer.id && !Whitelist.includes(message.channelId) && client.config.botSwitches.automod){
automodded = true; automodded = true;
const threshold = 30000; const threshold = 30000;
message.delete().catch(err=>console.log('bannedWords automod; msg got possibly deleted by another bot.')) message.delete().catch(err=>console.log('bannedWords automod; msg got possibly deleted by another bot.'))

View File

@ -6,7 +6,7 @@ export default {
const disabledChannels = ['548032776830582794', '541677709487505408'] const disabledChannels = ['548032776830582794', '541677709487505408']
if (oldMsg.guild?.id != client.config.mainServer.id || oldMsg.author == null || oldMsg?.author.bot || oldMsg.partial || newMsg.partial || !newMsg.member || disabledChannels.includes(newMsg.channelId)) return; if (oldMsg.guild?.id != client.config.mainServer.id || oldMsg.author == null || oldMsg?.author.bot || oldMsg.partial || newMsg.partial || !newMsg.member || disabledChannels.includes(newMsg.channelId)) return;
const msgarr = newMsg.content.toLowerCase().split(' '); const msgarr = newMsg.content.toLowerCase().split(' ');
if (client.bannedWords._content.some((word:string)=>msgarr.includes(word)) && (!client.isStaff(newMsg.member))) newMsg.delete(); if (await client.bannedWords._content.findOne({_id:msgarr}) && (!client.isStaff(newMsg.member))) newMsg.delete();
if (newMsg.content === oldMsg.content) return; if (newMsg.content === oldMsg.content) return;
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setTimestamp().setAuthor({name: `Author: ${oldMsg.author.tag} (${oldMsg.author.id})`, iconURL: `${oldMsg.author.displayAvatarURL()}`}).setTitle('Message edited').setDescription(`<@${oldMsg.author.id}>\nOld content:\n\`\`\`\n${oldMsg.content}\n\`\`\`\nNew content:\n\`\`\`\n${newMsg.content}\`\`\`\nChannel: <#${oldMsg.channelId}>`)], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]}); (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setTimestamp().setAuthor({name: `Author: ${oldMsg.author.tag} (${oldMsg.author.id})`, iconURL: `${oldMsg.author.displayAvatarURL()}`}).setTitle('Message edited').setDescription(`<@${oldMsg.author.id}>\nOld content:\n\`\`\`\n${oldMsg.content}\n\`\`\`\nNew content:\n\`\`\`\n${newMsg.content}\`\`\`\nChannel: <#${oldMsg.channelId}>`)], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]});
} }

View File

@ -1,31 +0,0 @@
import Discord, { AuditLogEvent } from 'discord.js';
import TClient from '../client';
export default {
async run(client:TClient, oldRole:Discord.Role, newRole:Discord.Role){
const fetchRoleUpdoot = await client.guilds.cache.get(oldRole.guild.id).fetchAuditLogs({
limit: 1,
type: AuditLogEvent.RoleUpdate
})
if (oldRole.guild?.id != client.config.mainServer.id) return;
const roleLog = fetchRoleUpdoot.entries.first();
if (!roleLog) return
const {executor, target} = roleLog;
if (target) {
const embed = new client.embed().setColor(newRole.hexColor).setThumbnail(newRole?.iconURL()).setTimestamp().setTitle(`Role modified: ${newRole.name}`).setDescription(`🔹 **Role**\n${target}\n\`${target.id}\``).addFields(
{name: `${executor.bot ? '🔹 Bot' : '🔹 Admin'}`, value: `<@${executor.id}>\n\`${executor.id}\``}
);
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [embed]})
// Moved addFields to these below cuz yes for each role changes, it seems inefficent to me but it will do. :)
// Permissions field seems to trigger when role is hoisted/unhoisted atleast to me.
if (oldRole.hexColor !== newRole.hexColor) {
embed.addFields({name: '🔹 Role changes', value: `**Old color:** ${oldRole.hexColor}\n**New color:** ${newRole.hexColor}`})
} else if (oldRole.name !== newRole.name) {
embed.addFields({name: '🔹 Role changes', value: `**Old name:** ${oldRole.name}\n**New name:** ${newRole.name}`})
} else if (!oldRole.permissions.equals(newRole.permissions)) {
embed.addFields({name: '🔹 Role changes', value: `**Old permission(s):** ${newRole.permissions.missing(oldRole.permissions).join(', ')}\n**New permission(s):** ${oldRole.permissions.missing(newRole.permissions).join(', ')}`})
}
} else {
console.log(`${target.id} was modified from ${client.guilds.cache.get(oldRole.guild.name)} but no audit log could be fetched.`)
}
}
}

View File

@ -4,7 +4,7 @@ const client = new TClient;
client.init(); client.init();
import fs from 'node:fs'; import fs from 'node:fs';
import MPDB from './models/MPServer'; import MPDB from './models/MPServer';
import {Punishment, UserLevels, FSData, FSCareerSavegame} from './typings/interfaces'; import {FSData, FSCareerSavegame} from './typings/interfaces';
client.on('ready', async()=>{ client.on('ready', async()=>{
setInterval(()=>client.user.setPresence(client.config.botPresence), 300000); setInterval(()=>client.user.setPresence(client.config.botPresence), 300000);
@ -27,7 +27,8 @@ client.on('ready', async()=>{
function DZ(error:Error, location:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so. function DZ(error:Error, location:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so.
if (['getaddrinfo ENOTFOUND discord.com'].includes(error.message)) return; if (['getaddrinfo ENOTFOUND discord.com'].includes(error.message)) return;
console.log(error); console.log(error);
(client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setFooter({text: location}).setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) const channel = client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel | null
channel?.send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setFooter({text: location}).setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]})
} }
process.on('unhandledRejection', async(error: Error)=>DZ(error, 'unhandledRejection')); process.on('unhandledRejection', async(error: Error)=>DZ(error, 'unhandledRejection'));
process.on('uncaughtException', async(error: Error)=>DZ(error, 'uncaughtException')); process.on('uncaughtException', async(error: Error)=>DZ(error, 'uncaughtException'));
@ -85,11 +86,11 @@ setInterval(async()=>{
}).catch((error)=>console.log(error)) }).catch((error)=>console.log(error))
if (FSdss.fetchResult.length != 0){ if (FSdss.fetchResult.length != 0){
error = true; error = true;
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}]`, FSdss.fetchResult); console.log(client.logTime(), FSdss.fetchResult);
} }
if (FScsg.fetchResult.length != 0){ if (FScsg.fetchResult.length != 0){
error = true; error = true;
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}]`, FScsg.fetchResult); console.log(client.logTime(), FScsg.fetchResult);
} }
if (error) { // Blame RedRover and Nawdic if (error) { // Blame RedRover and Nawdic
embed.setTitle('Host is not responding').setColor(client.config.embedColorRed); embed.setTitle('Host is not responding').setColor(client.config.embedColorRed);
@ -140,23 +141,24 @@ setInterval(async()=>{
const now = Date.now(); const now = Date.now();
const lrsStart = client.config.LRSstart; const lrsStart = client.config.LRSstart;
client.punishments._content.filter((x:Punishment)=>x.endTime<= now && !x.expired).forEach(async (punishment:Punishment)=>{ const punishments = await client.punishments._content.find({});
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] ` + `${punishment.member}\'s ${punishment.type} should expire now`); punishments.filter(x=>x.endTime && x.endTime<= now && !x.expired).forEach(async punishment=>{
const unpunishResult = await client.punishments.removePunishment(punishment.id, client.user.id, 'Time\'s up!'); console.log(client.logTime(), `${punishment.member}\'s ${punishment.type} should expire now`);
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] ` + unpunishResult); const unpunishResult = await client.punishments.removePunishment(punishment._id, client.user.id, 'Time\'s up!');
console.log(client.logTime(), unpunishResult);
}); });
const formattedDate = Math.floor((now - lrsStart)/1000/60/60/24); const formattedDate = Math.floor((now - lrsStart)/1000/60/60/24);
const dailyMsgs = JSON.parse(fs.readFileSync(__dirname + '/database/dailyMsgs.json', {encoding: 'utf8'})) const dailyMsgs = JSON.parse(fs.readFileSync(__dirname + '/database/dailyMsgs.json', {encoding: 'utf8'}))
if (!dailyMsgs.some((x:Array<number>)=>x[0] === formattedDate)){ if (!dailyMsgs.some((x:Array<number>)=>x[0] === formattedDate)){
let total = Object.values<UserLevels>(client.userLevels._content).reduce((a,b)=>a + b.messages, 0); // sum of all users let total = (await client.userLevels._content.find({})).reduce((a,b)=>a + b.messages, 0); // sum of all users
const yesterday = dailyMsgs.find((x:Array<number>)=>x[0] === formattedDate - 1); const yesterday = dailyMsgs.find((x:Array<number>)=>x[0] === formattedDate - 1);
if (total < yesterday){ // messages went down. if (total < yesterday){ // messages went down.
total = yesterday total = yesterday
} }
dailyMsgs.push([formattedDate, total]); dailyMsgs.push([formattedDate, total]);
fs.writeFileSync(__dirname + '/database/dailyMsgs.json', JSON.stringify(dailyMsgs)) fs.writeFileSync(__dirname + '/database/dailyMsgs.json', JSON.stringify(dailyMsgs))
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}]`, `Pushed [${formattedDate}, ${total}] to dailyMsgs`); console.log(client.logTime(), `Pushed [${formattedDate}, ${total}] to dailyMsgs`);
client.guilds.cache.get(client.config.mainServer.id).commands.fetch().then((commands)=>(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send(`:pencil: Pushed \`[${formattedDate}, ${total}]\` to </rank leaderboard:${commands.find(x=>x.name == 'rank').id}>`)) client.guilds.cache.get(client.config.mainServer.id).commands.fetch().then((commands)=>(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send(`:pencil: Pushed \`[${formattedDate}, ${total}]\` to </rank leaderboard:${commands.find(x=>x.name == 'rank').id}>`))
} }
}, 5000) }, 5000)

17
src/models/bannedWords.ts Normal file
View File

@ -0,0 +1,17 @@
import Discord from 'discord.js';
import TClient from 'src/client';
import mongoose from 'mongoose';
const Schema = mongoose.model('bannedWords', new mongoose.Schema({
_id: {type: String, required:true}
}));
export default class bannedWords extends Schema {
client: TClient;
_content: typeof Schema;
constructor(client:TClient){
super();
this.client = client;
this._content = Schema;
}
}

23
src/models/bonkCount.ts Normal file
View File

@ -0,0 +1,23 @@
import TClient from 'src/client';
import mongoose from 'mongoose';
const Schema = mongoose.model('bonkCount', new mongoose.Schema({
_id: {type: String, required:true},
value: {type: Number, required:true}
}));
export default class bonkCount extends Schema {
client: TClient;
_content: typeof Schema;
constructor(client:TClient){
super();
this.client = client;
this._content = Schema;
}
async _incrementUser(userid: string){
const amount = await this._content.findById(userid)
if (amount) await this._content.findByIdAndUpdate(userid, {value: amount.value + 1})
else await this._content.create({_id: userid, value: 1})
return this;
}
}

151
src/models/punishments.ts Normal file
View File

@ -0,0 +1,151 @@
import Discord from 'discord.js';
import TClient from 'src/client';
import mongoose from 'mongoose';
import ms from 'ms';
import {Punishment} from 'src/typings/interfaces';
const Schema = mongoose.model('punishments', new mongoose.Schema({
_id: {type: Number, required: true},
type: {type: String, required: true},
member: {type: String, required: true},
moderator: {type: String, required: true},
expired: {type: Boolean},
time: {type: Number, required: true},
reason: {type: String, required: true},
endTime: {type: Number},
cancels: {type: Number},
duration: {type: Number}
}));
export default class punishments extends Schema {
client: TClient;
_content: typeof Schema;
constructor(client:TClient){
super();
this.client = client;
this._content = Schema;
}
createId = async()=>Math.max(...(await this._content.find({})).map(x=>x.id), 0) + 1;
async makeModlogEntry(punishment:Punishment){
// Format data into an embed
const embed = new this.client.embed()
.setTitle(`${punishment.type[0].toUpperCase() + punishment.type.slice(1)} | Case #${punishment._id}`)
.addFields(
{name: '🔹 User', value: `<@${punishment.member}>\n\`${punishment.member}\``, inline: true},
{name: '🔹 Moderator', value: `<@${punishment.moderator}>\n\`${punishment.moderator}\``, inline: true},
{name: '\u200b', value: '\u200b', inline: true},
{name: '🔹 Reason', value: `\`${punishment.reason}\``, inline: true})
.setColor(this.client.config.embedColor)
.setTimestamp(punishment.time)
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: this.client.formatTime(punishment.duration, 100), inline: true}, {name: '\u200b', value: '\u200b', inline: true})
if (punishment.cancels) {
const cancels = await this._content.findById(punishment.cancels);
embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id}\n\`${cancels.reason}\``})
}
// Send it off to specific Discord channel.
(this.client.channels.cache.get(this.client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[embed]});
}// hi tae
getTense(type:string){// Get past tense form of punishment type, grammar yes
return {
ban: 'banned',
softban: 'softbanned',
kick: 'kicked',
mute: 'muted',
warn: 'warned'
}[type]
}
async addPunishment(type:string, options:{time?:string,interaction?:Discord.ChatInputCommandInteraction<'cached'>},moderator:string,reason:string,User:Discord.User,GuildMember?:Discord.GuildMember){
const {time,interaction} = options;
const now = Date.now();
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
const punData:Punishment={type, _id: await this.createId(), member:User.id, reason, moderator, time:now}
const inOrFromBoolean = ['warn', 'mute'].includes(type) ? 'in' : 'from';
const auditLogReason = `${reason || 'Reason unspecified'} | Case #${punData._id}`;
const embed = new this.client.embed()
.setColor(this.client.config.embedColor)
.setTitle(`Case #${punData._id}: ${type[0].toUpperCase()+type.slice(1)}`)
.setDescription(`${User.tag}\n<@${User.id}>\n(\`${User.id}\`)`)
.addFields({name: 'Reason', value: reason})
let punResult;
let timeInMillis;
let DM;
if (type == 'mute') timeInMillis = time ? ms(time) : 2419140000; // Timeouts have a limit of 4 weeks
else timeInMillis = time ? ms(time) : null;
const durationText = timeInMillis ? ` for ${this.client.formatTime(timeInMillis, 4, {longNames:true,commas:true})}` : '';
if (time) embed.addFields({name: 'Duration', value: durationText});
if (GuildMember){
try{
DM=await GuildMember.send(`You've been ${this.getTense(type)} ${inOrFromBoolean} ${guild.name}${durationText} for \`${reason}\` (Case #${punData._id})`);
}catch(err){
embed.setFooter({text: 'Failed to DM a member.'})
}
}
if (['ban', 'softban'].includes(type)){
const banned = await guild.bans.fetch(User.id).catch(()=>undefined);
if (!banned) punResult = await guild.bans.create(User.id, {reason: auditLogReason, deleteMessageSeconds: 172800}).catch((err:Error)=>err.message)
else punResult = 'User is already banned.';
}
else if (type == 'kick') punResult = await GuildMember?.kick(auditLogReason).catch((err:Error)=>err.message);
else if (type == 'mute') punResult = await GuildMember?.timeout(timeInMillis, auditLogReason).catch((err:Error)=>err.message);
if (type == 'softban' && typeof punResult != 'string') punResult = await guild.bans.remove(User.id, auditLogReason).catch((err:Error)=>err.message);
if (timeInMillis && ['mute','ban'].includes(type)){
punData.endTime = now + timeInMillis;
punData.duration = timeInMillis;
}
if (typeof punResult == 'string'){// Unsuccessful punishment
if (DM) DM.delete();
if (interaction) return interaction.editReply(punResult);
else return punResult;
} else {
await this.makeModlogEntry(punData);
await this._content.create(punData);
if (interaction) return interaction.editReply({embeds:[embed]});
else return punResult;
}
}
async removePunishment(caseId:number,moderator:string,reason:string,interaction?:Discord.ChatInputCommandInteraction<'cached'>){
const now = Date.now();
const _id = await this.createId();
const punishment = await this._content.findById(caseId);
if (!punishment) return 'Punishment not found.';
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
const auditLogReason = `${reason || 'Reason unspecified'} | Case #${punishment.id}`;
const User = await this.client.users.fetch(punishment.member);
const GuildMember = await guild.members.fetch(punishment.member);
let removePunishmentData:Punishment={type:`un${punishment.type}`, _id, cancels:punishment.id, member:punishment.member, reason, moderator, time:now};
let removePunishmentResult;
if (punishment.type == 'ban') removePunishmentResult = guild.bans.remove(punishment.member, auditLogReason).catch((err:Error)=>err.message);
else if (punishment.type == 'mute'){
if (GuildMember){
removePunishmentResult = GuildMember.timeout(null, auditLogReason).catch((err:Error)=>err.message);
GuildMember.send(`You've been unmuted in ${guild.name}.`).catch((err:Error)=>console.log(err.message));
} else await this._content.findByIdAndUpdate(caseId,{expired:true},{new:true});
} else removePunishmentData.type = 'removeOtherPunishment';
if (typeof removePunishmentResult == 'string'){//Unsuccessful punishment
if (interaction) return interaction.reply(removePunishmentResult);
else return removePunishmentResult;
} else {
await this._content.findByIdAndUpdate(caseId,{expired:true},{new:true});
await this._content.create(removePunishmentData);
await this.makeModlogEntry(removePunishmentData);
if (interaction) {
return interaction.reply({embeds:[new this.client.embed().setColor(this.client.config.embedColor)
.setTitle(`Case #${removePunishmentData._id}: ${removePunishmentData.type[0].toUpperCase()+removePunishmentData.type.slice(1)}`)
.setDescription(`${User.tag}\n<@${User.id}>\n(\`${User.id}\`)`)
.addFields({name: 'Reason', value: reason},{name: 'Overwrites', value: `Case #${punishment.id}`})
]})
} else return `Successfully ${this.getTense(removePunishmentData.type)} ${User.tag} (\`${User.id}\`) for ${reason}`
}
}
}

37
src/models/userLevels.ts Normal file
View File

@ -0,0 +1,37 @@
import Discord from 'discord.js';
import TClient from 'src/client';
import mongoose from 'mongoose';
const Schema = mongoose.model('userLevels', new mongoose.Schema({
_id: {type: String},
messages: {type: Number, required: true},
level: {type: Number, required: true}
}));
export default class userLevels extends Schema {
client: TClient;
_content: typeof Schema;
constructor(client:TClient){
super();
this.client = client;
this._content = Schema;
}
async incrementUser(userid:string){
const userData = await this._content.findById(userid)
if (userData){
await this._content.findByIdAndUpdate(userid, {messages: userData.messages + 1});
if (userData.messages >= this.algorithm(userData.level+2)){
while (userData.messages > this.algorithm(userData.level+1)){
const newData = await this._content.findByIdAndUpdate(userid, {level:userData.level+1}, {new: true});
console.log(`${userid} EXTENDED LEVELUP ${newData?.level}`)
}
} else if (userData.messages >= this.algorithm(userData.level+1)) {
const newData = await this._content.findByIdAndUpdate(userid, {level:userData.level+1}, {new: true});
(this.client.channels.resolve(this.client.config.mainServer.channels.botcommands) as Discord.TextChannel).send({content: `<@${userid}> has reached level **${newData.level}**. GG!`, allowedMentions: {parse: ['users']}})
}
} else await this._content.create({_id: userid, messages: 1, level: 0})
}
algorithm = (level:number)=>level*level*15;
// Algorithm for determining levels. If adjusting, recommended to only change the integer at the end of equation.
}

View File

@ -1,223 +0,0 @@
import TClient from './client';
import Discord from 'discord.js';
import { Database } from './database';
import { Punishment, punOpt } from './typings/interfaces';
export class bannedWords extends Database {
client: TClient;
constructor(client: TClient){
super('src/database/bannedWords.json', 'array');
this.client = client;
}
}
export class bonkCount extends Database {
client: TClient;
constructor(client: TClient){
super('src/database/bonkCount.json', 'object')
this.client = client
}
_incrementUser(userid: string){
const amount = this._content[userid];
if(amount) this._content[userid]++;
else this._content[userid] = 1;
return this;
}
getUser(userid: string){
return this._content[userid] as number || 0;
}
}
export class userLevels extends Database {
client: TClient;
constructor(client: TClient){
super('src/database/userLevels.json', 'object');
this.client = client
}
incrementUser(userid: string){
const data = this._content[userid];// User's data. Integer for old format, object for new format.
if (typeof data == 'number'){// If user's data is an integer, convert it into object for new format.
this._content[userid] = {messages: data, level: 0};
}
if (data) {// If user exists on file...
this._content[userid].messages++;// Increment their message count
if (data.messages >= this.algorithm(data.level+2)){// Quietly level up users who can surpass more than 2 levels at once, usually due to manually updating their message count
while (data.messages > this.algorithm(data.level+1)){
this._content[userid].level++;
console.log(`${userid} EXTENDED LEVELUP ${this._content[userid].level}`)
}
} else if (data.messages >= this.algorithm(data.level+1)){// If user's message count meets/exceeds message requirement for next level...
this._content[userid].level++;// Level them up.
(this.client.channels.resolve(this.client.config.mainServer.channels.botcommands) as Discord.TextChannel).send({content: `<@${userid}> has reached level **${data.level}**. GG!`, allowedMentions: {parse: ['users']}})
}
} else {// If user doesn't exist on file, create an object for it.
this._content[userid] = {messages: 1, level: 0};
}
}
algorithm(level: number){// Algorithm for determining levels. If adjusting, recommended to only change the integer at the end of equation.
return level*level*15;
}
}
export class punishments extends Database {
client: TClient;
constructor(client: TClient){
super('src/database/punishments.json', 'array');
this.client = client;
}
createId(){
return Math.max(...this.client.punishments._content.map((x:Punishment)=>x.id), 0)+1;
}
makeModlogEntry(data: Punishment) {
const cancels = data.cancels ? this.client.punishments._content.find((x: Punishment) => x.id === data.cancels) : null;
const channelId = ['kick', 'ban'].includes(data.type) ? '1048341961901363352' : this.client.config.mainServer.channels.logs;
// format data into embed
const embed = new this.client.embed()
.setTitle(`${this.client.formatPunishmentType(data, this.client, cancels)} | Case #${data.id}`)
.addFields(
{name: '🔹 User', value: `<@${data.member}> \`${data.member}\``, inline: true},
{name: '🔹 Moderator', value: `<@${data.moderator}> \`${data.moderator}\``, inline: true},
{name: '\u200b', value: '\u200b', inline: true},
{name: '🔹 Reason', value: `\`${data.reason}\``, inline: true})
.setColor(this.client.config.embedColor)
.setTimestamp(data.time)
if (data.duration) {
embed.addFields(
{name: '🔹 Duration', value: this.client.formatTime(data.duration, 100), inline: true},
{name: '\u200b', value: '\u200b', inline: true}
)
}
if (data.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id}\n\`${cancels.reason}\``});
// send embed in modlog channel
(this.client.channels.resolve(channelId) as Discord.TextChannel).send({embeds: [embed]});
};
getTense(type: string) { // Get past tense form of punishment type, grammar yes
switch (type) {
case 'ban': return 'banned';
case 'softban': return 'softbanned';
case 'kick': return 'kicked';
case 'mute': return 'muted';
case 'warn': return 'warned';
}
}
async addPunishment(type: string, options: punOpt, moderator: string, reason: string, User: Discord.User, GuildMember?: Discord.GuildMember) {
const { time, interaction } = options;
const ms = require('ms');
const now = Date.now();
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
const punData: Punishment = { type, id: this.createId(), member: User.id, reason, moderator, time: now }
const embed = new this.client.embed()
.setColor(this.client.config.embedColor)
.setTitle(`Case #${punData.id}: ${type[0].toUpperCase() + type.slice(1)}`)
.setDescription(`${User.tag}\n<@${User.id}>\n(\`${User.id}\`)`)
.addFields({name: 'Reason', value: reason})
let punResult: any;
let timeInMillis: number;
let DM: Discord.Message<false> | undefined;
if (type == "mute") {
timeInMillis = time ? ms(time) : 2419140000; // Timeouts have a limit of 4 weeks
} else {
timeInMillis = time ? ms(time) : null;
}
// Add field for duration if time is specified
if (time) embed.addFields({name: 'Duration', value: `${timeInMillis ? `for ${this.client.formatTime(timeInMillis, 4, { longNames: true, commas: true })}` : "forever"}`})
if (GuildMember) {
try {
DM = await GuildMember.send(`You've been ${this.getTense(type)} ${['warn', 'mute'].includes(type) ? 'in' : 'from'} ${guild.name}${time ? (timeInMillis ? ` for ${this.client.formatTime(timeInMillis, 4, { longNames: true, commas: true })}` : 'forever') : ''} for reason \`${reason}\` (Case #${punData.id})`);
} catch (err: any) {
embed.setFooter({text: 'Failed to DM member of punishment'});
}
}
if (['ban', 'softban'].includes(type)) {
const banned = await guild.bans.fetch(User.id).catch(() => undefined);
if (!banned) {
punResult = await guild.bans.create(User.id, {reason: `${reason} | Case #${punData.id}`, deleteMessageSeconds: 172800}).catch((err: Error) => err.message);
} else {
punResult = 'User is already banned.';
}
} else if (type == 'kick') {
punResult = await GuildMember?.kick(`${reason} | Case #${punData.id}`).catch((err: Error) => err.message);
} else if (type == 'mute') {
punResult = await GuildMember?.timeout(timeInMillis, `${reason} | Case #${punData.id}`).catch((err: Error) => err.message);
}
if (type == 'softban' && typeof punResult != 'string') { // If type was softban and it was successful, continue with softban (unban)
punResult = await guild.bans.remove(User.id, `${reason} | Case #${punData.id}`).catch((err: Error) => err.message);
}
if (timeInMillis && ['mute', 'ban'].includes(type)) { // If type is mute or ban, specify duration and endTime
punData.endTime = now + timeInMillis;
punData.duration = timeInMillis;
}
if (typeof punResult == 'string') { // Punishment was unsuccessful
if (DM) DM.delete();
if (interaction) {
return interaction.editReply(punResult);
} else {
return punResult;
}
} else { // Punishment was successful
this.makeModlogEntry(punData);
this.client.punishments.addData(punData).forceSave();
if (interaction) {
return interaction.editReply({embeds: [embed]});
} else {
return punResult;
}
}
}
async removePunishment(caseId:number, moderator:any, reason:string):Promise<any>{
const now = Date.now()
const punishment = this._content.find((x:Punishment)=>x.id === caseId);
const id = this.createId();
if (!punishment) return 'Punishment not found';
if (['ban','mute'].includes(punishment.type)) {
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
let removePunishmentResult;
if (punishment.type === 'ban'){
removePunishmentResult = await guild.members.unban(punishment.member, `${reason || 'Reason unspecified'} | Case #${id}`).catch((err:TypeError)=>err.message);
} else if (punishment.type === 'mute'){
const member = await guild.members.fetch(punishment.member).catch(err=>undefined);
if (member){
removePunishmentResult = await member
if (typeof removePunishmentResult !== 'string'){
member.timeout(null, `${reason || 'Reason unspecified'} | Case #${id}`)
removePunishmentResult.send(`You've been unmuted in ${removePunishmentResult.guild.name}.`);
removePunishmentResult = removePunishmentResult.user;
}
} else {
// user probably left, quietly remove punishment
const removePunishmentData = {type: `un${punishment.type}`, id, cancels: punishment.id, member: punishment.member, reason, moderator, time: now};
this._content[this._content.findIndex((x:Punishment)=>x.id === punishment.id)].expired = true
this.addData(removePunishmentData).forceSave();
}
}
if (typeof removePunishmentResult === 'string') return `Un${punishment.type} was unsuccessful: ${removePunishmentResult}`;
else {
const removePunishmentData = {type: `un${punishment.type}`, id, cancels: punishment.id, member: punishment.member, reason, moderator, time: now};
this.makeModlogEntry(removePunishmentData);
this._content[this._content.findIndex((x:Punishment)=>x.id === punishment.id)].expired = true;
this.addData(removePunishmentData).forceSave();
return `Successfully ${punishment.type === 'ban' ? 'unbanned' : 'unmuted'} **${removePunishmentResult?.tag}** (${removePunishmentResult?.id}) for reason \`${reason || 'Reason unspecified'}\``
}
} else {
try {
const removePunishmentData = {type: 'removeOtherPunishment', id, cancels: punishment.id, member: punishment.member, reason, moderator, time: now};
this.makeModlogEntry(removePunishmentData);
this._content[this._content.findIndex((x:Punishment)=>x.id === punishment.id)].expired = true;
this.addData(removePunishmentData).forceSave();
return `Successfully removed Case #${punishment.id} (type: ${punishment.type}, user: ${punishment.member}).`;
} catch (error:any){
return `${punishment.type[0].toUpperCase() + punishment.type.slice(1)} removal was unsuccessful: ${error.message}`;
}
}
}
}

View File

@ -17,7 +17,7 @@ export interface repeatedMessages {
[key:string]: {data: Discord.Collection<number,{cont:number,ch:string}>, timeout: NodeJS.Timeout} [key:string]: {data: Discord.Collection<number,{cont:number,ch:string}>, timeout: NodeJS.Timeout}
} }
export interface Punishment { export interface Punishment {
id: number; _id: number;
type: string; type: string;
member: string; member: string;
moderator: string; moderator: string;
@ -122,12 +122,14 @@ interface XMLText {
_text: string _text: string
} }
export interface Tokens { export interface Tokens {
token_main: string main: string
token_beta: string beta: string
token_toast: string toast: string
token_tae: string tae: string
webhook_url: string webhook_url: string
webhook_url_test: string webhook_url_test: string
mongodb_uri: string
mongodb_uri_dev: string
} }
export interface Config { export interface Config {
embedColor: Discord.ColorResolvable, embedColor: Discord.ColorResolvable,