Compare commits

..
37 changed files with 1530 additions and 2809 deletions

View file

@ -2,9 +2,6 @@ name: CI
on: [push]
permissions:
contents: read
jobs:
CI:
runs-on: node
@ -16,6 +13,6 @@ jobs:
with:
node-version: 'latest'
- name: install dependencies
run: npm ci
run: npm install
- name: run tests
run: npm test
run: npm test

View file

@ -1,4 +1,3 @@
const savePresence = require("../../util/savePresence");
const setPresence = require("../../util/setPresence");
module.exports = {
@ -11,20 +10,12 @@ module.exports = {
,"Updates once a minute if custom variables are used."
,""
,"Custom Variables:"
,"${guilds},${prefix},${uptime},{members}"],
,"${guilds},${prefix},${uptime}"],
admin: true,
execute({message, client, args, prefix}) {
let forceUpdate = false;
if(args.length > 1 && args[0] === "force") {
forceUpdate = true;
args.shift();
}
if(args.length < 1){
message.channel.send(`You need at least two arguments for this command, see \`${prefix}help setpresence\``)
return;
}
execute({message, client, args, globalPrefix}) {
const savePresence = require("../../util/savePresence");
let presenceType = args[0].toLocaleUpperCase();
let sendText = "Presence has been set.";
let sendText = "Updated presence";
switch (presenceType) {
case "PLAY":
@ -55,11 +46,8 @@ module.exports = {
const firstArg = args[0].length + 1;
let temp = args.join(" ");
let presenceText = temp.slice(firstArg, temp.length)
setPresence({presenceText: presenceText,presenceType: presenceType, client: client, globalPrefix: globalPrefix});
savePresence(presenceType, presenceText, client);
if(forceUpdate)
setPresence({presenceText, presenceType, client})
else
sendText = `${sendText} It will update <t:${Math.floor((client.lastPresenceUpdate + 60000) / 1000)}:R>`
}
message.channel.send(sendText);

View file

@ -1,5 +1,4 @@
const calculateReloaded = require("../../util/calculateReloaded");
const executeCommand = require("../../util/executeCommand");
const reloadCommands = require("../../util/reloadCommands");
module.exports = {
@ -28,12 +27,10 @@ module.exports = {
let commitCount = stdout.split(/\r\n|\r|\n/).length - 1
sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}`
if(sendText.length >= 2000){
sendText = sendText.slice(1955)
sendText.slice(1955)
sendText = `${sendText}\n... Message is too long to show everything`
}
message.channel.send(sendText)
const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]);
client.githash = githash.error ? "N/A" : githash.output;
if (err) console.log(stderr)
})
}

View file

@ -1,6 +1,6 @@
const {EmbedBuilder} = require('discord.js');
const getCreationDate = require('../../util/getCreationDate');
const getGuildInfo = require('../../util/getGuildInfo');
const getGuildCount = require('../../util/getGuildCount');
@ -8,28 +8,15 @@ module.exports = {
name: 'botinfo',
description: 'Shows information about the bot',
execute({message, client, prefix}) {
let guildInfo = getGuildInfo(client)
let descriptionArr = [`Name: ${client.user.username}`,
`Prefix: ${prefix}`,
`Total Servers: ${guildInfo.guildCount}`,
`Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`,
`Total Commands: ${client.commands.size}`,
`Creation Date: ${getCreationDate(client)}`,
`Source: [Click Here](https://github.com/SileNce5k/discord_bot)`,
`Current Version: ${client.githash}`
]
let description = "";
descriptionArr.forEach(desc => {
description += `${desc}\n`;
})
let guildCount = getGuildCount(client)
const embed = new EmbedBuilder()
.setColor(15780145)
.setTitle("Information about bot")
.setTimestamp()
.setAuthor({name: client.user.username, iconURL: client.user.avatarURL({ format: 'png', dynamic: true, size: 2048 })})
.setDescription(description)
.addFields({
name: "General info", value: `Name: ${client.user.username}\nPrefix: ${prefix}\nTotal Servers: ${guildCount}\nTotal Commands: ${client.commands.size}\nCreation Date: ${getCreationDate(client)}\nSource: [Click Here](https://github.com/SileNce5k/discord_bot)`,
},)
message.channel.send({embeds :[embed]})

View file

@ -3,10 +3,10 @@ module.exports = {
description: 'Returns guild names',
admin: true,
execute({message, client}) {
let guildNames = client.guilds.cache
.sort((a, b) => b.memberCount - a.memberCount)
.map(guild => `${guild.name} (${guild.memberCount} members)`)
.join("\n");
let guildNames = "";
client.guilds.cache.each(guild => {
guildNames = guildNames + guild.name + "\n";
});
message.channel.send(guildNames)
}
};

View file

@ -13,6 +13,7 @@ module.exports = {
execute({ message, args, prefix, client }) {
let commands = ""
let commandFiles = getSubdirFiles('commands/')
let x = false;
let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`;
let iteration = 0;
let num_in_args = false;
@ -31,6 +32,7 @@ module.exports = {
fieldName = fieldName.replace("[[page]]", page);
const max_commands = iteration + 10;
if (x) return;
const embed = new EmbedBuilder()
.setColor(15780145)

View file

@ -1,103 +0,0 @@
// Code is taken from https://github.com/stphnduvall/mcstatus/blob/master/src/index.ts
// and converted to pure js.
const net = require('net')
const { EmbedBuilder } = require('discord.js');
module.exports = {
name: 'mc',
description: 'get minecraft server information',
hidden: true,
needsWhitelist: true,
async execute({ message, args }) {
let host = "";
let port = 25565;
if (args[0]) host = args[0];
if (host.includes(":")) {
port = host.replace(/.+(?:\:)/g, "");
host = host.match(/.+(?:\:)/g, "")[0].replace(":", "");
}
if(host === "") return message.channel.send("No host provided")
let info = await getMinecraftServerInfo(host, port);
if (info) {
const embed = new EmbedBuilder()
embed.setColor("#ee7939")
embed.setTimestamp()
embed.addFields(
{ name: "ping", value: info.ping.toString(), inline: false },
{ name: "Player Count", value: info.playercount.toString(), inline: false },
{ name: "Max Players", value: info.maxPlayers.toString(), inline: false },
{ name: "MOTD", value: info.motd, inline: false },
)
message.channel.send({ embeds: [embed] });
} else {
message.channel.send("Something went wrong\nThe minecraft server is likely not reachable from the discord bot")
}
}
};
async function getMinecraftServerInfo(host, port = 25565) {
let serverInfo = {
ping: undefined,
maxPlayers: undefined,
version: undefined,
playercount: undefined,
motd: undefined
}
let startTime = new Date();
let data;
let ping;
serverInfo = await new Promise((resolve) => {
const client = net.connect({ host, port }, () => {
ping = Math.round(new Date().getMilliseconds() - startTime.getMilliseconds());
let buff = Buffer.from([0xFE, 0x01]);
client.write(buff);
})
let error = false;
client.on('data', (d) => {
data = d.toString()
client.destroy();
})
client.once('error', (error) => {
console.error(error)
error = true;
})
client.once('connectionAttemptFailed', (ip) => {
console.error("in attempt failed")
error = true;
})
client.once('connectionAttemptTimeout', (ip) => {
console.error("in attempt timeout")
error = true;
})
client.on('close', () => {
if (!error) {
let _serverInfo = data?.split('\x00\x00\x00');
if (!_serverInfo) {
console.log("Something went wrong.")
resolve(serverInfo)
}
serverInfo.version = _serverInfo[2].replace(/\u0000/g, '')
serverInfo.motd = _serverInfo[3].replace(/\u0000/g, '')
serverInfo.playercount = Number(_serverInfo[4].replace(/\u0000/g, ''))
serverInfo.maxPlayers = Number(_serverInfo[5].replace(/\u0000/g, ''))
serverInfo.ping = Number(ping)
resolve(serverInfo)
} else {
resolve(0)
}
})
})
return serverInfo
}

View file

@ -16,9 +16,9 @@ module.exports = {
let user = message.guild.members.cache.get(info);
let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096});
let globalPfp = user.user.avatarURL({format: 'png', dynamic: true, size: 4096});
let sendText = `${globalPfp}`;
let sendText = `Global pfp:\n${globalPfp}`;
if(guildPfp != null){
sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`;
sendText = `${sendText}\nGuild pfp:\n${guildPfp}`;
}
message.channel.send(sendText)

View file

@ -15,7 +15,6 @@ module.exports = {
{name: "Server Name", value: message.guild.name, inline: false},
{name: "Created", value: convertDateToISOString(message.guild.createdAt), inline: false},
{name: "Members", value: message.guild.memberCount.toString(), inline: false},
{name: "Channels", value: message.guild.channels.channelCountWithoutThreads.toString(), inline: false},
])

View file

@ -1,71 +0,0 @@
const path = require('path');
const fs = require('fs')
const executeCommand = require('../../util/executeCommand');
module.exports = {
name: 'dl',
description: 'Download a video',
moreHelp: [
"Usage: <prefix>dl <url>"
],
async execute({message, args}) {
const downloadsDir = path.resolve(process.cwd(), 'data', 'downloads', Date.now().toString());
const cookieFilepath = path.resolve(process.cwd(), 'data', 'cookies.txt')
if(!fs.existsSync(cookieFilepath)) {
message.channel.send("Some dependencies are needed for the command to work properly. Please let the bot's owner know.")
return;
}
fs.mkdirSync(downloadsDir, {recursive: true});
let url;
if(args.length > 0){
if(args[0].charAt(0) === '<' && args[0].charAt(args[0].length - 1) === '>'){
args[0] = args[0].slice(1, args[0].length - 1)
}
try {
url = new URL(args[0]);
url = url.href;
} catch (error) {
this.cleanUp(downloadsDir);
message.channel.send("Could not parse the provided argument as a URL.");
return;
}
} else {
this.cleanUp(downloadsDir);
return message.channel.send("You have to provide a URL in an argument.")
}
const originalMessage = await message.channel.send("Downloading video...")
if(executeCommand("yt-dlp", [url, "-P", downloadsDir, "--cookies", cookieFilepath]).error){
originalMessage.edit("An error occurred when downloading the video.");
this.cleanUp(downloadsDir);
return;
}
let files = fs.readdirSync(downloadsDir);
if(files.length < 1) {
this.cleanUp(downloadsDir);
originalMessage.edit("Something went wrong when downloading the video.")
return;
}
const filename = files[0];
await originalMessage.edit({
content: null,
files: [{
attachment: path.resolve(downloadsDir, filename)
}]})
this.cleanUp(downloadsDir);
},
cleanUp(downloadsDir){
fs.rmSync(downloadsDir, {force: true, recursive: true});
},
}

View file

@ -17,8 +17,6 @@ module.exports = {
"They behave the same (for example: `<prefix>fmtt` and `<prefix>fm tt`)",
"Set username: `<prefix>fmset <lastfm_username>`",
"Get current scrobble: `<prefix>fm`",
"Get top artists `<prefix>fmas`",
"Get top albums: `<prefix>fmabl`",
"Get top tracks: `<prefix>fmtt`",
"Get album cover for current scrobble: `<prefix>fmcover`",
"Get a roast from an LLM using your top artists and albums: `<prefix>fmroast`"
@ -37,19 +35,6 @@ module.exports = {
args.shift();
sendText = await getTopTracks(message.author.id, args, message.guild);
break;
case "topalbums":
case "topalbum":
case "abl":
args.shift();
sendText = await getTopAlbums(message.author.id, args, message.guild)
break;
case "topartists":
case "topartist":
case "ta":
case "as":
args.shift();
sendText = await getTopArtists(message.author.id, args, message.guild);
break;
case "cover":
sendText = await getCurrentCover(message.author.id, message.guild);
break;
@ -87,13 +72,11 @@ module.exports = {
if(sendText.embed != null){
let parse = parseMention(message.author.id, message.guild)
let user = message.guild.members.cache.get(parse);
if(!sendText.embed.data.color){
let roleColor = 15788778;
if (user.roles.color) {
roleColor = user.roles.color.color;
}
sendText.embed.setColor(roleColor);
let roleColor = 15788778;
if (user.roles.color) {
roleColor = user.roles.color.color;
}
sendText.embed.setColor(roleColor);
message.channel.send({embeds :[sendText.embed]})
}else{
message.channel.send(sendText.text.replaceAll("<prefix>", prefix));

View file

@ -1,28 +0,0 @@
module.exports = {
name: 'ig',
description: 'Replaces Instagram links with kkinstagram and deletes the original message',
execute({message, args}) {
const noUrlErr = "You need to provide an Instagram link to use this command."
if(args.length < 1) {
message.channel.send(noUrlErr);
return;
}
let replacedLink = "";
const regex = /(?<=\/)(instagram)\.com/g
if(args[0].startsWith("https://") || args[0].startsWith("http://")){
replacedLink = args[0].replace(regex, "kkinstagram.com");
}
if(replacedLink.length === 0 || args[0] === replacedLink){
message.channel.send(noUrlErr);
return;
}
message.channel.send(replacedLink);
try{
message.delete()
}catch(err){
console.error(`${this.name}: An error occurred while trying to delete the original message.`)
console.error(err);
}
}
};

View file

@ -1,11 +1,7 @@
module.exports = {
name: 'ping',
description: 'Just ping.',
async execute({message, client}) {
let apiLatency = client.ws.ping;
const time = process.hrtime.bigint();
let initialMessage = await message.channel.send(`Pong.`)
const diff = Number(process.hrtime.bigint() - time) * 1e-6;
initialMessage.edit(`Pong.\nAPI latency: ${apiLatency} ms\nMessage latency: ${diff.toFixed(0)} ms.`);
execute({message, client}) {
message.channel.send(`Pong.\n${client.ws.ping}ms`)
}
};

View file

@ -1,11 +1,10 @@
const setServerPrefix = require("../../util/setServerPrefix");
const { PermissionsBitField } = require('discord.js');
module.exports = {
name: 'prefix',
description: 'Change the prefix of the bot in this server.',
execute({ message, client, args, prefix }) {
if (!message.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
if (!message.member.permissions.has('MANAGE_GUILD')) {
message.channel.send("You do not have sufficient permissions(MANAGE_GUILD) to change the prefix of this server.")
return;
}

View file

@ -1,7 +1,6 @@
module.exports = {
name: 'say',
description: 'Repeats arguments',
admin: true,
execute({message, args}) {
if(args.length == 0)

View file

@ -3,18 +3,16 @@ const fs = require('fs');
const path = require('path');
const { writeFile } = require('node:fs/promises')
const { Readable } = require('node:stream')
const executeCommand = require('../../util/executeCommand');
module.exports = {
name: 'tdoss',
description: 'Combine picture with tdoss album cover template',
async execute({ message, args }) {
let tdossDir = path.resolve(process.cwd(), 'data', 'tdoss');
const tdossTemplate = path.resolve(process.cwd(), 'resources', 'tdoss_template.png');
const directory = path.resolve(tdossDir, Math.floor(new Date).toString())
fs.mkdirSync(directory, {recursive: true})
let dataDir = path.resolve(__dirname, '..', '..', 'data');
const directory = path.resolve(dataDir, Math.floor(new Date).toString())
fs.mkdirSync(directory)
let url = "";
if(message.attachments.size > 0){
@ -46,22 +44,15 @@ module.exports = {
}
// TODO: Download with correct extension.
message.channel.sendTyping();
let downloadResult = await this.downloadImage(url, path.resolve(directory, "input.png"));
if(downloadResult.value != this.ERROR_CODES.SUCCESS){
if(downloadResult.value === this.ERROR_CODES.FETCH_ERROR){
message.channel.send(`Failed to download the provided image, got error '${downloadResult.errorMessage}'`);
}else if (downloadResult.value === this.ERROR_CODES.HTTP_ERROR){
message.channel.send(`Failed to download the provided image, got response status '${downloadResult.errorMessage}'`);
}else if(downloadResult.value === this.ERROR_CODES.NOT_IMAGE){
message.channel.send(`The provided url was not an image.`)
}
if(await this.downloadImage(url, path.resolve(directory, "input.png")) != 0){
message.channel.send("Something went wrong during the download.\nThe link might be unreachable for the bot or it's not an image.")
fs.rmSync(`${directory}`, {recursive: true})
return;
}
const commandArgs = [tdossTemplate, "(", `${directory}/input.png`, "-resize", "800x800^", "-gravity", "center", "-extent", "1000x1000", ")", "-compose", "dst-over", "-composite", `${directory}/tdoss_result.png`]
if (executeCommand("magick", commandArgs).error === true) {
const command = `magick ${dataDir}/tdoss_template.png \\( ${directory}/input.png -resize 800x800^ -gravity center -extent 1000x1000 \\) -compose dst-over -composite ${directory}/tdoss_result.png`;
if (this.executeCommand(command).error === true) {
message.channel.send("Something went wrong during image manipulation.\nTry again and if it keeps happening, contact the owner of the bot.")
fs.rmSync(`${directory}`, {recursive: true})
return
@ -75,26 +66,30 @@ module.exports = {
await message.channel.send({files: [final_image]})
fs.rmSync(`${directory}`, {recursive: true})
},
executeCommand(command) {
console.log("Executing:", command)
try {
const output = execSync(command, { encoding: 'utf-8' })
if (output.length != 0)
console.log(output)
} catch (error) {
console.error(`Error executing ${command.split(" ")[0]} command:`, error);
return { error: true };
}
return { error: false };
},
// https://stackoverflow.com/a/77210219
async downloadImage(url, path) {
let res;
try {
res = await fetch(url);
} catch (error) {
return {value: this.ERROR_CODES.FETCH_ERROR, errorMessage: error.cause?.message || error.message};
}
if(!res.ok) return {value: this.ERROR_CODES.HTTP_ERROR, errorMessage: res.status.toString()};
const contentType = res.headers.get('content-type');
if(!contentType || !contentType.startsWith("image")) return {value: this.ERROR_CODES.NOT_IMAGE, errorMessage: contentType || "No content-type header"};
const res = await fetch(url);
if(!res.ok) return 1;
if(!res.headers.get('content-type').startsWith("image")) return 2;
const stream = Readable.fromWeb(res.body)
await writeFile(path, stream);
return {value: this.ERROR_CODES.SUCCESS, errorMessage: ""};
},
ERROR_CODES: {
SUCCESS: 0,
HTTP_ERROR: 1,
NOT_IMAGE: 2,
FETCH_ERROR: 3
return 0;
}
}

View file

@ -4,11 +4,11 @@ const parseTime = require('../../util/timer/parseTime');
const showTimer = require('../../util/timer/showTimer');
module.exports = {
name: "timer",
description: "Set a timer for a date or time duration.",
description: "Set a timer for a time in minutes.",
moreHelp: ["Usage:"
,"`<prefix>timer [add|create] <time_in_minutes> <message_to_send>`"
,"`<prefix>timer <time>(d|h|m|s|t) <message_to_send>`"
,"`<prefix>timer <future_date> <message_to_send>`"
,"`<prefix>timer <time_in_minutes> <message_to_send>`"
,"`<prefix>timer edit <timer_id> <new_time_in_minutes> <new_message_to_send>` (not implemented)"
,"`<prefix>timer [delete|remove] <timer_id>`"
,"`<prefix>timer show <timer_id>`"
@ -18,8 +18,7 @@ module.exports = {
switch (args[0]) {
case "add":
case "create":
args.shift()
sendText = await createTimer(message, args);
sendText = await createTimer(message, args, false);
break;
case "edit":
sendText = "not implemented yet"
@ -38,8 +37,8 @@ module.exports = {
sendText = "Please specify a time, and a message to send after the timer has finished";
break;
}
if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000))) || !isNaN(Date.parse(args[0])))
sendText = await createTimer(message, args);
if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000))))
sendText = await createTimer(message, args, true);
break;
}
message.channel.send(sendText);

View file

@ -1,28 +0,0 @@
module.exports = {
name: 'x',
description: 'Replaces X/Twitter links with fxtwitter and deletes the original message',
execute({message, args}) {
const noUrlErr = "You need to provide an X or twitter link to use this command."
if(args.length < 1) {
message.channel.send(noUrlErr);
return;
}
let replacedLink = "";
const regex = /(?<=\/)(x|twitter)\.com/g
if(args[0].startsWith("https://") || args[0].startsWith("http://")){
replacedLink = args[0].replace(regex, "fxtwitter.com");
}
if(replacedLink.length === 0 || args[0] === replacedLink){
message.channel.send(noUrlErr);
return;
}
message.channel.send(replacedLink);
try{
message.delete()
}catch(err){
console.error(`${this.name}: An error occurred while trying to delete the original message.`)
console.error(err);
}
}
};

3585
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,8 @@
"main": "server.js",
"dependencies": {
"@zuzak/owo": "^1.14.1",
"discord.js": "^14.21.0",
"dotenv": "^17.2.1",
"discord.js": "^14.16.3",
"dotenv": "^16.4.5",
"seedrandom": "^3.0.5",
"sqlite3": "^5.1.6"
},
@ -20,7 +20,7 @@
"author": "SileNce5k",
"license": "UNLICENSE",
"devDependencies": {
"@types/jest": "^30.0.0",
"jest": "^30.0.5"
"@types/jest": "^29.5.14",
"jest": "^29.7.0"
}
}

Some files were not shown because too many files have changed in this diff Show more