mirror of
https://github.com/toast-ts/Daggerbot-TS.git
synced 2024-11-17 04:10:59 -05:00
Major changes to the bot.
This commit is contained in:
parent
81f39d57ec
commit
c73e759553
@ -30,13 +30,14 @@
|
||||
"ms": "2.1.3",
|
||||
"sequelize": "6.28.2",
|
||||
"sqlite3": "5.1.4",
|
||||
"systeminformation": "5.17.9",
|
||||
"mongoose": "6.10.0",
|
||||
"systeminformation": "5.17.10",
|
||||
"@octokit/rest": "19.0.7",
|
||||
"typescript": "4.9.5",
|
||||
"xml-js": "1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.14.0",
|
||||
"@types/node": "18.14.1",
|
||||
"ts-node": "10.9.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
import Discord, { Client, WebhookClient, GatewayIntentBits, Partials } from 'discord.js';
|
||||
import fs from 'node:fs';
|
||||
import { exec } from 'node:child_process';
|
||||
import timeNames from './timeNames';
|
||||
import { Punishment, formatTimeOpt, Tokens, Config, repeatedMessages } from './typings/interfaces';
|
||||
import { bannedWords, bonkCount, userLevels, punishments } from './schoolClassroom';
|
||||
import mongoose from 'mongoose';
|
||||
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 axios from 'axios';
|
||||
import moment from 'moment';
|
||||
@ -82,11 +87,16 @@ export default class TClient extends Client {
|
||||
}
|
||||
async init(){
|
||||
MPDB.sync();
|
||||
this.login(this.tokens.token_main);
|
||||
this.punishments.initLoad();
|
||||
this.bannedWords.initLoad();
|
||||
this.bonkCount.initLoad();
|
||||
this.userLevels.initLoad().intervalSave(30000).disableSaveNotifs();
|
||||
mongoose.set('strictQuery', true);
|
||||
await mongoose.connect(this.tokens.mongodb_uri, {
|
||||
autoIndex: true,
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
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'));
|
||||
for (const file of commandFiles){
|
||||
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));
|
||||
});
|
||||
}
|
||||
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){
|
||||
let achievedAccuracy = 0;
|
||||
let text:any = '';
|
||||
@ -134,6 +138,9 @@ export default class TClient extends Client {
|
||||
youNeedRole(interaction: Discord.CommandInteraction, role:string){
|
||||
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 = ' '){
|
||||
if (alignment == 'right'){
|
||||
text = emptyChar.repeat(length - text.length)+text;
|
||||
@ -158,14 +165,6 @@ export default class TClient extends Client {
|
||||
|
||||
await interaction.deferReply();
|
||||
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){
|
||||
let Data:any;
|
||||
@ -177,7 +176,7 @@ export default class TClient extends Client {
|
||||
})
|
||||
} catch(err){
|
||||
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;
|
||||
@ -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.
|
@ -3,33 +3,34 @@ import TClient from 'src/client';
|
||||
export default {
|
||||
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')
|
||||
const word = interaction.options.getString('word');
|
||||
const word = interaction.options.getString('word', true);
|
||||
const wordExists = await client.bannedWords._content.findById(word);
|
||||
({
|
||||
add: ()=>{
|
||||
if (client.bannedWords._content.includes(word)) return interaction.reply({content: `\`${word}\` is already added.`, ephemeral: true});
|
||||
client.bannedWords.addData(word).forceSave();
|
||||
interaction.reply(`Successfully added \`${word}\` to the list.`)
|
||||
add: async()=>{
|
||||
if (wordExists) return interaction.reply({content: `\`${word}\` is already added.`, ephemeral: true});
|
||||
await client.bannedWords._content.create({_id:word}).then(a=>a.save());
|
||||
interaction.reply(`Successfully added \`${word}\` to the database.`)
|
||||
},
|
||||
remove: ()=>{
|
||||
if (client.bannedWords._content.includes(word) == false) return interaction.reply({content: `\`${word}\` doesn't exist on the list.`, ephemeral: true});
|
||||
client.bannedWords.removeData(word, 0, 0).forceSave();
|
||||
interaction.reply(`Successfully removed \`${word}\` from the list.`)
|
||||
remove: async()=>{
|
||||
if (!wordExists) return interaction.reply({content: `\`${word}\` doesn't exist on the list.`, ephemeral: true});
|
||||
await client.bannedWords._content.findOneAndDelete({_id:word});
|
||||
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()]();
|
||||
},
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('bannedwords')
|
||||
.setDescription('description placeholder')
|
||||
.addSubcommand((opt)=>opt
|
||||
/*.addSubcommand((opt)=>opt
|
||||
.setName('view')
|
||||
.setDescription('View the list of currently banned words.'))
|
||||
.addSubcommand((opt)=>opt
|
||||
*/.addSubcommand((opt)=>opt
|
||||
.setName('add')
|
||||
.setDescription('What word do you want to add?')
|
||||
.addStringOption((optt)=>optt
|
||||
.setName('word')
|
||||
.setDescription('Add the specific word to automod\'s bannedWords list.')
|
||||
.setDescription('Add the specific word to automod\'s bannedWords database.')
|
||||
.setRequired(true)))
|
||||
.addSubcommand((opt)=>opt
|
||||
.setName('remove')
|
||||
|
@ -8,11 +8,11 @@ export default {
|
||||
const adminPerm = member.permissions.has('Administrator');
|
||||
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)
|
||||
.setDescription(`> <@${member.id}> has been bonked!\n${reason?.length == null ? '' : `> Reason: **${reason}**`}`)
|
||||
.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()
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Discord,{SlashCommandBuilder} from "discord.js";
|
||||
import TClient from 'src/client';
|
||||
import { Punishment } from "src/typings/interfaces";
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod');
|
||||
@ -8,16 +7,15 @@ export default {
|
||||
({
|
||||
update: async()=>{
|
||||
const reason = interaction.options.getString('reason');
|
||||
client.punishments._content.find((x:Punishment)=>x.id==caseId).reason = reason;
|
||||
client.punishments.forceSave();
|
||||
await client.punishments._content.findByIdAndUpdate(caseId, {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: ()=>{
|
||||
const punishment = client.punishments._content.find((x:Punishment)=>x.id==caseId);
|
||||
if (!punishment) return interaction.reply('Invalid case #');
|
||||
const cancelledBy = punishment.expired ? client.punishments._content.find((x:Punishment)=>x.cancels==punishment.id) : null;
|
||||
const cancels = punishment.cancels ? client.punishments._content.find((x:Punishment)=>x.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(
|
||||
view: async()=>{
|
||||
const punishment = await client.punishments._content.findById(caseId);
|
||||
if (!punishment) return interaction.reply('Invalid Case #');
|
||||
const cancelledBy = punishment.expired ? await client.punishments._content.findOne({cancels:punishment.id}) : 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(`${punishment.type[0].toUpperCase()+punishment.type.slice(1)} | Case #${punishment.id}`).addFields(
|
||||
{name: '🔹 User', value: `<@${punishment.member}> \`${punishment.member}\``, inline: true},
|
||||
{name: '🔹 Moderator', value: `<@${punishment.moderator}> \`${punishment.moderator}\``, 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}\``})
|
||||
interaction.reply({embeds: [embed]});
|
||||
},
|
||||
member: ()=>{
|
||||
member: async()=>{
|
||||
// if caseid is user id, show their punishment history sorted by most recent.
|
||||
const user = (interaction.options.getUser('user') as Discord.User);
|
||||
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);
|
||||
if (!punishment) 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 userPunishment = client.punishments._content.filter((x:Punishment)=>x.member==user.id).sort((a:Punishment,b:Punishment)=>a.time-b.time).map((punishment:Punishment)=>{
|
||||
const punishments = await client.punishments._content.find({});
|
||||
if (!punishments) return interaction.reply(`<@${user.id}> has a clean record.`)
|
||||
const userPunishmentData = await client.punishments._content.find({'member':user.id});
|
||||
const userPunishment = userPunishmentData.sort((a,b)=>a.time-b.time).map((punishment)=>{
|
||||
return {
|
||||
name: `${client.formatPunishmentType(punishment, client, cancels)} | 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}` : ''}`
|
||||
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 #${punishments.find(x=>x.cancels===punishment._id)?._id}` : ''}${punishment.cancels ? `\nOverwrites case #${punishment.cancels}` : ''}`
|
||||
}
|
||||
});
|
||||
// if caseid is not a punishment nor a user, failed
|
||||
|
@ -2,7 +2,7 @@ import Discord,{SlashCommandBuilder} from 'discord.js';
|
||||
import {Octokit} from '@octokit/rest';
|
||||
import {exec} from 'node:child_process';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import * as util from 'node:util';
|
||||
import util from 'node:util';
|
||||
import TClient from '../client';
|
||||
import path from 'node:path';
|
||||
const removeUsername = (text: string)=>{
|
||||
@ -46,11 +46,11 @@ export default {
|
||||
}
|
||||
if (error) return;
|
||||
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 {
|
||||
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');
|
||||
output = output.replace(regexp, ':noblank: No token?');
|
||||
})
|
||||
@ -91,12 +91,10 @@ export default {
|
||||
const name = interaction.options.getString('name');
|
||||
const url = interaction.options.getString('url');
|
||||
const currentActivities = client.config.botPresence.activities as Discord.ActivitiesOptions[];
|
||||
|
||||
if (status) client.config.botPresence.status = status;
|
||||
if (type) currentActivities[0].type = type;
|
||||
if (name) currentActivities[0].name = name;
|
||||
if (url) currentActivities[0].url = url;
|
||||
|
||||
client.user.setPresence(client.config.botPresence);
|
||||
interaction.reply([
|
||||
'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}\``))
|
||||
},
|
||||
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'))
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
|
@ -3,7 +3,6 @@ import TClient from 'src/client';
|
||||
export default {
|
||||
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')]}),
|
||||
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:`),
|
||||
@ -20,7 +19,6 @@ export default {
|
||||
.setDescription('What question do you want answered?')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'Survival Roleplay', value: 'srp' },
|
||||
{ name: 'Daggerwin Logistics hex code', value: 'dlskin' },
|
||||
{ name: 'Scams in YT comments', value: 'ytscam' },
|
||||
{ name: 'VTC Role', value: 'vtcR' },
|
||||
|
@ -24,7 +24,7 @@ async function MPdata(client:TClient, interaction:Discord.ChatInputCommandIntera
|
||||
// Blame Nawdic & RedRover92
|
||||
embed.setTitle('Host is not responding.');
|
||||
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 FSserver
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Discord,{SlashCommandBuilder} from 'discord.js';
|
||||
import {UserLevels} from 'src/typings/interfaces';
|
||||
import TClient from 'src/client';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
@ -7,31 +6,32 @@ import canvas from 'canvas';
|
||||
export default {
|
||||
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});
|
||||
const allData = await client.userLevels._content.find({});
|
||||
({
|
||||
view: ()=>{
|
||||
view: async()=>{
|
||||
// fetch user or user interaction sender
|
||||
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.')
|
||||
// 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
|
||||
if (interaction.user.id === member.user.id) return you || true;
|
||||
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 memberDifference = information.messages - client.userLevels.algorithm(information.level);
|
||||
const levelDifference = client.userLevels.algorithm(information.level+1) - client.userLevels.algorithm(information.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)]})
|
||||
const index = allData.sort((a, b) => b.messages - a.messages).map(x => x._id).indexOf(member.id) + 1;
|
||||
const memberDifference = userData.messages - client.userLevels.algorithm(userData.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: **${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: ()=>{
|
||||
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 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] || [];
|
||||
return x[1] - (yesterday[1] || x[1]);
|
||||
}).slice(1).slice(-60);
|
||||
@ -147,12 +147,15 @@ export default {
|
||||
const ty = graphOrigin[1] + graphSize[1] + (textSize);
|
||||
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')
|
||||
.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>`)
|
||||
.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')})
|
||||
.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: topUsers})
|
||||
.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()]();
|
||||
},
|
||||
|
@ -2,7 +2,12 @@ import Discord,{SlashCommandBuilder} from 'discord.js';
|
||||
import TClient from 'src/client';
|
||||
export default {
|
||||
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()
|
||||
.setName('unpunish')
|
||||
|
@ -4,12 +4,14 @@ export default {
|
||||
async run(client:TClient, member:Discord.GuildMember){
|
||||
if (!client.config.botSwitches.logs) 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: '🔹 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: '🔹 Level messages', value: `${client.userLevels._content[member.user.id]?.messages.toLocaleString('en-US') || 0}`, inline: true}
|
||||
)]});
|
||||
delete client.userLevels._content[member.user.id];
|
||||
{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}
|
||||
);
|
||||
if (levelData && levelData.messages > 1) embed.addFields({name: '🔹 Total messages', value: levelData.messages.toLocaleString('en-US'), inline: true});
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[embed]});
|
||||
await client.userLevels._content.findByIdAndDelete(member.id)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export default {
|
||||
if (!interaction.inGuild() || !interaction.inCachedGuild()) return;
|
||||
if (interaction.isChatInputCommand()){
|
||||
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 (commandFile){
|
||||
try{
|
||||
|
@ -14,7 +14,7 @@ export default {
|
||||
// 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;
|
||||
const threshold = 30000;
|
||||
message.delete().catch(err=>console.log('bannedWords automod; msg got possibly deleted by another bot.'))
|
||||
|
@ -6,7 +6,7 @@ export default {
|
||||
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;
|
||||
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;
|
||||
(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'))]});
|
||||
}
|
||||
|
@ -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.`)
|
||||
}
|
||||
}
|
||||
}
|
22
src/index.ts
22
src/index.ts
@ -4,7 +4,7 @@ const client = new TClient;
|
||||
client.init();
|
||||
import fs from 'node:fs';
|
||||
import MPDB from './models/MPServer';
|
||||
import {Punishment, UserLevels, FSData, FSCareerSavegame} from './typings/interfaces';
|
||||
import {FSData, FSCareerSavegame} from './typings/interfaces';
|
||||
|
||||
client.on('ready', async()=>{
|
||||
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.
|
||||
if (['getaddrinfo ENOTFOUND discord.com'].includes(error.message)) return;
|
||||
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('uncaughtException', async(error: Error)=>DZ(error, 'uncaughtException'));
|
||||
@ -85,11 +86,11 @@ setInterval(async()=>{
|
||||
}).catch((error)=>console.log(error))
|
||||
if (FSdss.fetchResult.length != 0){
|
||||
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){
|
||||
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
|
||||
embed.setTitle('Host is not responding').setColor(client.config.embedColorRed);
|
||||
@ -140,23 +141,24 @@ setInterval(async()=>{
|
||||
const now = Date.now();
|
||||
const lrsStart = client.config.LRSstart;
|
||||
|
||||
client.punishments._content.filter((x:Punishment)=>x.endTime<= now && !x.expired).forEach(async (punishment:Punishment)=>{
|
||||
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] ` + `${punishment.member}\'s ${punishment.type} should expire now`);
|
||||
const unpunishResult = await client.punishments.removePunishment(punishment.id, client.user.id, 'Time\'s up!');
|
||||
console.log(`[${client.moment().format('DD/MM/YY HH:mm:ss')}] ` + unpunishResult);
|
||||
const punishments = await client.punishments._content.find({});
|
||||
punishments.filter(x=>x.endTime && x.endTime<= now && !x.expired).forEach(async punishment=>{
|
||||
console.log(client.logTime(), `${punishment.member}\'s ${punishment.type} should expire now`);
|
||||
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 dailyMsgs = JSON.parse(fs.readFileSync(__dirname + '/database/dailyMsgs.json', {encoding: 'utf8'}))
|
||||
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);
|
||||
if (total < yesterday){ // messages went down.
|
||||
total = yesterday
|
||||
}
|
||||
dailyMsgs.push([formattedDate, total]);
|
||||
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}>`))
|
||||
}
|
||||
}, 5000)
|
||||
|
17
src/models/bannedWords.ts
Normal file
17
src/models/bannedWords.ts
Normal 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
23
src/models/bonkCount.ts
Normal 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
151
src/models/punishments.ts
Normal 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
37
src/models/userLevels.ts
Normal 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.
|
||||
}
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/typings/interfaces.d.ts
vendored
12
src/typings/interfaces.d.ts
vendored
@ -17,7 +17,7 @@ export interface repeatedMessages {
|
||||
[key:string]: {data: Discord.Collection<number,{cont:number,ch:string}>, timeout: NodeJS.Timeout}
|
||||
}
|
||||
export interface Punishment {
|
||||
id: number;
|
||||
_id: number;
|
||||
type: string;
|
||||
member: string;
|
||||
moderator: string;
|
||||
@ -122,12 +122,14 @@ interface XMLText {
|
||||
_text: string
|
||||
}
|
||||
export interface Tokens {
|
||||
token_main: string
|
||||
token_beta: string
|
||||
token_toast: string
|
||||
token_tae: string
|
||||
main: string
|
||||
beta: string
|
||||
toast: string
|
||||
tae: string
|
||||
webhook_url: string
|
||||
webhook_url_test: string
|
||||
mongodb_uri: string
|
||||
mongodb_uri_dev: string
|
||||
}
|
||||
export interface Config {
|
||||
embedColor: Discord.ColorResolvable,
|
||||
|
Loading…
Reference in New Issue
Block a user