1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-11-17 16:30:58 -05:00

Major changes to the bot.

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

View File

@ -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"
}
}

View File

@ -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.

View File

@ -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')

View File

@ -1,28 +1,28 @@
import Discord,{SlashCommandBuilder} from 'discord.js';
import TClient from 'src/client';
export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
//if (!client.isStaff(interaction.member) && interaction.channelId == '468835415093411863') return interaction.reply('This command is restricted to staff only in this channel due to high usage.')
const member = interaction.options.getMember('member') as Discord.GuildMember;
const reason = interaction.options.getString('reason');
const adminPerm = member.permissions.has('Administrator');
if (adminPerm) return interaction.reply('You cannot bonk an admin!');
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
//if (!client.isStaff(interaction.member) && interaction.channelId == '468835415093411863') return interaction.reply('This command is restricted to staff only in this channel due to high usage.')
const member = interaction.options.getMember('member') as Discord.GuildMember;
const reason = interaction.options.getString('reason');
const adminPerm = member.permissions.has('Administrator');
if (adminPerm) return interaction.reply('You cannot bonk an admin!');
client.bonkCount._incrementUser(member.id).forceSave();
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')}`})
]})
},
data: new SlashCommandBuilder()
.setName('bonk')
.setDescription('Bonk a member')
.addUserOption((opt)=>opt
.setName('member')
.setDescription('Which member to bonk?')
.setRequired(true))
.addStringOption((opt)=>opt
.setName('reason')
.setDescription('Reason for the bonk'))
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}: ${await client.bonkCount._content.findById(member.id).then(b=>b.value.toLocaleString('en-US'))}`})
]})
},
data: new SlashCommandBuilder()
.setName('bonk')
.setDescription('Bonk a member')
.addUserOption((opt)=>opt
.setName('member')
.setDescription('Which member to bonk?')
.setRequired(true))
.addStringOption((opt)=>opt
.setName('reason')
.setDescription('Reason for the bonk'))
}

View File

@ -1,51 +1,49 @@
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');
const caseId = interaction.options.getInteger('id');
({
update: async()=>{
const reason = interaction.options.getString('reason');
client.punishments._content.find((x:Punishment)=>x.id==caseId).reason = 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}\``)]})
},
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(
{name: '🔹 User', value: `<@${punishment.member}> \`${punishment.member}\``, inline: true},
{name: '🔹 Moderator', value: `<@${punishment.moderator}> \`${punishment.moderator}\``, inline: true},
{name: '\u200b', value: '\u200b', inline: true},
{name: '🔹 Reason', value: `\`${punishment.reason || 'Reason unspecified'}\``, inline: true})
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: client.formatTime(punishment.duration, 100)})
if (punishment.expired) embed.addFields({name: '🔹 Expired', value: `This case has been overwritten by case #${cancelledBy.id} for reason \`${cancelledBy.reason}\``})
if (punishment.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites case #${cancels.id} with reason \`${cancels.reason}\``})
interaction.reply({embeds: [embed]});
},
member: ()=>{
// 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)=>{
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}` : ''}`
}
});
// if caseid is not a punishment nor a user, failed
if (!userPunishment || userPunishment.length == 0) return interaction.reply('No punishments found for that case # or User ID');
const pageNum = interaction.options.getInteger('page') ?? 1;
return interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`${user.username}'s punishment history`).setDescription(`**ID:** \`${user.id}\``).setFooter({text: `${userPunishment.length} total punishments. Viewing page ${pageNum} out of ${Math.ceil(userPunishment.length/6)}.`}).addFields(userPunishment.slice((pageNum - 1) * 6, pageNum * 6))]});
}
} as any)[interaction.options.getSubcommand()]();
if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod');
const caseId = interaction.options.getInteger('id');
({
update: async()=>{
const reason = interaction.options.getString('reason');
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: 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},
{name: '🔹 Reason', value: `\`${punishment.reason || 'Reason unspecified'}\``, inline: true})
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: client.formatTime(punishment.duration, 100)})
if (punishment.expired) embed.addFields({name: '🔹 Expired', value: `This case has been overwritten by case #${cancelledBy.id} for reason \`${cancelledBy.reason}\``})
if (punishment.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites case #${cancels.id} with reason \`${cancels.reason}\``})
interaction.reply({embeds: [embed]});
},
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 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: `${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
if (!userPunishment || userPunishment.length == 0) return interaction.reply('No punishments found for that case # or User ID');
const pageNum = interaction.options.getInteger('page') ?? 1;
return interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`${user.username}'s punishment history`).setDescription(`**ID:** \`${user.id}\``).setFooter({text: `${userPunishment.length} total punishments. Viewing page ${pageNum} out of ${Math.ceil(userPunishment.length/6)}.`}).addFields(userPunishment.slice((pageNum - 1) * 6, pageNum * 6))]});
}
} as any)[interaction.options.getSubcommand()]();
},
data: new SlashCommandBuilder()
.setName('case')

View File

@ -2,176 +2,173 @@ 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)=>{
let matchesLeft = true;
const array = text.split('\\');
while (matchesLeft){
let usersIndex = array.indexOf('Users');
if (usersIndex<1) matchesLeft = false;
else {
let usernameIndex = usersIndex+1;
if(array[usernameIndex].length == 0) usernameIndex += 1;
array[usernameIndex] = '*'.repeat(array[usernameIndex].length);
array[usersIndex] = 'Us\u200bers';
}
} return array.join('\\');
let matchesLeft = true;
const array = text.split('\\');
while (matchesLeft){
let usersIndex = array.indexOf('Users');
if (usersIndex<1) matchesLeft = false;
else {
let usernameIndex = usersIndex+1;
if(array[usernameIndex].length == 0) usernameIndex += 1;
array[usernameIndex] = '*'.repeat(array[usernameIndex].length);
array[usersIndex] = 'Us\u200bers';
}
} return array.join('\\');
};
export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) {
if (!client.config.eval.whitelist.includes(interaction.user.id)) return client.youNeedRole(interaction, 'bottech');
({
eval: async()=>{
if (!client.config.eval.allowed) return interaction.reply({content: 'Eval is disabled.', ephemeral: true});
const code = interaction.options.getString('code') as string;
let output = 'error';
let error = false;
try {
output = await eval(code);
} catch (err: any) {
error = true
const embed = new client.embed().setColor('#ff0000').setTitle('__Eval__').addFields(
{name: 'Input', value: `\`\`\`js\n${code.slice(0, 1010)}\n\`\`\``},
{name: 'Output', value: `\`\`\`\n${err}\`\`\``}
)
interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]})).then(errorEmbedMessage=>{
const filter = (x:any)=>x.content === 'stack' && x.author.id === interaction.user.id
const messagecollector = (interaction.channel as Discord.TextChannel).createMessageCollector({filter, max: 1, time: 60000});
messagecollector.on('collect', collected=>{
collected.reply({content: `\`\`\`\n${removeUsername(err.stack)}\n\`\`\``, allowedMentions: {repliedUser: false}});
});
});
}
if (error) return;
if (typeof output == 'object') {
output = 'js\n'+util.formatWithOptions({depth: 1}, '%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)=>{
const regexp = new RegExp(x as string,'g');
output = output.replace(regexp, ':noblank: No token?');
})
const embed = new client.embed().setColor(client.config.embedColor).setTitle('__Eval__').addFields(
{name: 'Input', value: `\`\`\`js\n${code.slice(0,1010)}\n\`\`\``},
{name: 'Output', value: `\`\`\`${removeUsername(output).slice(0,1016)}\n\`\`\``}
);
interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]}));
},
update: async()=>{
var githubRepo = {owner: 'AnxietyisReal', repo: 'Daggerbot-TS', ref: 'HEAD'}
const octokit = new Octokit({timeZone: 'Australia/NSW', userAgent: 'Daggerbot'})
const fetchCommitMsg = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.message);
const fetchCommitAuthor = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.author.name);
const clarkson = await interaction.reply({content: 'Pulling from repository...', fetchReply: true});
exec('git pull',(err:Error,stdout)=>{
if (err){
clarkson.edit(`\`\`\`${removeUsername(err.message)}\`\`\``)
} else if (stdout.includes('Already up to date')){
clarkson.edit('Bot is already up to date with the repository, did you forgor to push the changes? :skull:')
} else {
setTimeout(()=>{clarkson.edit(`Commit: **${fetchCommitMsg}**\nCommit author: **${fetchCommitAuthor}**\n\nUptime before restarting: **${client.formatTime(client.uptime as number, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot'))},650)
}
});
},
presence: ()=>{
function convertType(Type?: number){
switch (Type) {
case 0: return 'Playing';
case 1: return 'Streaming';
case 2: return 'Listening to';
case 3: return 'Watching';
case 5: return 'Competing in';
}
};
const status = interaction.options.getString('status') as Discord.PresenceStatusData | null;
const type = interaction.options.getInteger('type');
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:',
`Status: **${client.config.botPresence.status}**`,
`Type: **${convertType(currentActivities[0].type)}**`,
`Name: **${currentActivities[0].name}**`,
`URL: \`${currentActivities[0].url}\``
].join('\n'))
},
statsgraph: ()=>{
client.statsGraph = -(interaction.options.getInteger('number', true));
interaction.reply(`Successfully set to \`${client.statsGraph}\`\n*Total data points: **${JSON.parse(readFileSync(path.join(__dirname, '../database/MPPlayerData.json'), {encoding: 'utf8'})).length.toLocaleString()}***`)
},
logs: ()=>{
interaction.deferReply();
(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()]();
},
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) {
if (!client.config.eval.whitelist.includes(interaction.user.id)) return client.youNeedRole(interaction, 'bottech');
({
eval: async()=>{
if (!client.config.eval.allowed) return interaction.reply({content: 'Eval is disabled.', ephemeral: true});
const code = interaction.options.getString('code') as string;
let output = 'error';
let error = false;
try {
output = await eval(code);
} catch (err: any) {
error = true
const embed = new client.embed().setColor('#ff0000').setTitle('__Eval__').addFields(
{name: 'Input', value: `\`\`\`js\n${code.slice(0, 1010)}\n\`\`\``},
{name: 'Output', value: `\`\`\`\n${err}\`\`\``}
)
interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]})).then(errorEmbedMessage=>{
const filter = (x:any)=>x.content === 'stack' && x.author.id === interaction.user.id
const messagecollector = (interaction.channel as Discord.TextChannel).createMessageCollector({filter, max: 1, time: 60000});
messagecollector.on('collect', collected=>{
collected.reply({content: `\`\`\`\n${removeUsername(err.stack)}\n\`\`\``, allowedMentions: {repliedUser: false}});
});
});
}
if (error) return;
if (typeof output == 'object') {
output = 'js\n'+util.formatWithOptions({depth: 1, colors: true}, '%O', output)
} else {
output = '\n' + String(output);
}
[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?');
})
const embed = new client.embed().setColor(client.config.embedColor).setTitle('__Eval__').addFields(
{name: 'Input', value: `\`\`\`js\n${code.slice(0,1010)}\n\`\`\``},
{name: 'Output', value: `\`\`\`${removeUsername(output).slice(0,1016)}\n\`\`\``}
);
interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]}));
},
update: async()=>{
var githubRepo = {owner: 'AnxietyisReal', repo: 'Daggerbot-TS', ref: 'HEAD'}
const octokit = new Octokit({timeZone: 'Australia/NSW', userAgent: 'Daggerbot'})
const fetchCommitMsg = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.message);
const fetchCommitAuthor = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.author.name);
const clarkson = await interaction.reply({content: 'Pulling from repository...', fetchReply: true});
exec('git pull',(err:Error,stdout)=>{
if (err){
clarkson.edit(`\`\`\`${removeUsername(err.message)}\`\`\``)
} else if (stdout.includes('Already up to date')){
clarkson.edit('Bot is already up to date with the repository, did you forgor to push the changes? :skull:')
} else {
setTimeout(()=>{clarkson.edit(`Commit: **${fetchCommitMsg}**\nCommit author: **${fetchCommitAuthor}**\n\nUptime before restarting: **${client.formatTime(client.uptime as number, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot'))},650)
}
});
},
presence: ()=>{
function convertType(Type?: number){
switch (Type) {
case 0: return 'Playing';
case 1: return 'Streaming';
case 2: return 'Listening to';
case 3: return 'Watching';
case 5: return 'Competing in';
}
};
const status = interaction.options.getString('status') as Discord.PresenceStatusData | null;
const type = interaction.options.getInteger('type');
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:',
`Status: **${client.config.botPresence.status}**`,
`Type: **${convertType(currentActivities[0].type)}**`,
`Name: **${currentActivities[0].name}**`,
`URL: \`${currentActivities[0].url}\``
].join('\n'))
},
statsgraph: ()=>{
client.statsGraph = -(interaction.options.getInteger('number', true));
interaction.reply(`Successfully set to \`${client.statsGraph}\`\n*Total data points: **${JSON.parse(readFileSync(path.join(__dirname, '../database/MPPlayerData.json'), {encoding: 'utf8'})).length.toLocaleString()}***`)
},
logs: ()=>{
interaction.deferReply();
(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: ()=>{
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()]();
},
data: new SlashCommandBuilder()
.setName('dev')
.setDescription('Developer commands')
.addSubcommand((optt)=>optt
.setName('eval')
.setDescription('Execute the code to the bot')
.addStringOption((opt)=>opt
.setName('code')
.setDescription('Execute your code')
.setRequired(true)))
.addSubcommand((optt)=>optt
.setName('logs')
.setDescription('Retrieve the logs from host and sends it to dev server'))
.addSubcommand((optt)=>optt
.setName('restart')
.setDescription('Restart the bot for technical reasons'))
.addSubcommand((optt)=>optt
.setName('update')
.setDescription('Pull from repository and restart'))
.addSubcommand((optt)=>optt
.setName('statsgraph')
.setDescription('Edit the number of data points to pull')
.addIntegerOption((hiTae)=>hiTae
.setName('number')
.setDescription('Number of data points to pull')
.setRequired(true)))
.addSubcommand((optt)=>optt
.setName('presence')
.setDescription('Update the bot\'s presence')
.addIntegerOption((hiTae)=>hiTae
.setName('type')
.setDescription('Set an activity type')
.addChoices(
{name: 'Playing', value: Discord.ActivityType.Playing},
{name: 'Streaming', value: Discord.ActivityType.Streaming},
{name: 'Listening to', value: Discord.ActivityType.Listening},
{name: 'Watching', value: Discord.ActivityType.Watching},
{name: 'Competing in', value: Discord.ActivityType.Competing}
))
.addStringOption((hiAgain)=>hiAgain
.setName('name')
.setDescription('Set a message for the activity status'))
.addStringOption((hiAgainx2)=>hiAgainx2
.setName('url')
.setDescription('Set an url for streaming status'))
.addStringOption((hiAgainx3)=>hiAgainx3
.setName('status')
.setDescription('Set a status indicator for the bot')
.setChoices(
{name: 'Online', value: Discord.PresenceUpdateStatus.Online},
{name: 'Idle', value: Discord.PresenceUpdateStatus.Idle},
{name: 'Do Not Distrub', value: Discord.PresenceUpdateStatus.DoNotDisturb},
{name: 'Invisible', value: Discord.PresenceUpdateStatus.Offline}
)))
.setName('dev')
.setDescription('Developer commands')
.addSubcommand((optt)=>optt
.setName('eval')
.setDescription('Execute the code to the bot')
.addStringOption((opt)=>opt
.setName('code')
.setDescription('Execute your code')
.setRequired(true)))
.addSubcommand((optt)=>optt
.setName('logs')
.setDescription('Retrieve the logs from host and sends it to dev server'))
.addSubcommand((optt)=>optt
.setName('restart')
.setDescription('Restart the bot for technical reasons'))
.addSubcommand((optt)=>optt
.setName('update')
.setDescription('Pull from repository and restart'))
.addSubcommand((optt)=>optt
.setName('statsgraph')
.setDescription('Edit the number of data points to pull')
.addIntegerOption((hiTae)=>hiTae
.setName('number')
.setDescription('Number of data points to pull')
.setRequired(true)))
.addSubcommand((optt)=>optt
.setName('presence')
.setDescription('Update the bot\'s presence')
.addIntegerOption((hiTae)=>hiTae
.setName('type')
.setDescription('Set an activity type')
.addChoices(
{name: 'Playing', value: Discord.ActivityType.Playing},
{name: 'Streaming', value: Discord.ActivityType.Streaming},
{name: 'Listening to', value: Discord.ActivityType.Listening},
{name: 'Watching', value: Discord.ActivityType.Watching},
{name: 'Competing in', value: Discord.ActivityType.Competing}
))
.addStringOption((hiAgain)=>hiAgain
.setName('name')
.setDescription('Set a message for the activity status'))
.addStringOption((hiAgainx2)=>hiAgainx2
.setName('url')
.setDescription('Set an url for streaming status'))
.addStringOption((hiAgainx3)=>hiAgainx3
.setName('status')
.setDescription('Set a status indicator for the bot')
.setChoices(
{name: 'Online', value: Discord.PresenceUpdateStatus.Online},
{name: 'Idle', value: Discord.PresenceUpdateStatus.Idle},
{name: 'Do Not Distrub', value: Discord.PresenceUpdateStatus.DoNotDisturb},
{name: 'Invisible', value: Discord.PresenceUpdateStatus.Offline}
)))
}

View File

@ -1,31 +1,29 @@
import Discord,{SlashCommandBuilder} from 'discord.js';
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:`),
ytscam: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Scammers in YouTube comments section').setDescription('If you ever see a comment mentioning a giveaway or anything else, **it\'s a scam!**\nYou should report it to YouTube and move on or ignore it.\nP.S: They\'re on every channel and not just Daggerwin.').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1068078284996345916/image.png')]}),
fsShader: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Clearing your shader cache folder').setDescription('If your game kees crashing shortly after opening your game, then the shaders might be an issue.\nTo resolve this, you can go to `Documents/My Games/FarmingSimulator2022` and delete the folder called `shader_cache`').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1015195687970943016/unknown.png')]}),
fsLogfile: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Uploading your log file').setDescription('You can find `log.txt` in `Documents/My Games/FarmingSimulator2022` and upload it into <#596989522395398144> along with your issue, so people can assist you further and help you resolve.').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1015195643528101958/unknown.png')]})
} as any)[interaction.options.getString('question', true)]();
},
data: new SlashCommandBuilder()
.setName('faq')
.setDescription('List of questions, e.g; log file for FS, YT Scams and etc.')
.addStringOption((opt)=>opt
.setName('question')
.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' },
{ name: 'MP Role', value: 'mpR' },
{ name: '[FS22] Resolve shader_cache issue', value: 'fsShader' },
{ name: '[FS22] Log file location', value: 'fsLogfile' }
))
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
({
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:`),
ytscam: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Scammers in YouTube comments section').setDescription('If you ever see a comment mentioning a giveaway or anything else, **it\'s a scam!**\nYou should report it to YouTube and move on or ignore it.\nP.S: They\'re on every channel and not just Daggerwin.').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1068078284996345916/image.png')]}),
fsShader: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Clearing your shader cache folder').setDescription('If your game kees crashing shortly after opening your game, then the shaders might be an issue.\nTo resolve this, you can go to `Documents/My Games/FarmingSimulator2022` and delete the folder called `shader_cache`').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1015195687970943016/unknown.png')]}),
fsLogfile: ()=>interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Uploading your log file').setDescription('You can find `log.txt` in `Documents/My Games/FarmingSimulator2022` and upload it into <#596989522395398144> along with your issue, so people can assist you further and help you resolve.').setImage('https://cdn.discordapp.com/attachments/1015195575693627442/1015195643528101958/unknown.png')]})
} as any)[interaction.options.getString('question', true)]();
},
data: new SlashCommandBuilder()
.setName('faq')
.setDescription('List of questions, e.g; log file for FS, YT Scams and etc.')
.addStringOption((opt)=>opt
.setName('question')
.setDescription('What question do you want answered?')
.setRequired(true)
.addChoices(
{ name: 'Daggerwin Logistics hex code', value: 'dlskin' },
{ name: 'Scams in YT comments', value: 'ytscam' },
{ name: 'VTC Role', value: 'vtcR' },
{ name: 'MP Role', value: 'mpR' },
{ name: '[FS22] Resolve shader_cache issue', value: 'fsShader' },
{ name: '[FS22] Log file location', value: 'fsLogfile' }
))
}

View File

@ -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

View File

@ -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')})
.setImage('attachment://dailymsgs.png').setColor(client.config.embedColor)
interaction.reply({embeds: [embed], files: [yeahok]})
.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)
.setFooter({text: 'Graph updates daily.'})
interaction.reply({embeds: [embed], files: [graphImage]})
}
} as any)[interaction.options.getSubcommand()]();
},

View File

@ -1,17 +1,22 @@
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)
},
data: new SlashCommandBuilder()
.setName('unpunish')
.setDescription('Remove the active punishment from a member')
.addIntegerOption((opt)=>opt
.setName('case_id')
.setDescription('Case # of the punishment to be overwritten')
.setRequired(true))
.addStringOption((opt)=>opt
.setName('reason')
.setDescription('Reason for removing the punishment'))
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
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')
.setDescription('Remove the active punishment from a member')
.addIntegerOption((opt)=>opt
.setName('case_id')
.setDescription('Case # of the punishment to be overwritten')
.setRequired(true))
.addStringOption((opt)=>opt
.setName('reason')
.setDescription('Reason for removing the punishment'))
}

View File

@ -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)
}
}

View File

@ -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{

View File

@ -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.'))

View File

@ -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'))]});
}

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();
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
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}
}
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,