Compare commits

...
Author SHA1 Message Date
bc497c7dea
Fix sending links if they didn't get changed
Some checks failed
CI / CI (push) Has been cancelled
2026-02-27 19:55:37 +01:00
d7b8e706b8
Add ig link replacing command
Some checks are pending
CI / CI (push) Waiting to run
2026-02-27 19:28:54 +01:00
7791fa5654
Improve error message when message.delete() fails
Some checks are pending
CI / CI (push) Waiting to run
2026-02-27 19:25:40 +01:00
9796e9245a
Don't send an unchanged link in x command 2026-02-27 19:24:01 +01:00
62b8ebb4cf
Use lookbehind to improve x command regex 2026-02-27 19:23:36 +01:00
62fbb445e2
Send a message if no X/Twitter links are provided
Some checks are pending
CI / CI (push) Waiting to run
2026-02-26 18:30:02 +01:00
1f48a896a5
Add X command that replaces X/Twitter links with fxtwitter
Some checks failed
CI / CI (push) Has been cancelled
2026-02-26 10:58:18 +01:00
94d1b0f297
Make the say command admin only
Some checks are pending
CI / CI (push) Waiting to run
2026-02-26 10:52:40 +01:00
cafcc007c1
Don't specify global pfp when there's only one
All checks were successful
CI / CI (push) Successful in 22s
2026-01-29 22:37:59 +01:00
af4c42009c
Add channel count to serverinfo
All checks were successful
CI / CI (push) Successful in 22s
2025-10-22 09:45:52 +02:00
f1f9e3aaa3
Use correct variables
All checks were successful
CI / CI (push) Successful in 25s
2025-09-07 08:01:09 +02:00
4aaf72e4b4
Limit iterations to listens if less than 10
All checks were successful
CI / CI (push) Successful in 22s
2025-09-07 07:57:20 +02:00
69872ba0d1
Add artist count check to getTopArtists
All checks were successful
CI / CI (push) Successful in 22s
2025-09-07 07:49:10 +02:00
7dab688298
Slice off alpha from imagemagick output
All checks were successful
CI / CI (push) Successful in 22s
2025-09-06 03:47:45 +02:00
SileNce5k
3ee2a80201
Set embed to the average color of the album cover on fm command (#168)
And do not set a color in misc/fm.js when there already is one set.
2025-09-06 03:32:36 +02:00
8655f279f9
Use correct whitelist property
All checks were successful
CI / CI (push) Successful in 1m36s
2025-07-27 14:59:32 +02:00
aa10d6bc27
Fix mc command by converting all info to strings
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 14:55:15 +02:00
3d674f12e0
Fix mc command
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 14:45:23 +02:00
0a8df28a94
Add minecraft command to get server info
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 13:52:53 +02:00
dependabot[bot]
f78f4648d0
Bump dotenv from 17.2.0 to 17.2.1 (#161)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.2.0 to 17.2.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.2.0...v17.2.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 20:05:41 +02:00
dependabot[bot]
7f0029d9f9
Bump jest from 30.0.4 to 30.0.5 (#160)
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.4 to 30.0.5.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.5/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 19:59:22 +02:00
dependabot[bot]
681eca87e0
Bump dotenv from 17.1.0 to 17.2.0 (#159)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.1.0 to 17.2.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.1.0...v17.2.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 06:52:44 +02:00
dependabot[bot]
914aceeb44
Bump dotenv from 17.0.1 to 17.1.0 (#158)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.0.1 to 17.1.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.0.1...v17.1.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-09 04:27:47 +02:00
dependabot[bot]
dc7b49d255
Bump jest from 30.0.3 to 30.0.4 (#157)
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.3 to 30.0.4.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.4/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-04 03:30:50 +02:00
dependabot[bot]
c1d942e204
Bump dotenv from 17.0.0 to 17.0.1 (#156)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.0.0 to 17.0.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.0.0...v17.0.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-02 02:47:58 +02:00
dependabot[bot]
c53b707163
Bump dotenv from 16.6.1 to 17.0.0 (#155)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.6.1 to 17.0.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v16.6.1...v17.0.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 02:07:59 +02:00
dependabot[bot]
9dcc7adab4
Bump dotenv from 16.5.0 to 16.6.1 (#154)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.5.0 to 16.6.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v16.5.0...v16.6.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 16.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-27 23:52:30 +02:00
1de446aede
Use npm ci to install dependencies
All checks were successful
CI / CI (push) Successful in 23s
2025-06-26 23:30:55 +02:00
dependabot[bot]
94467bd57e
Bump discord.js from 14.20.0 to 14.21.0 (#153)
All checks were successful
CI / CI (push) Successful in 23s
Bumps [discord.js](https://github.com/discordjs/discord.js/tree/HEAD/packages/discord.js) from 14.20.0 to 14.21.0.
- [Release notes](https://github.com/discordjs/discord.js/releases)
- [Changelog](https://github.com/discordjs/discord.js/blob/14.21.0/packages/discord.js/CHANGELOG.md)
- [Commits](https://github.com/discordjs/discord.js/commits/14.21.0/packages/discord.js)

---
updated-dependencies:
- dependency-name: discord.js
  dependency-version: 14.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-26 23:25:57 +02:00
dependabot[bot]
be0091546f
Bump jest from 30.0.2 to 30.0.3 (#152)
All checks were successful
CI / CI (push) Successful in 21s
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.2 to 30.0.3.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.3/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 20:22:06 +02:00
6ce4ca6fb8
Add unique member count to botinfo
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:47:30 +02:00
a891b37a31
Update githash properly
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:46:17 +02:00
e79d2a7a7e
Add output to executeCommand return
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:40:44 +02:00
24e019e34f
Add latest git hash to botinfo command
All checks were successful
CI / CI (push) Successful in 21s
2025-06-24 03:38:24 +02:00
9c4a57a098
Assign sliced string 2025-06-24 03:35:54 +02:00
723f14405a
Rename uniqueMemberCount to uniqueMembers
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:29:41 +02:00
b9e0b6abd3
Use has instead of get
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 02:49:52 +02:00
28cc7d6f15
Use const 2025-06-24 02:48:41 +02:00
4f0e948a84
Remove getGuildCount test
This function has been renamed and stuff so I have to rethink how I'll
implement tests for it.
2025-06-24 02:47:01 +02:00
9611046393
Add uniqueMemberCount as a variable in setPresence
Some checks failed
CI / CI (push) Failing after 22s
2025-06-24 02:32:32 +02:00
bbaf2205ad
Add unique members to getGuildInfo 2025-06-24 02:32:10 +02:00
08b52216a9
Compare with strict equality
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 17:51:56 +02:00
SileNce5k
4d42609f89
Separate executeCommand into its own util function 2025-06-21 17:50:07 +02:00
f55b54eb49
Update package-lock.json
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 16:51:17 +02:00
SileNce5k
18c2d9dc18
Add dl command for downloading videos
All checks were successful
CI / CI (push) Successful in 1m25s
2025-06-21 16:47:36 +02:00
23 changed files with 1930 additions and 1166 deletions

View file

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

View file

@ -1,4 +1,5 @@
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 = {
@ -27,10 +28,12 @@ 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.slice(1955) sendText = 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,10 +12,11 @@ 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}`, `Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`,
`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 = "";

103
commands/info/mc.js Normal file
View file

@ -0,0 +1,103 @@
// 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 = `Global pfp:\n${globalPfp}`; let sendText = `${globalPfp}`;
if(guildPfp != null){ if(guildPfp != null){
sendText = `${sendText}\nGuild pfp:\n${guildPfp}`; sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`;
} }
message.channel.send(sendText) message.channel.send(sendText)

View file

@ -15,6 +15,7 @@ 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},
]) ])

71
commands/misc/dl.js Normal file
View file

@ -0,0 +1,71 @@
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

@ -87,11 +87,13 @@ 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));

28
commands/misc/ig.js Normal file
View file

@ -0,0 +1,28 @@
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,6 +1,7 @@
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

@ -3,7 +3,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const { writeFile } = require('node:fs/promises') const { writeFile } = require('node:fs/promises')
const { Readable } = require('node:stream') const { Readable } = require('node:stream')
const executeCommand = require('../../util/executeCommand');
module.exports = { module.exports = {
name: 'tdoss', name: 'tdoss',
@ -60,8 +60,8 @@ module.exports = {
} }
const command = `magick ${tdossTemplate} \\( ${directory}/input.png -resize 800x800^ -gravity center -extent 1000x1000 \\) -compose dst-over -composite ${directory}/tdoss_result.png`; const commandArgs = [tdossTemplate, "(", `${directory}/input.png`, "-resize", "800x800^", "-gravity", "center", "-extent", "1000x1000", ")", "-compose", "dst-over", "-composite", `${directory}/tdoss_result.png`]
if (this.executeCommand(command).error === true) { if (executeCommand("magick", commandArgs).error === true) {
message.channel.send("Something went wrong during image manipulation.\nTry again and if it keeps happening, contact the owner of the bot.") 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}) fs.rmSync(`${directory}`, {recursive: true})
return return
@ -75,20 +75,6 @@ module.exports = {
await message.channel.send({files: [final_image]}) await message.channel.send({files: [final_image]})
fs.rmSync(`${directory}`, {recursive: true}) 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 // https://stackoverflow.com/a/77210219
async downloadImage(url, path) { async downloadImage(url, path) {
let res; let res;

28
commands/misc/x.js Normal file
View file

@ -0,0 +1,28 @@
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);
}
}
};

2599
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.20.0", "discord.js": "^14.21.0",
"dotenv": "^16.5.0", "dotenv": "^17.2.1",
"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.0" "jest": "^30.0.5"
} }
} }

View file

@ -1,6 +1,7 @@
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();
@ -56,6 +57,9 @@ 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

@ -1,17 +0,0 @@
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);
}
})

15
util/executeCommand.js Normal file
View file

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

View file

@ -1,9 +1,16 @@
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 => {
return {guildCount: guildCount, totalMembers: totalMembers}; if(!uniqueMembers.has(member.id)){
uniqueMembers.set(member.id, true);
}
})
});
const uniqueMemberCount = uniqueMembers.size;
return {guildCount, totalMembers, uniqueMemberCount};
} }

View file

@ -2,6 +2,11 @@ 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) {
@ -58,10 +63,22 @@ 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(15780145) .setColor(color)
.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}`
},) },)
@ -71,8 +88,31 @@ 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,7 +77,8 @@ 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 => {
for(let i = 0; i < 10; i++){ const maxIterations = data.topalbums.album.length >= 10 ? 10 : data.topalbums.album.length;
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,7 +77,8 @@ 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 => {
for(let i = 0; i < 10; i++){ const maxIterations = data.topartists.artist.length >= 10 ? 10 : data.topartists.artist.length;
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,7 +78,8 @@ 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 => {
for(let i = 0; i < 10; i++){ const maxIterations = data.toptracks.track.length >= 10 ? 10 : data.toptracks.track.length;
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,7 +22,8 @@ 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;