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

Move essentials out of client.ts

This commit is contained in:
toast-ts 2023-08-30 18:34:59 +10:00
parent 277fdacf9f
commit 51a6f2e566
21 changed files with 176 additions and 162 deletions

View File

@ -1,6 +1,6 @@
import Discord, {Client, GatewayIntentBits, Partials} from 'discord.js';
import Discord from 'discord.js';
import {readFileSync, readdirSync} from 'node:fs';
import {formatTimeOpt, Tokens, Config, repeatedMessages, type MPServerCache} from './typings/interfaces';
import {Tokens, Config, repeatedMessages, type MPServerCache} from './typings/interfaces';
import bannedWords from './models/bannedWords.js';
import userLevels from './models/userLevels.js';
import suggestion from './models/suggestion.js';
@ -23,7 +23,7 @@ try{
console.log('Using production config')
}
export default class TClient extends Client {
export default class TClient extends Discord.Client {
invites: Map<any, any>;
commands: Discord.Collection<string, any>;
registry: Array<Discord.ApplicationCommandDataResolvable>;
@ -36,7 +36,6 @@ export default class TClient extends Client {
attachmentBuilder: any;
moment: typeof moment;
xjs: typeof xjs;
ms: any;
userLevels: userLevels;
punishments: punishments;
bonkCount: bonkCount;
@ -51,13 +50,13 @@ export default class TClient extends Client {
constructor(){
super({
intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildPresences,
GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.DirectMessages
Discord.GatewayIntentBits.Guilds, Discord.GatewayIntentBits.GuildMembers,
Discord.GatewayIntentBits.GuildModeration, Discord.GatewayIntentBits.GuildInvites,
Discord.GatewayIntentBits.GuildMessageReactions, Discord.GatewayIntentBits.GuildPresences,
Discord.GatewayIntentBits.MessageContent, Discord.GatewayIntentBits.GuildMessages,
Discord.GatewayIntentBits.GuildVoiceStates, Discord.GatewayIntentBits.DirectMessages
], partials: [
Partials.Channel, Partials.Reaction, Partials.Message
Discord.Partials.Channel, Discord.Partials.Reaction, Discord.Partials.Message
], allowedMentions: {users:[],roles:[]}
})
this.invites = new Map();
@ -75,7 +74,6 @@ export default class TClient extends Client {
this.attachmentBuilder = Discord.AttachmentBuilder;
this.moment = moment;
this.xjs = xjs;
this.ms = import('ms').then(i=>i);
this.userLevels = new userLevels(this);
this.bonkCount = new bonkCount(this);
this.punishments = new punishments(this);
@ -109,106 +107,7 @@ export default class TClient extends Client {
}
}
}
formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){
let achievedAccuracy = 0;
let text:any = '';
for (const timeName of [
{name: 'year', length: 31536000000},
{name: 'month', length: 2592000000},
{name: 'week', length: 604800000},
{name: 'day', length: 86400000},
{name: 'hour', length: 3600000},
{name: 'minute', length: 60000},
{name: 'second', length: 1000}
]){
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();
}
formatPlayerUptime(oldTime:number){
var Hours=0;
oldTime=Math.floor(Number(oldTime));
if(oldTime>=60){
var Hours=Math.floor(Number(oldTime)/60);
var Minutes=(Number(oldTime)-(Hours*60));
} else Minutes=Number(oldTime)
if(Hours>=24){
var Days=Math.floor(Number(Hours)/24);
var Hours=(Hours-(Days*24));
} return (Days>0?Days+' d ':'')+(Hours>0?Hours+' h ':'')+(Minutes>0?Minutes+' m':'')
}
isStaff = (guildMember:Discord.GuildMember)=>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)=>interaction.reply(`This command is restricted to <@&${this.config.mainServer.roles[role]}>`);
logTime = ()=>`[${this.moment().format('DD/MM/YY HH:mm:ss')}]`;
async punish(interaction: Discord.ChatInputCommandInteraction<'cached'>, type: string){
if (!this.isStaff(interaction.member as Discord.GuildMember)) return this.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);
console.log(this.logTime(), `[PunishmentLog] ${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(this.punishments.type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in /${interaction.commandName} for ${reason}`);
(this.channels.cache.get(this.config.mainServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new this.embed().setColor(this.config.embedColor).setAuthor({name: interaction?.user?.username, iconURL: interaction?.user?.displayAvatarURL({size:2048})}).setTitle('Punishment Log').setDescription(`${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(this.punishments.type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in \`/${interaction.commandName}\` for \`${reason}\``).setTimestamp()]});
if (interaction.user.id === User.id) return interaction.reply(`You cannot ${type} yourself.`);
if (!GuildMember && type != 'unban') 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 this.punishments.addPunishment(type, { time, interaction }, interaction.user.id, reason, User, GuildMember);
}
async YTLoop(YTChannelID: string, YTChannelName: string, DCChannelID: string){
let Data:any;
try {
await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelID}`, {signal: AbortSignal.timeout(6000),headers:{'User-Agent':`Daggerbot - Notification/undici`}}).then(async xml=>Data = this.xjs.xml2js(await xml.text(), {compact: true}))
} catch(err){
console.log(this.logTime(), `${YTChannelName} YT fail`)
}
if (!Data) return;
if (!this.YTCache[YTChannelID]) return this.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text;
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}`)
}
}
formatBytes(bytes:number, decimals:number = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals < 0 ? 0 : decimals)) + ' ' + ['Bytes', 'KB', 'MB', 'GB'][i];
}
removeUsername = (text: string)=>{
let matchesLeft = true;
const dirSlash = process.platform === 'linux' ? '\/' : '\\';
const array = text.split(dirSlash);
while (matchesLeft){
let usersIndex = array.indexOf(process.platform === 'linux' ? 'home' : 'Users');
if (usersIndex<1) matchesLeft = false;
else {
let usernameIndex = usersIndex+1;
if(array[usernameIndex].length == 0) usernameIndex += 1;
array[usernameIndex] = '・'.repeat(array[usernameIndex].length);
array[usersIndex] = process.platform === 'linux' ? 'ho\u200bme' : 'Us\u200bers';
}
} return array.join(dirSlash);
}
}

View File

@ -1,8 +1,9 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import Punish from '../funcs/Punish.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.punish(interaction, 'ban');
Punish(client, interaction, 'ban');
},
data: new Discord.SlashCommandBuilder()
.setName('ban')

View File

@ -1,5 +1,6 @@
import Discord from "discord.js";
import Discord from 'discord.js';
import TClient from '../client.js';
import FormatTime from '../helpers/FormatTime.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!client.isStaff(interaction.member)) return client.youNeedRole(interaction, 'dcmod');
@ -21,7 +22,7 @@ export default {
{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.duration) embed.addFields({name: '🔹 Duration', value: `${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]});
@ -34,7 +35,7 @@ export default {
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}` : ''}`
value: `Reason: \`${punishment.reason}\`\n${punishment.duration ? `Duration: ${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 (!punishments || !userPunishment) return interaction.reply(`**${user.username}** has a clean record.`)

View File

@ -3,6 +3,8 @@ import {Octokit} from '@octokit/rest';
import {createTokenAuth} from '@octokit/auth-token';
import {exec} from 'node:child_process';
import MessageTool from '../helpers/MessageTool.js';
import UsernameHelper from '../helpers/UsernameHelper.js';
import FormatTime from '../helpers/FormatTime.js';
import fs from 'node:fs';
import util from 'node:util';
import TClient from '../client.js';
@ -27,7 +29,7 @@ export default {
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${client.removeUsername(err.stack)}\n\`\`\``, allowedMentions: {repliedUser: false}});
collected.reply({content: `\`\`\`\n${UsernameHelper.stripName(err.stack)}\n\`\`\``, allowedMentions: {repliedUser: false}});
});
});
}
@ -40,7 +42,7 @@ export default {
].forEach(x=>output = output.replace(new RegExp(x as string,'g'),':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: `\`\`\`${client.removeUsername(output).slice(0,1016)}\n\`\`\``}
{name: 'Output', value: `\`\`\`${UsernameHelper.stripName(output).slice(0,1016)}\n\`\`\``}
);
interaction.reply({embeds: [embed]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed]}));
},
@ -63,11 +65,11 @@ export default {
}
};
exec('git pull',{windowsHide:true},(err:Error,stdout)=>{
if (err) clarkson.edit(`\`\`\`${client.removeUsername(err.message)}\`\`\``)
if (err) clarkson.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
else if (stdout.includes('Already up to date')) clarkson.edit('I am already up to date with the upstream repository.')
else clarkson.edit('Compiling TypeScript files...').then(()=>exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
if (err) clarkson.edit(`\`\`\`${client.removeUsername(err.message)}\`\`\``)
if (interaction.options.getBoolean('restart')) clarkson.edit(`[Commit:](<${github.fetchCommit.url}>) **${github.fetchCommit.msg}**\nCommit author: **${github.fetchCommit.author}**\n\n__Commit changes__\nTotal: **${github.fetchChanges.total}**\nAdditions: **${github.fetchChanges.addition}**\nDeletions: **${github.fetchChanges.deletion}**\n\nSuccessfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${client.formatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}));
if (err) clarkson.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
if (interaction.options.getBoolean('restart')) clarkson.edit(`[Commit:](<${github.fetchCommit.url}>) **${github.fetchCommit.msg}**\nCommit author: **${github.fetchCommit.author}**\n\n__Commit changes__\nTotal: **${github.fetchChanges.total}**\nAdditions: **${github.fetchChanges.addition}**\nDeletions: **${github.fetchChanges.deletion}**\n\nSuccessfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${FormatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}));
else clarkson.edit(`[Commit:](<${github.fetchCommit.url}>) **${github.fetchCommit.msg}**\nCommit author: **${github.fetchCommit.author}**\n\n__Commit changes__\nTotal: **${github.fetchChanges.total}**\nAdditions: **${github.fetchChanges.addition}**\nDeletions: **${github.fetchChanges.deletion}**\n\nSuccessfully compiled TypeScript files into JavaScript!`)
}))
})
@ -112,15 +114,15 @@ export default {
restart: async()=>{
const i = await interaction.reply({content: 'Compiling TypeScript files...', fetchReply: true});
exec('yarn tsc',{windowsHide:true},(err:Error)=>{
if (err) i.edit(`\`\`\`${client.removeUsername(err.message)}\`\`\``)
else i.edit(`Successfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${client.formatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}))
if (err) i.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
else i.edit(`Successfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${FormatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}))
})
},
file: ()=>interaction.reply({files:[`./src/database/${interaction.options.getString('name')}.json`]}).catch(()=>'Filesize is too large, upload cancelled.'),
wake_device: async()=>{
const i = await interaction.reply({content: 'Spawning a task...', fetchReply: true});
exec(`cd "../../Desktop/System Tools/wakemeonlan" && WakeMeOnLan.exe /wakeup ${interaction.options.getString('name')}`, {windowsHide:true}, (err:Error)=>{
if (err) i.edit(client.removeUsername(err.message))
if (err) i.edit(UsernameHelper.stripName(err.message))
else i.edit('Your device should be awake by now!\n||Don\'t blame me if it isn\'t on.||')
})
}

View File

@ -1,8 +1,9 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import Punish from '../funcs/Punish.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.punish(interaction, 'kick');
Punish(client, interaction, 'kick');
},
data: new Discord.SlashCommandBuilder()
.setName('kick')

View File

@ -2,9 +2,10 @@ import Discord from 'discord.js';
import TClient from '../client.js';
import path from 'node:path';
import canvas from 'canvas';
import FormatPlayer from '../helpers/FormatPlayer.js';
import MessageTool from '../helpers/MessageTool.js';
import {readFileSync} from 'node:fs';
import {FSData} from 'src/typings/interfaces.js';
import {FSData} from '../typings/interfaces.js';
const serverChoices = [
{name: 'Main Server', value: 'mainServer'},
@ -149,11 +150,7 @@ export default {
else if (endpoint.slots.used > 8) Color = client.config.embedColorYellow;
else Color = client.config.embedColorGreen;
for (const player of endpoint.slots.players.filter(x=>x.isUsed)){
let decorator = player.isAdmin ? ':detective:' : '';
decorator += player.name.includes('Toast') ? '<:toastv2:1132681026662056079>' : '';
playerData.push(`**${player.name}${decorator}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`)
}
for (const player of endpoint.slots.players.filter(x=>x.isUsed)) playerData.push(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}**\nFarming for ${FormatPlayer.uptimeFormat(player.uptime)}`)
const slot = `${endpoint.slots.used}/${endpoint.slots.capacity}`;
const ingameTime = `${('0'+Math.floor((endpoint.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((endpoint.server.dayTime/60/1000)%60)).slice(-2)}`;

View File

@ -1,8 +1,9 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import Punish from '../funcs/Punish.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.punish(interaction, 'mute');
Punish(client, interaction, 'mute');
},
data: new Discord.SlashCommandBuilder()
.setName('mute')

View File

@ -1,10 +1,11 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import FormatTime from '../helpers/FormatTime.js';
export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (client.uptime < 15500) return interaction.reply('I just restarted, wait 15 seconds and try again.')
const msg = await interaction.reply({content: 'Pinging...', fetchReply: true})
msg.edit(`API Latency: \`${client.formatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot Latency: \`${client.formatTime(msg.createdTimestamp - interaction.createdTimestamp, 3, {longNames: false, commas: true})}\``)
msg.edit(`API Latency: \`${FormatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot Latency: \`${FormatTime(msg.createdTimestamp - interaction.createdTimestamp, 3, {longNames: false, commas: true})}\``)
},
data: new Discord.SlashCommandBuilder()
.setName('ping')

View File

@ -1,8 +1,9 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import Punish from '../funcs/Punish.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.punish(interaction, 'softban');
Punish(client, interaction, 'softban');
},
data: new Discord.SlashCommandBuilder()
.setName('softban')

View File

@ -1,6 +1,8 @@
import Discord from 'discord.js';
import pkg from 'typescript';
import MessageTool from '../helpers/MessageTool.js';
import FormatBytes from '../helpers/FormatBytes.js';
import FormatTime from '../helpers/FormatTime.js';
import si from 'systeminformation';
import TClient from '../client.js';
import os from 'node:os';
@ -16,7 +18,7 @@ export default {
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, 3, {longNames: true, commas: true})}**`);
if (includedCommands.size === 0) return interaction.reply(`No commands have been used yet.\nUptime: **${FormatTime(client.uptime, 3, {longNames: true, commas: true})}**`);
const nameLength = Math.max(...includedCommands.map(x=>x.command.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'];
@ -50,13 +52,13 @@ export default {
{name: '> __Host__', value: MessageTool.concatMessage(
`**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`,
`**CPU:** ${cpu.manufacturer} ${cpu.brand}`,
`**Memory:** ${client.formatBytes(ram.used)}/${client.formatBytes(ram.total)}`,
`**Process:** ${client.formatBytes(process.memoryUsage().heapUsed)}/${client.formatBytes(process.memoryUsage().heapTotal)}`,
`**Memory:** ${FormatBytes(ram.used)}/${FormatBytes(ram.total)}`,
`**Process:** ${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, 2, {commas: true, longNames: true})}`
`**Uptime:**\nHost: ${FormatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${FormatTime(client.uptime, 2, {commas: true, longNames: true})}`
)}
);
waitForData.edit({content:null,embeds:[embed]}).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})}`})]}))
waitForData.edit({content:null,embeds:[embed]}).then(x=>x.edit({embeds:[new client.embed(x.embeds[0].data).setFooter({text: `Load time: ${FormatTime(x.createdTimestamp - interaction.createdTimestamp, 2, {longNames: true, commas: true})}`})]}))
},
data: new Discord.SlashCommandBuilder()
.setName('statistics')

View File

@ -1,8 +1,9 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import Punish from '../funcs/Punish.js';
export default {
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
client.punish(interaction, 'warn');
Punish(client, interaction, 'warn');
},
data: new Discord.SlashCommandBuilder()
.setName('warn')

View File

@ -1,5 +1,6 @@
import Discord from 'discord.js';
import TClient from '../client';
import FormatPlayer from '../helpers/FormatPlayer.js';
import {writeFileSync, readFileSync} from 'node:fs';
import {FSPlayer, FSData, FSCareerSavegame, TServer} from '../typings/interfaces';
@ -12,12 +13,6 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer
const serverErrorEmbed = new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time');
const genericEmbed = new client.embed();
const decoPlayer = (player:FSPlayer)=>{
let decorator = player.isAdmin ? ':detective:' : '';
decorator += player.name.includes('Toast') ? '<:toastv2:1132681026662056079>' : '';
return decorator
}
const HITALL = async()=>{
let sessionInit = {signal: AbortSignal.timeout(8200),headers:{'User-Agent':`Daggerbot - HITALL/undici`}};
try {
@ -42,17 +37,17 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer
// Join/Leave log
function playerLogEmbed(player:FSPlayer,joinLog:boolean){
const logEmbed = new client.embed().setDescription(`**${player.name}${decoPlayer(player)}** ${joinLog ? 'joined' : 'left'} **${client.MPServerCache[ServerName].name}** at <t:${Math.round(Date.now()/1000)}:t>`);
const logEmbed = new client.embed().setDescription(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}** ${joinLog ? 'joined' : 'left'} **${client.MPServerCache[ServerName].name}** at <t:${Math.round(Date.now()/1000)}:t>`);
if (joinLog) return logEmbed.setColor(client.config.embedColorGreen);
else if (player.uptime > 0) return logEmbed.setColor(client.config.embedColorRed).setFooter({text:`Farmed for ${client.formatPlayerUptime(player.uptime)}`});
else if (player.uptime > 0) return logEmbed.setColor(client.config.embedColorRed).setFooter({text:`Farmed for ${FormatPlayer.uptimeFormat(player.uptime)}`});
else return logEmbed.setColor(client.config.embedColorRed);
}
const serverLog = client.channels.resolve(client.config.mainServer.channels.fs_server_log) as Discord.TextChannel;
const playersOnServer = hitDSS.slots?.players.filter(x=>x.isUsed);
const playersInCache = client.MPServerCache[ServerName].players;
if (!playersOnServer ?? playersOnServer === undefined) return new Error('[MPLoop] Empty array, ignoring...'); // For the love of god, stop throwing errors everytime.
playersOnServer.forEach(player=>playerData.push(`**${player.name}${decoPlayer(player)}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`));
if (!playersOnServer ?? playersOnServer === undefined) return console.log('[MPLoop] Empty array, ignoring...'); // For the love of god, stop throwing errors everytime.
playersOnServer.forEach(player=>playerData.push(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}**\nFarming for ${FormatPlayer.uptimeFormat(player.uptime)}`));
// Player leaving
for (const player of playersInCache.filter(x=>!playersOnServer.some(y=>y.name === x.name))){

20
src/funcs/Punish.ts Normal file
View File

@ -0,0 +1,20 @@
import Discord from 'discord.js';
import TClient from '../client.js';
export default async(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);
console.log(client.logTime(), `[PunishmentLog] ${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in /${interaction.commandName} for ${reason}`);
(client.channels.cache.get(client.config.mainServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction?.user?.username, iconURL: interaction?.user?.displayAvatarURL({size:2048})}).setTitle('Punishment Log').setDescription(`${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(client.punishments.type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in \`/${interaction.commandName}\` for \`${reason}\``).setTimestamp()]});
if (interaction.user.id === User.id) return interaction.reply(`You cannot ${type} yourself.`);
if (!GuildMember && type != 'unban') 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);
}

18
src/funcs/YTLoop.ts Normal file
View File

@ -0,0 +1,18 @@
import {TextChannel} from 'discord.js';
import TClient from '../client.js';
export default async(client: TClient, YTChannelID: string, YTChannelName: string, DiscordChannelID: string, DiscordRoleID: string)=>{
let Data: any;
try {
await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelID}`, {signal: AbortSignal.timeout(8000), headers: {'User-Agent': 'Daggerbot - Notification/undici'}}).then(async xml=>Data = client.xjs.xml2js(await xml.text(), {compact: true}))
} catch(err){
console.log(client.logTime(), `Failed to fetch "${YTChannelName}" from YouTube`)
}
if (!Data) return;
if (!client.YTCache[YTChannelID]) return client.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text;
if (Data.feed.entry[1]['yt:videoId']._text === client.YTCache[YTChannelID]){
client.YTCache[YTChannelID] = Data.feed.entry[0]['yt:videoId']._text;
(client.channels.resolve(DiscordChannelID) as TextChannel).send({content: `<@&${DiscordRoleID}> (Ping notification are currently WIP, no eta when complete, the mentioned role is a placeholder for now)\n**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`, allowedMentions: {parse: ['roles']}})
}
}

View File

@ -0,0 +1,5 @@
export default (bytes:number, decimals:number = 2)=>{
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return parseFloat((bytes / Math.pow(1024, i)).toFixed(decimals < 0 ? 0 : decimals))+ ' ' +['Bytes', 'KB', 'MB', 'GB', 'TB'][i]
}

View File

@ -0,0 +1,22 @@
import {FSPlayer} from '../typings/interfaces';
export default class FormatPlayer {
static uptimeFormat(playTime: number){
var Hours = 0;
playTime = Math.floor(Number(playTime));
if(playTime >= 60){
var Hours = Math.floor(Number(playTime)/60);
var Minutes = (Number(playTime)-(Hours*60));
} else Minutes = Number(playTime)
if(Hours >= 24){
var Days = Math.floor(Number(Hours)/24);
var Hours = (Hours-(Days*24));
} return (Days > 0 ? Days+' d ':'')+(Hours > 0 ? Hours+' h ':'')+(Minutes > 0 ? Minutes+' m':'')
}
static decoratePlayerIcons(player:FSPlayer){
let decorator = player.isAdmin ? ':detective:' : '';
decorator += player.name.includes('Toast') ? '<:toast:1132681026662056079>' : '';
decorator += player.name.includes('Daggerwin') ? '<:Daggerwin:549283056079339520>' : ''; // Probably useless lol, but we'll see.
return decorator
}
}

35
src/helpers/FormatTime.ts Normal file
View File

@ -0,0 +1,35 @@
interface formatTimeOpt {
longNames: boolean,
commas: boolean
}
export default (integer:number, accuracy:number = 1, options?:formatTimeOpt)=>{
let achievedAccuracy = 0;
let text:any = '';
for (const timeName of [
{name: 'year', length: 31536000000},
{name: 'month', length: 2592000000},
{name: 'week', length: 604800000},
{name: 'day', length: 86400000},
{name: 'hour', length: 3600000},
{name: 'minute', length: 60000},
{name: 'second', length: 1000}
]){
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();
}

View File

@ -0,0 +1,18 @@
export default class UsernameHelper {
static stripName(text: string){
let matchesLeft = true;
const dirSlash = process.platform === 'linux' ? '\/' : '\\';
const array = text.split(dirSlash);
while (matchesLeft) {
let usersIndex = array.indexOf(process.platform === 'linux' ? 'media' : 'Users');
if (usersIndex < 1) matchesLeft = false;
else {
let usernameIndex = usersIndex + 1;
if (array[usernameIndex].length === 0) usernameIndex += 1;
array[usernameIndex] = '・'.repeat(array[usernameIndex].length);
array[usersIndex] = process.platform === 'linux' ? 'med\u200bia' : 'Us\u200bers';
}
return array.join(dirSlash);
}
}
}

View File

@ -2,6 +2,7 @@ import Discord from 'discord.js';
import TClient from './client.js';
const client = new TClient;
client.init();
import YTLoop from './funcs/YTLoop.js';
import MPLoop from './funcs/MPLoop.js';
import {Player} from 'discord-player';
const player = Player.singleton(client);
@ -10,11 +11,7 @@ import {writeFileSync, readFileSync} from 'node:fs';
// Error handler
function DZ(error:Error, type:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so.
if ([
'ConnectTimeoutError: Connect Timeout Error', 'getaddrinfo EAI_AGAIN discord.com',
'[Error: 30130000:error:0A000410:SSL', '[Error: F8200000:error:0A000410:SSL',
'HTTPError: Internal Server Error'
].includes(error.message)) return;
if (JSON.parse(readFileSync('src/errorBlocklist.json', 'utf8')).includes(error.message)) return;// I wonder if my idea works, if not then please run me over with a bulldozer.
console.error(error);
(client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel | null)?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: 'Error type: ' + type}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${error.stack}`.slice(0, 2500)}\`\`\``)]})
}
@ -41,9 +38,9 @@ if (client.config.botSwitches.mpstats) setInterval(async()=>{
const serverlake = (await client.MPServer._content.findById(client.config.mainServer.id));
for await (const [locName, locArea] of Object.entries(client.config.MPStatsLocation)) await MPLoop(client, locArea.channel, locArea.message, serverlake[locName], locName)
}, 35000);
setInterval(async()=>{
client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); // 528967918772551702 = #videos-and-streams
client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') // 767444045520961567 = #machinery-restorer
setInterval(async()=>{// Ping notification is currently WIP, it might be active in production but I want to see how it goes with role mentions first so I can make any further changes.
YTLoop(client, 'UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702', '1011341005389307925'); // 528967918772551702 = #videos-and-streams; 1011341005389307925 = Bot Tech;
YTLoop(client, 'UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567', '989591094524276796') // 767444045520961567 = #machinery-restorer; 989591094524276796 = Temp;
}, 300000)
// Event loop for punishments and daily msgs

View File

@ -2,6 +2,7 @@ import Discord from 'discord.js';
import TClient from '../client.js';
import mongoose from 'mongoose';
import ms from 'ms';
import FormatTime from '../helpers/FormatTime.js';
import {Punishment} from '../typings/interfaces.js';
const Schema = mongoose.model('punishments', new mongoose.Schema({
@ -36,7 +37,7 @@ export default class punishments extends Schema {
{name: '\u200b', value: '\u200b', inline: true},
{name: '🔹 Reason', value: `\`${punishment.reason}\``, inline: true})
.setColor(this.client.config.embedColor).setTimestamp(punishment.time)
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: this.client.formatTime(punishment.duration, 100), inline: true}, {name: '\u200b', value: '\u200b', inline: true})
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: `${FormatTime(punishment.duration, 100)}`, inline: true}, {name: '\u200b', value: '\u200b', inline: true})
if (punishment.cancels) {
const cancels = await this._content.findById(punishment.cancels);
embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id}\n\`${cancels.reason}\``})
@ -72,7 +73,7 @@ export default class punishments extends Schema {
if (type == 'mute') timeInMillis = time ? ms(time) : 2419140000; // Timeouts have a limit of 4 weeks
else timeInMillis = time ? ms(time) : null;
const durationText = timeInMillis ? ` for ${this.client.formatTime(timeInMillis, 4, {longNames:true,commas:true})}` : '';
const durationText = timeInMillis ? ` for ${FormatTime(timeInMillis, 4, {longNames:true,commas:true})}` : '';
if (time) embed.addFields({name: 'Duration', value: durationText});
if (GuildMember){

View File

@ -4,10 +4,6 @@ export interface UserLevels {
messages: number,
level: number
}
export interface formatTimeOpt {
longNames: boolean,
commas: boolean
}
export interface punOpt {
time?: string,
reason?: string,