diff --git a/README.md b/README.md index 80f630f..6a9c5c3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# Daggerbot-TS -TypeScript-based Daggerbot converted from JavaScript at [SpaceManBuzz/DaggerBot-](https://github.com/SpaceManBuzz/DaggerBot-) +![https://discord.gg/4SnUAFu](https://cdn.discordapp.com/attachments/1015195575693627442/1081877631068295178/DaggerwinServerBanner2023.gif) +# Daggerbot-TS Description +This is 1st generation bot that is a TypeScript-based Daggerbot converted from JavaScript at (now archived and privated) ~~[SpaceManBuzz/DaggerBot-](https://github.com/SpaceManBuzz/DaggerBot-)~~ \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index e010380..a762a1f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -16,185 +16,184 @@ import tokens from './tokens.json'; let importconfig:Config try{ - importconfig = require('./DB-Beta.config.json') - console.log('Using development config :: Daggerbot Beta') - //importconfig = require('./Toast-Testbot.config.json') - //console.log('Using development config :: Toast-Testbot') + importconfig = require('./DB-Beta.config.json') + console.log('Using development config :: Daggerbot Beta') + //importconfig = require('./Toast-Testbot.config.json') + //console.log('Using development config :: Toast-Testbot') } catch(e){ - importconfig = require('./config.json') - console.log('Using production config') + importconfig = require('./config.json') + console.log('Using production config') } export default class TClient extends Client { - invites: Map; - commands: Discord.Collection; - registry: Array; - config: Config; - tokens: Tokens; - YTCache: any; - embed: typeof Discord.EmbedBuilder; - collection: any; - messageCollector: any; - attachmentBuilder: any; - moment: typeof moment; - xjs: any; - axios: typeof axios; - ms: any; - userLevels: userLevels; - punishments: punishments; - bonkCount: bonkCount; - bannedWords: bannedWords; - MPServer: MPServer; - suggestion: suggestion; - repeatedMessages: repeatedMessages; - statsGraph: number; + invites: Map; + commands: Discord.Collection; + registry: Array; + config: Config; + tokens: Tokens; + YTCache: any; + embed: typeof Discord.EmbedBuilder; + collection: any; + messageCollector: any; + attachmentBuilder: any; + moment: typeof moment; + xjs: any; + axios: typeof axios; + ms: any; + userLevels: userLevels; + punishments: punishments; + bonkCount: bonkCount; + bannedWords: bannedWords; + MPServer: MPServer; + suggestion: suggestion; + repeatedMessages: repeatedMessages; + statsGraph: number; - constructor(){ - super({ - intents: [ - GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildBans, GatewayIntentBits.GuildInvites, - GatewayIntentBits.GuildPresences, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages - ], - partials: [ - Partials.Channel, - Partials.Reaction, - Partials.Message - ], - allowedMentions: { users: [], roles: [] } - }) - this.invites = new Map(); - this.commands = new Discord.Collection(); - this.registry = []; - this.config = importconfig as Config; - this.tokens = tokens as Tokens; - this.YTCache = { - 'UCQ8k8yTDLITldfWYKDs3xFg': undefined, // Daggerwin - 'UCguI73--UraJpso4NizXNzA': undefined // Machinery Restorer - } - this.embed = Discord.EmbedBuilder; - this.collection = Discord.Collection; - this.messageCollector = Discord.MessageCollector; - this.attachmentBuilder = Discord.AttachmentBuilder; - this.moment = moment; - this.xjs = require('xml-js'); - this.axios = axios; - this.ms = require('ms'); - this.userLevels = new userLevels(this); - this.bonkCount = new bonkCount(this); - this.punishments = new punishments(this); - this.bannedWords = new bannedWords(this); - this.MPServer = new MPServer(this); - this.suggestion = new suggestion(this); - this.repeatedMessages = {}; - this.setMaxListeners(80); - this.statsGraph = -60; + constructor(){ + super({ + intents: [ + GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildInvites, + GatewayIntentBits.GuildPresences, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages + ], + partials: [ + Partials.Channel, + Partials.Reaction, + Partials.Message + ], + allowedMentions: {users:[],roles:[]} + }) + this.invites = new Map(); + this.commands = new Discord.Collection(); + this.registry = []; + this.config = importconfig as Config; + this.tokens = tokens as Tokens; + this.YTCache = { + 'UCQ8k8yTDLITldfWYKDs3xFg': undefined, // Daggerwin + 'UCguI73--UraJpso4NizXNzA': undefined // Machinery Restorer } - async init(){ - mongoose.set('strictQuery', true); - await mongoose.connect(this.tokens.mongodb_uri, { - replicaSet: 'toastyy', - autoIndex: true, - keepAlive: true, - serverSelectionTimeoutMS: 15000, - waitQueueTimeoutMS: 50000, - socketTimeoutMS: 30000, - family: 4 - }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch((err)=>{console.error(this.logTime(), `Failed to connect to MongoDB\n${err.reason}`); 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}`); - this.commands.set(command.default.data.name, command) - this.registry.push(command.default.data.toJSON()) - } - fs.readdirSync('src/events').forEach((file)=>{ - const eventFile = require(`./events/${file}`); - this.on(file.replace('.ts', ''), async(...args)=>eventFile.default.run(this,...args)); - }); + this.embed = Discord.EmbedBuilder; + this.collection = Discord.Collection; + this.messageCollector = Discord.MessageCollector; + this.attachmentBuilder = Discord.AttachmentBuilder; + this.moment = moment; + this.xjs = require('xml-js'); + this.axios = axios; + this.ms = require('ms'); + this.userLevels = new userLevels(this); + this.bonkCount = new bonkCount(this); + this.punishments = new punishments(this); + this.bannedWords = new bannedWords(this); + this.MPServer = new MPServer(this); + this.suggestion = new suggestion(this); + this.repeatedMessages = {}; + this.setMaxListeners(80); + this.statsGraph = -60; + } + async init(){ + console.time('Startup'); + mongoose.set('strictQuery', true); + await mongoose.connect(this.tokens.mongodb_uri, { + replicaSet: 'toastyy', + autoIndex: true, + keepAlive: true, + serverSelectionTimeoutMS: 15000, + waitQueueTimeoutMS: 50000, + socketTimeoutMS: 30000, + family: 4 + }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch((err)=>{console.error(this.logTime(), `Failed to connect to MongoDB\n${err.reason}`); 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}`); + this.commands.set(command.default.data.name, command) + this.registry.push(command.default.data.toJSON()) } - formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){ - let achievedAccuracy = 0; - let text:any = ''; - for (const timeName of timeNames){ - if (achievedAccuracy < accuracy){ - const fullTimelengths = Math.floor(integer/timeName.length); - if (fullTimelengths == 0) continue; - achievedAccuracy++; - text += fullTimelengths + (options?.longNames ? (' '+timeName.name+(fullTimelengths === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (options?.commas ? ', ' : ' '); - integer -= fullTimelengths*timeName.length; - } else { - break; - } - } - if (text.length == 0) text = integer + (options?.longNames ? ' milliseconds' : 'ms') + (options?.commas ? ', ' : ''); - if (options?.commas){ - text = text.slice(0, -2); - if (options?.longNames){ - text = text.split(''); - text[text.lastIndexOf(',')] = ' and'; - text = text.join(''); - } - } return text.trim(); + fs.readdirSync('src/events').forEach((file)=>{ + const eventFile = require(`./events/${file}`); + this.on(file.replace('.ts', ''), async(...args)=>eventFile.default.run(this,...args)); + }); + } + formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){ + let achievedAccuracy = 0; + let text:any = ''; + for (const timeName of timeNames){ + if (achievedAccuracy < accuracy){ + const fullTimelengths = Math.floor(integer/timeName.length); + if (fullTimelengths == 0) continue; + achievedAccuracy++; + text += fullTimelengths + (options?.longNames ? (' '+timeName.name+(fullTimelengths === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (options?.commas ? ', ' : ' '); + integer -= fullTimelengths*timeName.length; + } else break; } - isStaff(guildMember: Discord.GuildMember){ - return this.config.mainServer.staffRoles.map((x: string)=>this.config.mainServer.roles[x]).some((x: string)=>guildMember.roles.cache.has(x)) - } - 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; - } else if (alignment == 'middle'){ - const emptyCharsPerSide = (length - text.length)/2; - text = emptyChar.repeat(Math.floor(emptyCharsPerSide))+text+emptyChar.repeat(Math.floor(emptyCharsPerSide)); - } else { - text = text + emptyChar.repeat(length - text.length); - } return text; - } - async punish(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>, type: string){ - if (!client.isStaff(interaction.member as Discord.GuildMember)) return client.youNeedRole(interaction, "dcmod"); + if (text.length == 0) text = integer + (options?.longNames ? ' milliseconds' : 'ms') + (options?.commas ? ', ' : ''); + if (options?.commas){ + text = text.slice(0, -2); + if (options?.longNames){ + text = text.split(''); + text[text.lastIndexOf(',')] = ' and'; + text = text.join(''); + } + } return text.trim(); + } + isStaff = (guildMember:Discord.GuildMember)=>this.config.mainServer.staffRoles.map((x: string)=>this.config.mainServer.roles[x]).some((x: string)=>guildMember.roles.cache.has(x)); - const time = interaction.options.getString('time') ?? undefined; - const reason = interaction.options.getString('reason') ?? 'Reason unspecified'; - const GuildMember = interaction.options.getMember('member') ?? undefined; - const User = interaction.options.getUser('member', true); + youNeedRole = (interaction:Discord.CommandInteraction, role:string)=>interaction.reply(`This command is restricted to <@&${this.config.mainServer.roles[role]}>`); - if (interaction.user.id == User.id) return interaction.reply(`You cannot ${type} yourself.`); - if (!GuildMember && type != 'ban') return interaction.reply(`You cannot ${type} someone who is not in the server.`); - if (User.bot) return interaction.reply(`You cannot ${type} a bot!`); + logTime = ()=>`[${this.moment().format('DD/MM/YY HH:mm:ss')}]`; - await interaction.deferReply(); - await client.punishments.addPunishment(type, { time, interaction }, interaction.user.id, reason, User, GuildMember); + alignText(text: string, length: number, alignment: string, emptyChar = ' '){ + if (alignment == 'right') text = emptyChar.repeat(length - text.length)+text; + else if (alignment == 'middle'){ + const emptyCharsPerSide = (length - text.length)/2; + text = emptyChar.repeat(Math.floor(emptyCharsPerSide))+text+emptyChar.repeat(Math.floor(emptyCharsPerSide)); + } else text = text + emptyChar.repeat(length - text.length); + return text; + } + async punish(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>, type: string){ + if (!client.isStaff(interaction.member as Discord.GuildMember)) return client.youNeedRole(interaction, "dcmod"); + + const time = interaction.options.getString('time') ?? undefined; + const reason = interaction.options.getString('reason') ?? 'Reason unspecified'; + const GuildMember = interaction.options.getMember('member') ?? undefined; + const User = interaction.options.getUser('member', true); + + if (interaction.user.id == User.id) return interaction.reply(`You cannot ${type} yourself.`); + if (!GuildMember && type != 'ban') return interaction.reply(`You cannot ${type} someone who is not in the server.`); + if (User.bot) return interaction.reply(`You cannot ${type} a bot!`); + + await interaction.deferReply(); + await client.punishments.addPunishment(type, { time, interaction }, interaction.user.id, reason, User, GuildMember); + } + async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){ + let Data:any; + let error; + + try { + await this.axios.get(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelID}`, {timeout: 5000}).then((xml:any)=>Data = this.xjs.xml2js(xml.data, {compact: true, spaces: 2})) + } catch(err){ + error = true; + console.log(this.logTime(), `${YTChannelName} YT fail`) } - async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){ - let Data:any; - let error; - try { - await this.axios.get(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelID}`, {timeout: 5000}).then((xml:any)=>{ - Data = this.xjs.xml2js(xml.data, {compact: true, spaces: 2}); - }) - } catch(err){ - error = true; - console.log(this.logTime(), `${YTChannelName} YT fail`) - } - - if (!Data) return; - if (this.YTCache[YTChannelID] == undefined){ - this.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text; - return; - } - if (Data.feed.entry[1]['yt:videoId']._text == this.YTCache[YTChannelID]){ - this.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text; - (this.channels.resolve(DCChannelID) as Discord.TextChannel).send(`**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`) - } + if (!Data) return; + if (this.YTCache[YTChannelID] == undefined){ + this.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text; + return; } + if (Data.feed.entry[1]['yt:videoId']._text == this.YTCache[YTChannelID]){ + this.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text; + (this.channels.resolve(DCChannelID) as Discord.TextChannel).send(`**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`) + } + } } -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 + }) + } +} // hi tae, ik you went to look for secret hello msgs in here too. \ No newline at end of file diff --git a/src/commands/ban.ts b/src/commands/ban.ts index e707720..a11c8a3 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -1,20 +1,20 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - client.punish(client, interaction, 'ban'); - }, - data: new SlashCommandBuilder() - .setName('ban') - .setDescription('Ban a member from the server') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member to ban?') - .setRequired(true)) - .addStringOption((opt)=>opt - .setName('time') - .setDescription('How long the ban will be?')) - .addStringOption((opt)=>opt - .setName('reason') - .setDescription('Reason for the ban')) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + client.punish(client, interaction, 'ban'); + }, + data: new SlashCommandBuilder() + .setName('ban') + .setDescription('Ban a member from the server') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member to ban?') + .setRequired(true)) + .addStringOption((opt)=>opt + .setName('time') + .setDescription('How long the ban will be?')) + .addStringOption((opt)=>opt + .setName('reason') + .setDescription('Reason for the ban')) } \ No newline at end of file diff --git a/src/commands/bannedWords.ts b/src/commands/bannedWords.ts index 2833528..bbff4a0 100644 --- a/src/commands/bannedWords.ts +++ b/src/commands/bannedWords.ts @@ -1,42 +1,42 @@ 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) && !client.config.eval.whitelist.includes(interaction.member.id)) return client.youNeedRole(interaction, 'admin') - const word = interaction.options.getString('word', true); - const wordExists = await client.bannedWords._content.findById(word); - ({ - 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: 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}) - } as any)[interaction.options.getSubcommand()](); - }, - data: new SlashCommandBuilder() - .setName('bannedwords') - .setDescription('description placeholder') - /*.addSubcommand((opt)=>opt - .setName('view') - .setDescription('View the list of currently banned words.')) - */.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 database.') - .setRequired(true))) - .addSubcommand((opt)=>opt - .setName('remove') - .setDescription('What word do you want to remove?') - .addStringOption((optt)=>optt - .setName('word') - .setDescription('Remove the specific word from automod\'s bannedWords list.') - .setRequired(true))) + 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', true); + const wordExists = await client.bannedWords._content.findById(word); + ({ + 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: 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}) + } as any)[interaction.options.getSubcommand()](); + }, + data: new SlashCommandBuilder() + .setName('bannedwords') + .setDescription('description placeholder')/* + .addSubcommand((opt)=>opt + .setName('view') + .setDescription('View the list of currently banned words.'))*/ + .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 database.') + .setRequired(true))) + .addSubcommand((opt)=>opt + .setName('remove') + .setDescription('What word do you want to remove?') + .addStringOption((optt)=>optt + .setName('word') + .setDescription('Remove the specific word from automod\'s bannedWords list.') + .setRequired(true))) } diff --git a/src/commands/case.ts b/src/commands/case.ts index 29af153..0cb3d12 100644 --- a/src/commands/case.ts +++ b/src/commands/case.ts @@ -1,79 +1,79 @@ import Discord,{SlashCommandBuilder} from "discord.js"; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + 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'); - 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))]}); - } + 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() + data: new SlashCommandBuilder() .setName('case') .setDescription('Retrieve case information or user\'s punishment history') .addSubcommand((opt)=>opt - .setName('view') - .setDescription('View a single case.') - .addIntegerOption((optt)=>optt - .setName('id') - .setDescription('Case #') - .setRequired(true))) + .setName('view') + .setDescription('View a single case.') + .addIntegerOption((optt)=>optt + .setName('id') + .setDescription('Case #') + .setRequired(true))) .addSubcommand((opt)=>opt - .setName('member') - .setDescription('View member\'s punishment history') - .addUserOption((optt)=>optt - .setName('user') - .setDescription('Which user do you want to view their punishment history?') - .setRequired(true)) - .addIntegerOption((optt)=>optt - .setName('page') - .setDescription('Select the page number'))) + .setName('member') + .setDescription('View member\'s punishment history') + .addUserOption((optt)=>optt + .setName('user') + .setDescription('Which user do you want to view their punishment history?') + .setRequired(true)) + .addIntegerOption((optt)=>optt + .setName('page') + .setDescription('Select the page number'))) .addSubcommand((opt)=>opt - .setName('update') - .setDescription('Update the case with new reason') - .addIntegerOption((optt)=>optt - .setName('id') - .setDescription('Case # to be updated') - .setRequired(true)) - .addStringOption((optt)=>optt - .setName('reason') - .setDescription('New reason for the case') - .setRequired(true))) + .setName('update') + .setDescription('Update the case with new reason') + .addIntegerOption((optt)=>optt + .setName('id') + .setDescription('Case # to be updated') + .setRequired(true)) + .addStringOption((optt)=>optt + .setName('reason') + .setDescription('New reason for the case') + .setRequired(true))) }; \ No newline at end of file diff --git a/src/commands/contributors.ts b/src/commands/contributors.ts index 4893f32..0751b1f 100644 --- a/src/commands/contributors.ts +++ b/src/commands/contributors.ts @@ -1,20 +1,20 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Daggerbot contributors').setDescription([ - '**Thanks to those below that contributed to the bot!**', - 'Toast <@190407856527376384>', - 'TÆMBØ <@615761944154210305>', - 'Buzz <@593696856165449749>', - 'Monster <@215497515934416896>', - 'RainbowDave <@141304507249197057>', - 'Hitchhiker <@506022868157595648>', - 'RedRover92 <@633345781780185099>', - 'Nawdic <@178941218510602240>' - ].join('\n'))]}) - }, - data: new SlashCommandBuilder() - .setName('contributors') - .setDescription('List of people who contributed to the bot.') + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Daggerbot contributors').setDescription([ + '**Thanks to those below that contributed to the bot!**', + 'Toast <@190407856527376384>', + 'TÆMBØ <@615761944154210305>', + 'Buzz <@593696856165449749>', + 'Monster <@215497515934416896>', + 'RainbowDave <@141304507249197057>', + 'Hitchhiker <@506022868157595648>', + 'RedRover92 <@633345781780185099>', + 'Nawdic <@178941218510602240>' + ].join('\n'))]}) + }, + data: new SlashCommandBuilder() + .setName('contributors') + .setDescription('List of people who contributed to the bot.') } \ No newline at end of file diff --git a/src/commands/dev.ts b/src/commands/dev.ts index 6789c6c..5217bcb 100644 --- a/src/commands/dev.ts +++ b/src/commands/dev.ts @@ -20,155 +20,147 @@ const removeUsername = (text: string)=>{ } 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.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).catch(err=>{console.log(err); interaction.reply({content: 'Placeholder error for `fetchCommitMsg`', ephemeral: true})}); - const fetchCommitAuthor = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.author.name).catch(err=>{console.log(err); interaction.reply({content: 'Placeholder error for `fetchCommitAuthor`', ephemeral: true})}); - 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 `, 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')) + 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.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).catch(err=>{console.log(err); interaction.reply({content: 'Placeholder error for `fetchCommitMsg`', ephemeral: true})}); + const fetchCommitAuthor = await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.author.name).catch(err=>{console.log(err); interaction.reply({content: 'Placeholder error for `fetchCommitAuthor`', ephemeral: true})}); + 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'; } - } as any)[interaction.options.getSubcommand()](); + }; + 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 `, 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} - ))) + 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} + ))) } diff --git a/src/commands/faq.ts b/src/commands/faq.ts index 5e0eff7..dcc524f 100644 --- a/src/commands/faq.ts +++ b/src/commands/faq.ts @@ -1,7 +1,7 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ ({ srp: ()=>interaction.reply('Ballyspring is the map that is used in Survival Roleplay S4.'), 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')]}), diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 6f64101..d38a1ab 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -1,17 +1,17 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - client.punish(client, interaction, 'kick'); - }, - data: new SlashCommandBuilder() - .setName('kick') - .setDescription('Boot a member from the server') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member to kick?') - .setRequired(true)) - .addStringOption((opt)=>opt - .setName('reason') - .setDescription('Reason for the kick')) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + client.punish(client, interaction, 'kick'); + }, + data: new SlashCommandBuilder() + .setName('kick') + .setDescription('Boot a member from the server') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member to kick?') + .setRequired(true)) + .addStringOption((opt)=>opt + .setName('reason') + .setDescription('Reason for the kick')) } \ No newline at end of file diff --git a/src/commands/mp.ts b/src/commands/mp.ts index cc0c3b8..e906217 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -5,296 +5,284 @@ import canvas from 'canvas'; import fs from 'node:fs'; async function MPdata(client:TClient, interaction:Discord.ChatInputCommandInteraction, embed: Discord.EmbedBuilder) { - let FSserver; - if (!await client.MPServer._content.findOne({_id:interaction.guildId})) return interaction.reply('This server isn\'t linked to the bot.'); - const ServerURL = await client.MPServer._content.findById(interaction.guildId); - if (!ServerURL) return interaction.reply(`No FS server found, please notify <@&${client.config.mainServer.roles.mpmanager}> to add it.`) - const MPURL = ServerURL.ip - const MPCode = ServerURL.code - const verifyURL = MPURL.match(/http|https/) - const completedURL = MPURL+'/feed/dedicated-server-stats.json?code='+MPCode - if (!verifyURL) return interaction.reply(`The server IP for this server is currently invalid, please notify <@&${client.config.mainServer.roles.mpmanager}>`) + let FSserver; + if (!await client.MPServer._content.findOne({_id:interaction.guildId})) return interaction.reply('This server isn\'t linked to the bot.'); + const ServerURL = await client.MPServer._content.findById(interaction.guildId); + if (!ServerURL) return interaction.reply(`No FS server found, please notify <@&${client.config.mainServer.roles.mpmanager}> to add it.`) + const MPURL = ServerURL.ip + const MPCode = ServerURL.code + const verifyURL = MPURL.match(/http|https/) + const completedURL = MPURL+'/feed/dedicated-server-stats.json?code='+MPCode + if (!verifyURL) return interaction.reply(`The server IP for this server is currently invalid, please notify <@&${client.config.mainServer.roles.mpmanager}>`) - // Fetch dss - try { // v I am aware timeout has decreased from 2800 to 2588 to fit within Discord's interaction timeouts (3s) -Toast - FSserver = await client.axios.get(completedURL, {timeout: 2588, headers: {'User-Agent': `Daggerbot - mp cmd/axios ${client.axios.VERSION}`}}) - } catch(err) { - // Blame Nawdic & RedRover92 - embed.setTitle('Host is not responding.'); - embed.setColor(client.config.embedColorRed); - console.log(client.logTime(), 'DagMP failed to fetch, host didn\'t respond in time.'); - return interaction.reply('Server didn\'t respond in time.'); - } - return FSserver + // Fetch dss + try { // v I am aware timeout has decreased from 2800 to 2588 to fit within Discord's interaction timeouts (3s) -Toast + FSserver = await client.axios.get(completedURL, {timeout: 2588, headers: {'User-Agent': `Daggerbot - mp cmd/axios ${client.axios.VERSION}`}}) + } catch(err) { + // Blame Nawdic & RedRover92 + embed.setTitle('Host is not responding.'); + embed.setColor(client.config.embedColorRed); + console.log(client.logTime(), 'DagMP failed to fetch, host didn\'t respond in time.'); + return interaction.reply('Server didn\'t respond in time.'); + } return FSserver } export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - if (interaction.channelId == '468835769092669461' && !client.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) { - interaction.reply(`Please use <#739084625862852715> for \`/mp status/players\` commands to prevent clutter in this channel.`).then((msg)=>{setTimeout(()=>{interaction.deleteReply()}, 6000)}); - return; + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + if (interaction.channelId == '468835769092669461' && !client.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) { + interaction.reply(`Please use <#739084625862852715> for \`/mp status/players\` commands to prevent clutter in this channel.`).then((msg)=>{setTimeout(()=>{interaction.deleteReply()}, 6000)}); + return; + } + ({ + status: async()=>{ + const embed0 = new client.embed(); + const FSserver0 = await MPdata(client, interaction, embed0); + if (!FSserver0?.data) return console.log('FSserver0 failed - status'); + try { + if (FSserver0.data.server.name.length > 1){ + interaction.reply({embeds: [embed0.setTitle('Status/Details').setColor(client.config.embedColor).addFields( + {name: 'Server name', value: `${FSserver0?.data.server.name.length == 0 ? '\u200b' : `\`${FSserver0?.data.server.name}\``}`, inline: true}, + {name: 'Players', value: `${FSserver0.data.slots.used} out of ${FSserver0.data.slots.capacity}`, inline: true}, + {name: 'Current map', value: `${FSserver0?.data.server.mapName.length == 0 ? '\u200b' : FSserver0.data.server.mapName}`, inline: true}, + {name: 'Version', value: `${FSserver0?.data.server.version.length == 0 ? '\u200b' : FSserver0.data.server.version}`, inline: true}, + {name: 'In-game Time', value: `${('0' + Math.floor((FSserver0.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSserver0.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true} + )]}) + } else if (FSserver0.data.server.name.length == 0) interaction.reply('Server is currently offline.') + } catch (err){ + console.log(err) + interaction.reply('FSserver0 Error placeholder') + }; + }, + info: async()=>{ + const embed2 = new client.embed().setColor(client.config.embedColor) + const FSserver2 = await MPdata(client, interaction, embed2) + if (!FSserver2?.data) return console.log('FSserver2 failed - info') + const MPURL = await client.MPServer._content.findById(interaction.guildId); + interaction.reply({embeds: [embed2.setDescription([ + `**Server name**: \`${FSserver2?.data.server.name.length == 0 ? '\u200b' : FSserver2?.data.server.name}\``, + '**Password:** `mf4700`', + '**Crossplay server**', + `**Map:** ${FSserver2.data.server.mapName.length == 0 ? 'Null Island' : FSserver2.data.server.mapName}`, + `**Mods:** [Click here](${MPURL.ip}/mods.html) **|** [Direct Download](${MPURL.ip}/all_mods_download?onlyActive=true)`, + '**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)', + 'Please see <#543494084363288637> for additional information.' + ].join('\n'))]}); + if (FSserver2?.data.server.name.length == 0) embed2.setFooter({text: 'Server is currently offline.'}) + }, + url: async()=>{ + if (client.config.mainServer.id == interaction.guildId) { + if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech) && !interaction.member.roles.cache.has(client.config.mainServer.roles.admin)) return client.youNeedRole(interaction, 'mpmanager'); } - ({ - status: async()=>{ - const embed0 = new client.embed(); - const FSserver0 = await MPdata(client, interaction, embed0); - if (!FSserver0?.data) return console.log('FSserver0 failed - status'); - try { - if (FSserver0.data.server.name.length > 1){ - embed0.setTitle('Status/Details').setColor(client.config.embedColor).addFields( - {name: 'Server name', value: `${FSserver0?.data.server.name.length == 0 ? '\u200b' : `\`${FSserver0?.data.server.name}\``}`, inline: true}, - {name: 'Players', value: `${FSserver0.data.slots.used} out of ${FSserver0.data.slots.capacity}`, inline: true}, - {name: 'Current map', value: `${FSserver0?.data.server.mapName.length == 0 ? '\u200b' : FSserver0.data.server.mapName}`, inline: true}, - {name: 'Version', value: `${FSserver0?.data.server.version.length == 0 ? '\u200b' : FSserver0.data.server.version}`, inline: true}, - {name: 'In-game Time', value: `${('0' + Math.floor((FSserver0.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSserver0.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true} - ) - interaction.reply({embeds: [embed0]}) - } else if (FSserver0.data.server.name.length == 0) interaction.reply('Server is currently offline.') - } catch (err){ - console.log(err) - interaction.reply('FSserver0 Error placeholder') - }; - }, - info: async()=>{ - const embed2 = new client.embed().setColor(client.config.embedColor) - const FSserver2 = await MPdata(client, interaction, embed2) - if (!FSserver2?.data) return console.log('FSserver2 failed - info') - const MPURL = await client.MPServer._content.findById(interaction.guildId); - embed2.setDescription([ - `**Server name**: \`${FSserver2?.data.server.name.length == 0 ? '\u200b' : FSserver2?.data.server.name}\``, - '**Password:** `mf4700`', - '**Crossplay server**', - `**Map:** ${FSserver2.data.server.mapName.length == 0 ? 'Null Island' : FSserver2.data.server.mapName}`, - `**Mods:** [Click here](${MPURL.ip}/mods.html) **|** [Direct Download](${MPURL.ip}/all_mods_download?onlyActive=true)`, - '**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)', - 'Please see <#543494084363288637> for additional information.' - ].join('\n')); - if (FSserver2?.data.server.name.length == 0) embed2.setFooter({text: 'Server is currently offline.'}) - interaction.reply({embeds: [embed2]}) - }, - url: async()=>{ - if (client.config.mainServer.id == interaction.guildId) { - if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech) && !interaction.member.roles.cache.has(client.config.mainServer.roles.admin)) return client.youNeedRole(interaction, 'mpmanager'); - } - const address = interaction.options.getString('address'); - if (!address){ - try { - const Url = await client.MPServer._content.findById(interaction.guildId); - if (Url.ip && Url.code) return interaction.reply(`${Url.get('ip')}`+'/feed/dedicated-server-stats.json?code='+`${Url.get('code')}`) - } catch(err){ - console.log(`MPDB :: ${err}`) - interaction.reply('**Database error:**\nTry inserting an URL first.') - } - }else{ - const verifyURL = address.match(/dedicated-server-stats/) - if (!verifyURL) return interaction.reply('The URL does not match `dedicated-server-stats.xml`') - const newURL = address.replace('xml','json').split('/feed/dedicated-server-stats.json?code=') - try{ - console.log(`MPDB :: URL for ${interaction.guild.name} has been updated by ${interaction.member.displayName} (${interaction.member.id})`); - await client.MPServer._content.create({_id: interaction.guildId, ip: newURL[0], code: newURL[1], timesUpdated: 0}) - return interaction.reply('This server is now linked and URL has been added.'); - } catch(err){ - const affectedValues = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {ip: newURL[0], code: newURL[1]}); - await client.MPServer._increment(interaction.guildId); - if (affectedValues) return interaction.reply('URL successfully updated.') - } - } - }, - players: async()=>{ - const embed1 = new client.embed(); - const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../database/MPPlayerData.json'), {encoding: 'utf8'})).slice(client.statsGraph) - // handle negative days - data.forEach((change: number, i: number) => { - if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0; - }); + const address = interaction.options.getString('address'); + if (!address){ + try { + const Url = await client.MPServer._content.findById(interaction.guildId); + if (Url.ip && Url.code) return interaction.reply(`${Url.get('ip')}`+'/feed/dedicated-server-stats.json?code='+`${Url.get('code')}`) + } catch(err){ + console.log(`MPDB :: ${err}`) + interaction.reply('**Database error:**\nTry inserting an URL first.') + } + }else{ + const verifyURL = address.match(/dedicated-server-stats/) + if (!verifyURL) return interaction.reply('The URL does not match `dedicated-server-stats.xml`') + const newURL = address.replace('xml','json').split('/feed/dedicated-server-stats.json?code=') + try{ + console.log(`MPDB :: URL for ${interaction.guild.name} has been updated by ${interaction.member.displayName} (${interaction.member.id})`); + await client.MPServer._content.create({_id: interaction.guildId, ip: newURL[0], code: newURL[1], timesUpdated: 0}) + return interaction.reply('This server is now linked and URL has been added.'); + } catch(err){ + const affectedValues = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {ip: newURL[0], code: newURL[1]}); + await client.MPServer._increment(interaction.guildId); + if (affectedValues) return interaction.reply('URL successfully updated.') + } + } + }, + players: async()=>{ + const embed1 = new client.embed(); + const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../database/MPPlayerData.json'), {encoding: 'utf8'})).slice(client.statsGraph) + // handle negative days + data.forEach((change: number, i: number) => { + if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0; + }); - const first_graph_top = 16; - const second_graph_top = 16; - const textSize = 40; + const first_graph_top = 16; + const second_graph_top = 16; + const textSize = 40; - const img = canvas.createCanvas(1500, 750); - const ctx = img.getContext('2d'); - - const graphOrigin = [15, 65]; - const graphSize = [1300, 630]; - const nodeWidth = graphSize[0] / (data.length - 1); - ctx.fillStyle = '#36393f'; - ctx.fillRect(0, 0, img.width, img.height); - - // grey horizontal lines - ctx.lineWidth = 5; - - let interval_candidates = []; - for (let i = 4; i < 10; i++) { - const interval = first_graph_top / i; - if (Number.isInteger(interval)) { - let intervalString = interval.toString(); - const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67) - interval_candidates.push([interval, i, reference_number]); - } - } - const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0]; - - const previousY: Array = []; - - ctx.strokeStyle = '#202225'; - for (let i = 0; i <= chosen_interval[1]; i++) { - const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); - if (y < graphOrigin[1]) continue; - const even = ((i + 1) % 2) === 0; - if (even) ctx.strokeStyle = '#2c2f33'; - ctx.beginPath(); - ctx.lineTo(graphOrigin[0], y); - ctx.lineTo(graphOrigin[0] + graphSize[0], y); - ctx.stroke(); - ctx.closePath(); - if (even) ctx.strokeStyle = '#202225'; - previousY.push(y, i * chosen_interval[0]); - } - - // 30m mark - ctx.setLineDash([8, 16]); - ctx.beginPath(); - const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); - ctx.lineTo(lastMonthStart, graphOrigin[1]); - ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); - ctx.stroke(); - ctx.closePath(); - ctx.setLineDash([]); - - // draw points - ctx.lineWidth = 5; - - function getYCoordinate(value: number) { - return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; - } + const img = canvas.createCanvas(1500, 750); + const ctx = img.getContext('2d'); - function colorAtPlayercount(playercount: number) { - if (playercount === first_graph_top) { - return client.config.embedColorRed as string; - } else if (playercount > 9) { - return client.config.embedColorYellow as string; - } else {return client.config.embedColorGreen as string} - } - let lastCoords: Array = []; - data.forEach((curPC: number /* current player count */, i: number) => { - if (curPC < 0) curPC = 0; - const x = i * nodeWidth + graphOrigin[0]; - const y = getYCoordinate(curPC); - const nexPC /* next player count */ = data[i + 1]; - const prvPC /* previous player count */ = data[i - 1]; - const curColor = colorAtPlayercount(curPC); // color now - const prvColor = colorAtPlayercount(prvPC); // color at last point - if (curColor !== prvColor && !isNaN(prvPC) && lastCoords.length > 0) { // gradient should be used when the color between now and last point is not the same - // gradient from now to last point - const grd = ctx.createLinearGradient(lastCoords[0], lastCoords[1], x, y); - grd.addColorStop(0, colorAtPlayercount(prvPC)); // prev color at the beginning - grd.addColorStop(1, colorAtPlayercount(curPC)); // cur color at the end - // special case: playercount rises or falls rapidly accross all colors (eg. straight from red to green) - if (curColor !== client.config.embedColorYellow && prvColor !== client.config.embedColorYellow) { - const yellowY = getYCoordinate(10); // y coordinate at which line should be yellow - const stop = (yellowY - lastCoords[1]) / (y - lastCoords[1]); // between 0 and 1, where is yellowY between y and nextPointCoords[1] ? - grd.addColorStop(stop, client.config.embedColorYellow as string); // add a yellow stop to the gradient - } - ctx.strokeStyle = grd; - } else { - ctx.strokeStyle = colorAtPlayercount(curPC); - } - ctx.beginPath(); - if (lastCoords.length > 0) ctx.moveTo(lastCoords[0], lastCoords[1]); - // if the line being drawn is horizontal, make it go until it has to go down - if (y === lastCoords[1]) { - let newX = x; - for (let j = i + 1; j <= data.length; j++) { - if (data[j] === curPC) newX += nodeWidth; else break; - } - ctx.lineTo(newX, y); - } else { - ctx.lineTo(x, y); - } - lastCoords = [x, y]; - ctx.stroke(); - ctx.closePath(); - - if (curPC === prvPC && curPC === nexPC) { - return; // no ball because no vertical difference to next or prev point - } else { - // ball - ctx.fillStyle = colorAtPlayercount(curPC); - ctx.beginPath(); - ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI) - ctx.closePath(); - ctx.fill(); - } - }); - - // draw text - ctx.font = '400 ' + textSize + 'px sans-serif'; - ctx.fillStyle = 'white'; - - // highest value - const maxx = graphOrigin[0] + graphSize[0] + textSize / 2; - const maxy = previousY[0] + (textSize / 3); - ctx.fillText(previousY[1].toLocaleString('en-US'), maxx, maxy); + const graphOrigin = [15, 65]; + const graphSize = [1300, 630]; + const nodeWidth = graphSize[0] / (data.length - 1); + ctx.fillStyle = '#36393f'; + ctx.fillRect(0, 0, img.width, img.height); - // lowest value - const lowx = graphOrigin[0] + graphSize[0] + textSize / 2; - const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3); - ctx.fillText('0 players', lowx, lowy); + // grey horizontal lines + ctx.lineWidth = 5; - // 30m - ctx.fillText('30 mins ago', lastMonthStart, graphOrigin[1] - (textSize / 2)); + let interval_candidates = []; + for (let i = 4; i < 10; i++) { + const interval = first_graph_top / i; + if (Number.isInteger(interval)) { + let intervalString = interval.toString(); + const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67) + interval_candidates.push([interval, i, reference_number]); + } + } + const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0]; - // time -> - const tx = graphOrigin[0] + (textSize / 2); - const ty = graphOrigin[1] + graphSize[1] + (textSize); - ctx.fillText('time ->', tx, ty); - - const Image = new client.attachmentBuilder(img.toBuffer(),{name: 'FSStats.png'}) - embed1.setImage('attachment://FSStats.png') - const FSserver1 = await MPdata(client, interaction, embed1) - if (!FSserver1?.data) return console.log('FSserver1 failed - players') - embed1.setTitle(FSserver1?.data.server.name.length == 0 ? 'Offline' : FSserver1?.data.server.name) - .setDescription(`${FSserver1?.data.slots.used}/${FSserver1?.data.slots.capacity}`) - .setColor(FSserver1?.data.server.name.length == 0 ? client.config.embedColorRed : client.config.embedColor); - FSserver1?.data.slots.players.filter(x=>x.isUsed).forEach(player=>{ - embed1.addFields({name: `${player.name} ${player.isAdmin ? '| admin' : ''}`, value: `Farming for ${(Math.floor(player.uptime/60))} hr, ${('0' + (player.uptime % 60)).slice(-2)} min`}) - }) - interaction.reply({embeds: [embed1], files: [Image]}) - }/*, - series: ()=>{ - const embed3 = new client.embed().setColor(client.config.embedColor).setTitle('How to join the Daggerwin MP series') - .setDescription([ - 'To join the Daggerwin MP series, you first need to:', - '**1:** Note that only PC players can join the MP series due to the mods that are used.', - '**2:** Become a YouTube Member by pressing the `Join` button on [Daggerwin\'s YouTube page](https://www.youtube.com/c/Daggerwin) next to the `Subscribe` button.', - '**3:** Link your YouTube account to your Discord account via Settings>Connections>Add connection. Be sure that you link the same YouTube account you used to become a channel member.', - '**4:** If you don\'t receive the role within a day or so, please message an Admin and they will sort it out.', - '**5:** Take a look in <#511657659364147200> to get information on how to join the server.' - ].join('\n')); - interaction.reply({embeds: [embed3]}) - }*/ - } as any)[interaction.options.getSubcommand()](); - }, - data: new SlashCommandBuilder() - .setName('mp') - .setDescription('Display MP status and other things') - .addSubcommand((opt)=>opt - .setName('status') - .setDescription('Check server status and details')) - .addSubcommand((opt)=>opt - .setName('players') - .setDescription('Check who\'s playing on the server')) - .addSubcommand((opt)=>opt - .setName('info') - .setDescription('Provides you with server information such as filters and so on')) - .addSubcommand((opt)=>opt - .setName('url') - .setDescription('View the URL for this server\'s FSMP server or update the URL') - .addStringOption((opt)=>opt - .setName('address') - .setDescription('Insert a \'dedicated-server-stats\' URL'))) - /* .addSubcommand((opt)=>opt - .setName('series') - .setDescription('Step-by-step on joining Daggerwin\'s MP series')) */ + const previousY: Array = []; + + ctx.strokeStyle = '#202225'; + for (let i = 0; i <= chosen_interval[1]; i++) { + const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); + if (y < graphOrigin[1]) continue; + const even = ((i + 1) % 2) === 0; + if (even) ctx.strokeStyle = '#2c2f33'; + ctx.beginPath(); + ctx.lineTo(graphOrigin[0], y); + ctx.lineTo(graphOrigin[0] + graphSize[0], y); + ctx.stroke(); + ctx.closePath(); + if (even) ctx.strokeStyle = '#202225'; + previousY.push(y, i * chosen_interval[0]); + } + + // 30m mark + ctx.setLineDash([8, 16]); + ctx.beginPath(); + const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); + ctx.lineTo(lastMonthStart, graphOrigin[1]); + ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); + ctx.stroke(); + ctx.closePath(); + ctx.setLineDash([]); + + // draw points + ctx.lineWidth = 5; + + function getYCoordinate(value: number) { + return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; + } + + function colorAtPlayercount(playercount: number) { + if (playercount === first_graph_top) return client.config.embedColorRed as string; + else if (playercount > 9) return client.config.embedColorYellow as string; + else return client.config.embedColorGreen as string + } + let lastCoords: Array = []; + data.forEach((curPC: number /* current player count */, i: number) => { + if (curPC < 0) curPC = 0; + const x = i * nodeWidth + graphOrigin[0]; + const y = getYCoordinate(curPC); + const nexPC /* next player count */ = data[i + 1]; + const prvPC /* previous player count */ = data[i - 1]; + const curColor = colorAtPlayercount(curPC); // color now + const prvColor = colorAtPlayercount(prvPC); // color at last point + if (curColor !== prvColor && !isNaN(prvPC) && lastCoords.length > 0) { // gradient should be used when the color between now and last point is not the same + // gradient from now to last point + const grd = ctx.createLinearGradient(lastCoords[0], lastCoords[1], x, y); + grd.addColorStop(0, colorAtPlayercount(prvPC)); // prev color at the beginning + grd.addColorStop(1, colorAtPlayercount(curPC)); // cur color at the end + // special case: playercount rises or falls rapidly accross all colors (eg. straight from red to green) + if (curColor !== client.config.embedColorYellow && prvColor !== client.config.embedColorYellow) { + const yellowY = getYCoordinate(10); // y coordinate at which line should be yellow + const stop = (yellowY - lastCoords[1]) / (y - lastCoords[1]); // between 0 and 1, where is yellowY between y and nextPointCoords[1] ? + grd.addColorStop(stop, client.config.embedColorYellow as string); // add a yellow stop to the gradient + } + ctx.strokeStyle = grd; + } else ctx.strokeStyle = colorAtPlayercount(curPC); + ctx.beginPath(); + if (lastCoords.length > 0) ctx.moveTo(lastCoords[0], lastCoords[1]); + // if the line being drawn is horizontal, make it go until it has to go down + if (y === lastCoords[1]) { + let newX = x; + for (let j = i + 1; j <= data.length; j++) { + if (data[j] === curPC) newX += nodeWidth; else break; + } + ctx.lineTo(newX, y); + } else ctx.lineTo(x, y); + lastCoords = [x, y]; + ctx.stroke(); + ctx.closePath(); + + if (curPC === prvPC && curPC === nexPC) return; // no ball because no vertical difference to next or prev point + else { + // ball + ctx.fillStyle = colorAtPlayercount(curPC); + ctx.beginPath(); + ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI) + ctx.closePath(); + ctx.fill(); + } + }); + + // draw text + ctx.font = '400 ' + textSize + 'px sans-serif'; + ctx.fillStyle = 'white'; + + // highest value + const maxx = graphOrigin[0] + graphSize[0] + textSize / 2; + const maxy = previousY[0] + (textSize / 3); + ctx.fillText(previousY[1].toLocaleString('en-US'), maxx, maxy); + + // lowest value + const lowx = graphOrigin[0] + graphSize[0] + textSize / 2; + const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3); + ctx.fillText('0 players', lowx, lowy); + + // 30m + ctx.fillText('30 mins ago', lastMonthStart, graphOrigin[1] - (textSize / 2)); + + // time -> + const tx = graphOrigin[0] + (textSize / 2); + const ty = graphOrigin[1] + graphSize[1] + (textSize); + ctx.fillText('time ->', tx, ty); + + const Image = new client.attachmentBuilder(img.toBuffer(),{name: 'FSStats.png'}) + embed1.setImage('attachment://FSStats.png') + const FSserver1 = await MPdata(client, interaction, embed1) + if (!FSserver1?.data) return console.log('FSserver1 failed - players') + embed1.setTitle(FSserver1?.data.server.name.length == 0 ? 'Offline' : FSserver1?.data.server.name) + .setDescription(`${FSserver1?.data.slots.used}/${FSserver1?.data.slots.capacity}`) + .setColor(FSserver1?.data.server.name.length == 0 ? client.config.embedColorRed : client.config.embedColor); + FSserver1?.data.slots.players.filter(x=>x.isUsed).forEach(player=>embed1.addFields({name: `${player.name} ${player.isAdmin ? '| admin' : ''}`, value: `Farming for ${(Math.floor(player.uptime/60))} hr, ${('0' + (player.uptime % 60)).slice(-2)} min`})) + interaction.reply({embeds: [embed1], files: [Image]}) + }/*, + series: ()=>{ + const embed3 = new client.embed().setColor(client.config.embedColor).setTitle('How to join the Daggerwin MP series') + .setDescription([ + 'To join the Daggerwin MP series, you first need to:', + '**1:** Note that only PC players can join the MP series due to the mods that are used.', + '**2:** Become a YouTube Member by pressing the `Join` button on [Daggerwin\'s YouTube page](https://www.youtube.com/c/Daggerwin) next to the `Subscribe` button.', + '**3:** Link your YouTube account to your Discord account via Settings>Connections>Add connection. Be sure that you link the same YouTube account you used to become a channel member.', + '**4:** If you don\'t receive the role within a day or so, please message an Admin and they will sort it out.', + '**5:** Take a look in <#511657659364147200> to get information on how to join the server.' + ].join('\n')); + interaction.reply({embeds: [embed3]}) + }*/ + } as any)[interaction.options.getSubcommand()](); + }, + data: new SlashCommandBuilder() + .setName('mp') + .setDescription('Display MP status and other things') + .addSubcommand((opt)=>opt + .setName('status') + .setDescription('Check server status and details')) + .addSubcommand((opt)=>opt + .setName('players') + .setDescription('Check who\'s playing on the server')) + .addSubcommand((opt)=>opt + .setName('info') + .setDescription('Provides you with server information such as filters and so on')) + .addSubcommand((opt)=>opt + .setName('url') + .setDescription('View the URL for this server\'s FSMP server or update the URL') + .addStringOption((opt)=>opt + .setName('address') + .setDescription('Insert a \'dedicated-server-stats\' URL')))/* + .addSubcommand((opt)=>opt + .setName('series') + .setDescription('Step-by-step on joining Daggerwin\'s MP series'))*/ } diff --git a/src/commands/mute.ts b/src/commands/mute.ts index c3227f9..b3a2981 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -1,20 +1,20 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - client.punish(client, interaction, 'mute'); - }, - data: new SlashCommandBuilder() - .setName('mute') - .setDescription('Mute a member') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member to mute?') - .setRequired(true)) - .addStringOption((opt)=>opt - .setName('time') - .setDescription('Mute duration')) - .addStringOption((opt)=>opt - .setName('reason') - .setDescription('Reason for the mute')) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + client.punish(client, interaction, 'mute'); + }, + data: new SlashCommandBuilder() + .setName('mute') + .setDescription('Mute a member') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member to mute?') + .setRequired(true)) + .addStringOption((opt)=>opt + .setName('time') + .setDescription('Mute duration')) + .addStringOption((opt)=>opt + .setName('reason') + .setDescription('Reason for the mute')) } \ No newline at end of file diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 5f86d26..9c3d2e6 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,12 +1,12 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - const msg = await interaction.reply({content: 'Pinging...', fetchReply: true}) - const time = msg.createdTimestamp - interaction.createdTimestamp; - msg.edit(`Websocket: \`${client.formatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot: \`${client.formatTime(time, 3, {longNames: false, commas: true})}\``) - }, - data: new SlashCommandBuilder() - .setName('ping') - .setDescription('Check bot\'s latency') + async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + const msg = await interaction.reply({content: 'Pinging...', fetchReply: true}) + const time = msg.createdTimestamp - interaction.createdTimestamp; + msg.edit(`Websocket: \`${client.formatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot: \`${client.formatTime(time, 3, {longNames: false, commas: true})}\``) + }, + data: new SlashCommandBuilder() + .setName('ping') + .setDescription('Check bot\'s latency') } \ No newline at end of file diff --git a/src/commands/purge.ts b/src/commands/purge.ts index 7148ad2..dad204c 100644 --- a/src/commands/purge.ts +++ b/src/commands/purge.ts @@ -1,37 +1,35 @@ 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)) return client.youNeedRole(interaction, 'dcmod'); - const amount = interaction.options.getInteger('amount') as number; - if (amount > 100) return interaction.reply({content: 'Discord API limits purging up to 100 messages.', ephemeral: true}) - const user = interaction.options.getUser('user'); + async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod'); + const amount = interaction.options.getInteger('amount') as number; + if (amount > 100) return interaction.reply({content: 'Discord API limits purging up to 100 messages.', ephemeral: true}) + const user = interaction.options.getUser('user'); - let messagesArray: Array = []; + let messagesArray: Array = []; - if (user){ - (interaction.channel as Discord.TextChannel).messages.fetch({limit: amount}).then((msgs)=>{ - const msgList = msgs.filter(x=>x.author.id == user.id); - (interaction.channel as Discord.TextChannel).bulkDelete(msgList); - }) - } else { - (interaction.channel as Discord.TextChannel).messages.fetch({limit: amount}).then(async messages=>{ - messages.forEach(message=>{ - messagesArray.push(message.id); - }); - await (interaction.channel as Discord.TextChannel).bulkDelete(messagesArray); - }) - } - await interaction.reply({content: `Successfully purged ${amount} messages.`, ephemeral: true}) - }, - data: new SlashCommandBuilder() - .setName('purge') - .setDescription('Purge the amount of messages in this channel') - .addIntegerOption((opt)=>opt - .setName('amount') - .setDescription('Amount of messages to be obliterated') - .setRequired(true)) - .addUserOption((opt)=>opt - .setName('user') - .setDescription('Which user to have their messages obliterated?')) + if (user){ + (interaction.channel as Discord.TextChannel).messages.fetch({limit: amount}).then(msgs=>{ + const msgList = msgs.filter(x=>x.author.id == user.id); + (interaction.channel as Discord.TextChannel).bulkDelete(msgList); + }) + } else { + (interaction.channel as Discord.TextChannel).messages.fetch({limit: amount}).then(async messages=>{ + messages.forEach(message=>messagesArray.push(message.id)); + await (interaction.channel as Discord.TextChannel).bulkDelete(messagesArray); + }) + } + await interaction.reply({content: `Successfully purged ${amount} messages.`, ephemeral: true}) + }, + data: new SlashCommandBuilder() + .setName('purge') + .setDescription('Purge the amount of messages in this channel') + .addIntegerOption((opt)=>opt + .setName('amount') + .setDescription('Amount of messages to be obliterated') + .setRequired(true)) + .addUserOption((opt)=>opt + .setName('user') + .setDescription('Which user to have their messages obliterated?')) } \ No newline at end of file diff --git a/src/commands/randomcolor.ts b/src/commands/randomcolor.ts index b0e84f5..e576bcf 100644 --- a/src/commands/randomcolor.ts +++ b/src/commands/randomcolor.ts @@ -1,12 +1,12 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; 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)); - embed.addFields({name: 'Hex code', value: `#${embed.data.color.toString(16).toUpperCase()}`}); - interaction.reply({embeds: [embed]}); - }, - data: new SlashCommandBuilder() - .setName('randomcolor') - .setDescription('Generate a random hex code') + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + const embed = new client.embed().setColor(Math.floor(Math.random()*16777215)); + embed.addFields({name: 'Hex code', value: `#${embed.data.color.toString(16).toUpperCase()}`}); + interaction.reply({embeds: [embed]}); + }, + data: new SlashCommandBuilder() + .setName('randomcolor') + .setDescription('Generate a random hex code') } \ No newline at end of file diff --git a/src/commands/rank.ts b/src/commands/rank.ts index 953b2d9..66fce18 100644 --- a/src/commands/rank.ts +++ b/src/commands/rank.ts @@ -4,171 +4,171 @@ import path from 'node:path'; import fs from 'node:fs'; 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: 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 userData = await client.userLevels._content.findById(member.user.id); + 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: 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 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 (!userData) return interaction.reply(`${pronounBool('You', 'They')} currently don't have a level, send some messages to level up.`) + 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 (!userData) return interaction.reply(`${pronounBool('You', 'They')} currently don't have a level, send some messages to level up.`) - 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 = allData.reduce((a, b) => a + b.messages, 0); - const timeActive = Math.floor((Date.now() - client.config.LRSstart)/1000/60/60/24); + 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 = 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, 'utf8')).map((x: Array, i: number, a: any) => { - const yesterday = a[i - 1] || []; - return x[1] - (yesterday[1] || x[1]); - }).slice(1).slice(-60); + const dailyMsgsPath = path.join(__dirname, '../database/dailyMsgs.json'); + const data = JSON.parse(fs.readFileSync(dailyMsgsPath, 'utf8')).map((x: Array, i: number, a: any) => { + const yesterday = a[i - 1] || []; + return x[1] - (yesterday[1] || x[1]); + }).slice(1).slice(-60); - // handle negative days - data.forEach((change: number, i: number) => { - if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0; - }); + // handle negative days + data.forEach((change: number, i: number) => { + if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0; + }); - const maxValue = Math.max(...data); - const maxValueArr = maxValue.toString().split(''); + const maxValue = Math.max(...data); + const maxValueArr = maxValue.toString().split(''); - const first_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 1)) * 10 ** (maxValueArr.length - 1); - const second_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 2)) * 10 ** (maxValueArr.length - 2); - const textSize = 32; + const first_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 1)) * 10 ** (maxValueArr.length - 1); + const second_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 2)) * 10 ** (maxValueArr.length - 2); + const textSize = 32; - const img = canvas.createCanvas(950, 450); - const ctx = img.getContext('2d'); + const img = canvas.createCanvas(950, 450); + const ctx = img.getContext('2d'); - const graphOrigin = [10, 50]; - const graphSize = [700, 360]; - const nodeWidth = graphSize[0] / (data.length - 1); - ctx.fillStyle = '#36393f'; - ctx.fillRect(0, 0, img.width, img.height); + const graphOrigin = [10, 50]; + const graphSize = [700, 360]; + const nodeWidth = graphSize[0] / (data.length - 1); + ctx.fillStyle = '#36393f'; + ctx.fillRect(0, 0, img.width, img.height); - // grey horizontal lines - ctx.lineWidth = 3; + // grey horizontal lines + ctx.lineWidth = 3; - let interval_candidates = []; - for (let i = 4; i < 10; i++) { - const interval = first_graph_top / i; - if (Number.isInteger(interval)) { - let intervalString = interval.toString(); - const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67) - interval_candidates.push([interval, i, reference_number]); - } - } - const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0]; - let previousY: Array = []; + let interval_candidates = []; + for (let i = 4; i < 10; i++) { + const interval = first_graph_top / i; + if (Number.isInteger(interval)) { + let intervalString = interval.toString(); + const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67) + interval_candidates.push([interval, i, reference_number]); + } + } + const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0]; + let previousY: Array = []; - ctx.strokeStyle = '#202225'; - for (let i = 0; i <= chosen_interval[1]; i++) { - const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); - if (y < graphOrigin[1]) continue; - const even = ((i + 1) % 2) === 0; - if (even) ctx.strokeStyle = '#2c2f33'; - ctx.beginPath(); - ctx.lineTo(graphOrigin[0], y); - ctx.lineTo(graphOrigin[0] + graphSize[0], y); - ctx.stroke(); - ctx.closePath(); - if (even) ctx.strokeStyle = '#202225'; - previousY = [y, i * chosen_interval[0]]; - } + ctx.strokeStyle = '#202225'; + for (let i = 0; i <= chosen_interval[1]; i++) { + const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); + if (y < graphOrigin[1]) continue; + const even = ((i + 1) % 2) === 0; + if (even) ctx.strokeStyle = '#2c2f33'; + ctx.beginPath(); + ctx.lineTo(graphOrigin[0], y); + ctx.lineTo(graphOrigin[0] + graphSize[0], y); + ctx.stroke(); + ctx.closePath(); + if (even) ctx.strokeStyle = '#202225'; + previousY = [y, i * chosen_interval[0]]; + } - // 30d mark - ctx.setLineDash([8, 16]); - ctx.beginPath(); - const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); - ctx.lineTo(lastMonthStart, graphOrigin[1]); - ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); - ctx.stroke(); - ctx.closePath(); - ctx.setLineDash([]); + // 30d mark + ctx.setLineDash([8, 16]); + ctx.beginPath(); + const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); + ctx.lineTo(lastMonthStart, graphOrigin[1]); + ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); + ctx.stroke(); + ctx.closePath(); + ctx.setLineDash([]); - // draw points - ctx.strokeStyle = client.config.embedColor as string; - ctx.fillStyle = client.config.embedColor as string; - ctx.lineWidth = 3; + // draw points + ctx.strokeStyle = client.config.embedColor as string; + ctx.fillStyle = client.config.embedColor as string; + ctx.lineWidth = 3; - function getYCoordinate(value: number) { - return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; - } + function getYCoordinate(value: number) { + return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; + } - let lastCoords: Array = []; - data.forEach((val: number, i: number) => { - ctx.beginPath(); - if (lastCoords) ctx.moveTo(lastCoords[0], lastCoords[1]); - if (val < 0) val = 0; - const x = i * nodeWidth + graphOrigin[0]; - const y = getYCoordinate(val); - ctx.lineTo(x, y); - lastCoords = [x, y]; - ctx.stroke(); - ctx.closePath(); + let lastCoords: Array = []; + data.forEach((val: number, i: number) => { + ctx.beginPath(); + if (lastCoords) ctx.moveTo(lastCoords[0], lastCoords[1]); + if (val < 0) val = 0; + const x = i * nodeWidth + graphOrigin[0]; + const y = getYCoordinate(val); + ctx.lineTo(x, y); + lastCoords = [x, y]; + ctx.stroke(); + ctx.closePath(); - // ball - ctx.beginPath(); - ctx.arc(x, y, ctx.lineWidth * 1.2, 0, 2 * Math.PI) - ctx.closePath(); - ctx.fill(); - }); + // ball + ctx.beginPath(); + ctx.arc(x, y, ctx.lineWidth * 1.2, 0, 2 * Math.PI) + ctx.closePath(); + ctx.fill(); + }); - // draw text - ctx.font = '400 ' + textSize + 'px sans-serif'; - ctx.fillStyle = 'white'; + // draw text + ctx.font = '400 ' + textSize + 'px sans-serif'; + ctx.fillStyle = 'white'; - // highest value - const maxx = graphOrigin[0] + graphSize[0] + textSize; - const maxy = previousY[0] + (textSize / 3); - ctx.fillText(previousY[1].toLocaleString('en-US'), maxx, maxy); + // highest value + const maxx = graphOrigin[0] + graphSize[0] + textSize; + const maxy = previousY[0] + (textSize / 3); + ctx.fillText(previousY[1].toLocaleString('en-US'), maxx, maxy); - // lowest value - const lowx = graphOrigin[0] + graphSize[0] + textSize; - const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3); - ctx.fillText('0 msgs/day', lowx, lowy); + // lowest value + const lowx = graphOrigin[0] + graphSize[0] + textSize; + const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3); + ctx.fillText('0 msgs/day', lowx, lowy); - // 30d - ctx.fillText('30d ago', lastMonthStart, graphOrigin[1] - (textSize / 3)); + // 30d + ctx.fillText('30d ago', lastMonthStart, graphOrigin[1] - (textSize / 3)); - // time -> - const tx = graphOrigin[0] + (textSize / 2); - const ty = graphOrigin[1] + graphSize[1] + (textSize); - ctx.fillText('time ->', tx, ty); + // time -> + const tx = graphOrigin[0] + (textSize / 2); + const ty = graphOrigin[1] + graphSize[1] + (textSize); + ctx.fillText('time ->', tx, ty); - 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 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.`) - .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()](); - }, - data: new SlashCommandBuilder() - .setName('rank') - .setDescription('Level system') - .addSubcommand((optt)=>optt - .setName('view') - .setDescription('View your rank or someone else\'s rank') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member do you want to view?'))) - .addSubcommand((optt)=>optt - .setName('leaderboard') - .setDescription('View top 10 users on leaderboard')) + 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.`) + .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()](); + }, + data: new SlashCommandBuilder() + .setName('rank') + .setDescription('Level system') + .addSubcommand((optt)=>optt + .setName('view') + .setDescription('View your rank or someone else\'s rank') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member do you want to view?'))) + .addSubcommand((optt)=>optt + .setName('leaderboard') + .setDescription('View top 10 users on leaderboard')) } \ No newline at end of file diff --git a/src/commands/roleinfo.ts b/src/commands/roleinfo.ts index 3b4ae35..1c5083e 100644 --- a/src/commands/roleinfo.ts +++ b/src/commands/roleinfo.ts @@ -1,23 +1,23 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - const role = interaction.options.getRole('role') as Discord.Role; - const permissions = role.permissions.toArray(); - const Role = role.members.map((e:Discord.GuildMember)=>`**${e.user.tag}**`).join('\n') || ''; - interaction.reply({embeds: [new client.embed().setColor(role.color || '#fefefe').setThumbnail(role?.iconURL()).setTitle(`Role Info: ${role.name}`).addFields( - {name: '🔹 ID', value: `\`${role.id}\``, inline: true}, - {name: '🔹 Color', value: `\`${role.hexColor}\``, inline: true}, - {name: '🔹 Creation Date', value: `\n`, inline: true}, - {name: '🔹 Misc', value: `Hoist: \`${role.hoist}\`\nMentionable: \`${role.mentionable}\`\nPosition: \`${role.position}\` from bottom\nMembers: \`${role.members.size}\`\n${role.members.size < 21 ? Role : ''}`, inline: true}, - {name: '🔹 Permissions', value: `${permissions.includes('Administrator') ? ['Administrator'] : permissions.join(', ') || 'None'}`, inline: true} - )]}) - }, - data: new SlashCommandBuilder() - .setName('roleinfo') - .setDescription('View information about the selected role') - .addRoleOption((opt)=>opt - .setName('role') - .setDescription('Role name to view information') - .setRequired(true)) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + const role = interaction.options.getRole('role') as Discord.Role; + const permissions = role.permissions.toArray(); + const Role = role.members.map((e:Discord.GuildMember)=>`**${e.user.tag}**`).join('\n') || ''; + interaction.reply({embeds: [new client.embed().setColor(role.color || '#fefefe').setThumbnail(role?.iconURL()).setTitle(`Role Info: ${role.name}`).addFields( + {name: '🔹 ID', value: `\`${role.id}\``, inline: true}, + {name: '🔹 Color', value: `\`${role.hexColor}\``, inline: true}, + {name: '🔹 Creation Date', value: `\n`, inline: true}, + {name: '🔹 Misc', value: `Hoist: \`${role.hoist}\`\nMentionable: \`${role.mentionable}\`\nPosition: \`${role.position}\` from bottom\nMembers: \`${role.members.size}\`\n${role.members.size < 21 ? Role : ''}`, inline: true}, + {name: '🔹 Permissions', value: `${permissions.includes('Administrator') ? ['Administrator'] : permissions.join(', ') || 'None'}`, inline: true} + )]}) + }, + data: new SlashCommandBuilder() + .setName('roleinfo') + .setDescription('View information about the selected role') + .addRoleOption((opt)=>opt + .setName('role') + .setDescription('Role name to view information') + .setRequired(true)) } \ No newline at end of file diff --git a/src/commands/softban.ts b/src/commands/softban.ts index 7cbd8f2..1d252fc 100644 --- a/src/commands/softban.ts +++ b/src/commands/softban.ts @@ -1,17 +1,17 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - client.punish(client, interaction, 'softban'); - }, - data: new SlashCommandBuilder() - .setName('softban') - .setDescription('Softban a member from the server') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member to softban?') - .setRequired(true)) - .addStringOption((opt)=>opt - .setName('reason') - .setDescription('Reason for the softban')) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + client.punish(client, interaction, 'softban'); + }, + data: new SlashCommandBuilder() + .setName('softban') + .setDescription('Softban a member from the server') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member to softban?') + .setRequired(true)) + .addStringOption((opt)=>opt + .setName('reason') + .setDescription('Reason for the softban')) } \ No newline at end of file diff --git a/src/commands/statistics.ts b/src/commands/statistics.ts index 546a29c..6e3c76e 100644 --- a/src/commands/statistics.ts +++ b/src/commands/statistics.ts @@ -4,73 +4,69 @@ import si from 'systeminformation'; import TClient from 'src/client'; import os from 'node:os'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - // Host specification (L9-L22, L55-L70) - // Bytes conversion - function formatBytes(bytes:number, decimals:number = 2) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; - }; - var DJSver = require('discord.js').version; - const cpu = await si.cpu(); - const ram = await si.mem(); - const osInfo = await si.osInfo(); - const currentLoad = await si.currentLoad(); - - // Command usage (L25-L54) - const columns = ['Command name', 'Count']; - const includedCommands = client.commands.filter(x=>x.uses).sort((a,b)=>b.uses - a.uses); - if (includedCommands.size == 0) return interaction.reply(`No commands have been used yet.\nUptime: **${client.formatTime(client.uptime as number, 3, {longNames: true, commas: true})}**`); - const nameLength = Math.max(...includedCommands.map(x=>x.default.data.name.length), columns[0].length) + 2; - const amountLength = Math.max(...includedCommands.map(x=>x.uses.toString().length), columns[1].length) + 1; - const rows = [`${columns[0] + ' '.repeat(nameLength - columns[0].length)}|${' '.repeat(amountLength - columns[1].length) + columns[1]}\n`, '-'.repeat(nameLength) + '-'.repeat(amountLength) + '\n']; - includedCommands.forEach(command=>{ - const name = command.default.data.name; - const count = command.uses.toString(); - rows.push(`${name + ' '.repeat(nameLength - name.length)}${' '.repeat(amountLength - count.length) + count}\n`); - }); - const embed = new client.embed().setColor(client.config.embedColor).setTitle('Statistics: Command Usage') - .setDescription([ - 'List of commands that have been used in this session, ordered by amount of use. Table contains command name and amount of uses.', - `Total amount of commands used in this session: ${client.commands.filter(x=>x.uses).map(x=>x.uses).reduce((a,b)=>a+b, 0)}` - ].join('\n')) - if (rows.join('').length > 1024){ - let fieldValue = ''; - rows.forEach(row=>{ - if (fieldValue.length + row.length > 1024){ - embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``}); - fieldValue = row; - } else { - fieldValue += row; - } - }); - embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``}); - } else { - embed.addFields({name: '\u200b', value: `\`\`\`\n${rows.join('')}\`\`\``}) - }; - embed.addFields( - {name: '> __Dependencies__', value: [ - `**TypeScript:** ${version}`, - `**NodeJS:** ${process.version}`, - `**DiscordJS:** ${DJSver}`, - `**Axios:** ${client.axios.VERSION}` - ].join('\n')}, - {name: '> __Host__', value: [ - `**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`, - `**CPU:** ${cpu.manufacturer} ${cpu.brand}`, - `**Memory:** ${formatBytes(ram.used)}/${formatBytes(ram.total)}`, - `**NodeJS:** ${formatBytes(process.memoryUsage().heapUsed)}/${formatBytes(process.memoryUsage().heapTotal)}`, - `**Load Usage:**\nUser: ${currentLoad.currentLoadUser.toFixed(1)}%\nSystem: ${currentLoad.currentLoadSystem.toFixed(1)}%`, - `**Uptime:**\nHost: ${client.formatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${client.formatTime(client.uptime as number, 2, {commas: true, longNames: true})}` - ].join('\n')} // Nice - ); - interaction.reply({embeds: [embed], fetchReply: true}).then((x)=>x.edit({embeds: [new client.embed(x.embeds[0].data).setFooter({text: `Load time: ${client.formatTime(x.createdTimestamp - interaction.createdTimestamp, 2, {longNames: true, commas: true})}`})]})) - }, - data: new SlashCommandBuilder() - .setName('statistics') - .setDescription('See a list of commands ordered by their usage or host stats') + async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + // Host specification (L9-L22, L51-L66) + // Bytes conversion + function formatBytes(bytes:number, decimals:number = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }; + var DJSver = require('discord.js').version; + const cpu = await si.cpu(); + const ram = await si.mem(); + const osInfo = await si.osInfo(); + const currentLoad = await si.currentLoad(); + + // Command usage (L25-L50) + const columns = ['Command name', 'Count']; + const includedCommands = client.commands.filter(x=>x.uses).sort((a,b)=>b.uses - a.uses); + if (includedCommands.size == 0) return interaction.reply(`No commands have been used yet.\nUptime: **${client.formatTime(client.uptime as number, 3, {longNames: true, commas: true})}**`); + const nameLength = Math.max(...includedCommands.map(x=>x.default.data.name.length), columns[0].length) + 2; + const amountLength = Math.max(...includedCommands.map(x=>x.uses.toString().length), columns[1].length) + 1; + const rows = [`${columns[0] + ' '.repeat(nameLength - columns[0].length)}|${' '.repeat(amountLength - columns[1].length) + columns[1]}\n`, '-'.repeat(nameLength) + '-'.repeat(amountLength) + '\n']; + includedCommands.forEach(command=>{ + const name = command.default.data.name; + const count = command.uses.toString(); + rows.push(`${name + ' '.repeat(nameLength - name.length)}${' '.repeat(amountLength - count.length) + count}\n`); + }); + const embed = new client.embed().setColor(client.config.embedColor).setTitle('Statistics: Command Usage') + .setDescription([ + 'List of commands that have been used in this session, ordered by amount of use. Table contains command name and amount of uses.', + `Total amount of commands used in this session: ${client.commands.filter(x=>x.uses).map(x=>x.uses).reduce((a,b)=>a+b, 0)}` + ].join('\n')) + if (rows.join('').length > 1024){ + let fieldValue = ''; + rows.forEach(row=>{ + if (fieldValue.length + row.length > 1024){ + embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``}); + fieldValue = row; + } else fieldValue += row + }); + embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``}); + } else embed.addFields({name: '\u200b', value: `\`\`\`\n${rows.join('')}\`\`\``}); + embed.addFields( + {name: '> __Dependencies__', value: [ + `**TypeScript:** ${version}`, + `**NodeJS:** ${process.version}`, + `**DiscordJS:** ${DJSver}`, + `**Axios:** ${client.axios.VERSION}` + ].join('\n')}, + {name: '> __Host__', value: [ + `**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`, + `**CPU:** ${cpu.manufacturer} ${cpu.brand}`, + `**Memory:** ${formatBytes(ram.used)}/${formatBytes(ram.total)}`, + `**NodeJS:** ${formatBytes(process.memoryUsage().heapUsed)}/${formatBytes(process.memoryUsage().heapTotal)}`, + `**Load Usage:**\nUser: ${currentLoad.currentLoadUser.toFixed(1)}%\nSystem: ${currentLoad.currentLoadSystem.toFixed(1)}%`, + `**Uptime:**\nHost: ${client.formatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${client.formatTime(client.uptime as number, 2, {commas: true, longNames: true})}` + ].join('\n')} + ); + interaction.reply({embeds: [embed], fetchReply: true}).then((x)=>x.edit({embeds: [new client.embed(x.embeds[0].data).setFooter({text: `Load time: ${client.formatTime(x.createdTimestamp - interaction.createdTimestamp, 2, {longNames: true, commas: true})}`})]})) + }, + data: new SlashCommandBuilder()// Nice + .setName('statistics') + .setDescription('See a list of commands ordered by their usage or host stats') } \ No newline at end of file diff --git a/src/commands/suggest.ts b/src/commands/suggest.ts index 16a23e2..eea68f3 100644 --- a/src/commands/suggest.ts +++ b/src/commands/suggest.ts @@ -56,7 +56,7 @@ export default { ]}); await client.suggestion._content.findByIdAndUpdate(suggestionIDReply, {state: 'Rejected'}); return interaction.reply({embeds:[new client.embed().setColor(client.config.embedColorRed).setTitle(`Suggestion rejected | ${suggestionIDReply}`).setDescription(stateChanged)]}); - }, + } } as any)[interaction.options.getSubcommand()](); }, data: new SlashCommandBuilder() diff --git a/src/commands/warn.ts b/src/commands/warn.ts index 02742de..9bc58ae 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -1,18 +1,18 @@ import Discord,{SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - client.punish(client, interaction, 'warn'); - }, - data: new SlashCommandBuilder() - .setName('warn') - .setDescription('Warn a member') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Which member to warn?') - .setRequired(true)) - .addStringOption((opt)=>opt - .setName('reason') - .setDescription('Reason for the warning') - .setRequired(false)) + run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + client.punish(client, interaction, 'warn'); + }, + data: new SlashCommandBuilder() + .setName('warn') + .setDescription('Warn a member') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Which member to warn?') + .setRequired(true)) + .addStringOption((opt)=>opt + .setName('reason') + .setDescription('Reason for the warning') + .setRequired(false)) } \ No newline at end of file diff --git a/src/commands/whois.ts b/src/commands/whois.ts index 51023e7..a20d7d6 100644 --- a/src/commands/whois.ts +++ b/src/commands/whois.ts @@ -2,64 +2,59 @@ import Discord,{GuildMember, SlashCommandBuilder} from 'discord.js'; import TClient from 'src/client'; function convert(status?:Discord.ClientPresenceStatus){ - if (status){ - return { - idle: '🟡', - dnd: '🔴', - online: '🟢' - }[status]; - } else { - return '⚫' - } + if (status) return { + idle: '🟡', + dnd: '🔴', + online: '🟢' + }[status]; + else return '⚫' } export default { - async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - const member = interaction.options.getMember('member') as GuildMember; - if (member == null){ - const user = interaction.options.getUser('member') as Discord.User; - const embed = new client.embed() - .setColor(client.config.embedColor) - .setURL(`https://discord.com/users/${user.id}`) - .setThumbnail(user.avatarURL({size:2048}) || user.defaultAvatarURL) - .setTitle(`${user.bot ? 'Bot': 'User'} Info: ${user.tag}`) - .setDescription(`<@${user.id}>\n\`${user.id}\``) - .addFields({name: '🔹 Account Creation Date', value: `\n`}) - interaction.reply({embeds: [embed]}) - } else { - await member.user.fetch(); - const presence = member.presence?.clientStatus as Discord.ClientPresenceStatusData; - const embedArray = []; - let title = 'Member'; - if (member.user.bot) { - title = 'Bot' - } else if (member.user.id == interaction.guild.ownerId) { - title = ':crown: Server Owner' - }; - const embed = new client.embed() - .setColor(member.displayColor || client.config.embedColor) - .setURL(`https://discord.com/users/${member.user.id}`) - .setThumbnail(member.user.avatarURL({size:2048}) || member.user.defaultAvatarURL) - .setImage(member.user.bannerURL({size:1024}) as string) - .setTitle(`${title} Info: ${member.user.tag}`) - .setDescription(`<@${member.user.id}>\n\`${member.user.id}\``) - .addFields( - {name: '🔹 Account Creation Date', value: `\n`}, - {name: '🔹 Server Join Date', value: `\n`}, - {name: `🔹 Roles: ${member.roles.cache.size - 1}`, value: member.roles.cache.size > 1 ? member.roles.cache.filter(x=>x.id !== interaction.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'} - ) - if (member.premiumSinceTimestamp !== null) embed.addFields({name: '🔹 Server Boosting since', value: `\n`, inline: true}) - if (!presence) embed.addFields({name: `🔹 Status: Unavailable to retrieve`, value: '\u200b'}) - if (member.presence) embed.addFields({name: `🔹 Status: ${member.presence.status}`, value: `${member.presence.status === 'offline' ? '⚫' : `Desktop: ${convert(presence.desktop)}\nWeb: ${convert(presence.web)}\nMobile: ${convert(presence.mobile)}`}`, inline: true}) - embedArray.push(embed) - interaction.reply({embeds: embedArray}) - } - }, - data: new SlashCommandBuilder() - .setName('whois') - .setDescription('View your own or someone else\'s information') - .addUserOption((opt)=>opt - .setName('member') - .setDescription('Member or user to view their information') - .setRequired(true)) + async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + const member = interaction.options.getMember('member') as GuildMember; + if (member == null){ + const user = interaction.options.getUser('member') as Discord.User; + const embed = new client.embed() + .setColor(client.config.embedColor) + .setURL(`https://discord.com/users/${user.id}`) + .setThumbnail(user.avatarURL({size:2048}) || user.defaultAvatarURL) + .setTitle(`${user.bot ? 'Bot': 'User'} Info: ${user.tag}`) + .setDescription(`<@${user.id}>\n\`${user.id}\``) + .addFields({name: '🔹 Account Creation Date', value: `\n`}) + interaction.reply({embeds: [embed]}) + } else { + await member.user.fetch(); + const presence = member.presence?.clientStatus as Discord.ClientPresenceStatusData; + const embedArray = []; + let title = 'Member'; + if (member.user.bot) title = 'Bot'; + else if (member.user.id == interaction.guild.ownerId) title = ':crown: Server Owner'; + + const embed = new client.embed() + .setColor(member.displayColor || client.config.embedColor) + .setURL(`https://discord.com/users/${member.user.id}`) + .setThumbnail(member.user.avatarURL({size:2048}) || member.user.defaultAvatarURL) + .setImage(member.user.bannerURL({size:1024}) as string) + .setTitle(`${title} Info: ${member.user.tag}`) + .setDescription(`<@${member.user.id}>\n\`${member.user.id}\``) + .addFields( + {name: '🔹 Account Creation Date', value: `\n`}, + {name: '🔹 Server Join Date', value: `\n`}, + {name: `🔹 Roles: ${member.roles.cache.size - 1}`, value: member.roles.cache.size > 1 ? member.roles.cache.filter(x=>x.id !== interaction.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'} + ) + if (member.premiumSinceTimestamp !== null) embed.addFields({name: '🔹 Server Boosting since', value: `\n`, inline: true}) + if (!presence) embed.addFields({name: `🔹 Status: Unavailable to retrieve`, value: '\u200b'}) + if (member.presence) embed.addFields({name: `🔹 Status: ${member.presence.status}`, value: `${member.presence.status === 'offline' ? '⚫' : `Desktop: ${convert(presence.desktop)}\nWeb: ${convert(presence.web)}\nMobile: ${convert(presence.mobile)}`}`, inline: true}) + embedArray.push(embed) + interaction.reply({embeds: embedArray}) + } + }, + data: new SlashCommandBuilder() + .setName('whois') + .setDescription('View your own or someone else\'s information') + .addUserOption((opt)=>opt + .setName('member') + .setDescription('Member or user to view their information') + .setRequired(true)) } \ No newline at end of file diff --git a/src/config.json b/src/config.json index bc125b1..6b63f73 100644 --- a/src/config.json +++ b/src/config.json @@ -1,73 +1,73 @@ { - "embedColor": "#0052cf", - "embedColorBackup": "#0052cf", - "embedColorGreen": "#57f287", - "embedColorYellow": "#ffea00", - "embedColorRed": "#e62c3b", - "embedColorBCA": "#ff69b4", - "embedColorXmas": "#FFFFFF", - "LRSstart": 1661236321433, - "whitelistedServers": [ - "929807948748832798", "468835415093411861", "1058183358267543552" - ], - "botSwitches": { - "registerCommands": true, - "commands": true, - "logs": true, - "automod": true, - "mpstats": true, - "autores": true - }, - "botPresence": { - "activities": [ - {"name": "the new SRP", "url": "https://www.youtube.com/watch?v=5BDEzgbJ7c0", "type": 1} - ], - "status": "idle" + "embedColor": "#0052cf", + "embedColorBackup": "#0052cf", + "embedColorGreen": "#57f287", + "embedColorYellow": "#ffea00", + "embedColorRed": "#e62c3b", + "embedColorBCA": "#ff69b4", + "embedColorXmas": "#FFFFFF", + "LRSstart": 1661236321433, + "whitelistedServers": [ + "929807948748832798", "468835415093411861", "1058183358267543552" + ], + "botSwitches": { + "registerCommands": true, + "commands": true, + "logs": true, + "automod": true, + "mpstats": true, + "autores": true + }, + "botPresence": { + "activities": [ + {"name": "the new SRP", "url": "https://www.youtube.com/watch?v=5BDEzgbJ7c0", "type": 1} + ], + "status": "idle" }, - "eval": { - "allowed": true, - "whitelist": [ - "190407856527376384", - "615761944154210305", - "593696856165449749", - "633345781780185099", - "506022868157595648", - "215497515934416896", - "309373272594579456" - ] + "eval": { + "allowed": true, + "whitelist": [ + "190407856527376384", + "615761944154210305", + "593696856165449749", + "633345781780185099", + "506022868157595648", + "215497515934416896", + "309373272594579456" + ] + }, + "mainServer": { + "id": "468835415093411861", + "staffRoles": [ + "admin", + "dcmod", + "mpmanager", + "mpmod", + "vtcmanager", + "vtcstaff", + "ytmod" + ], + "roles": { + "admin": "468842789053136897", + "bottech": "1011341005389307925", + "dcmod": "468841295150972929", + "mpmanager": "1028735939813585029", + "mpmod": "572151330710487041", + "vtcmanager": "1028735871370940490", + "vtcstaff": "801945627268481046", + "ytmod": "734888335851388958", + "mphelper": "1034453973412884500", + "mpplayer": "798285830669598762", + "vtcmember": "802282391652663338" }, - "mainServer": { - "id": "468835415093411861", - "staffRoles": [ - "admin", - "dcmod", - "mpmanager", - "mpmod", - "vtcmanager", - "vtcstaff", - "ytmod" - ], - "roles": { - "admin": "468842789053136897", - "bottech": "1011341005389307925", - "dcmod": "468841295150972929", - "mpmanager": "1028735939813585029", - "mpmod": "572151330710487041", - "vtcmanager": "1028735871370940490", - "vtcstaff": "801945627268481046", - "ytmod": "734888335851388958", - "mphelper": "1034453973412884500", - "mpplayer": "798285830669598762", - "vtcmember": "802282391652663338" - }, - "channels": { - "console": "1011318687065710663", - "errors": "1009754872188506192", - "thismeanswar": "930588618085502987", - "bot_status": "1009753780188884992", - "logs": "548032776830582794", - "welcome": "621134751897616406", - "botcommands": "468888722210029588" - } + "channels": { + "console": "1011318687065710663", + "errors": "1009754872188506192", + "thismeanswar": "930588618085502987", + "bot_status": "1009753780188884992", + "logs": "548032776830582794", + "welcome": "621134751897616406", + "botcommands": "468888722210029588" } + } } diff --git a/src/database.ts b/src/database.ts index c3beed9..2940f90 100644 --- a/src/database.ts +++ b/src/database.ts @@ -15,26 +15,17 @@ export class Database { this._content = dataType === 'array' ? [] : {}; } addData(data: any, data1?: any){ - if (Array.isArray(this._content)){ - this._content.push(data); - } else if (typeof this._content === 'object'){ - this._content[data] = data1; - } + if (Array.isArray(this._content)) this._content.push(data); + else if (typeof this._content === 'object') this._content[data] = data1; return this; } removeData(key: any, type: number, element: any){ if (this._dataType === 'array'){ - switch (type){ - case 0: - this._content = this._content.filter((x:any)=>x != key); - break; - case 1: - this._content = this._content.filter((x:any)=>x[element] != key); - break; - } - } else if (this._dataType === 'object'){ - delete this._content[key]; - } + ({ + 0: ()=>this._content = this._content.filter((x:any)=>x!=key), + 1: ()=>this._content = this._content.filter((x:any)=>x[element]!=key) + })[type]() + } else if (this._dataType === 'object') delete this._content[key]; return this; } initLoad(){ @@ -64,6 +55,4 @@ export class Database { console.log(this._path + ' "DB saved" Notifications disabled'); return this; } - - -} // Nice. \ No newline at end of file +} diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts index eff1eeb..a55fc0f 100644 --- a/src/events/guildBanAdd.ts +++ b/src/events/guildBanAdd.ts @@ -1,20 +1,17 @@ import Discord, { AuditLogEvent } from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, member:Discord.GuildMember){ - if (member.guild?.id != client.config.mainServer.id) return; - const fetchBanlog = await member.guild.fetchAuditLogs({limit: 1, type: AuditLogEvent.MemberBanAdd}) - const banLog = fetchBanlog.entries.first(); - if (!banLog) return console.log(`${member.user.tag} was banned from ${member.guild.name} but no audit log for this user.`) - const {executor, target, reason } = banLog; - if (target.id == member.user.id) { - (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})).setTitle(`Member Banned: ${target.tag}`).setDescription(`🔹 **User**\n<@${target.id}>\n\`${target.id}\``).addFields( - {name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``}, - {name: '🔹 Reason', value: `${reason == null ? 'Reason unspecified': reason}`} - )]}) - } else { - console.log(`${target.tag} was banned from ${member.guild.name} but no audit log could be fetched.`) - } - } + async run(client:TClient, member:Discord.GuildMember){ + if (member.guild?.id != client.config.mainServer.id) return; + const fetchBanlog = await member.guild.fetchAuditLogs({limit: 1, type: AuditLogEvent.MemberBanAdd}) + const banLog = fetchBanlog.entries.first(); + if (!banLog) return console.log(`${member.user.tag} was banned from ${member.guild.name} but no audit log for this user.`) + const {executor, target, reason } = banLog; + if (target.id == member.user.id) (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})).setTitle(`Member Banned: ${target.tag}`).setDescription(`🔹 **User**\n<@${target.id}>\n\`${target.id}\``).addFields( + {name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``}, + {name: '🔹 Reason', value: `${reason == null ? 'Reason unspecified': reason}`} + )]}); + else console.log(`${target.tag} was banned from ${member.guild.name} but no audit log could be fetched.`) + } } diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts index beb8132..457c48a 100644 --- a/src/events/guildBanRemove.ts +++ b/src/events/guildBanRemove.ts @@ -1,20 +1,17 @@ import Discord, { AuditLogEvent } from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, member:Discord.GuildMember){ - if (member.guild?.id != client.config.mainServer.id) return; - const fetchUnbanlog = await member.guild.fetchAuditLogs({limit: 1, type: AuditLogEvent.MemberBanRemove}) - const unbanLog = fetchUnbanlog.entries.first(); - if (!unbanLog) return console.log(`${member.user.tag} was unbanned from ${member.guild.name} but no audit log for this user.`) - const { executor, target, reason } = unbanLog; - if (target.id == member.user.id) { - (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [ - new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`Member Unbanned: ${target.tag}`).setDescription(`🔹 **User**\n<@${target.id}>\n\`${target.id}\``).addFields( - {name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``}, - {name: '🔹 Reason', value: `${reason == null ? 'Reason unspecified.': reason}`} - )]}) - } else { - console.log(`${target.tag} was unbanned from ${member.guild.name} but no audit log could be fetched.`) - } - } + async run(client:TClient, member:Discord.GuildMember){ + if (member.guild?.id != client.config.mainServer.id) return; + const fetchUnbanlog = await member.guild.fetchAuditLogs({limit: 1, type: AuditLogEvent.MemberBanRemove}) + const unbanLog = fetchUnbanlog.entries.first(); + if (!unbanLog) return console.log(`${member.user.tag} was unbanned from ${member.guild.name} but no audit log for this user.`) + const { executor, target, reason } = unbanLog; + if (target.id == member.user.id) (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [ + new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`Member Unbanned: ${target.tag}`).setDescription(`🔹 **User**\n<@${target.id}>\n\`${target.id}\``).addFields( + {name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``}, + {name: '🔹 Reason', value: `${reason == null ? 'Reason unspecified.': reason}`} + )]}); + else console.log(`${target.tag} was unbanned from ${member.guild.name} but no audit log could be fetched.`) + } } diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index 1fac5d8..d43fe9b 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -1,31 +1,29 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, member:Discord.GuildMember){ - if (member.partial || member.guild?.id != client.config.mainServer.id) return; - const index = member.guild.memberCount; - const suffix = ((index)=>{ - const numbers = index.toString().split('').reverse(); // eg 1850 --> [0,5,8,1] - if (numbers[1] === '1'){// this is some -teen - return 'th'; - } else { - if (numbers[0] === '1') return 'st'; - else if (numbers[0] === '2') return 'nd'; - else if (numbers[0] === '3') return 'rd'; - else return 'th'; - } - })(index); - - (client.channels.resolve(client.config.mainServer.channels.welcome) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setThumbnail(member.user.displayAvatarURL({size: 2048}) || member.user.defaultAvatarURL).setTitle(`Welcome to Daggerwin, ${member.user.tag}!`).setFooter({text: `${index}${suffix} member`})]}) - if (!client.config.botSwitches.logs) return; - const newInvites = await member.guild.invites.fetch(); - const usedInvite = newInvites.find((inv:any)=>client.invites.get(inv.code)?.uses < inv.uses); - newInvites.forEach((inv:any)=>client.invites.set(inv.code,{uses: inv.uses, creator: inv.inviter.id})); - - (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [ - new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`Member Joined: ${member.user.tag}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).setFooter({text: `Total members: ${index}${suffix}`}).addFields( - {name: '🔹 Account Creation Date', value: `\n`}, - {name: '🔹 Invite Data:', value: usedInvite ? `Invite: \`${usedInvite.code}\`\nCreated by: **${usedInvite.inviter?.tag}**` : 'No invite data could be found.'} - )]}) + async run(client:TClient, member:Discord.GuildMember){ + if (member.partial || member.guild?.id != client.config.mainServer.id) return; + const index = member.guild.memberCount; + const suffix = ((index)=>{ + const numbers = index.toString().split('').reverse(); // eg 1850 --> [0,5,8,1] + if (numbers[1] === '1') return 'th'; // this is some -teen + else { + if (numbers[0] === '1') return 'st'; + else if (numbers[0] === '2') return 'nd'; + else if (numbers[0] === '3') return 'rd'; + else return 'th'; } + })(index); + (client.channels.resolve(client.config.mainServer.channels.welcome) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setThumbnail(member.user.displayAvatarURL({size: 2048}) || member.user.defaultAvatarURL).setTitle(`Welcome to Daggerwin, ${member.user.tag}!`).setFooter({text: `${index}${suffix} member`})]}) + if (!client.config.botSwitches.logs) return; + const newInvites = await member.guild.invites.fetch(); + const usedInvite = newInvites.find((inv:any)=>client.invites.get(inv.code)?.uses < inv.uses); + newInvites.forEach((inv:any)=>client.invites.set(inv.code,{uses: inv.uses, creator: inv.inviter.id})); + + (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [ + new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`Member Joined: ${member.user.tag}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).setFooter({text: `Total members: ${index}${suffix}`}).addFields( + {name: '🔹 Account Creation Date', value: `\n`}, + {name: '🔹 Invite Data:', value: usedInvite ? `Invite: \`${usedInvite.code}\`\nCreated by: **${usedInvite.inviter?.tag}**` : 'No invite data could be found.'} + )]}) + } } diff --git a/src/events/guildMemberRemove.ts b/src/events/guildMemberRemove.ts index ff0deef..27520c7 100644 --- a/src/events/guildMemberRemove.ts +++ b/src/events/guildMemberRemove.ts @@ -1,17 +1,17 @@ import Discord from 'discord.js'; import TClient from '../client'; 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; - 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: `\n`}, - {name: '🔹 Server Join Date', value: `\n`}, - {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) - } + async run(client:TClient, member:Discord.GuildMember){ + if (!client.config.botSwitches.logs) return; + if (!member.joinedTimestamp || member.guild?.id != client.config.mainServer.id) return; + 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: `\n`}, + {name: '🔹 Server Join Date', value: `\n`}, + {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) + } } diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 6b90acb..d83c183 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -1,23 +1,23 @@ import Discord from 'discord.js'; 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; - if (!client.config.botSwitches.logs) return; - const channel = (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel) - if (oldMember.nickname != newMember.nickname){ - const embed = new client.embed().setColor(client.config.embedColor).setTimestamp().setThumbnail(newMember.user.displayAvatarURL({size: 2048})).setTitle(`Nickname updated: ${newMember.user.tag}`).setDescription(`<@${newMember.user.id}>\n\`${newMember.user.id}\``) - oldMember.nickname == null ? '' : embed.addFields({name: '🔹 Old nickname', value: `\`\`\`${oldMember.nickname}\`\`\``}) - newMember.nickname == null ? '' : embed.addFields({name: '🔹 New nickname', value: `\`\`\`${newMember.nickname}\`\`\``}) - channel.send({embeds: [embed]}) - } - - const newRoles = newMember.roles.cache.map((x,i)=>i).filter(x=>!oldMember.roles.cache.map((x,i)=>i).some(y=>y==x)); - const oldRoles = oldMember.roles.cache.map((x,i)=>i).filter(x=>!newMember.roles.cache.map((x,i)=>i).some(y=>y==x)); - if (newRoles.length == 0 && oldRoles.length == 0) return; - const embed = new client.embed().setColor(client.config.embedColor).setThumbnail(newMember.user.displayAvatarURL({size: 2048})).setTitle(`Role updated: ${newMember.user.tag}`).setDescription(`<@${newMember.user.id}>\n\`${newMember.user.id}\``) - if (newRoles.length != 0) embed.addFields({name: '🔹 Role added', value: newRoles.map((x)=>`<@&${x}>`).join(' ')}); - if (oldRoles.length != 0) embed.addFields({name: '🔹 Role removed', value: oldRoles.map((x)=>`<@&${x}>`).join(' ')}); - channel.send({embeds: [embed]}) + run(client:TClient, oldMember:Discord.GuildMember, newMember:Discord.GuildMember){ + if (oldMember.guild.id != client.config.mainServer.id) return; + if (!client.config.botSwitches.logs) return; + const channel = (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel) + if (oldMember.nickname != newMember.nickname){ + const embed = new client.embed().setColor(client.config.embedColor).setTimestamp().setThumbnail(newMember.user.displayAvatarURL({size: 2048})).setTitle(`Nickname updated: ${newMember.user.tag}`).setDescription(`<@${newMember.user.id}>\n\`${newMember.user.id}\``) + oldMember.nickname == null ? '' : embed.addFields({name: '🔹 Old nickname', value: `\`\`\`${oldMember.nickname}\`\`\``}) + newMember.nickname == null ? '' : embed.addFields({name: '🔹 New nickname', value: `\`\`\`${newMember.nickname}\`\`\``}) + channel.send({embeds: [embed]}) } -} \ No newline at end of file + + const newRoles = newMember.roles.cache.map((x,i)=>i).filter(x=>!oldMember.roles.cache.map((x,i)=>i).some(y=>y==x)); + const oldRoles = oldMember.roles.cache.map((x,i)=>i).filter(x=>!newMember.roles.cache.map((x,i)=>i).some(y=>y==x)); + if (newRoles.length == 0 && oldRoles.length == 0) return; + const embed = new client.embed().setColor(client.config.embedColor).setThumbnail(newMember.user.displayAvatarURL({size: 2048})).setTitle(`Role updated: ${newMember.user.tag}`).setDescription(`<@${newMember.user.id}>\n\`${newMember.user.id}\``) + if (newRoles.length != 0) embed.addFields({name: '🔹 Role added', value: newRoles.map((x)=>`<@&${x}>`).join(' ')}); + if (oldRoles.length != 0) embed.addFields({name: '🔹 Role removed', value: oldRoles.map((x)=>`<@&${x}>`).join(' ')}); + channel.send({embeds: [embed]}) + } +} diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index dbe0611..d2aac06 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,7 +1,7 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, interaction:Discord.BaseInteraction){ + run(client:TClient, interaction:Discord.BaseInteraction){ if (!interaction.inGuild() || !interaction.inCachedGuild()) return; if (interaction.isChatInputCommand()){ const commandFile = client.commands.get(interaction.commandName); @@ -9,13 +9,13 @@ export default { 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{ - commandFile.default.run(client, interaction); - commandFile.uses ? commandFile.uses++ : commandFile.uses = 1; + commandFile.default.run(client, interaction); + commandFile.uses ? commandFile.uses++ : commandFile.uses = 1; } catch (error){ - console.log(`An error occured while running command "${commandFile.name}"`, error, error.stack); - return interaction.reply('An error occured while executing that command.'); + console.log(`An error occured while running command "${commandFile.name}"`, error, error.stack); + return interaction.reply('An error occured while executing that command.'); } } } } -} \ No newline at end of file +} diff --git a/src/events/inviteCreate.ts b/src/events/inviteCreate.ts index 8c7994a..cfc6160 100644 --- a/src/events/inviteCreate.ts +++ b/src/events/inviteCreate.ts @@ -1,9 +1,9 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, invite: Discord.Invite){ - if (!invite.guild) return; - const newInvites = await (invite.guild as Discord.Guild).invites.fetch(); - newInvites.forEach(inv=>client.invites.set(inv.code,{uses: inv.code, creator: inv.inviterId})) - } -} \ No newline at end of file + async run(client:TClient, invite: Discord.Invite){ + if (!invite.guild) return; + const newInvites = await (invite.guild as Discord.Guild).invites.fetch(); + newInvites.forEach(inv=>client.invites.set(inv.code,{uses: inv.code, creator: inv.inviterId})) + } +} diff --git a/src/events/inviteDelete.ts b/src/events/inviteDelete.ts index 9a60276..9216ed7 100644 --- a/src/events/inviteDelete.ts +++ b/src/events/inviteDelete.ts @@ -1,7 +1,7 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, invite: Discord.Invite){ - client.invites.delete(invite.code) - } -} \ No newline at end of file + run(client:TClient, invite: Discord.Invite){ + client.invites.delete(invite.code) + } +} diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 695ebb2..837fd35 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,131 +1,130 @@ import Discord, { ChannelType } from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, message:Discord.Message){ - if (message.author.bot || message.channel.type === ChannelType.DM) return; - const msgarr = message.content.toLowerCase().split(' '); - let automodded: boolean; + async run(client:TClient, message:Discord.Message){ + if (message.author.bot || message.channel.type === ChannelType.DM) return; + const msgarr = message.content.toLowerCase().split(' '); + let automodded: boolean; - function onTimeout(){ - delete client.repeatedMessages[message.author.id] - } - - const Whitelist = [ - // Arrary of channel ids for automod to be disabled in - ] - - 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.')) - message.channel.send('That word is banned here.').then((x)=>setTimeout(()=>x.delete(), 10000)); - if (client.repeatedMessages[message.author.id]){ - // add this message to the list - client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 0, ch: message.channelId}); - - // reset timeout - clearTimeout(client.repeatedMessages[message.author.id].timeout); - client.repeatedMessages[message.author.id].timeout = setTimeout(onTimeout, threshold); - - // message mustve been sent after (now - threshold), so purge those that were sent earlier - client.repeatedMessages[message.author.id].data = client.repeatedMessages[message.author.id].data.filter((x, i)=>i >= Date.now() - threshold) - - // a spammed message is one that has been sent atleast 4 times in the last threshold milliseconds - const spammedMessage = client.repeatedMessages[message.author.id]?.data.find((x)=>{ - return client.repeatedMessages[message.author.id].data.size >= 4; - }); - - // if a spammed message exists; - if (spammedMessage){ - delete client.repeatedMessages[message.author.id]; - await client.punishments.addPunishment('mute', { time: '30m' }, (client.user as Discord.User).id, 'Automod; Banned words', message.author, message.member as Discord.GuildMember); - } - } else { - client.repeatedMessages[message.author.id] = { data: new client.collection(), timeout: setTimeout(onTimeout, threshold) }; - client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 0, ch: message.channelId}); - } - } - if (message.content.toLowerCase().includes('discord.gg/') && !message.member.roles.cache.has(client.config.mainServer.roles.dcmod) && message.guildId == client.config.mainServer.id && !Whitelist.includes(message.channelId)) { - automodded = true; - const threshold = 60000; - message.delete().catch(err=>console.log('advertisement automod; msg got possibly deleted by another bot.')) - message.channel.send('Advertising other Discord servers is not allowed.').then(x=>setTimeout(()=>x.delete(), 15000)) - if (client.repeatedMessages[message.author.id]){ - client.repeatedMessages[message.author.id].data.set(message.createdTimestamp,{cont:1,ch:message.channelId}); - - clearTimeout(client.repeatedMessages[message.author.id].timeout); - client.repeatedMessages[message.author.id].timeout = setTimeout(onTimeout, threshold); - client.repeatedMessages[message.author.id].data = client.repeatedMessages[message.author.id].data.filter((x, i)=> i >= Date.now() - threshold) - const spammedMessage = client.repeatedMessages[message.author.id].data.find((x)=>{ - return client.repeatedMessages[message.author.id].data.filter((y)=>x.cont === y.cont).size >= 4; - }); - - if (spammedMessage){ - delete client.repeatedMessages[message.author.id]; - await client.punishments.addPunishment('mute', {time: '1h'}, (client.user as Discord.User).id, 'Automod; Discord advertisement', message.author, message.member as Discord.GuildMember); - } - }else{ - client.repeatedMessages[message.author.id] = { data: new client.collection(), timeout: setTimeout(onTimeout, threshold) }; - client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 1, ch: message.channelId}); - } - } - - if (message.guildId == client.config.mainServer.id && !automodded) client.userLevels.incrementUser(message.author.id) - // Mop gifs from banned channels without Monster having to mop them. - const bannedChannels = [ - '516344221452599306', // #mp-moderators - '742324777934520350', // #discord-moderators - ] - const gifURL = ['tenor.com/view', 'giphy.com/gifs', 'giphy.com/media'] - if (gifURL.some(e=>message.content.toLowerCase().includes(e)) && bannedChannels.includes(message.channelId)) message.reply('Gifs are not allowed in this channel.').then((msg)=>message.delete()) - - // Autoresponse:tm: - if (client.config.botSwitches.autores && !automodded) { - const MorningArray = ['good morning all', 'good morning everyone', 'morning all', 'morning everyone', 'morning lads', 'morning guys', 'good morning everybody'] - const AfternoonArray = ['good afternoon', 'afternoon all', 'afternoon everyone'] - const EveningArray = ['good evening', 'evening all', 'evening everyone'] - const NightArray = ['night all', 'night everyone', 'night guys'] - const PasswordArray = ['whats the password', 'what\'s the password', 'password pls'] - const cantRead = ['i cant read', 'i can\'t read', 'cant read', 'can\'t read'] - const NawdicBrokeIt = ['break', 'broke', 'broken'] - const deadChat = ['dead chat', 'chat is dead', 'dead server'] - - const PersonnyMcPerson = `**${message.member.displayName}**`; - const GeneralChatID = '468835415093411863'; - const MorningPhrases = [ - `Morning ${PersonnyMcPerson}, did you sleep great?`, `Good morning ${PersonnyMcPerson}!`, `Hope you enjoyed your breakfast, ${PersonnyMcPerson}!`, - `Gm ${PersonnyMcPerson}`, `Uh.. What time is it? Oh yea, morning ${PersonnyMcPerson}`, `Morning and hope you had a good dream last night, ${PersonnyMcPerson}`, - 'Time to get started with today\'s stuff!', `Don't forget to do your morning routine, ${PersonnyMcPerson}!`, 'Enjoy the breakfast and start your day.', - 'Nuh! No morning message for you!\n*Just kidding, good morning!*' - ] - const AfternoonPhrases = [ - `Afternoon ${PersonnyMcPerson}!`, `What a nice day outside, ${PersonnyMcPerson}`, `Good afternoon ${PersonnyMcPerson}`, - 'Hope you had a good day so far.', `Did you enjoy your day yet, ${PersonnyMcPerson}?`, 'Weather doesn\'t look too bad outside right?', - `How's the trip outside, ${PersonnyMcPerson}?`, `~~Morning~~ Afternoon ${PersonnyMcPerson}!` - ] - const EveningPhrases = [ - 'I can\'t believe the time flies so quickly!', `Evening ${PersonnyMcPerson}!`, `Hope you enjoyed the dinner, ${PersonnyMcPerson}!`, - `Good evening ${PersonnyMcPerson}!`, 'You look tired, ready to go to sleep yet?', 'Being outside was an exhausting task isn\'t it?', - 'Did you have a good day so far?', 'May I suggest sleep?', `You heard me! ${PersonnyMcPerson}, it's almost dinner time!` - ] - const NightPhrases = [ - `Good night ${PersonnyMcPerson}!`, `Night ${PersonnyMcPerson}!`, `Sweet dreams, ${PersonnyMcPerson}.`, `Don't fall out of sky in your dreamworld, ${PersonnyMcPerson}!`, - 'Nighty night!', `I hope tomorrow is a good day for you, ${PersonnyMcPerson}!`, `Have a good sleep, ${PersonnyMcPerson}!`, `I :b:et you a cookie if you actually slept through the night! ${PersonnyMcPerson}` - ] - - if (message.mentions.members.has('309373272594579456') && !client.isStaff(message.member)) message.reply('Please don\'t tag Daggerwin, read rule 14 in <#468846117405196289>'); - if (message.mentions.members.has('215497515934416896') && !client.isStaff(message.member) && message.type != 19) message.reply('Please don\'t tag Monster unless it\'s important!'); - if (PasswordArray.some(e=>msgarr.includes(e))) message.reply('Password and other details can be found in <#543494084363288637>'); - if (cantRead.some(e=>msgarr.includes(e))) message.reply('https://tenor.com/view/aristocats-george-pen-cap-meticulous-gif-5330931'); - if (msgarr.includes('is daggerbot working')) message.reply('https://tenor.com/view/i-still-feel-alive-living-existing-active-singing-gif-14630579'); - if (deadChat.some(e=>msgarr.includes(e))) message.reply('https://cdn.discordapp.com/attachments/925589318276382720/1011333656167579849/F57G5ZS.png'); - if (msgarr.includes('nawdic') && NawdicBrokeIt.some(e=>msgarr.includes(e)) && message.channelId !== '516344221452599306') message.reply({embeds: [new client.embed().setTitle('*Nawdic has done an oopsie*').setImage('https://c.tenor.com/JSj9ie_MD9kAAAAC/kopfsch%C3%BCtteln-an-kopf-fassen-oh-no.gif').setColor(client.config.embedColor)]}); - if (MorningArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${MorningPhrases[Math.floor(Math.random()*MorningPhrases.length)]}`); - if (AfternoonArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${AfternoonPhrases[Math.floor(Math.random()*AfternoonPhrases.length)]}`); - if (EveningArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${EveningPhrases[Math.floor(Math.random()*EveningPhrases.length)]}`); - if (NightArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${NightPhrases[Math.floor(Math.random()*NightPhrases.length)]}`); - // Failsafe thingy (Toastproof maybe) - if (message.content.startsWith('!!_wepanikfrfr') && client.config.eval.whitelist.includes(message.author.id)) (client.guilds.cache.get(message.guildId) as Discord.Guild).commands.set(client.registry).then(()=>message.reply('How did you manage to lose the commands??? Anyways, it\'s re-registered now.')).catch((e:Error)=>message.reply(`Failed to deploy slash commands:\n\`\`\`${e.message}\`\`\``)); - } + function onTimeout(){ + delete client.repeatedMessages[message.author.id] } + + const Whitelist = [] // Array of channel ids for automod to be disabled in (Disables bannedWords and advertisement, mind you.) + + 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.')) + message.channel.send('That word is banned here.').then((x)=>setTimeout(()=>x.delete(), 10000)); + if (client.repeatedMessages[message.author.id]){ + // add this message to the list + client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 0, ch: message.channelId}); + + // reset timeout + clearTimeout(client.repeatedMessages[message.author.id].timeout); + client.repeatedMessages[message.author.id].timeout = setTimeout(onTimeout, threshold); + + // message mustve been sent after (now - threshold), so purge those that were sent earlier + client.repeatedMessages[message.author.id].data = client.repeatedMessages[message.author.id].data.filter((x, i)=>i >= Date.now() - threshold) + + // a spammed message is one that has been sent atleast 4 times in the last threshold milliseconds + const spammedMessage = client.repeatedMessages[message.author.id]?.data.find((x)=>{ + return client.repeatedMessages[message.author.id].data.size >= 4; + }); + + // if a spammed message exists; + if (spammedMessage){ + delete client.repeatedMessages[message.author.id]; + await client.punishments.addPunishment('mute', { time: '30m' }, (client.user as Discord.User).id, 'Automod; Banned words', message.author, message.member as Discord.GuildMember); + } + } else { + client.repeatedMessages[message.author.id] = { data: new client.collection(), timeout: setTimeout(onTimeout, threshold) }; + client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 0, ch: message.channelId}); + } + } + + if (message.content.toLowerCase().includes('discord.gg/') && !message.member.roles.cache.has(client.config.mainServer.roles.dcmod) && message.guildId == client.config.mainServer.id && !Whitelist.includes(message.channelId)) { + automodded = true; + const threshold = 60000; + message.delete().catch(err=>console.log('advertisement automod; msg got possibly deleted by another bot.')) + message.channel.send('Advertising other Discord servers is not allowed.').then(x=>setTimeout(()=>x.delete(), 15000)) + if (client.repeatedMessages[message.author.id]){ + client.repeatedMessages[message.author.id].data.set(message.createdTimestamp,{cont:1,ch:message.channelId}); + + clearTimeout(client.repeatedMessages[message.author.id].timeout); + client.repeatedMessages[message.author.id].timeout = setTimeout(onTimeout, threshold); + client.repeatedMessages[message.author.id].data = client.repeatedMessages[message.author.id].data.filter((x, i)=> i >= Date.now() - threshold) + const spammedMessage = client.repeatedMessages[message.author.id].data.find((x)=>{ + return client.repeatedMessages[message.author.id].data.filter((y)=>x.cont === y.cont).size >= 4; + }); + + if (spammedMessage){ + delete client.repeatedMessages[message.author.id]; + await client.punishments.addPunishment('mute', {time: '1h'}, (client.user as Discord.User).id, 'Automod; Discord advertisement', message.author, message.member as Discord.GuildMember); + } + }else{ + client.repeatedMessages[message.author.id] = { data: new client.collection(), timeout: setTimeout(onTimeout, threshold) }; + client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {cont: 1, ch: message.channelId}); + } + } + + if (message.guildId == client.config.mainServer.id && !automodded) client.userLevels.incrementUser(message.author.id) + // Mop gifs from banned channels without Monster having to mop them. + const bannedChannels = [ + '516344221452599306', // #mp-moderators + '742324777934520350', // #discord-moderators + ] + const gifURL = ['tenor.com/view', 'giphy.com/gifs', 'giphy.com/media'] + if (gifURL.some(e=>message.content.toLowerCase().includes(e)) && bannedChannels.includes(message.channelId)) message.reply('Gifs are not allowed in this channel.').then((msg)=>message.delete()) + + // Autoresponse:tm: + if (client.config.botSwitches.autores && !automodded) { + const MorningArray = ['good morning all', 'good morning everyone', 'morning all', 'morning everyone', 'morning lads', 'morning guys', 'good morning everybody'] + const AfternoonArray = ['good afternoon', 'afternoon all', 'afternoon everyone'] + const EveningArray = ['good evening', 'evening all', 'evening everyone'] + const NightArray = ['night all', 'night everyone', 'night guys'] + const PasswordArray = ['whats the password', 'what\'s the password', 'password pls'] + const cantRead = ['i cant read', 'i can\'t read', 'cant read', 'can\'t read'] + const NawdicBrokeIt = ['break', 'broke', 'broken'] + const deadChat = ['dead chat', 'chat is dead', 'dead server'] + + const PersonnyMcPerson = `**${message.member.displayName}**`; + const GeneralChatID = '468835415093411863'; + const MorningPhrases = [ + `Morning ${PersonnyMcPerson}, did you sleep great?`, `Good morning ${PersonnyMcPerson}!`, `Hope you enjoyed your breakfast, ${PersonnyMcPerson}!`, + `Gm ${PersonnyMcPerson}`, `Uh.. What time is it? Oh yea, morning ${PersonnyMcPerson}`, `Morning and hope you had a good dream last night, ${PersonnyMcPerson}`, + 'Time to get started with today\'s stuff!', `Don't forget to do your morning routine, ${PersonnyMcPerson}!`, 'Enjoy the breakfast and start your day.', + 'Nuh! No morning message for you!\n*Just kidding, good morning!*' + ] + const AfternoonPhrases = [ + `Afternoon ${PersonnyMcPerson}!`, `What a nice day outside, ${PersonnyMcPerson}`, `Good afternoon ${PersonnyMcPerson}`, + 'Hope you had a good day so far.', `Did you enjoy your day yet, ${PersonnyMcPerson}?`, 'Weather doesn\'t look too bad outside right?', + `How's the trip outside, ${PersonnyMcPerson}?`, `~~Morning~~ Afternoon ${PersonnyMcPerson}!` + ] + const EveningPhrases = [ + 'I can\'t believe the time flies so quickly!', `Evening ${PersonnyMcPerson}!`, `Hope you enjoyed the dinner, ${PersonnyMcPerson}!`, + `Good evening ${PersonnyMcPerson}!`, 'You look tired, ready to go to sleep yet?', 'Being outside was an exhausting task isn\'t it?', + 'Did you have a good day so far?', 'May I suggest sleep?', `You heard me! ${PersonnyMcPerson}, it's almost dinner time!` + ] + const NightPhrases = [ + `Good night ${PersonnyMcPerson}!`, `Night ${PersonnyMcPerson}!`, `Sweet dreams, ${PersonnyMcPerson}.`, `Don't fall out of sky in your dreamworld, ${PersonnyMcPerson}!`, + 'Nighty night!', `I hope tomorrow is a good day for you, ${PersonnyMcPerson}!`, `Have a good sleep, ${PersonnyMcPerson}!`, `I :b:et you a cookie if you actually slept through the night! ${PersonnyMcPerson}` + ] + + if (message.mentions.members.has('309373272594579456') && !client.isStaff(message.member)) message.reply('Please don\'t tag Daggerwin, read rule 14 in <#468846117405196289>'); + if (message.mentions.members.has('215497515934416896') && !client.isStaff(message.member) && message.type != 19) message.reply('Please don\'t tag Monster unless it\'s important!'); + if (PasswordArray.some(e=>msgarr.includes(e))) message.reply('Password and other details can be found in <#543494084363288637>'); + if (cantRead.some(e=>msgarr.includes(e))) message.reply('https://tenor.com/view/aristocats-george-pen-cap-meticulous-gif-5330931'); + if (msgarr.includes('is daggerbot working')) message.reply('https://tenor.com/view/i-still-feel-alive-living-existing-active-singing-gif-14630579'); + if (deadChat.some(e=>msgarr.includes(e))) message.reply('https://cdn.discordapp.com/attachments/925589318276382720/1011333656167579849/F57G5ZS.png'); + if (msgarr.includes('nawdic') && NawdicBrokeIt.some(e=>msgarr.includes(e)) && message.channelId !== '516344221452599306') message.reply({embeds: [new client.embed().setTitle('*Nawdic has done an oopsie*').setImage('https://c.tenor.com/JSj9ie_MD9kAAAAC/kopfsch%C3%BCtteln-an-kopf-fassen-oh-no.gif').setColor(client.config.embedColor)]}); + if (MorningArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${MorningPhrases[Math.floor(Math.random()*MorningPhrases.length)]}`); + if (AfternoonArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${AfternoonPhrases[Math.floor(Math.random()*AfternoonPhrases.length)]}`); + if (EveningArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${EveningPhrases[Math.floor(Math.random()*EveningPhrases.length)]}`); + if (NightArray.some(e=>message.content.toLowerCase().startsWith(e)) && message.channelId == GeneralChatID && message.type == 0) message.reply(`${NightPhrases[Math.floor(Math.random()*NightPhrases.length)]}`); + // Failsafe thingy (Toastproof maybe) + if (message.content.startsWith('!!_wepanikfrfr') && client.config.eval.whitelist.includes(message.author.id)) (client.guilds.cache.get(message.guildId) as Discord.Guild).commands.set(client.registry).then(()=>message.reply('How did you manage to lose the commands??? Anyways, it\'s re-registered now.')).catch((e:Error)=>message.reply(`Failed to deploy slash commands:\n\`\`\`${e.message}\`\`\``)); + } + } } diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 5fe9186..7e96aef 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -1,19 +1,19 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, msg:Discord.Message){ - if (!client.config.botSwitches.logs) return; - const channel = client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel; - const disabledChannels = ['548032776830582794', '541677709487505408'] - if (msg.guild?.id != client.config.mainServer.id || msg.partial || msg.author.bot || disabledChannels.includes(msg.channelId)) return; - const embed = new client.embed().setColor(client.config.embedColorRed).setTimestamp().setAuthor({name: `Author: ${msg.author.tag} (${msg.author.id})`, iconURL: `${msg.author.displayAvatarURL()}`}).setTitle('Message deleted').setDescription(`<@${msg.author.id}>\n\`${msg.author.id}\``); - if (msg.content.length != 0) embed.addFields({name: 'Content', value: `\`\`\`\n${msg.content.slice(0,1000)}\n\`\`\``}); - embed.addFields( - { name: 'Channel', value: `<#${msg.channelId}>` }, - { name: 'Sent at', value: `\n` } - ) - const attachments: Array = []; - msg.attachments.forEach((x) => attachments.push(x.url)); - channel.send({embeds: [embed], files: attachments}) - } + run(client:TClient, msg:Discord.Message){ + if (!client.config.botSwitches.logs) return; + const channel = client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel; + const disabledChannels = [ '548032776830582794', '541677709487505408'] + if (msg.guild?.id != client.config.mainServer.id || msg.partial || msg.author.bot || disabledChannels.includes(msg.channelId)) return; + const embed = new client.embed().setColor(client.config.embedColorRed).setTimestamp().setAuthor({name: `Author: ${msg.author.tag} (${msg.author.id})`, iconURL: `${msg.author.displayAvatarURL()}`}).setTitle('Message deleted').setDescription(`<@${msg.author.id}>\n\`${msg.author.id}\``); + if (msg.content.length != 0) embed.addFields({name: 'Content', value: `\`\`\`\n${msg.content.slice(0,1000)}\n\`\`\``}); + embed.addFields( + { name: 'Channel', value: `<#${msg.channelId}>` }, + { name: 'Sent at', value: `\n` } + ) + const attachments: Array = []; + msg.attachments.forEach((x) => attachments.push(x.url)); + channel.send({embeds: [embed], files: attachments}) + } } diff --git a/src/events/messageDeleteBulk.ts b/src/events/messageDeleteBulk.ts index b917f4e..8bc7770 100644 --- a/src/events/messageDeleteBulk.ts +++ b/src/events/messageDeleteBulk.ts @@ -1,10 +1,14 @@ import Discord from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, messages:Discord.Collection>){ - if (!client.config.botSwitches.logs) return; - if (client.config.mainServer.id != '468835415093411861') return; - const channel = client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel; - channel.send({embeds: [new client.embed().setColor(client.config.embedColorRed).setTimestamp().setTitle(`${messages.size} messages were purged`).setDescription(`\`\`\`${messages.map((msgs)=>`${msgs.member?.displayName}: ${msgs.content}`).reverse().join('\n').slice(0,3900)}\`\`\``).addFields({name: 'Channel', value: `<#${messages.first().channel.id}>`})]}) - } -} \ No newline at end of file + run(client:TClient, messages:Discord.Collection>){ + if (!client.config.botSwitches.logs) return; + if (client.config.mainServer.id != '468835415093411861') return; + const channel = client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel; + channel.send({embeds: [new client.embed().setColor(client.config.embedColorRed).setTimestamp().setTitle(`${messages.size} messages were purged`) + .setDescription(`\`\`\`${messages.map((msgs)=>`${msgs.member?.displayName}: ${msgs.content}`).reverse().join('\n').slice(0,3900)}\`\`\``).addFields( + {name: 'Channel', value: `<#${messages.first().channel.id}>`} + )] + }) + } +} diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts index 8593d70..da5be7d 100644 --- a/src/events/messageUpdate.ts +++ b/src/events/messageUpdate.ts @@ -1,13 +1,13 @@ import Discord, { ActionRowBuilder, ButtonBuilder } from 'discord.js'; import TClient from '../client'; export default { - async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){ - if (!client.config.botSwitches.logs) return; - 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 (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().addComponents(new ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]}); - } + async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){ + if (!client.config.botSwitches.logs) return; + 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 (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.length < 1 ? '(Attachment)' : oldMsg.content}\n\`\`\`\nNew content:\n\`\`\`\n${newMsg.content}\`\`\`\nChannel: <#${oldMsg.channelId}>`)], components: [new ActionRowBuilder().addComponents(new ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]}); + } } diff --git a/src/index.ts b/src/index.ts index 8872ffb..970b454 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,14 +12,15 @@ client.on('ready', async()=>{ setInterval(()=>guild.invites.fetch().then(invites=>invites.forEach(inv=>client.invites.set(inv.code, {uses: inv.uses, creator: inv.inviterId}))),300000) }); if (client.config.botSwitches.registerCommands){ - client.config.whitelistedServers.forEach((guildId)=>(client.guilds.cache.get(guildId) as Discord.Guild).commands.set(client.registry).catch((e:Error)=>{ - console.log(`Couldn't register slash commands for ${guildId} because`, e.stack); - (client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send(`Cannot register slash commands for **${client.guilds.cache.get(guildId).name}** (\`${guildId}\`):\n\`\`\`${e.message}\`\`\``) - })) + client.config.whitelistedServers.forEach((guildId)=>(client.guilds.cache.get(guildId) as Discord.Guild).commands.set(client.registry).catch((e:Error)=>{ + console.log(`Couldn't register slash commands for ${guildId} because`, e.stack); + (client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send(`Cannot register slash commands for **${client.guilds.cache.get(guildId).name}** (\`${guildId}\`):\n\`\`\`${e.message}\`\`\``) + })) }; console.log(`${client.user.tag} has logged into Discord API`); console.log(client.config.botSwitches, client.config.whitelistedServers); (client.channels.resolve(client.config.mainServer.channels.bot_status) as Discord.TextChannel).send(`${client.user.username} is active\n\`\`\`json\n${Object.entries(client.config.botSwitches).map((hi)=>`${hi[0]}: ${hi[1]}`).join('\n')}\`\`\``); + console.timeEnd('Startup') }) // Handle errors @@ -49,80 +50,78 @@ setInterval(async()=>{ const verifyURL = MPURL.match(/http|https/); const completedURL_DSS = MPURL+'/feed/dedicated-server-stats.json?code='+MPCode; const completedURL_CSG = MPURL+'/feed/dedicated-server-savegame.html?code='+MPCode+'&file=careerSavegame'; - const FSdss = { - data: {} as FSData, - fetchResult: '' as string - }; - const FScsg = { - data: {} as FSCareerSavegame, - fetchResult: '' as string - }; - if (!verifyURL) return msg.edit({content: '*Detected an invalid IP.*', embeds: null}) - async function serverData(client:TClient, URL: string){ - return await client.axios.get(URL, {timeout: 4000, maxContentLength: Infinity, headers: {'User-Agent': `Daggerbot/axios ${client.axios.VERSION}`}}).catch((error:Error)=>error.message) - } - await Promise.all([serverData(client, completedURL_DSS), serverData(client, completedURL_CSG)]).then(function(results){ - if (typeof results[0] == 'string'){ - FSdss.fetchResult = `DagMP DSS failed, ${results[0]}`; - embed.addFields({name: 'DSS Status', value: results[0]}) - } else if (results[0].status != 200){ - FSdss.fetchResult = `DagMP DSS failed with ${results[0].status + ' ' + results[0].statusText}`; - embed.addFields({name: 'DSS Status', value: results[0].status + ' ' + results[0].statusText}) - } else FSdss.data = results[0].data as FSData + const FSdss = { + data: {} as FSData, + fetchResult: '' as string + }; + const FScsg = { + data: {} as FSCareerSavegame, + fetchResult: '' as string + }; + if (!verifyURL) return msg.edit({content: '*Detected an invalid IP.*', embeds: null}) + async function serverData(client:TClient, URL: string){ + return await client.axios.get(URL, {timeout: 4000, maxContentLength: Infinity, headers: {'User-Agent': `Daggerbot/axios ${client.axios.VERSION}`}}).catch((error:Error)=>error.message) + } + await Promise.all([serverData(client, completedURL_DSS), serverData(client, completedURL_CSG)]).then(function(results){ + if (typeof results[0] == 'string'){ + FSdss.fetchResult = `DagMP DSS failed, ${results[0]}`; + embed.addFields({name: 'DSS Status', value: results[0]}) + } else if (results[0].status != 200){ + FSdss.fetchResult = `DagMP DSS failed with ${results[0].status + ' ' + results[0].statusText}`; + embed.addFields({name: 'DSS Status', value: results[0].status + ' ' + results[0].statusText}) + } else FSdss.data = results[0].data as FSData - if (typeof results[1] == 'string'){ - FScsg.fetchResult = `DagMP CSG failed, ${results[1]}`; - embed.addFields({name: 'CSG Status', value: results[1]}) - } else if (results[1].status != 200){ - if (results[1].status == 204) embed.setImage('https://http.cat/204'); - FScsg.fetchResult = `DagMP CSG failed with ${results[1].status + ' ' + results[1].statusText}`; - embed.addFields({name: 'CSG Status', value: results[1].status + ' ' + results[1].statusText}) - } else FScsg.data = client.xjs.xml2js(results[1].data,{compact:true,spaces:2}).careerSavegame as FSCareerSavegame; - }).catch((error)=>console.log(error)) - if (FSdss.fetchResult.length != 0){ - error = true; - console.log(client.logTime(), FSdss.fetchResult); - } - if (FScsg.fetchResult.length != 0){ - error = true; - console.log(client.logTime(), FScsg.fetchResult); - } - if (error) { // Blame RedRover and Nawdic - embed.setTitle('Host is not responding').setColor(client.config.embedColorRed); - msg.edit({content: null, embeds: [embed]}) - return; - } + if (typeof results[1] == 'string'){ + FScsg.fetchResult = `DagMP CSG failed, ${results[1]}`; + embed.addFields({name: 'CSG Status', value: results[1]}) + } else if (results[1].status != 200){ + if (results[1].status == 204) embed.setImage('https://http.cat/204'); + FScsg.fetchResult = `DagMP CSG failed with ${results[1].status + ' ' + results[1].statusText}`; + embed.addFields({name: 'CSG Status', value: results[1].status + ' ' + results[1].statusText}) + } else FScsg.data = client.xjs.xml2js(results[1].data,{compact:true,spaces:2}).careerSavegame as FSCareerSavegame; + }).catch((error)=>console.log(error)) + if (FSdss.fetchResult.length != 0){ + error = true; + console.log(client.logTime(), FSdss.fetchResult); + } + if (FScsg.fetchResult.length != 0){ + error = true; + console.log(client.logTime(), FScsg.fetchResult); + } + if (error) { // Blame RedRover and Nawdic + embed.setTitle('Host is not responding').setColor(client.config.embedColorRed); + msg.edit({content: null, embeds: [embed]}) + return; + } - const DB = JSON.parse(fs.readFileSync(__dirname + '/database/MPPlayerData.json', {encoding: 'utf8'})); - DB.push(FSdss.data.slots.used) - fs.writeFileSync(__dirname + '/database/MPPlayerData.json', JSON.stringify(DB)) - - // Number format function - function formatNumber(number: any, digits: any, icon: any){ - var n = Number(number) - return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon - } // Temporary workaround for fresh save. - const slotSystem = isNaN(Number(FScsg.data.slotSystem?._attributes.slotUsage)) == true ? 'Unavailable' : Number(FScsg.data.slotSystem?._attributes.slotUsage).toLocaleString('en-US'); - const timeScale = isNaN(Number(FScsg.data.settings?.timeScale._text)) == true ? 'Unavailable' : formatNumber(Number(FScsg.data.settings?.timeScale._text), 0, 'x'); + const DB = JSON.parse(fs.readFileSync(__dirname + '/database/MPPlayerData.json', {encoding: 'utf8'})); + DB.push(FSdss.data.slots.used) + fs.writeFileSync(__dirname + '/database/MPPlayerData.json', JSON.stringify(DB)) - if (FSdss.data.server.name.length == 0){ - embed.setTitle('The server seems to be offline.').setColor(client.config.embedColorRed); - msg.edit({content: 'This embed will resume when server is back online.', embeds: [embed]}) - } else { - const embed1 = new client.embed().setColor(client.config.embedColor).setTitle('Server details').addFields( - {name: 'Current Map', value: `${FSdss.data.server.mapName.length == 0 ? '\u200b' : FSdss.data.server.mapName}`, inline: true}, - {name: 'Version', value: `${FSdss.data.server.version.length == 0 ? '\u200b' : FSdss.data.server.version}`, inline: true}, - {name: 'In-game Time', value: `${('0' + Math.floor((FSdss.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSdss.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, - {name: 'Slot Usage', value: `${slotSystem}`, inline: true}, - {name: 'Timescale', value: `${timeScale}`, inline: true} - ); - FSdss.data.slots.players.filter((x)=>x.isUsed !== false).forEach(player=>{ - Players.push(`**${player.name} ${player.isAdmin ? '| admin' : ''}**\nFarming for ${(Math.floor(player.uptime/60))} hr & ${('' + (player.uptime % 60)).slice(-2)} min`) - }) - embed.setDescription(`${FSdss.data.slots.used == 0 ? '*No players online*' : Players.join('\n\n')}`).setTitle(FSdss.data.server.name).setColor(client.config.embedColor) - embed.setAuthor({name: `${FSdss.data.slots.used}/${FSdss.data.slots.capacity}`}); - msg.edit({content: 'This embed updates every minute.', embeds: [embed1, embed]}) - } + // Number format function + function formatNumber(number: any, digits: any, icon: any){ + var n = Number(number) + return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon + } // Temporary workaround for fresh save. + const slotSystem = isNaN(Number(FScsg.data.slotSystem?._attributes.slotUsage)) == true ? 'Unavailable' : Number(FScsg.data.slotSystem?._attributes.slotUsage).toLocaleString('en-US'); + const timeScale = isNaN(Number(FScsg.data.settings?.timeScale._text)) == true ? 'Unavailable' : formatNumber(Number(FScsg.data.settings?.timeScale._text), 0, 'x'); + + if (FSdss.data.server.name.length == 0){ + embed.setTitle('The server seems to be offline.').setColor(client.config.embedColorRed); + msg.edit({content: 'This embed will resume when server is back online.', embeds: [embed]}) + } else { + const embed1 = new client.embed().setColor(client.config.embedColor).setTitle('Server details').addFields( + {name: 'Current Map', value: `${FSdss.data.server.mapName.length == 0 ? '\u200b' : FSdss.data.server.mapName}`, inline: true}, + {name: 'Version', value: `${FSdss.data.server.version.length == 0 ? '\u200b' : FSdss.data.server.version}`, inline: true}, + {name: 'In-game Time', value: `${('0' + Math.floor((FSdss.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSdss.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, + {name: 'Slot Usage', value: `${slotSystem}`, inline: true}, + {name: 'Timescale', value: `${timeScale}`, inline: true} + ); + FSdss.data.slots.players.filter((x)=>x.isUsed !== false).forEach(player=>Players.push(`**${player.name} ${player.isAdmin ? '| admin' : ''}**\nFarming for ${(Math.floor(player.uptime/60))} hr & ${('' + (player.uptime % 60)).slice(-2)} min`)) + embed.setDescription(`${FSdss.data.slots.used == 0 ? '*No players online*' : Players.join('\n\n')}`).setTitle(FSdss.data.server.name).setColor(client.config.embedColor) + embed.setAuthor({name: `${FSdss.data.slots.used}/${FSdss.data.slots.capacity}`}); + msg.edit({content: 'This embed updates every minute.', embeds: [embed1, embed]}) + } }, 60000) // YouTube Upload notification @@ -133,27 +132,25 @@ setInterval(async()=>{ // Event loop for punishments and daily msgs setInterval(async()=>{ - const now = Date.now(); - const lrsStart = client.config.LRSstart; + const now = Date.now(); + const lrsStart = client.config.LRSstart; - 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 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)=>x[0] === formattedDate)){ - let total = (await client.userLevels._content.find({})).reduce((a,b)=>a + b.messages, 0); // sum of all users - const yesterday = dailyMsgs.find((x:Array)=>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.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 x.name == 'rank').id}>`)) - } + 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)=>x[0] === formattedDate)){ + let total = (await client.userLevels._content.find({})).reduce((a,b)=>a + b.messages, 0); // sum of all users + const yesterday = dailyMsgs.find((x:Array)=>x[0] === formattedDate - 1); + if (total < yesterday) total = yesterday // messages went down. + dailyMsgs.push([formattedDate, total]); + fs.writeFileSync(__dirname + '/database/dailyMsgs.json', JSON.stringify(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 x.name == 'rank').id}>`)) + } }, 5000) diff --git a/src/typings/interfaces.d.ts b/src/typings/interfaces.d.ts index 6ea1f73..a8dfceb 100644 --- a/src/typings/interfaces.d.ts +++ b/src/typings/interfaces.d.ts @@ -1,187 +1,187 @@ import Discord from 'discord.js'; export interface UserLevels { - messages: number, - level: number + messages: number, + level: number } export interface formatTimeOpt { - longNames: boolean, - commas: boolean + longNames: boolean, + commas: boolean } export interface punOpt { - time?: string, - reason?: string, - interaction?: Discord.ChatInputCommandInteraction<"cached"> + time?: string, + reason?: string, + interaction?: Discord.ChatInputCommandInteraction<"cached"> } export interface repeatedMessages { [key:string]: {data: Discord.Collection, timeout: NodeJS.Timeout} } export interface Punishment { - _id: number; - type: string; - member: string; - moderator: string; - expired?: boolean; - time: number; - reason: string; - endTime?: number; - cancels?: number; - duration?: number; + _id: number; + type: string; + member: string; + moderator: string; + expired?: boolean; + time: number; + reason: string; + endTime?: number; + cancels?: number; + duration?: number; } export interface DSS_serverName { - data: FSData + data: FSData } export interface FSData { - server: FSServer, - slots: FSslots + server: FSServer, + slots: FSslots } export interface FSServer { - dayTime: number, - game: string, - mapName: string, - mapSize: number, - mapOverviewFilename: string, - money: number, - name: string, - server: string, - version: string + dayTime: number, + game: string, + mapName: string, + mapSize: number, + mapOverviewFilename: string, + money: number, + name: string, + server: string, + version: string } export interface FSslots { - capacity: number, - used: number, - players: Array + capacity: number, + used: number, + players: Array } export interface FSPlayers { - isUsed: boolean, - isAdmin: boolean, - uptime: number, - name: string + isUsed: boolean, + isAdmin: boolean, + uptime: number, + name: string } export interface FSCareerSavegame { - settings: FSCareerSavegameSettings, - statistics: FSCareerSavegameStatistics, - slotSystem: FSCareerSavegameSlotSystem + settings: FSCareerSavegameSettings, + statistics: FSCareerSavegameStatistics, + slotSystem: FSCareerSavegameSlotSystem } export interface FSCareerSavegameSettings { - savegameName: XMLText, - creationDate: XMLText, - mapId: XMLText, - mapTitle: XMLText, - saveDataFormatted: XMLText, - saveDate: XMLText, - resetVehicles: XMLText, - trafficeEnabled: XMLText, - stopAndGoBraking: XMLText, - trailerFillLimit: XMLText, - automaticMotorStartEnabled: XMLText, - growthMode: XMLText, - fixedSeasonalVisuals: XMLText, - plannedDaysPerPeriod: XMLText, - fruitDestruction: XMLText, - plowingRequiredEnabled: XMLText, - stonesEnabled: XMLText, - weedsEnabled: XMLText, - limeRequired: XMLText, - isSnowEnabled: XMLText, - fuelUsage: XMLText, - helperBuyFuel: XMLText, - helperBuySeeds: XMLText, - helperSlurrySource: XMLText, - helperManureSource: XMLText, - densityMapRevision: XMLText, - terrainTextureRevision: XMLText, - terrainLodTextureRevision: XMLText, - splitShapesRevision: XMLText, - tipCollisionRevision: XMLText, - placementCollisionRevision: XMLText, - navigationCollisionRevision: XMLText, - mapDensityMapRevision: XMLText, - mapTerrainTextureRevision: XMLText, - mapTerrainLodTextureRevision: XMLText, - mapSplitShapesRevision: XMLText, - mapTipCollisionRevision: XMLText, - mapPlacementCollisionRevision: XMLText, - mapNavigationCollisionRevision: XMLText, - difficulty: XMLText, - economicDifficulty: XMLText, - dirtInterval: XMLText, - timeScale: XMLText, - autoSaveInterval: XMLText + savegameName: XMLText, + creationDate: XMLText, + mapId: XMLText, + mapTitle: XMLText, + saveDataFormatted: XMLText, + saveDate: XMLText, + resetVehicles: XMLText, + trafficeEnabled: XMLText, + stopAndGoBraking: XMLText, + trailerFillLimit: XMLText, + automaticMotorStartEnabled: XMLText, + growthMode: XMLText, + fixedSeasonalVisuals: XMLText, + plannedDaysPerPeriod: XMLText, + fruitDestruction: XMLText, + plowingRequiredEnabled: XMLText, + stonesEnabled: XMLText, + weedsEnabled: XMLText, + limeRequired: XMLText, + isSnowEnabled: XMLText, + fuelUsage: XMLText, + helperBuyFuel: XMLText, + helperBuySeeds: XMLText, + helperSlurrySource: XMLText, + helperManureSource: XMLText, + densityMapRevision: XMLText, + terrainTextureRevision: XMLText, + terrainLodTextureRevision: XMLText, + splitShapesRevision: XMLText, + tipCollisionRevision: XMLText, + placementCollisionRevision: XMLText, + navigationCollisionRevision: XMLText, + mapDensityMapRevision: XMLText, + mapTerrainTextureRevision: XMLText, + mapTerrainLodTextureRevision: XMLText, + mapSplitShapesRevision: XMLText, + mapTipCollisionRevision: XMLText, + mapPlacementCollisionRevision: XMLText, + mapNavigationCollisionRevision: XMLText, + difficulty: XMLText, + economicDifficulty: XMLText, + dirtInterval: XMLText, + timeScale: XMLText, + autoSaveInterval: XMLText } export interface FSCareerSavegameStatistics { - money: XMLText, - playTime: XMLText + money: XMLText, + playTime: XMLText } export interface FSCareerSavegameSlotSystem { - _attributes: slotUsage + _attributes: slotUsage } interface slotUsage { - slotUsage: string + slotUsage: string } interface XMLText { - _text: string + _text: string } export interface Tokens { - main: string - beta: string - toast: string - tae: string - webhook_url: string - webhook_url_test: string - mongodb_uri: string - mongodb_uri_dev: 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, - embedColorGreen: Discord.ColorResolvable, - embedColorYellow: Discord.ColorResolvable, - embedColorRed: Discord.ColorResolvable, - embedColorBCA: Discord.ColorResolvable, - embedColorXmas: Discord.ColorResolvable, - LRSstart: number, - whitelistedServers: Array, - botSwitches: botSwitches, - botPresence: Discord.PresenceData, - eval: Eval, - mainServer: mainServer + embedColor: Discord.ColorResolvable, + embedColorGreen: Discord.ColorResolvable, + embedColorYellow: Discord.ColorResolvable, + embedColorRed: Discord.ColorResolvable, + embedColorBCA: Discord.ColorResolvable, + embedColorXmas: Discord.ColorResolvable, + LRSstart: number, + whitelistedServers: Array, + botSwitches: botSwitches, + botPresence: Discord.PresenceData, + eval: Eval, + mainServer: mainServer } interface botSwitches { - registerCommands: boolean, - commands: boolean, - logs: boolean, - automod: boolean, - mpstats: boolean, - autores: boolean + registerCommands: boolean, + commands: boolean, + logs: boolean, + automod: boolean, + mpstats: boolean, + autores: boolean } interface Eval { - allowed: boolean, - whitelist: Array + allowed: boolean, + whitelist: Array } interface mainServer { - id: string, - staffRoles: Array, - roles: mainServerRoles, - channels: mainServerChannels + id: string, + staffRoles: Array, + roles: mainServerRoles, + channels: mainServerChannels } interface mainServerRoles { - admin: string, - bottech: string, - dcmod: string, - mpmanager: string, - mpmod: string, - vtcmanager: string, - vtcstaff: string, - ytmod: string, - mphelper: string, - mpplayer: string, - vtcmember: string + admin: string, + bottech: string, + dcmod: string, + mpmanager: string, + mpmod: string, + vtcmanager: string, + vtcstaff: string, + ytmod: string, + mphelper: string, + mpplayer: string, + vtcmember: string } interface mainServerChannels { - console: string, - errors: string, - thismeanswar: string, - bot_status: string, - logs: string, - welcome: string, - botcommands: string + console: string, + errors: string, + thismeanswar: string, + bot_status: string, + logs: string, + welcome: string, + botcommands: string } \ No newline at end of file