diff --git a/src/client.ts b/src/client.ts index 1de11d2..9b045c4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,8 +1,8 @@ import Discord, { Client, GatewayIntentBits, Partials } from 'discord.js'; import fs from 'node:fs'; -import { Database } from './database'; import timeNames from './timeNames'; -import { Punishment, formatTimeOpt, punOpt, Tokens, Config } from './typings/interfaces'; +import { Punishment, formatTimeOpt, Tokens, Config } from './typings/interfaces'; +import { bannedWords, bonkCount, userLevels, punishments } from './schoolClassroom'; import MPDB from './models/MPServer'; import axios from 'axios'; import moment from 'moment'; @@ -19,7 +19,7 @@ try{ console.log('Using production config') } -export class TClient extends Client { +export default class TClient extends Client { invites: Map; commands: Discord.Collection; registry: Array; @@ -187,228 +187,3 @@ export class TClient extends Client { } } } - -//class -class bannedWords extends Database { - client: TClient; - constructor(client: TClient){ - super('src/database/bannedWords.json', 'array'); - this.client = client; - } -} -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.cache.get(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 | 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}`}).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{ - 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}`; - } - } - } -} -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; - } -} -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] || 0; - } -} diff --git a/src/commands/ban.ts b/src/commands/ban.ts index 896300d..e707720 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.punish(client, interaction, 'ban'); diff --git a/src/commands/bannedWords.ts b/src/commands/bannedWords.ts index e1f632e..ddcdf1f 100644 --- a/src/commands/bannedWords.ts +++ b/src/commands/bannedWords.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +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') diff --git a/src/commands/bonk.ts b/src/commands/bonk.ts index 9a4b442..d50cd79 100644 --- a/src/commands/bonk.ts +++ b/src/commands/bonk.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +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.') diff --git a/src/commands/case.ts b/src/commands/case.ts index 2f9e921..8d49f33 100644 --- a/src/commands/case.ts +++ b/src/commands/case.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from "discord.js"; -import { TClient } from 'src/client'; +import TClient from 'src/client'; import { Punishment } from "src/typings/interfaces"; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ diff --git a/src/commands/contributors.ts b/src/commands/contributors.ts index 563f5a7..ca837cb 100644 --- a/src/commands/contributors.ts +++ b/src/commands/contributors.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ const embed = new client.embed().setColor(client.config.embedColor).setTitle('Daggerbot contributors').setDescription([ diff --git a/src/commands/dev.ts b/src/commands/dev.ts index 7181cfd..41960c3 100644 --- a/src/commands/dev.ts +++ b/src/commands/dev.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; import * as util from 'node:util'; import {exec} from 'node:child_process'; import { readFileSync } from 'node:fs'; diff --git a/src/commands/faq.ts b/src/commands/faq.ts index 77a4a8c..e05dbbf 100644 --- a/src/commands/faq.ts +++ b/src/commands/faq.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ switch(interaction.options.getString('question')){ diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 10de051..6f64101 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.punish(client, interaction, 'kick'); diff --git a/src/commands/mp.ts b/src/commands/mp.ts index 476cd8e..cb8485f 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -1,5 +1,5 @@ import Discord,{EmbedBuilder, SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; import MPDB from '../models/MPServer'; import path from 'node:path'; import fs from 'node:fs'; diff --git a/src/commands/mute.ts b/src/commands/mute.ts index 693ef1b..c3227f9 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.punish(client, interaction, 'mute'); diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 4f5ffee..5f86d26 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ const msg = await interaction.reply({content: 'Pinging...', fetchReply: true}) diff --git a/src/commands/purge.ts b/src/commands/purge.ts index 3198e74..7148ad2 100644 --- a/src/commands/purge.ts +++ b/src/commands/purge.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod'); diff --git a/src/commands/randomcolor.ts b/src/commands/randomcolor.ts index c4b2ba4..b0e84f5 100644 --- a/src/commands/randomcolor.ts +++ b/src/commands/randomcolor.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ const embed = new client.embed().setColor(Math.floor(Math.random()*16777215)); diff --git a/src/commands/rank.ts b/src/commands/rank.ts index 478dee5..4d583a1 100644 --- a/src/commands/rank.ts +++ b/src/commands/rank.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; import { UserLevels } from 'src/typings/interfaces'; import path from 'node:path'; import fs from 'node:fs'; diff --git a/src/commands/roleinfo.ts b/src/commands/roleinfo.ts index abd6359..7112319 100644 --- a/src/commands/roleinfo.ts +++ b/src/commands/roleinfo.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ const role = interaction.options.getRole('role') as Discord.Role; diff --git a/src/commands/softban.ts b/src/commands/softban.ts index ca8dbd4..7cbd8f2 100644 --- a/src/commands/softban.ts +++ b/src/commands/softban.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.punish(client, interaction, 'softban'); diff --git a/src/commands/statistics.ts b/src/commands/statistics.ts index a1b9824..feea196 100644 --- a/src/commands/statistics.ts +++ b/src/commands/statistics.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; import si from 'systeminformation'; import os from 'node:os'; import { version } from 'typescript'; diff --git a/src/commands/unpunish.ts b/src/commands/unpunish.ts index 315219f..cd7e4a7 100644 --- a/src/commands/unpunish.ts +++ b/src/commands/unpunish.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.unPunish(client, interaction) diff --git a/src/commands/warn.ts b/src/commands/warn.ts index fac443a..02742de 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ client.punish(client, interaction, 'warn'); diff --git a/src/commands/whois.ts b/src/commands/whois.ts index b64931f..5b6f67d 100644 --- a/src/commands/whois.ts +++ b/src/commands/whois.ts @@ -1,5 +1,5 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; -import { TClient } from 'src/client'; +import TClient from 'src/client'; function convert(status:string){ switch (status){ diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts index a5be463..71f8fd0 100644 --- a/src/events/guildBanAdd.ts +++ b/src/events/guildBanAdd.ts @@ -1,5 +1,5 @@ import Discord, { AuditLogEvent } from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, member:Discord.GuildMember){ if (member.guild?.id != client.config.mainServer.id) return; diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts index b05216d..25ee584 100644 --- a/src/events/guildBanRemove.ts +++ b/src/events/guildBanRemove.ts @@ -1,5 +1,5 @@ import Discord, { AuditLogEvent } from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, member:Discord.GuildMember){ if (member.guild?.id != client.config.mainServer.id) return; diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index d9588f6..ae07aac 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, member:Discord.GuildMember){ if ( diff --git a/src/events/guildMemberRemove.ts b/src/events/guildMemberRemove.ts index 4b21879..e8cf3b6 100644 --- a/src/events/guildMemberRemove.ts +++ b/src/events/guildMemberRemove.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, member:Discord.GuildMember){ if (!client.config.botSwitches.logs) return; diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 73c9423..a5a5755 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, oldMember:Discord.GuildMember, newMember:Discord.GuildMember){ if (oldMember.guild.id != client.config.mainServer.id) return; diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 2cabff5..eff4ac5 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, interaction:Discord.ChatInputCommandInteraction){ if (!interaction.inGuild() || !interaction.inCachedGuild() || !interaction.command) return; diff --git a/src/events/inviteCreate.ts b/src/events/inviteCreate.ts index 16691d5..8c7994a 100644 --- a/src/events/inviteCreate.ts +++ b/src/events/inviteCreate.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, invite: Discord.Invite){ if (!invite.guild) return; diff --git a/src/events/inviteDelete.ts b/src/events/inviteDelete.ts index 8fe2b04..9a60276 100644 --- a/src/events/inviteDelete.ts +++ b/src/events/inviteDelete.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, invite: Discord.Invite){ client.invites.delete(invite.code) diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 4fd8c54..94ca6a8 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,5 +1,5 @@ import Discord, { ChannelType } from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, message:Discord.Message){ if ( diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 5f260f4..e6b47d1 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, msg:Discord.Message){ if (!client.config.botSwitches.logs) return; diff --git a/src/events/messageDeleteBulk.ts b/src/events/messageDeleteBulk.ts index 28dec23..ae5ea39 100644 --- a/src/events/messageDeleteBulk.ts +++ b/src/events/messageDeleteBulk.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, messages:Discord.Collection>){ const channel = client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel; diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts index a7c88d8..d1d5f19 100644 --- a/src/events/messageUpdate.ts +++ b/src/events/messageUpdate.ts @@ -1,5 +1,5 @@ import Discord, { ActionRowBuilder, ButtonBuilder } from 'discord.js'; -import { TClient } from '../client'; +import TClient from '../client'; export default { async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){ if (!client.config.botSwitches.logs) return; diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts index 0d23be1..26091b4 100644 --- a/src/events/roleUpdate.ts +++ b/src/events/roleUpdate.ts @@ -1,5 +1,5 @@ import Discord, { AuditLogEvent } from 'discord.js'; -import { TClient } from '../client'; +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({ diff --git a/src/index.ts b/src/index.ts index 32aa890..7ec1822 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import Discord from 'discord.js'; -import { TClient } from './client'; +import TClient from './client'; const client = new TClient; client.init(); import fs from 'node:fs'; diff --git a/src/schoolClassroom.ts b/src/schoolClassroom.ts new file mode 100644 index 0000000..e2cea72 --- /dev/null +++ b/src/schoolClassroom.ts @@ -0,0 +1,228 @@ +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] || 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.cache.get(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 | 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}`}).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{ + 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}`; + } + } + } +} \ No newline at end of file