Compare commits

..
Author SHA1 Message Date
f5819e4df0
Compare with strict equality
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 17:50:29 +02:00
e15eaf38b0
Don't escape parenthesis
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 17:47:06 +02:00
924a4b82fd
Separate executeCommand into its own util function
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 17:14:51 +02:00
21 changed files with 514 additions and 706 deletions

View file

@ -16,6 +16,6 @@ jobs:
with: with:
node-version: 'latest' node-version: 'latest'
- name: install dependencies - name: install dependencies
run: npm ci run: npm install
- name: run tests - name: run tests
run: npm test run: npm test

View file

@ -1,5 +1,4 @@
const calculateReloaded = require("../../util/calculateReloaded"); const calculateReloaded = require("../../util/calculateReloaded");
const executeCommand = require("../../util/executeCommand");
const reloadCommands = require("../../util/reloadCommands"); const reloadCommands = require("../../util/reloadCommands");
module.exports = { module.exports = {
@ -28,12 +27,10 @@ module.exports = {
let commitCount = stdout.split(/\r\n|\r|\n/).length - 1 let commitCount = stdout.split(/\r\n|\r|\n/).length - 1
sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}` sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}`
if(sendText.length >= 2000){ if(sendText.length >= 2000){
sendText = sendText.slice(1955) sendText.slice(1955)
sendText = `${sendText}\n... Message is too long to show everything` sendText = `${sendText}\n... Message is too long to show everything`
} }
message.channel.send(sendText) 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) if (err) console.log(stderr)
}) })
} }

View file

@ -12,11 +12,10 @@ module.exports = {
let descriptionArr = [`Name: ${client.user.username}`, let descriptionArr = [`Name: ${client.user.username}`,
`Prefix: ${prefix}`, `Prefix: ${prefix}`,
`Total Servers: ${guildInfo.guildCount}`, `Total Servers: ${guildInfo.guildCount}`,
`Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`, `Total Members: ${guildInfo.totalMembers}`,
`Total Commands: ${client.commands.size}`, `Total Commands: ${client.commands.size}`,
`Creation Date: ${getCreationDate(client)}`, `Creation Date: ${getCreationDate(client)}`,
`Source: [Click Here](https://github.com/SileNce5k/discord_bot)`, `Source: [Click Here](https://github.com/SileNce5k/discord_bot)`
`Current Version: ${client.githash}`
] ]
let description = ""; let description = "";

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 user = message.guild.members.cache.get(info);
let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096}); let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096});
let globalPfp = user.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){ if(guildPfp != null){
sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`; sendText = `${sendText}\nGuild pfp:\n${guildPfp}`;
} }
message.channel.send(sendText) message.channel.send(sendText)

View file

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

View file

@ -87,13 +87,11 @@ module.exports = {
if(sendText.embed != null){ if(sendText.embed != null){
let parse = parseMention(message.author.id, message.guild) let parse = parseMention(message.author.id, message.guild)
let user = message.guild.members.cache.get(parse); let user = message.guild.members.cache.get(parse);
if(!sendText.embed.data.color){
let roleColor = 15788778; let roleColor = 15788778;
if (user.roles.color) { if (user.roles.color) {
roleColor = user.roles.color.color; roleColor = user.roles.color.color;
} }
sendText.embed.setColor(roleColor); sendText.embed.setColor(roleColor);
}
message.channel.send({embeds :[sendText.embed]}) message.channel.send({embeds :[sendText.embed]})
}else{ }else{
message.channel.send(sendText.text.replaceAll("<prefix>", prefix)); 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,7 +1,6 @@
module.exports = { module.exports = {
name: 'say', name: 'say',
description: 'Repeats arguments', description: 'Repeats arguments',
admin: true,
execute({message, args}) { execute({message, args}) {
if(args.length == 0) if(args.length == 0)

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);
}
}
};

924
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,8 @@
"main": "server.js", "main": "server.js",
"dependencies": { "dependencies": {
"@zuzak/owo": "^1.14.1", "@zuzak/owo": "^1.14.1",
"discord.js": "^14.21.0", "discord.js": "^14.20.0",
"dotenv": "^17.2.1", "dotenv": "^16.5.0",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"sqlite3": "^5.1.6" "sqlite3": "^5.1.6"
}, },
@ -21,6 +21,6 @@
"license": "UNLICENSE", "license": "UNLICENSE",
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"jest": "^30.0.5" "jest": "^30.0.0"
} }
} }

View file

@ -1,7 +1,6 @@
const fs = require('fs'); const fs = require('fs');
const createInitialConfig = require("./util/createInitialConfig") const createInitialConfig = require("./util/createInitialConfig")
const convertJSONToSQL = require('./util/timer/convertJSONToSQL'); const convertJSONToSQL = require('./util/timer/convertJSONToSQL');
const executeCommand = require('./util/executeCommand.js');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
if(!fs.existsSync("./data/config.json")) { if(!fs.existsSync("./data/config.json")) {
createInitialConfig(); createInitialConfig();
@ -57,9 +56,6 @@ client.settings.set("presenceType", presenceType);
client.settings.set("presenceText", presenceText); client.settings.set("presenceText", presenceText);
client.settings.set("globalPrefix", globalPrefix); client.settings.set("globalPrefix", globalPrefix);
const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]);
client.githash = githash.error ? "N/A" : githash.output;
const reloadCommands = require("./util/reloadCommands.js"); const reloadCommands = require("./util/reloadCommands.js");
const onMessage = require('./server/message'); const onMessage = require('./server/message');
const onReady = require('./server/ready'); const onReady = require('./server/ready');

View file

@ -0,0 +1,17 @@
const getGuildInfo = require('../util/getGuildInfo');
test("Testing getGuildCount", () => {
for(let i = 1; i < 200000; i = i+i*30 ){
let client = {guilds: {cache: new Map()}}
client.guilds.cache.each = client.guilds.cache.forEach;
for(let j = 0; j < i; j++){
client.guilds.cache.set(`num: ${j}`, j);
}
expect(getGuildInfo(client).guildCount).toBe(i);
}
})

View file

@ -2,14 +2,13 @@ const { execFileSync } = require('child_process');
module.exports = function(command, commandArgs, verbose=false) { module.exports = function(command, commandArgs, verbose=false) {
if (typeof command !== 'string' || !Array.isArray(commandArgs)) return { error: true }; if (typeof command !== 'string' || !Array.isArray(commandArgs)) return { error: true };
console.log("Executing:", command, commandArgs.join(" ")); console.log("Executing:", command, commandArgs.join(" "));
let output;
try { try {
output = execFileSync(command, commandArgs, {encoding: 'utf8'}) const output = execFileSync(command, commandArgs, {encoding: 'utf8'})
if (output.length !== 0 && verbose) if (output.length !== 0 && verbose)
console.log(output) console.log(output)
} catch (error) { } catch (error) {
console.error(`Error executing ${command} command:`, error); console.error(`Error executing ${command} command:`, error);
return { error: true }; return { error: true };
} }
return { error: false, output}; return { error: false };
} }

View file

@ -1,16 +1,9 @@
module.exports = function(client){ module.exports = function(client){
let guildCount = 0; let guildCount = 0;
let totalMembers = 0; let totalMembers = 0;
const uniqueMembers = new Map();
client.guilds.cache.each(guild => { client.guilds.cache.each(guild => {
guildCount++ guildCount++
totalMembers += guild.memberCount; totalMembers += guild.memberCount;
guild.members.cache.each(member => {
if(!uniqueMembers.has(member.id)){
uniqueMembers.set(member.id, true);
}
})
}); });
const uniqueMemberCount = uniqueMembers.size; return {guildCount: guildCount, totalMembers: totalMembers};
return {guildCount, totalMembers, uniqueMemberCount};
} }

View file

@ -2,11 +2,6 @@ const getNickname = require("../getNickname");
const parseMention = require("../parseMention"); const parseMention = require("../parseMention");
const getFmUsername = require("./getFmUsername"); const getFmUsername = require("./getFmUsername");
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const path = require('path');
const fs = require('fs');
const { writeFile } = require('node:fs/promises')
const executeCommand = require('../executeCommand');
const { Readable } = require('node:stream')
require("dotenv").config(); require("dotenv").config();
module.exports = async function (userID, guild) { module.exports = async function (userID, guild) {
@ -63,22 +58,10 @@ module.exports = async function (userID, guild) {
sendText.text = tracks.errorMsg; sendText.text = tracks.errorMsg;
return sendText; return sendText;
} }
let coverDir = path.resolve(process.cwd(), 'data', 'covers');
let color = "#C27D0E"
const directory = path.resolve(coverDir, Math.floor(new Date).toString())
fs.mkdirSync(directory, {recursive: true})
const coverFile = path.resolve(directory, "cover")
let downloadResult = await downloadImage(tracks[0].cover, coverFile);
if(downloadResult.value === ERROR_CODES.SUCCESS){
const commandArgs = [`${coverFile}.${downloadResult.ext}`, "-resize", "1x1", "txt:-"]
let res = executeCommand("magick", commandArgs);
if(!res.error) color = res.output.split("\n")[1].split(" ")[3].slice(0,7);
}
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })}) .setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setThumbnail(tracks[0].cover) .setThumbnail(tracks[0].cover)
.setColor(color) .setColor(15780145)
.addFields({ .addFields({
name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}` name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}`
},) },)
@ -88,31 +71,8 @@ module.exports = async function (userID, guild) {
},) },)
} }
sendText.embed = embed; sendText.embed = embed;
fs.rmSync(`${directory}`, {recursive: true})
} else { } else {
sendText.text = "You haven't set your last.fm username yet. Use `<prefix>fm set <lastfm_username>` to set it."; sendText.text = "You haven't set your last.fm username yet. Use `<prefix>fm set <lastfm_username>` to set it.";
} }
return sendText; return sendText;
} }
const ERROR_CODES = {
SUCCESS: 0,
HTTP_ERROR: 1,
NOT_IMAGE: 2,
FETCH_ERROR: 3
}
async function downloadImage(url, downloadPath) {
let res;
try {
res = await fetch(url);
} catch (error) {
return {value: ERROR_CODES.FETCH_ERROR, errorMessage: error.cause?.message || error.message};
}
if(!res.ok) return {value: ERROR_CODES.HTTP_ERROR, errorMessage: res.status.toString()};
const contentType = res.headers.get('content-type');
if(!contentType || !contentType.startsWith("image")) return {value: ERROR_CODES.NOT_IMAGE, errorMessage: contentType || "No content-type header"};
const fileExt = contentType.split("/")[1]
const stream = Readable.fromWeb(res.body)
await writeFile(`${downloadPath}.${fileExt}`, stream);
return {value: ERROR_CODES.SUCCESS, errorMessage: "", ext: fileExt};
}

View file

@ -77,8 +77,7 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const maxIterations = data.topalbums.album.length >= 10 ? 10 : data.topalbums.album.length; for(let i = 0; i < 10; i++){
for(let i = 0; i < maxIterations; i++){
let album = {} let album = {}
let currentAlbum = data.topalbums.album[i]; let currentAlbum = data.topalbums.album[i];
album.artist = currentAlbum.artist.name; album.artist = currentAlbum.artist.name;

View file

@ -77,8 +77,7 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const maxIterations = data.topartists.artist.length >= 10 ? 10 : data.topartists.artist.length; for(let i = 0; i < 10; i++){
for(let i = 0; i < maxIterations; i++){
let artist = {} let artist = {}
let currentArtist = data.topartists.artist[i]; let currentArtist = data.topartists.artist[i];
artist.name = currentArtist.name; artist.name = currentArtist.name;

View file

@ -78,8 +78,7 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${lastfmUsername}&period=${option[0]}&api_key=${apiKey}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${lastfmUsername}&period=${option[0]}&api_key=${apiKey}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const maxIterations = data.toptracks.track.length >= 10 ? 10 : data.toptracks.track.length; for(let i = 0; i < 10; i++){
for(let i = 0; i < maxIterations; i++){
let track = {} let track = {}
let currentTrack = data.toptracks.track[i]; let currentTrack = data.toptracks.track[i];
track.artist = currentTrack.artist.name; track.artist = currentTrack.artist.name;

View file

@ -22,8 +22,7 @@ module.exports = function ({presenceText, presenceType, client}) {
guilds: guildInfo.guildCount, guilds: guildInfo.guildCount,
prefix: globalPrefix, prefix: globalPrefix,
uptime: uptimeFormat, uptime: uptimeFormat,
members: guildInfo.totalMembers, members: guildInfo.totalMembers
uniqueMembers: guildInfo.uniqueMemberCount
} }
const regex = /(?<=\${)(.*?)(?=})/g; const regex = /(?<=\${)(.*?)(?=})/g;