diff --git a/src/Sharding.ts b/src/Sharding.ts new file mode 100644 index 0000000..e0ac628 --- /dev/null +++ b/src/Sharding.ts @@ -0,0 +1,5 @@ +import {token_main} from './tokens.json' +import { ShardingManager } from "discord.js"; +const sharder = new ShardingManager('./index.ts',{token: token_main, totalShards: 1, mode: 'worker'}) +sharder.on('shardCreate',async(shard)=>{console.log(`Shard ${shard.id} launched`)}) +sharder.spawn(); \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index 9036c0d..4e3ef2f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -7,6 +7,16 @@ interface formatTimeOpt { longNames: boolean, commas: boolean } +interface CommandInfoOpt { + insertNewline: boolean, + parts: string[], //idfk + titles: string[] +} +interface punOpt { + time?: string, + reason?: string; + interaction?: any +} interface Punishment { id: number; type: string; @@ -19,25 +29,33 @@ interface Punishment { cancels?: number; duration?: number; } -interface CommandInfoOpt { - insertNewline: boolean, - parts: string[], //idfk - titles: string[] +interface punData { + id: number; + type: string; + member: string; + moderator: string; + expired?: boolean; + time?: number; + reason: string; + endTime?: number; + cancels?: number; + duration?: number; } import Discord, { Client, GatewayIntentBits, Partials } from 'discord.js'; import fs from 'node:fs'; import { Database } from './database'; import timeNames from './timeNames'; export class TClient extends Client { - invites: any; - commands: any; + invites: Map; + commands: Discord.Collection; + registry: Array; config: any; tokens: any; categoryNames: any; commandPages: any; helpDefaultOptions: any; YTCache: any; - embed: any; + embed: typeof Discord.EmbedBuilder; collection: any; messageCollector: any; attachmentBuilder: any; @@ -45,12 +63,11 @@ export class TClient extends Client { xjs: any; axios: any; memberCount_LastGuildFetchTimestamp: any; - userLevels: Database; - punishments: Database; - bonkCount: Database; - bannedWords: Database; + userLevels: userLevels; + punishments: punishments; + bonkCount: bonkCount; + bannedWords: bannedWords; repeatedMessages: any; - setMaxListeners: any; constructor(){ super({ @@ -69,6 +86,7 @@ export class TClient extends Client { }) this.invites = new Map(); this.commands = new Discord.Collection(); + this.registry = []; this.config = require('./config.json'); this.tokens = require('./tokens.json'); this.categoryNames; @@ -89,10 +107,10 @@ export class TClient extends Client { this.xjs = import('xml-js'); this.axios = import('axios'); this.memberCount_LastGuildFetchTimestamp = 0; - this.userLevels = new Database('./database/userLevels.json', 'object'); - this.bonkCount = new Database('./database/bonkCount.json', 'object'); - this.punishments = new Database('./database/punishments.json', 'array'); - this.bannedWords = new Database('./database/bannedWords.json', 'array'); + this.userLevels(this); + this.bonkCount(this); + this.punishments(this); + this.bannedWords(this); this.repeatedMessages = {}; this.setMaxListeners(80) } @@ -102,6 +120,12 @@ export class TClient extends Client { this.bannedWords.initLoad(); this.bonkCount.initLoad(); this.userLevels.initLoad().intervalSave(15000).disableSaveNotifs(); + const commandFiles = fs.readdirSync('./commands/slash').filter(file=>file.endsWith('.ts')); + for (const file of commandFiles){ + const command = require(`./commands/slash/${file}`); + this.commands.set(command.data.name, command) + this.registry.push(command.data.toJSON()) + } } commandInfo(client: TClient, command: any, options?: CommandInfoOpt){ let text = ':small_orange_diamond: '; @@ -368,4 +392,73 @@ export class TClient extends Client { (this.channels.resolve(DCChannelID) as Discord.TextChannel).send(`**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`) } } +} + + +//class +class bannedWords extends Database { + client: TClient; + constructor(client: TClient){ + super('./database/bannedWords.json', 'array'); + this.client = client; + } +} +class punishments extends Database { + client: TClient; + constructor(client: TClient){ + super('./database/punishments.json', 'array'); + this.client = client; + } + createId(){ + return Math.max(...this.client.punishments._content.map((x:punData)=>x.id), 0)+1; + } + async addPunishment(type: string, member: any, options: punOpt, moderator: string){ + const now = Date.now(); + const {time, reason, interaction}=options; + const ms = require('ms'); + let timeInMillis; + if(type !== 'mute'){ + timeInMillis = time ? ms(time) : null; + } else { + timeInMillis = time ? ms(time) : 2419200000; + } + switch (type) { + case 'ban': + const banData: punData={type, id: this.createId(), member: member.id, moderator, time: now}; + const dm1: Discord.Message = await member.send(`You've been banned from ${interaction.guild.name} ${timeInMillis ? `for ${this.client.formatTime(timeInMillis, 4, {longNames: true, commas: true})} (${timeInMillis}ms)` : 'forever'} for reason \`${reason || 'Unspecified'}\` (Case #${banData.id})`).catch(()=>{return interaction.channel.send('Failed to DM user.')}) + const banResult = await interaction.guild.bans.create(member.id, {reason: `${reason || 'Unspecified'} | Case #${banData.id}`}).catch((err:Error)=>err.message); + case 'softban': + case 'kick': + case 'warn': + case 'mute': + } + } +} +class userLevels extends Database { + client: TClient; + constructor(client: TClient){ + super('./database/userLevels.json', 'object'); + this.client = client + } + incrementUser(userid: string){ + const data = this._content[userid]; + + if (data) { + this._content[userid].messages++; + if (data.messages >= this.algorithm(data.level+2)){ + while (data.messages > this.algorithm(data.level+1)){ + this._content[userid].level++; + console.log(`${userid} EXTENDED LEVELUP ${this._content[userid].level}`) + } + } else if (data.messages >= this.algorithm(data.level+1)){ + this._content[userid].level++; + (this.client.channels.resolve(this.client.config.mainServer.channels.thismeanswar) as Discord.TextChannel).send({content: `<@${userid}> has reached level **${data.level}**. GG!`}) + } + } else { + this._content[userid] = {messages: 1, level: 0}; + } + } + algorithm(level: number){ + return level*level*15; + } } \ No newline at end of file diff --git a/src/commands/eval.ts b/src/commands/eval.ts index 3d2a309..6880fb9 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -26,7 +26,7 @@ export default { output = await eval(code) } catch (err: any) { error = true - const embed = new client.embed().setColor('ff0000').setTitle('__Eval__').addFields( + 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}\`\`\``} ) @@ -39,11 +39,6 @@ export default { }); } if (error) return; - if (typeof output !== 'string') { - output = 'js\n'+util.formatWithOptions({depth: 1}, '%O', output) - } else { - output = '\n' + String(output); - } if (typeof output == 'object') { output = 'js\n'+util.formatWithOptions({depth: 1}, '%O', output) } else { diff --git a/src/commands/slash/eval.ts b/src/commands/slash/eval.ts new file mode 100644 index 0000000..3bc1fcf --- /dev/null +++ b/src/commands/slash/eval.ts @@ -0,0 +1,60 @@ +import Discord,{SlashCommandBuilder} from 'discord.js'; +import { TClient } from 'src/client'; +import * as util from 'node:util'; +const removeUsername = (text: string)=>{ + let matchesLeft = true; + const array = text.split('\/'); + while (matchesLeft){ + let usersIndex = array.indexOf('home'); + if (usersIndex<1) matchesLeft = false; + else { + let usernameIndex = usersIndex+1; + if(array[usernameIndex].length == 0) usernameIndex += 1; + array[usernameIndex] = '*'.repeat(array[usernameIndex].length); + array[usersIndex] = 'ho\u200bme'; + } + } return array.join('\/'); +}; +export default { + async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) { + if (!client.config.eval.allowed) return interaction.reply({content: 'Eval is disabled.', ephemeral: true}); + if (!client.config.eval.whitelist.includes(interaction.user.id)) return interaction.reply({content: 'You\'re not allowed to use this command.', ephemeral: true}); + const code = interaction.options.getString('code') as string; + let output = 'error'; + let error = false; + try { + output = await eval(code); + } catch (err: any) { + error = true + const embed = new client.embed().setColor('#ff0000').setTitle('__Eval__').addFields( + {name: 'Input', value: `\`\`\`js\n${code.slice(0, 1010)}\n\`\`\``}, + {name: 'Output', value: `\`\`\`\n${err}\`\`\``} + ) + interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]})).then(errorEmbedMessage=>{ + const filter = (x:any)=>x.content === 'stack' && x.author.id === interaction.user.id + const messagecollector = (interaction.channel as Discord.TextChannel).createMessageCollector({filter, max: 1, time: 60000}); + messagecollector.on('collect', collected=>{ + collected.reply({content: `\`\`\`\n${removeUsername(err.stack)}\n\`\`\``, allowedMentions: {repliedUser: false}}); + }); + }); + } + if (error) return; + if (typeof output == 'object') { + output = 'js\n'+util.formatWithOptions({depth: 1}, '%O', output) + } else { + output = '\n' + String(output); + } + [client.tokens.token_main,client.tokens.token_beta,client.tokens.token_toast,client.tokens.token_tae].forEach((x)=>{ + const regexp = new RegExp(x as string,'g'); + output = output.replace(regexp, 'TOKEN_LEAK'); + }) + 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]})) + }, + name: 'eval', + description: 'Run code for debugging purposes', + category: 'bot' +} \ No newline at end of file diff --git a/src/config.json b/src/config.json index 2ee00e4..634d071 100644 --- a/src/config.json +++ b/src/config.json @@ -7,6 +7,7 @@ "embedColorBCA": "#ff69b4", "LRSstart": 1661236321433, "botSwitches": { + "registerCommands": false, "commands": false, "logs": false, "automod": false, @@ -47,6 +48,7 @@ "console": "904192878140608563", "errors": "904192878140608563", "bot_status": "904192878140608563", + "thismeanswar": "904192878140608563", "logs": "904192878140608563", "welcome": "904192878140608563", "botcommands": "904192878140608563" diff --git a/src/index.ts b/src/index.ts index 0d2fc9d..f9ebb21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ client.on('ready', async()=>{ client.user.setPresence({activities: [{ name: 'Running under TS', type: 0 }], status: 'online'}); // Playing: 0, Streaming (Requires YT/Twitch URL to work): 1, Listening to: 2, Watching: 3, Competing in: 5 }, 60000); + if (client.config.botSwitches.registerCommands) (client.guilds.cache.get(client.config.mainServer.id) as Discord.Guild).commands.set(client.registry).catch((e)=>{console.log(`Couldn't register slash commands: ${e}`)}) setInterval(()=>{ const guild = client.guilds.cache.get(client.config.mainServer.id) as Discord.Guild; guild.invites.fetch().then((invs)=>{ @@ -51,7 +52,6 @@ for (const file of commandFiles){ const command = require(`./commands/${file}`); client.commands.set(command.name, command) } -// Slash command handler todo // Daggerwin MP loop setInterval(async()=>{