1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-09-29 16:30:59 -04:00
Daggerbot-TS/src/client.ts

464 lines
22 KiB
TypeScript
Raw Normal View History

2022-11-13 08:46:50 -05:00
interface createTableOpt {
columnAlign: any,
columnSeparator: any,
columnEmptyChar: any
}
interface formatTimeOpt {
longNames: boolean,
commas: boolean
}
interface CommandInfoOpt {
insertNewline: boolean,
parts: string[], //idfk
titles: string[]
}
interface punOpt {
time?: string,
reason?: string;
interaction?: any
}
2022-11-13 08:46:50 -05:00
interface Punishment {
id: number;
type: string;
member: string;
moderator: string;
expired?: boolean;
time?: number;
reason: string;
endTime?: number;
cancels?: number;
duration?: number;
}
interface punData {
id: number;
type: string;
member: string;
moderator: string;
expired?: boolean;
time?: number;
reason: string;
endTime?: number;
cancels?: number;
duration?: number;
2022-11-13 08:46:50 -05:00
}
import Discord, { Client, GatewayIntentBits, Partials } from 'discord.js';
import fs from 'node:fs';
import { Database } from './database';
2022-11-13 19:18:15 -05:00
import timeNames from './timeNames';
2022-11-13 08:46:50 -05:00
export class TClient extends Client {
invites: Map<any, any>;
commands: Discord.Collection<string, any>;
registry: Array<Discord.ApplicationCommandDataResolvable>;
2022-11-11 19:58:11 -05:00
config: any;
tokens: any;
categoryNames: any;
commandPages: any;
helpDefaultOptions: any;
YTCache: any;
embed: typeof Discord.EmbedBuilder;
2022-11-11 19:58:11 -05:00
collection: any;
messageCollector: any;
attachmentBuilder: any;
moment: any;
2022-11-13 08:46:50 -05:00
xjs: any;
axios: any;
2022-11-11 19:58:11 -05:00
memberCount_LastGuildFetchTimestamp: any;
userLevels: userLevels;
punishments: punishments;
bonkCount: bonkCount;
bannedWords: bannedWords;
2022-11-11 19:58:11 -05:00
repeatedMessages: any;
constructor(){
super({
intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildBans, GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.Reaction,
Partials.Message
],
2022-11-13 08:46:50 -05:00
allowedMentions: { repliedUser: false, parse: ['roles', 'users'] }
2022-11-11 19:58:11 -05:00
})
this.invites = new Map();
this.commands = new Discord.Collection();
this.registry = [];
2022-11-11 19:58:11 -05:00
this.config = require('./config.json');
this.tokens = require('./tokens.json');
this.categoryNames;
this.commandPages = [];
this.helpDefaultOptions = {
parts: ['name', 'usage', 'shortDescription', 'alias'],
titles: ['alias']
}
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;
2022-11-13 08:46:50 -05:00
this.moment = import('moment');
this.xjs = import('xml-js');
this.axios = import('axios');
2022-11-11 19:58:11 -05:00
this.memberCount_LastGuildFetchTimestamp = 0;
this.userLevels(this);
this.bonkCount(this);
this.punishments(this);
this.bannedWords(this);
2022-11-11 19:58:11 -05:00
this.repeatedMessages = {};
2022-11-13 08:46:50 -05:00
this.setMaxListeners(80)
2022-11-11 19:58:11 -05:00
}
async init(){
this.login(this.tokens.token_toast);
this.punishments.initLoad();
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())
}
2022-11-11 19:58:11 -05:00
}
2022-11-13 08:46:50 -05:00
commandInfo(client: TClient, command: any, options?: CommandInfoOpt){
let text = ':small_orange_diamond: ';
if (!options.titles) options.titles = [];
function e(){
text += '\n';
if (options.insertNewline){
text += '\n';
} return;
}
if (options.parts.includes('name') && command.name){
if (options.titles.includes('name') && options.titles.includes('usage')){
text += 'Name & Usage: ';
} else if (options.titles.includes('name')){
text += 'Name: ';
}
text += '`' + client.config.prefix + command.name;
if (options.parts.includes('usage') && command.usage){
text += ' ' + command.usage.map((x:string)=>x.startsWith('?') ? '?['+x.slice(1)+']' : '['+x+']').join(' ');
}
text += '`';
e();
} else if (options.parts.includes('usage') && command.usage){
if (options.titles.includes('usage')) text += 'Usage: ';
text += '`'+command.usage.map((x:string)=>x.startsWith('?') ? '?['+x+']' : '['+x+']').join(' ') + '`';
e();
}
if (options.parts.includes('description') && command.description){
if (options.titles.includes('description')) text += 'Description: ';
text += command.description;
e();
}
if (options.parts.includes('shortDescription')){
if (command.shortDescription){
if (options.titles.includes('shortDescription')) text += 'Shorter description: ';
text += command.shortDescription;
e();
} else if (!options.titles.includes('shortDescription') && command.description){
text += command.description;
e();
}
}
if (options.parts.includes('alias') && command.alias){
if (options.titles.includes('alias')) text += 'Aliases: ';
text += command.alias.map((x:any)=>'`'+x+'`').join(', ');
e();
}
if (options.parts.includes('category') && command.category){
if (options.titles.includes('category')) text += 'Category: ';
text += command.category;
e();
}
if (options.parts.includes('autores') && command.autores){
if (options.titles.includes('autores')) text += 'AutoResponse:tm: Requirements: ';
text += '`['+command.autores.join('] [')+']`';
e();
}
e();
return text;
}
formatPunishmentType(punishment: Punishment, client: TClient, cancels: Punishment){
2022-11-11 19:58:11 -05:00
if (punishment.type == 'removeOtherPunishment'){
2022-11-13 08:46:50 -05:00
cancels ||= this.punishments._content.find((x: Punishment)=>x.id === punishment.cancels)
2022-11-11 19:58:11 -05:00
return cancels.type[0].toUpperCase()+cancels.type.slice(1)+' Removed';
} else return punishment.type[0].toUpperCase()+punishment.type.slice(1);
}
2022-11-13 08:46:50 -05:00
formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){
2022-11-11 19:58:11 -05:00
let achievedAccuracy = 0;
let text = '';
const { longNames, commas } = options
for (const timeName of timeNames){
if (achievedAccuracy < accuracy){
const fullTimelengths = Math.floor(integer/timeName.length);
if (fullTimelengths == 0) continue;
achievedAccuracy++;
text += fullTimelengths + (longNames ? (' '+timeName.name+(fullTimelengths === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (commas ? ', ' : ' ');
integer -= fullTimelengths*timeName.length;
} else {
break;
}
}
2022-11-13 08:46:50 -05:00
if (text.length == 0) text = integer + (options?.longNames ? ' milliseconds' : 'ms') + (options?.commas ? ', ' : '');
if (options?.commas){
2022-11-11 19:58:11 -05:00
text = text.slice(0, -2);
2022-11-13 08:46:50 -05:00
if (options?.longNames){
2022-11-11 19:58:11 -05:00
text = text.split('');
text[text.lastIndexOf(',')] = ' and';
text = text.join('');
}
} return text.trim();
}
2022-11-13 08:46:50 -05:00
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))
}
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;
}
createTable(columnTitles = [], rowsData = [], options: createTableOpt, client: TClient){
const rows: any = [];
let { columnAlign = [], columnSeparator = [], columnEmptyChar = [] } = options;
if (columnSeparator.length < 1) columnSeparator.push('|');
columnSeparator = columnSeparator.map((x: string)=>`${x}`);
// col widths
const columnWidths = columnTitles.map((title: any, i)=>Math.max(title.length, ...rowsData.map((x: any)=>x[i].length)));
// first row
rows.push(columnTitles.map((title, i)=>{
let text = client.alignText(title, columnWidths[i], columnAlign[i], columnEmptyChar[i]);
if (columnSeparator[i]){
text += ' '.repeat(columnSeparator[i].length);
}
return text;
}).join(''))
// big line
rows.push('━'.repeat(rows[0].length));
//data
// remove unicode
rowsData.map((row: any)=>{
return row.map((element: string)=>{
return element.split('').map((char: string)=>{
if (char.charCodeAt(0)>128) return '□';
}).join('')
})
})
rows.push(rowsData.map((row: any)=>row.map((element: string, i: number)=>{
return client.alignText(element, columnWidths[i], columnEmptyChar[i])+(i === columnTitles.length - 1 ? '' : columnSeparator[i]);
}).join('')
).join('\n'))
2022-11-11 19:58:11 -05:00
2022-11-13 08:46:50 -05:00
return rows.join('\n');
}
makeModlogEntry(data: Punishment, client: TClient){
const cancels = data.cancels ? client.punishments._content.find((x: Punishment)=>x.id === data.cancels) : null;
// turn data into embed
const embed = new this.embed().setColor(this.config.embedColor).setTimestamp(data.time)
.setTitle(`${this.formatPunishmentType(data, client, cancels)} | Case #${data.id}`).addFields(
{name: '🔹 User', value: `<@${data.member}> \`${data.member}\``, inline: true},
{name: '🔹 Moderator', value: `<@${data.moderator}> \`${data.moderator}\``, inline: true},
{name: '\u200b', value: `\u200b`, inline: true},
{name: '🔹 Reason', value: `\`${data.reason || 'Unspecified'}\``, inline: true},
)
if (data.duration) {
embed.addFields(
{name: '🔹 Duration', value: client.formatTime(data.duration, 100), inline: true},
{name: '\u200b', value: '\u200b', inline: true}
)
}
if (data.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id} \`${cancels.reason}\``})
// send embed to log channel
(client.channels.cache.get(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [embed]})
}
async punish(client: TClient, message: Discord.Message, args: string, type: string){
let member: any;
if (message.guildId !== client.config.mainServer.id) return message.channel.send('This command doesn\'t work in this server');
if (!message.member.roles.cache.has(client.config.mainServer.roles.dcmod)) return message.reply(`You need the <@&${client.config.mainServer.roles.dcmod}> role to use this command.`)
if (type == 'ban' && args[1]){
member = message.mentions.users?.first() || (await client.users.fetch(args[1]).catch(()=>undefined));
} else if (args[1]){
member = message.mentions.members?.first() || (await message.guild.members.fetch(args[1]).catch(()=>undefined));
}
let memberAsked = false;
if (!member){
memberAsked = true;
await message.channel.send(`Which member would you like to ${type}?\nSend another message with a mention or a user ID. (30s)`).then(async (x:any)=>{
const filter = m=>m.author.id == message.author.id;
member = await message.channel.awaitMessages({filter, time: 30000, errors: ['time'], max: 1}).then(async (z:any)=>{
if (z.first().content.startsWith(client.config.prefix)) return 'timedout';
if (type == 'ban'){
return z.first().mentions.users?.first() || (await client.users.fetch(z.first().content).catch(()=>undefined));
} else {
return z.first().mentions.members?.first() || (await message.guild.members.fetch(z.first().content).catch(()=>undefined))
}
}).catch(async()=>{
message.channel.send('Command cancelled after 30 seconds of inactivity.');
return 'timedout';
});
})
}
if (member === 'timedout') return;
else if (!member) return message.channel.send('You failed to mention a member.');
let time;
let reason;
let col1; // idfk if this should be included here but just wanted to get rid of red underline.
let result: any;
if (args[2] && !memberAsked){
// if the first character of args 2 is a number, args 2 is the time. otherwise its the reason.
time = (args[2][0].match(/[0-9]/) && !['softban', 'kick', 'warn'].includes(type)) ? args[2] : undefined;
// if time is in args 2, reason starts at args 3. if no time was provided, reason starts at args 2
reason = args.slice(time ? 3 : 2).join(' '); // "Property 'join' does not exist on type 'string'." :linus: x99
} else {
if (!['softban', 'kick', 'warn'].includes(type)){
await message.channel.send(`How long do you want to ${type} this user for?\nSend another message with a time name, or 'forever' to ${type} this user forever. (30s)`);
const filter = m=>m.author.id === message.author.id;
col1 = await message.channel.awaitMessages({filter, time: 30000, errors: ['time'], max: 1}).then(collected=>{
if (collected.first()?.content.startsWith(client.config.prefix)) return 'timedout';
return collected.first()?.content.toLowerCase() === 'forever' ? 'inf' : collected.first()?.content;
}).catch(()=>{
message.channel.send('Command cancelled after 30 seconds of inactivity.');
return 'timedout';
});
if (time === 'timedout') return;
}
await message.channel.send(`Send another message with a reason for this ${type}.\nSend another message with "-" to leave the reason unspecified. (30s)`);
const filter = m=>m.author.id === message.author.id;
reason = await message.channel.awaitMessages({filter, time: 30000, errors: ['time'], max: 1}).then(collected=>{
if (collected.first()?.content.startsWith(client.config)) return 0;
return collected.first()?.content == '-' ? undefined : collected.first()?.content;
}).catch(()=>{
message.channel.send('Command cancelled after 30 seconds of inactivity.');
return 0;
})
if (reason === 0) return;
}
const punishmentResult = await client.punishments.addPunishment(type, member, {time, reason}, message.author.id, message);
(typeof result == 'string' ? message.reply(punishmentResult) : message.reply({embeds: [punishmentResult]}))
};
async unPunish(client: TClient, message: Discord.Message, args: string){
if (message.guildId !== client.config.mainServer.id) return message.channel.send('This command doesn\'t work in this server');
if (!message.member.roles.cache.has(client.config.mainServer.roles.dcmod)) return message.reply(`You need the <@&${client.config.mainServer.roles.dcmod}> role to use this command.`)
let punishment;
if (args[1]) punishment = client.punishments._content.find((x:any)=>x.id == args[1])
if (!punishment){
await message.channel.send(`Which punishment would you like to remove?\nSend another message with a Case # (30s)`).then(async (x:any)=>{
const filter = m=>m.author.id === message.author.id;
punishment = await message.channel.awaitMessages({filter, time: 30000, errors: ['time'], max: 1}).then(async (z:any)=>{
return client.punishments._content.find((x:any)=>x.id == z.first()?.content);
}).catch(async()=>{
message.channel.send('Command cancelled after 30 seconds of inactivity.');
return 'timedout';
});
})
}
if (punishment === 'timedout') return;
else if (!punishment) return message.channel.send('You failed to mention a Case #');
//if (punishment.type !== 'warn' && message.member.roles.cache.has(client.config.mainServer.roles.trialmoderator)) return message.channel.send('Trial moderators can only remove warnings.');
let reason;
if (args[2]){
reason = args.slice(2).join(' '); // "Property 'join' does not exist on type 'string'." :linus: x50
}else{
await message.channel.send(`Send another message with a reason for this ${punishment.type} removal. (30s)\n*Send \`-\` to leave the reason unspecified.*`);
const filter = m=>m.author.id === message.author.id;
reason = await message.channel.awaitMessages({filter, time: 30000, errors: ['time'], max: 1}).then(collected=>collected.first()?.content === '-' ? undefined : collected.first()?.content).catch(()=>0);
if (reason === 0) return message.channel.send('Invalid reason.');
}
const unpunishResult = await client.punishments.removePunishment(punishment.id, message.author.id, reason);
message.channel.send(unpunishResult);
}
async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){
const Data = this.xjs.xml2js((await this.axios.get(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelID}`, {timeout: 5000})), {compact: true, spaces: 2}).catch(()=>{return null});
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}`)
}
}
}
//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;
}
2022-11-13 08:46:50 -05:00
}