Compare commits

..
Author SHA1 Message Date
0c45055e0f
Use SQL on owner custom command subcommand
All checks were successful
CI / CI (push) Successful in 21s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 20:19:38 +01:00
345a03a3b0
Use sqlite database for creating new custom commands
All checks were successful
CI / CI (push) Successful in 21s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 19:22:42 +01:00
7702f10587
Use set for listing all custom commands
All checks were successful
CI / CI (push) Successful in 25s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 18:57:48 +01:00
eae1e9130e
Change onmessage to use new custom commands set instead of json file
All checks were successful
CI / CI (push) Successful in 20s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 18:26:59 +01:00
570a8e49b7
Add conversion of custom commands to SQL
All checks were successful
CI / CI (push) Successful in 20s
Lint Codebase / eslint (push) Successful in 15s
2024-12-17 11:10:45 +01:00
904fa25a6d
Add error if prepareBot fails 2024-11-06 09:12:14 +01:00
f28055e024
Refactor server.js
All checks were successful
CI / CI (push) Successful in 16s
Lint Codebase / eslint (push) Successful in 17s
2024-11-06 04:39:19 +01:00
5a4cfd0a5f
Rename database table functions to be clearer
All checks were successful
CI / CI (push) Successful in 19s
Lint Codebase / eslint (push) Successful in 12s
2024-11-06 01:44:16 +01:00
4b53d875e4
Remove njsscan workflow
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 12s
2024-11-05 06:55:47 +01:00
aaa10cb57a
update actions/checkout and actions/setup-node to v4
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 11s
2024-11-05 06:45:08 +01:00
b89b926f9a
Update actions/checkout to v4
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 13s
2024-11-05 06:37:03 +01:00
34c5f08806
Use scopes on switch cases
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 12s
2024-11-05 06:10:56 +01:00
a3e9e1f90c
Add count subcommand to custom command
Some checks failed
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Failing after 12s
Use scopes in each case in the switch statement
2024-11-05 06:07:51 +01:00
bab508e4b9
Add eslint as linter and add linter workflow
Some checks failed
CI / CI (push) Successful in 18s
Lint Codebase / eslint (push) Failing after 12s
2024-11-05 06:03:14 +01:00
85821f5fa3
Update README
All checks were successful
CI / CI (push) Successful in 17s
2024-11-02 22:48:21 +01:00
60 changed files with 2928 additions and 3564 deletions

View file

@ -2,9 +2,6 @@ name: CI
on: [push] on: [push]
permissions:
contents: read
jobs: jobs:
CI: CI:
runs-on: node runs-on: node
@ -16,6 +13,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

22
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Lint Codebase
on: [push]
jobs:
eslint:
runs-on: node
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 'latest'
- name: Install dependencies
run: npm install
- name: Run ESLint
run: npm run lint

View file

@ -1,42 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow integrates njsscan with GitHub's Code Scanning feature
# nodejsscan is a static security code scanner that finds insecure code patterns in your Node.js applications
name: njsscan sarif
on:
push:
branches: [ "master", "devbot" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master", "devbot" ]
schedule:
- cron: '43 12 * * 6'
permissions:
contents: read
jobs:
njsscan:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
runs-on: ubuntu-latest
name: njsscan code scanning
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: nodejsscan scan
id: njsscan
uses: ajinabraham/njsscan-action@v7
with:
args: '. --sarif --output results.sarif || true'
- name: Upload njsscan report
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif

View file

@ -27,7 +27,7 @@ You can also change the global prefix.
You should enter you discord user id, so you can use the admin commands. You should enter you discord user id, so you can use the admin commands.
Every time you want to change something in this file, you have to restart the bot. Every time you want to change something in this file, you have to restart the bot.
If the config is ever changed, you need to either delete it and repeat the steps above or take a look at the util/createInitialConfig.js file. If the config is ever changed, you need to either delete it and repeat the steps above or take a look at the [util/createInitialConfig.js](./util/createInitialConfig.js) file.
## Known issues ## Known issues

View file

@ -1,4 +1,3 @@
const savePresence = require("../../util/savePresence");
const setPresence = require("../../util/setPresence"); const setPresence = require("../../util/setPresence");
module.exports = { module.exports = {
@ -11,20 +10,12 @@ module.exports = {
,"Updates once a minute if custom variables are used." ,"Updates once a minute if custom variables are used."
,"" ,""
,"Custom Variables:" ,"Custom Variables:"
,"${guilds},${prefix},${uptime},{members}"], ,"${guilds},${prefix},${uptime}"],
admin: true, admin: true,
execute({message, client, args, prefix}) { execute({message, client, args, globalPrefix}) {
let forceUpdate = false; const savePresence = require("../../util/savePresence");
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;
}
let presenceType = args[0].toLocaleUpperCase(); let presenceType = args[0].toLocaleUpperCase();
let sendText = "Presence has been set."; let sendText = "Updated presence";
switch (presenceType) { switch (presenceType) {
case "PLAY": case "PLAY":
@ -55,11 +46,8 @@ module.exports = {
const firstArg = args[0].length + 1; const firstArg = args[0].length + 1;
let temp = args.join(" "); let temp = args.join(" ");
let presenceText = temp.slice(firstArg, temp.length) let presenceText = temp.slice(firstArg, temp.length)
setPresence({presenceText: presenceText,presenceType: presenceType, client: client, globalPrefix: globalPrefix});
savePresence(presenceType, presenceText, client); 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); message.channel.send(sendText);

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

@ -1,107 +0,0 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = {
name: 'whitelist',
description: 'Whitelist a command in a specific server, or for a user.',
admin: true,
async execute({message, args, prefix, client}) {
if(args.length < 3){
message.channel.send(`You need to supply the command and guild that you want to whitelist\n\`${prefix}whitelist <user|guild> <command> <user_id|guild_id>\``);
return;
}
let option = args[0];
let command = args[1];
let guildOrUser = args[2];
if(option === "guild" || option === "server"){
if(guildOrUser === "this") guildOrUser = message.guild.id;
const whitelistedCommands = client.whitelist.guild.get(guildOrUser);
if(whitelistedCommands && whitelistedCommands.includes(command)){
message.channel.send("Command is already whitelisted in that server.")
return;
}
// TODO: First check if the bot has access to that guild before whitelisting.
const databasePath = 'data/database.db'
const db = new sqlite3.Database(databasePath)
let err = false;
await new Promise((resolve, reject)=>{
db.run(`INSERT INTO guild_whitelist (
guildId,
command,
dateAdded
) VALUES (?, ?, ?)`,
[
guildOrUser,
command,
new Date().getTime()
], function (error) {
if (error) {
console.error(error);
err = true;
resolve();
}else{
resolve();
}
});
})
db.close();
if(!err){
if(whitelistedCommands){
whitelistedCommands.push(command);
client.whitelist.guild.set(guildOrUser, whitelistedCommands);
}else {
client.whitelist.guild.set(guildOrUser, command);
}
message.channel.send(`"${command}" has been whitelisted in that server.`)
} else message.channel.send("Could not whitelist the server with that command. Check the logs.")
return;
}else if (option === "user"){
const whitelistedCommands = client.whitelist.user.get(guildOrUser);
if(whitelistedCommands && whitelistedCommands.includes(command)){
message.channel.send("Command is already whitelisted for that user.")
return;
}
const databasePath = 'data/database.db'
const db = new sqlite3.Database(databasePath)
let err = false;
await new Promise((resolve, reject)=>{
db.run(`INSERT INTO user_whitelist (
userId,
command,
dateAdded
) VALUES (?, ?, ?)`,
[
guildOrUser,
command,
new Date().getTime()
], function (error) {
if (error) {
console.error(error);
err = true;
resolve();
}else{
resolve();
}
});
})
db.close();
if(!err){
if(whitelistedCommands){
whitelistedCommands.push(command);
client.whitelist.user.set(guildOrUser, whitelistedCommands);
}else {
client.whitelist.user.set(guildOrUser, command);
}
message.channel.send(`"${command}" has been whitelisted for ${guildOrUser}.`)
} else message.channel.send("Could not whitelist the server with that command. Check the logs.")
return;
}
// TODO: Add ability to remove server from whitelist.
}
};

View file

@ -1,6 +1,6 @@
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const getCreationDate = require('../../util/getCreationDate'); const getCreationDate = require('../../util/getCreationDate');
const getGuildInfo = require('../../util/getGuildInfo'); const getGuildCount = require('../../util/getGuildCount');
@ -8,28 +8,15 @@ module.exports = {
name: 'botinfo', name: 'botinfo',
description: 'Shows information about the bot', description: 'Shows information about the bot',
execute({message, client, prefix}) { execute({message, client, prefix}) {
let guildInfo = getGuildInfo(client) let guildCount = getGuildCount(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`;
})
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(15780145) .setColor(15780145)
.setTitle("Information about bot") .setTitle("Information about bot")
.setTimestamp() .setTimestamp()
.setAuthor({name: client.user.username, iconURL: client.user.avatarURL({ format: 'png', dynamic: true, size: 2048 })}) .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]}) message.channel.send({embeds :[embed]})

View file

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

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const getSubdirFiles = require('../../util/getSubdirFiles'); const getSubdirFiles = require('../../util/getSubdirFiles');
@ -13,10 +12,10 @@ module.exports = {
execute({ message, args, prefix, client }) { execute({ message, args, prefix, client }) {
let commands = "" let commands = ""
let commandFiles = getSubdirFiles('commands/') let commandFiles = getSubdirFiles('commands/')
let x = false;
let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`; let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`;
let iteration = 0; let iteration = 0;
let num_in_args = false; let num_in_args = false;
let added_commands = 0;
let page = -1; let page = -1;
@ -31,6 +30,7 @@ module.exports = {
fieldName = fieldName.replace("[[page]]", page); fieldName = fieldName.replace("[[page]]", page);
const max_commands = iteration + 10; const max_commands = iteration + 10;
if (x) return;
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(15780145) .setColor(15780145)
@ -51,14 +51,12 @@ module.exports = {
if (!command.admin){ if (!command.admin){
if(start < iteration){ if(start < iteration){
}else{ }else{
added_commands++
commands = commands + `${prefix}${command.name} | ${command.description}\n`; commands = commands + `${prefix}${command.name} | ${command.description}\n`;
iteration++ iteration++
} }
start++; start++;
} }
}else if(args[0] === command.name){ }else if(args[0] === command.name){
fieldName = "Description";
commands = commands + `${prefix}${command.name}\n` commands = commands + `${prefix}${command.name}\n`
embed.setTitle(command.name.charAt(0).toUpperCase() + command.name.slice(1)) embed.setTitle(command.name.charAt(0).toUpperCase() + command.name.slice(1))
if(command.moreHelp){ if(command.moreHelp){
@ -66,6 +64,7 @@ module.exports = {
commands = commands + `${element}\n` commands = commands + `${element}\n`
}); });
} else { } else {
fieldName = "Description";
commands = commands + `${command.description}`; commands = commands + `${command.description}`;
} }
break; break;

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

@ -17,13 +17,10 @@ module.exports = {
"<prefix>custom remove - Delete your custom commands.", "<prefix>custom remove - Delete your custom commands.",
"<prefix>custom owner - check owner of custom command", "<prefix>custom owner - check owner of custom command",
"<prefix>custom list - list all custom commands", "<prefix>custom list - list all custom commands",
"<prefix>custom variables - list all variables you can use" "<prefix>custom variables - list all variables you can use",
"<prefix>custom count - display the amount of custom commands"
], ],
execute({message, args, client, prefix, owners}) { async execute({message, args, client, prefix, owners}) {
const customPath = 'data/customCommands.json';
if(!fs.existsSync(customPath)){
fs.writeFileSync(customPath,"[]")
}
let sendText; let sendText;
let isEmbed = false; let isEmbed = false;
if (args){ if (args){
@ -31,19 +28,21 @@ module.exports = {
let customMessage = args.slice(2, args.length).join(" "); let customMessage = args.slice(2, args.length).join(" ");
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
case "add": case "add":{
if(!customMessage) { if(!customMessage) {
message.channel.send("Message can't be empty"); message.channel.send("Message can't be empty");
return; return;
} }
sendText = addCustomCommand(customName, customMessage, message.author.id); sendText = await addCustomCommand(client, customName, customMessage, message.author.id);
break; break;
}
case "remove": case "remove":
case "delete": case "delete":{
sendText = deleteCustomCommand(customName, message.author.id, owners); sendText = deleteCustomCommand(customName, message.author.id, owners);
break; break;
case "owner": }
let author = getOwnerOfCustomCommand(customName); case "owner":{
let author = await getOwnerOfCustomCommand(customName);
let user; let user;
if(!author) if(!author)
sendText = `${customName} does not exist` sendText = `${customName} does not exist`
@ -55,10 +54,11 @@ module.exports = {
} }
break; break;
case "list": }
case "list": {
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
sendText = getAllCustomCommands(); sendText = getAllCustomCommands(client);
if(sendText != ""){ if(sendText !== ""){
embed.setColor(15780145) embed.setColor(15780145)
embed.addFields( embed.addFields(
{ name: "Custom commands", value: sendText }, { name: "Custom commands", value: sendText },
@ -67,13 +67,16 @@ module.exports = {
isEmbed = true; isEmbed = true;
}else sendText = "NO CUSTOM COMMANDS" }else sendText = "NO CUSTOM COMMANDS"
break; break;
case "variables": }
case "variables": {
sendText = "The variables you can use are:\n<prefix>\n<globalPrefix>\n<username>\n<nickname>\n<user_id>\n<guild_name>\n<guild_id>" sendText = "The variables you can use are:\n<prefix>\n<globalPrefix>\n<username>\n<nickname>\n<user_id>\n<guild_name>\n<guild_id>"
break; break;
case "edit": }
case "edit": {
sendText = editCustomCommand(customName, message.author.id, customMessage) sendText = editCustomCommand(customName, message.author.id, customMessage)
break; break;
case "show": }
case "show": {
let json = fs.readFileSync(customPath, 'utf8'); let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json) let customCommands = JSON.parse(json)
sendText = "Command not found." sendText = "Command not found."
@ -83,14 +86,24 @@ module.exports = {
} }
}); });
break; break;
case "rename": }
case "rename": {
sendText = renameCustomCommand(customName, args[2], message.author.id); sendText = renameCustomCommand(customName, args[2], message.author.id);
break; break;
default: }
case "count": {
const customPath = './data/customCommands.json';
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
sendText = `There are ${customCommands.length} custom commands in total`;
break;
}
default: {
sendText = `Argument not recognized.\n"${prefix}help custom" to see all arguments you can use.` sendText = `Argument not recognized.\n"${prefix}help custom" to see all arguments you can use.`
break; break;
} }
} }
}
if(isEmbed) message.channel.send({embeds :[sendText]}) if(isEmbed) message.channel.send({embeds :[sendText]})
else message.channel.send(sendText); else message.channel.send(sendText);
} }

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

@ -19,7 +19,8 @@ module.exports = {
try { try {
num = num.slice(0, -1); num = num.slice(0, -1);
} catch (e) { } catch (e) {
message.channel.send("There was an error."); console.error(e)
message.channel.send("There was an error. Check the logs");
return; return;
} }
message.channel.send("https://cdn.discordapp.com/emojis/" + num + extension); message.channel.send("https://cdn.discordapp.com/emojis/" + num + extension);

View file

@ -4,12 +4,6 @@ const getCurrentCover = require("../../util/lastfm/getCurrentCover");
const getTopTracks = require("../../util/lastfm/getTopTracks"); const getTopTracks = require("../../util/lastfm/getTopTracks");
const help = require("../info/help"); const help = require("../info/help");
const parseMention = require("../../util/parseMention"); const parseMention = require("../../util/parseMention");
const roast = require("../../util/lastfm/roast");
const getTopArtists = require("../../util/lastfm/getTopArtists");
const getTopAlbums = require("../../util/lastfm/getTopAlbums");
const {EmbedBuilder} = require('discord.js');
const getNickname = require('../../util/getNickname');
const isWhitelisted = require("../../util/isWhitelisted");
module.exports = { module.exports = {
name: 'fm', name: 'fm',
description: 'Last fm commands. See `<prefix>help fm` for more info.', description: 'Last fm commands. See `<prefix>help fm` for more info.',
@ -17,11 +11,8 @@ module.exports = {
"They behave the same (for example: `<prefix>fmtt` and `<prefix>fm tt`)", "They behave the same (for example: `<prefix>fmtt` and `<prefix>fm tt`)",
"Set username: `<prefix>fmset <lastfm_username>`", "Set username: `<prefix>fmset <lastfm_username>`",
"Get current scrobble: `<prefix>fm`", "Get current scrobble: `<prefix>fm`",
"Get top artists `<prefix>fmas`",
"Get top albums: `<prefix>fmabl`",
"Get top tracks: `<prefix>fmtt`", "Get top tracks: `<prefix>fmtt`",
"Get album cover for current scrobble: `<prefix>fmcover`", "Get album cover for current scrobble: `<prefix>fmcover`",
"Get a roast from an LLM using your top artists and albums: `<prefix>fmroast`"
], ],
async execute({ message, args, prefix, client }) { async execute({ message, args, prefix, client }) {
let sendText = {text: "Something went wrong.", embed: null}; let sendText = {text: "Something went wrong.", embed: null};
@ -37,46 +28,9 @@ module.exports = {
args.shift(); args.shift();
sendText = await getTopTracks(message.author.id, args, message.guild); sendText = await getTopTracks(message.author.id, args, message.guild);
break; 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": case "cover":
sendText = await getCurrentCover(message.author.id, message.guild); sendText = await getCurrentCover(message.author.id, message.guild);
break; break;
case "roast":
const commandName = "fmroast"
if(!isWhitelisted.isWhitelisted(commandName, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)) {
message.channel.send("fmroast has not been whitelisted for you or this server.")
return;
}
let topArtists = await getTopArtists(message.author.id, ["yearly"], message.guild, true);
let topAlbums = await getTopAlbums(message.author.id, ["yearly"], message.guild, true);
message.channel.sendTyping();
let result = await roast(topArtists, topAlbums);
let parse = parseMention(message.author.id, message.guild)
let user = message.guild.members.cache.get(parse);
let nickname = getNickname(user, message.guild)
if (nickname == null) {
nickname = user.user.username;
}
const possesive = nickname.at(nickname.length - 1) === "s" ? "'" : "'s"
const embed = new EmbedBuilder()
.setAuthor({name: `${nickname}${possesive} AI roast`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setColor(15780145)
.setDescription(result);
sendText.embed = embed;
break;
default: default:
sendText.text = `${args[0]} is not a valid subcommand.\nSee \`${prefix}help fm\` for more info.`; sendText.text = `${args[0]} is not a valid subcommand.\nSee \`${prefix}help fm\` for more info.`;
break; break;
@ -87,13 +41,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,11 +1,7 @@
module.exports = { module.exports = {
name: 'ping', name: 'ping',
description: 'Just ping.', description: 'Just ping.',
async execute({message, client}) { execute({message, client}) {
let apiLatency = client.ws.ping; message.channel.send(`Pong.\n${client.ws.ping}ms`)
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.`);
} }
}; };

View file

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

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,100 +0,0 @@
const { execSync } = require('child_process');
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 url = "";
if(message.attachments.size > 0){
url = message.attachments.first().url
} else if(args[0] && args[0].startsWith("https://") ){
url = args[0];
}else if(message.reference){
let referencedMessage = await message.fetchReference();
if(referencedMessage.attachments.size > 0){
url = await referencedMessage.attachments.first().url;
}
else {
message.channel.send("The message you replied to doesn't have any attachments.");
return;
}
}
else {
let lastMessages = await message.channel.messages.fetch({limit: 20, cache: false});
let lastMessageWithAttachment = lastMessages.find(m => m.attachments.size > 0);
if(!lastMessageWithAttachment){
message.channel.send("Couldn't find an image to use this command on.")
fs.rmSync(`${directory}`, {recursive: true})
return
}
url = lastMessageWithAttachment.attachments.first().url;
}
// 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.`)
}
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) {
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
}
let final_image = {
attachment: path.resolve(`${directory}/tdoss_result.png`),
name: "tdoss_result.png"
}
await message.channel.send({files: [final_image]})
fs.rmSync(`${directory}`, {recursive: true})
},
// 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 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
}
}

View file

@ -4,11 +4,11 @@ const parseTime = require('../../util/timer/parseTime');
const showTimer = require('../../util/timer/showTimer'); const showTimer = require('../../util/timer/showTimer');
module.exports = { module.exports = {
name: "timer", name: "timer",
description: "Set a timer for a date or time duration.", description: "Set a timer for a time in minutes.",
moreHelp: ["Usage:" moreHelp: ["Usage:"
,"`<prefix>timer [add|create] <time_in_minutes> <message_to_send>`" ,"`<prefix>timer [add|create] <time_in_minutes> <message_to_send>`"
,"`<prefix>timer <time>(d|h|m|s|t) <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 edit <timer_id> <new_time_in_minutes> <new_message_to_send>` (not implemented)"
,"`<prefix>timer [delete|remove] <timer_id>`" ,"`<prefix>timer [delete|remove] <timer_id>`"
,"`<prefix>timer show <timer_id>`" ,"`<prefix>timer show <timer_id>`"
@ -17,31 +17,35 @@ module.exports = {
let sendText = "This should never happen."; let sendText = "This should never happen.";
switch (args[0]) { switch (args[0]) {
case "add": case "add":
case "create": case "create": {
args.shift() sendText = await createTimer(message, args, false);
sendText = await createTimer(message, args);
break; break;
case "edit": }
case "edit": {
sendText = "not implemented yet" sendText = "not implemented yet"
break; break;
}
case "delete": case "delete":
case "remove": case "remove": {
let timerID = args[1]; let timerID = args[1];
sendText = await deleteTimer(message.author.id, timerID); sendText = await deleteTimer(message.author.id, timerID);
break; break;
case "show": }
case "show": {
sendText = await showTimer(message.author.id, args[1]); sendText = await showTimer(message.author.id, args[1]);
break; break;
default: }
default: {
sendText = "not sure what you mean"; sendText = "not sure what you mean";
if(args.length < 2){ if(args.length < 2){
sendText = "Please specify a time, and a message to send after the timer has finished"; sendText = "Please specify a time, and a message to send after the timer has finished";
break; break;
} }
if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000))) || !isNaN(Date.parse(args[0]))) if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000))))
sendText = await createTimer(message, args); sendText = await createTimer(message, args, true);
break; break;
} }
}
message.channel.send(sendText); 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);
}
}
};

32
eslint.config.mjs Normal file
View file

@ -0,0 +1,32 @@
import jest from "eslint-plugin-jest";
import globals from "globals";
export default [{
plugins: {
jest,
},
languageOptions: {
globals: {
...globals.jest,
...jest.environments.globals.globals,
},
},
rules: {
"no-fallthrough": "error",
"no-case-declarations": "error",
"no-unused-vars": "warn"
},
},
];
// /** @type {import('eslint').Linter.Config[]} */
// export default [
// {files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
// {languageOptions: { globals: globals.node }},
// pluginJs.configs.recommended,
// ];

4600
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,13 +5,14 @@
"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.16.3",
"dotenv": "^17.2.1", "dotenv": "^16.4.5",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"sqlite3": "^5.1.6" "sqlite3": "^5.1.6"
}, },
"scripts": { "scripts": {
"test": "jest" "test": "jest",
"lint": "npx eslint ."
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -20,7 +21,11 @@
"author": "SileNce5k", "author": "SileNce5k",
"license": "UNLICENSE", "license": "UNLICENSE",
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0", "@eslint/js": "^9.14.0",
"jest": "^30.0.5" "@types/jest": "^29.5.14",
"eslint": "^9.14.0",
"eslint-plugin-jest": "^28.8.3",
"globals": "^15.12.0",
"jest": "^29.7.0"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

153
server.js
View file

@ -1,29 +1,15 @@
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 convertTimerJSONToSQL = require('./util/timer/convertTimerJSONToSQL.js');
const executeCommand = require('./util/executeCommand.js'); const createTimersTable = require('./server/createDatabaseTables/createTimersTable');
const createCustomCommandsTable = require('./server/createDatabaseTables/createCustomCommandsTable.js');
const convertCustomCommandsJSONToSQL = require('./server/convertCustomCommandsJSONToSQL.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();
} }
async function checkAndConvertJSONToSQL(){
process.stdout.write("Checking if timers.json exists... ")
if(fs.existsSync("./data/timers.json")){
process.stdout.write(true + "\n")
await createDatabaseTables();
await convertJSONToSQL();
fs.renameSync('data/timers.json', 'data/timers.json.old');
console.log("Renamed timers.json to timers.json.old");
}else{
process.stdout.write(false + "\n")
}
}
const createDatabaseTables = require('./server/createDatabaseTables');
const createLastfmTable = require('./server/createLastfmTable');
const createAndLoadWhitelistTable = require('./server/createAndLoadWhitelistTable.js')
createLastfmTable();
checkAndConvertJSONToSQL();
const { Collection, Client, GatewayIntentBits, Partials } = require('discord.js'); const { Collection, Client, GatewayIntentBits, Partials } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds, const client = new Client({ intents: [GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
@ -31,7 +17,50 @@ const client = new Client({ intents: [GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildPresences GatewayIntentBits.GuildPresences
], partials: [Partials.Channel] }); ], partials: [Partials.Channel] });
const {
async function checkAndConvertJSONToSQL(){
process.stdout.write("Checking if timers.json exists... ")
if(fs.existsSync("./data/timers.json")){
process.stdout.write(true + "\n")
await convertTimerJSONToSQL();
fs.renameSync('data/timers.json', 'data/timers.json.old');
console.log("Renamed timers.json to timers.json.old");
}else{
process.stdout.write(false + "\n")
}
process.stdout.write("Checking if customCommands.json exists... ")
if(fs.existsSync('./data/customCommands.json')){
process.stdout.write(true + "\n")
await convertCustomCommandsJSONToSQL();
fs.renameSync('data/customCommands.json', 'data/customCommands.json.old');
}else{
process.stdout.write(false + "\n")
}
}
async function loadcustomCommands() {
const db = new sqlite3.Database('data/database.db');
client.customCommands = new Collection();
db.all("SELECT * FROM customCommands", (error, commands) => {
if(error){
console.error("Error while loading custom commands:\n",error.message)
}else {
commands.forEach(command => {
client.customCommands.set(command.customName, command.customMessage);
})
}
})
}
function startBot(){
const {
globalPrefix, globalPrefix,
token, token,
loginMessage, loginMessage,
@ -40,47 +69,71 @@ const {
owners, owners,
presenceType, presenceType,
presenceText presenceText
} = require('./data/config.json'); } = require('./data/config.json');
client.settings = new Collection(); client.settings = new Collection();
client.commands = new Collection(); client.commands = new Collection();
client.serverPrefixes = new Collection(); client.serverPrefixes = new Collection();
client.whitelist = {
guild: new Collection(),
user: new Collection()
}
process.env.TZ = "UTC";
createAndLoadWhitelistTable(client.whitelist);
client.settings.set("presenceType", presenceType); client.settings.set("presenceType", presenceType);
client.settings.set("presenceText", presenceText); client.settings.set("presenceText", presenceText);
client.settings.set("globalPrefix", globalPrefix);
const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]); const reloadCommands = require("./util/reloadCommands.js");
client.githash = githash.error ? "N/A" : githash.output; const onMessage = require('./server/message');
const onReady = require('./server/ready');
const reloadCommands = require("./util/reloadCommands.js"); reloadCommands(client)
const onMessage = require('./server/message');
const onReady = require('./server/ready');
reloadCommands(client) client.once('ready', () => {
client.once('ready', () => {
onReady(client, enableLoginMessage, loginChannel, loginMessage) onReady(client, enableLoginMessage, loginChannel, loginMessage)
}); });
client.once('reconnecting', () => { client.once('reconnecting', () => {
console.log('Reconnecting!'); console.log('Reconnecting!');
}); });
client.once('disconnect', () => { client.once('disconnect', () => {
console.log('Disconnect!'); console.log('Disconnect!');
}); });
client.on('messageCreate', async message => { client.on('messageCreate', async message => {
onMessage(client, owners, message); onMessage(client, owners, message, globalPrefix);
}); });
client.login(token); client.login(token);
}
async function prepareBot(){
const createLastfmTable = require('./server/createDatabaseTables/createLastfmTable');
const taskGroups = [
[
createTimersTable,
createLastfmTable,
createCustomCommandsTable
],
[
checkAndConvertJSONToSQL
]
];
(async () => {
for (const taskGroup of taskGroups) {
await Promise.all(taskGroup.map(task => task()));
}
loadcustomCommands()
})();
}
prepareBot().then( () => {
startBot();
}).catch(error => {
console.error("Error preparing the bot:\n", error)
})

View file

@ -0,0 +1,28 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = async function () {
const customCommands = require('../data/customCommands.json')
const db = new sqlite3.Database('data/database.db');
return new Promise((resolve, reject) => {
customCommands.forEach(command => {
const isDeleted = false;
db.run(`INSERT INTO customCommands (
customName,
customMessage,
author,
isDeleted
) VALUES (?, ?, ?, ?)`, [command.customName, command.customMessage, command.author, isDeleted], function (error) {
if (error) {
console.error(`Error while converting customCommands.json to SQL: ${error}`)
reject(error);
}
})
});
db.close();
console.log("Converted customCommands.json to SQL successfully.");
resolve();
})
}

View file

@ -1,95 +0,0 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = async function (clientWhitelist) {
const db = new sqlite3.Database('data/database.db');
await createGuildWhitelist(db);
await createUserWhitelist(db);
db.close();
loadWhitelist(clientWhitelist);
}
async function createGuildWhitelist(db) {
await new Promise ((resolve, reject)=>{
db.run(
`CREATE TABLE IF NOT EXISTS guild_whitelist (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
guildId TEXT,
command TEXT,
dateAdded INTEGER)`,
(err) => {
if (err) {
db.close();
console.error(`Error while creating table 'guild_whitelist': ${err}`);
reject(err);
} else {
console.log("Table 'guild_whitelist' created successfully.");
resolve();
}
}
);
});
}
async function createUserWhitelist(db) {
await new Promise ((resolve, reject)=>{
db.run(
`CREATE TABLE IF NOT EXISTS user_whitelist (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
userId TEXT,
command TEXT,
dateAdded INTEGER)`,
(err) => {
if (err) {
db.close();
console.error(`Error while creating table 'user_whitelist': ${err}`);
reject(err);
} else {
console.log("Table 'user_whitelist' created successfully.");
resolve();
}
}
);
});
}
async function loadWhitelist(clientWhitelist) {
const db = new sqlite3.Database('data/database.db');
let guildRows = await new Promise((resolve) => {
db.all(`SELECT * FROM guild_whitelist`, function (error, rows){
if(error){
console.error("Failed to read guild_whitelist table")
console.error(error);
resolve([]);
}else{
resolve(rows);
}
});
});
let userRows = await new Promise((resolve) => {
db.all(`SELECT * FROM user_whitelist`, function (error, rows){
if(error){
console.error("Failed to read user_whitelist table")
console.error(error);
resolve([]);
}
})
})
guildRows.forEach(row => {
if(clientWhitelist.guild.has(row.guildId)){
let oldEntry = clientWhitelist.guild.get(row.guildId);
oldEntry.push(row.command)
clientWhitelist.guild.set(row.guildId, oldEntry)
}else {
clientWhitelist.guild.set(row.guildId, [row.command])
}
});
userRows.forEach(row => {
if(clientWhitelist.user.has(row.userId)){
let oldEntry = clientWhitelist.user.get(row.userId);
oldEntry.push(row.command)
clientWhitelist.user.set(row.userId, oldEntry)
}else {
clientWhitelist.user.set(row.userId, [row.command])
}
});
db.close();
}

View file

@ -0,0 +1,24 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = async function () {
const db = new sqlite3.Database('data/database.db');
return new Promise ((resolve, reject)=>{
db.run(
`CREATE TABLE IF NOT EXISTS customCommands (
customName TEXT PRIMARY KEY,
customMessage TEXT,
author TEXT,
isDeleted INTEGER)`,
(err) => {
if (err) {
console.error(`Error while creating table 'customCommands': ${err}`);
reject(err);
} else {
console.log("Table 'customCommands' created successfully.");
resolve();
}
db.close();
}
);
})
}

View file

@ -1,6 +1,8 @@
const isWhitelisted = require('../util/isWhitelisted') const fs = require('fs');
module.exports = function(client, owners, message){ const customReplaceWithVariables = require('../util/custom_commands/customReplaceWithVariables');
let prefix = client.settings.get("globalPrefix");
module.exports = function(client, owners, message, globalPrefix){
let prefix = globalPrefix;
let serverPrefix = client.serverPrefixes.get(message.guild.id); let serverPrefix = client.serverPrefixes.get(message.guild.id);
if (serverPrefix) { if (serverPrefix) {
prefix = serverPrefix; prefix = serverPrefix;
@ -20,15 +22,18 @@ module.exports = function(client, owners, message){
const commandName = args.shift().toLowerCase(); const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName); const command = client.commands.get(commandName);
if(!command) return; if (!command){
if(command.needsWhitelist && !isWhitelisted.isWhitelisted(command.name, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)){ const command = client.customCommands.get(commandName);
message.channel.send(`\`${command.name}\` is not whitelisted for you or this server. The bot admin needs to whitelist the command for you or this server.`) if(command){
let customMessage = customReplaceWithVariables(command, message, prefix, globalPrefix);
message.channel.send(customMessage);
}
return; return;
} }
if (command.admin && owners.indexOf(message.author.id.toString()) == -1) return; if (command.admin && owners.indexOf(message.author.id.toString()) == -1) return;
try { try {
command.execute({ message: message, args: args, client: client, prefix: prefix, owners: owners, globalPrefix: globalPrefix})
console.log(`${message.author.username}(id: ${message.author.id}) executed ${command.name} with '${args}' as arguments`) console.log(`${message.author.username}(id: ${message.author.id}) executed ${command.name} with '${args}' as arguments`)
command.execute({ message: message, args: args, client: client, prefix: prefix, owners: owners})
} catch (error) { } catch (error) {
let divider = "------------------------" let divider = "------------------------"
console.log(divider) console.log(divider)

View file

@ -3,21 +3,14 @@ const checkTimer = require('../util/timer/checkTimer');
const updatePresence = require('../util/updatePresence'); const updatePresence = require('../util/updatePresence');
module.exports = function(client, enableLoginMessage, loginChannel, loginMessage) { module.exports = function(client, enableLoginMessage, loginChannel, loginMessage) {
updatePresence(client) updatePresence(client)
client.lastPresenceUpdate = Date.now()
setInterval(() => {
updatePresence(client)
client.lastPresenceUpdate = Date.now()
}, 60 * 1000);
console.log('Ready!'); console.log('Ready!');
if (enableLoginMessage === true) if (enableLoginMessage === true)
try{ try{
client.channels.cache.get(loginChannel).send(loginMessage) client.channels.cache.get(loginChannel).send(loginMessage)
}catch(err){ }catch(err){
console.log("Failed trying to send a message on login.\n") console.error("Failed trying to send a message on login.\n")
console.error(err)
} }
loadServerPrefixes(client) loadServerPrefixes(client)
checkTimer(client); checkTimer(client);

View file

@ -0,0 +1,17 @@
const getGuildCount = require('../util/getGuildCount');
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(getGuildCount(client)).toBe(i);
}
})

View file

@ -1,30 +0,0 @@
const isWhitelisted = require('../util/isWhitelisted');
const whitelist = {
guild: new Map(),
user: new Map()
}
whitelist.guild.set(20, ["test1", "test3"]);
whitelist.guild.set(21, ["test2", "test4"]);
whitelist.user.set(1001, ["test1", "test3"]);
whitelist.user.set(1002, ["test2", "test4"]);
test('Test isWhitelisted', () => {
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1001, isWhitelisted.whitelistTypes.BOTH)).toBe(true);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 20, 1002, isWhitelisted.whitelistTypes.BOTH)).toBe(false);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 21, 1001, isWhitelisted.whitelistTypes.BOTH)).toBe(false);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1001, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 21, 1001, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1002, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(false);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 20, 1001, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(false);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 21, 1001, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 21, 1002, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1002, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test2", whitelist, 20, 1002, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 21, 1001, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 20, 1001, isWhitelisted.whitelistTypes.EITHER)).toBe(false);
});

View file

@ -1,25 +1,33 @@
const fs = require('fs'); const sqlite3 = require('sqlite3').verbose();
module.exports = function(customName, customMessage, author){ module.exports = async function(client, customName, customMessage, author){
let sendText;
const customPath = './data/customCommands.json';
let json = fs.readFileSync(customPath, 'utf8'); let sendText = "";
let customCommands = JSON.parse(json) let isExists = client.customCommands.get(customName);
let isExists = false;
customCommands.forEach(function (customCommand) {
if (customCommand.customName === customName) {
sendText = "This custom command already exists";
isExists = true;
}
});
if (!isExists) { if (!isExists) {
let newCustomCommand = { const db = new sqlite3.Database("data/database.db");
"customName": customName, "customMessage": customMessage, "author": author sendText = await new Promise((resolve, reject)=>{
db.run(`INSERT INTO customCommands (
customName,
customMessage,
author,
isDeleted
) VALUES (?, ?, ?, ?)`, [customName, customMessage, author, false],
function(error){
if(error){
console.error(error)
let sendText = "Error while inserting new custom command.";
reject(sendText);
}else{
client.customCommands.set(customName, customMessage)
let sendText = `New custom command with the name "${customName}" added`
resolve(sendText)
} }
customCommands.push(newCustomCommand) })
sendText = `New custom command with the name "${customName}" added` })
db.close();
} }
fs.writeFileSync(customPath, JSON.stringify(customCommands, null, 4))
return sendText; return sendText;
} }

View file

@ -1,13 +1,7 @@
const fs = require('fs');
module.exports = function(){
const customPath = './data/customCommands.json'; module.exports = function(client){
let list = ""; let list = "";
client.customCommands.keys().forEach(commandName => list += `${commandName}\n`)
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
customCommands.forEach(function (customCommand) {
list = list + customCommand.customName+"\n";
});
return list; return list;
} }

View file

@ -1,10 +1,24 @@
const fs = require('fs');
module.exports = function(customName){
const customPath = './data/customCommands.json';
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
let author = customCommands.filter(customCommands => customCommands.customName === customName);
if(author.length === 0) return false;
return author[0].author;
const sqlite3 = require('sqlite3').verbose();
module.exports = async function(customName){
const db = new sqlite3.Database("data/database.db");
let author = await new Promise((resolve, reject)=>{
db.get("SELECT * FROM customCommands WHERE customName = ? AND isDeleted = 0", [customName],
function(error, command){
if(error){
console.error(error)
let author = "Error when getting the owner of this custom command. Check console.";
reject(author)
}else {
let author;
if(command){
author = command.author;
}
resolve(author)
}
})
})
return author;
} }

View file

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

8
util/getGuildCount.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = function(client){
let guildCount = 0;
client.guilds.cache.each(() => {
guildCount++
});
return guildCount;
}

View file

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

View file

@ -1,22 +0,0 @@
// type: 0 for both, 1 for guild, 2 for userId, 3 for either.
function isWhitelisted(command, whitelist, guildId, userId, type=0){
const isGuildWhitelisted = Boolean(whitelist.guild.get(guildId)?.includes(command))
if(type === whitelistTypes.GUILD_ONLY) return isGuildWhitelisted;
if(type === whitelistTypes.BOTH && !isGuildWhitelisted) return false;
if(type === whitelistTypes.EITHER && isGuildWhitelisted) return true;
const isUserWhitelisted = Boolean(whitelist.user.get(userId)?.includes(command))
if(type === whitelistTypes.USER_ONLY) return isUserWhitelisted;
if(type === whitelistTypes.BOTH && isGuildWhitelisted && isUserWhitelisted) return true;
if(type === whitelistTypes.EITHER && (isGuildWhitelisted || isUserWhitelisted)) return true;
return false;
}
const whitelistTypes = {
BOTH: 0,
GUILD_ONLY: 1,
USER_ONLY: 2,
EITHER: 3
}
module.exports = {isWhitelisted, whitelistTypes}

View file

@ -25,6 +25,7 @@ module.exports = async function(userID, guild) {
try { try {
track = data.recenttracks.track[0]; track = data.recenttracks.track[0];
} catch (error) { } catch (error) {
console.error(error);
scrobble.error = true; scrobble.error = true;
switch(data.error){ switch(data.error){
case 6: case 6:

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) {
@ -44,6 +39,7 @@ module.exports = async function (userID, guild) {
} }
resolve(tracks); resolve(tracks);
} catch (error) { } catch (error) {
console.error(error)
let scrobble = {}; let scrobble = {};
scrobble.error = true; scrobble.error = true;
if (data.error === 6) { if (data.error === 6) {
@ -63,22 +59,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 +72,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

@ -1,117 +0,0 @@
// http://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=username&api_key=YOUR_API_KEY&format=json
const getFmUsername = require("./getFmUsername");
const {EmbedBuilder} = require('discord.js');
const getNickname = require('../getNickname')
const parseMention = require('../parseMention')
module.exports = async function (userID, option, guild, compatibility=false) {
let lastfmUsername = await getFmUsername(userID)
let parse = parseMention(userID, guild)
let user = guild.members.cache.get(parse);
let nickname = getNickname(user, guild)
if (nickname == null) {
nickname = user.user.username;
}
let albums = [];
const options = {
"weekly": "7day",
"week": "7day",
"w": "7day",
"monthly": "1month",
"month": "1month",
"m": "1month",
"quarterly": "3month",
"q": "3month",
"halfyear": "6month",
"hy": "6month",
"h": "6month",
"yearly": "12month",
"year": "12month",
"y": "12month",
"alltime": "overall",
"a": "overall",
"at": "overall",
"all": "overall",
undefined: "7day"
};
if(option.length === 0){
option[0] = "weekly"
}
option[0] = options[option[0]];
if(option[0] === undefined)
option[0] = options[option[0]];
let duration = "";
switch (option[0]){
case "overall":
duration = "all time";
break;
case "7day":
duration = "weekly";
break;
case "1month":
duration = "monthly";
break;
case "3month":
duration = "quarterly";
break;
case "6month":
duration = "half year";
break;
case "12month":
duration = "yearly";
break;
}
let sendText = { text: "", embed: null }
if(lastfmUsername != undefined){
albums = await new Promise ((resolve, reject) => {
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(data => {
const maxIterations = data.topalbums.album.length >= 10 ? 10 : data.topalbums.album.length;
for(let i = 0; i < maxIterations; i++){
let album = {}
let currentAlbum = data.topalbums.album[i];
album.artist = currentAlbum.artist.name;
album.name = currentAlbum.name;
album.playcount = currentAlbum.playcount;
albums.push(album);
}
resolve(albums);
})
.catch(error => {
console.error(error);
reject(error);
});
});
}
const embed = new EmbedBuilder()
.setAuthor({name: `Top ${duration} albums for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setColor(15780145)
let albumInfo = "";
for(let i = 0; i < albums.length; i++){
let pluralCharacter = albums[i].playcount > 1 ? 's' : '';
let album = `${i}. **${albums[i].artist}** - ${albums[i].name} - *${albums[i].playcount} play${pluralCharacter}*`;
if(i < albums.length - 1){
albumInfo += `${album}\n`;
}else{
albumInfo += `${album}`;
}
}
embed.setDescription(albumInfo);
sendText.embed = embed;
if(compatibility)
return albums;
else
return sendText;
}

View file

@ -1,116 +0,0 @@
// http://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=username&api_key=YOUR_API_KEY&format=json
const getFmUsername = require("./getFmUsername");
const {EmbedBuilder} = require('discord.js');
const getNickname = require('../getNickname')
const parseMention = require('../parseMention')
module.exports = async function (userID, option, guild, compatibility=false) {
let lastfmUsername = await getFmUsername(userID)
let parse = parseMention(userID, guild)
let user = guild.members.cache.get(parse);
let nickname = getNickname(user, guild)
if (nickname == null) {
nickname = user.user.username;
}
let artists = [];
const options = {
"weekly": "7day",
"week": "7day",
"w": "7day",
"monthly": "1month",
"month": "1month",
"m": "1month",
"quarterly": "3month",
"q": "3month",
"halfyear": "6month",
"hy": "6month",
"h": "6month",
"yearly": "12month",
"year": "12month",
"y": "12month",
"alltime": "overall",
"a": "overall",
"at": "overall",
"all": "overall",
undefined: "7day"
};
if(option.length === 0){
option[0] = "weekly"
}
option[0] = options[option[0]];
if(option[0] === undefined)
option[0] = options[option[0]];
let duration = "";
switch (option[0]){
case "overall":
duration = "all time";
break;
case "7day":
duration = "weekly";
break;
case "1month":
duration = "monthly";
break;
case "3month":
duration = "quarterly";
break;
case "6month":
duration = "half year";
break;
case "12month":
duration = "yearly";
break;
}
let sendText = { text: "", embed: null }
if(lastfmUsername != undefined){
artists = await new Promise ((resolve, reject) => {
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(data => {
const maxIterations = data.topartists.artist.length >= 10 ? 10 : data.topartists.artist.length;
for(let i = 0; i < maxIterations; i++){
let artist = {}
let currentArtist = data.topartists.artist[i];
artist.name = currentArtist.name;
artist.playcount = currentArtist.playcount;
artists.push(artist);
}
resolve(artists);
})
.catch(error => {
console.error(error);
reject(error);
});
});
}
const embed = new EmbedBuilder()
.setAuthor({name: `Top ${duration} artists for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setColor(15780145)
let artistsInfo = "";
for(let i = 0; i < artists.length; i++){
let pluralCharacter = artists[i].playcount > 1 ? 's' : '';
let track = `${i}. **${artists[i].name}** - *${artists[i].playcount} play${pluralCharacter}*`;
if(i < artists.length - 1){
artistsInfo += `${track}\n`;
}else{
artistsInfo += `${track}`;
}
}
embed.setDescription(artistsInfo);
sendText.embed = embed;
if(compatibility)
return artists;
else
return sendText;
}

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

@ -1,37 +0,0 @@
require("dotenv").config();
module.exports = async function(topArtists, topAlbums) {
let prompt = "Roast the top 10 artists albums the user has listened to most for the last 365 days. Be mean. Be concise and create 3 small paragraphs in total. DO NOT CREATE ONE PARAGRAPH PER ARTIST OR ALBUM.\n"
prompt += "Top artists and their playcounts:\n"
topArtists.forEach(topArtist => {
prompt += `${topArtist.name} (${topArtist.playcount})\n`
});
prompt += "Top albums and their playcounts:\n"
topAlbums.forEach(topAlbum => {
prompt += `${topAlbum.artist} - ${topAlbum.name} (${topAlbum.playcount})\n`
})
let answer = "";
await fetch(`https://openrouter.ai/api/v1/chat/completions`, {
method: `POST`,
headers: {
"Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": `application/json`
},
body: JSON.stringify({
"model": `google/gemini-2.0-flash-001`,
"messages": [
{
"role": `system`,
"content": prompt
}
],
"max_tokens": 400
})
}).then(response => response.json()).then(data => {
answer = data.choices[0].message.content;
}).catch(error => {
console.error(error);
});
return answer;
}

View file

@ -1,35 +1,41 @@
module.exports = function (user) { module.exports = function (user) {
let details; let details;
switch (user.presence.activities[0].name) { switch (user.presence.activities[0].name) {
case "foobar2000": case "foobar2000": {
let artist = user.presence.activities[0].state.split(":")[0]; let artist = user.presence.activities[0].state.split(":")[0];
let album = user.presence.activities[0].state.split(":")[1].slice(1); let album = user.presence.activities[0].state.split(":")[1].slice(1);
details = `Artist: ${artist}\nAlbum: ${album}\nSong: ${user.presence.activities[0].details}\n` details = `Artist: ${artist}\nAlbum: ${album}\nSong: ${user.presence.activities[0].details}\n`
break; break;
case "Apple Music": }
case "Apple Music": {
details = `Artist/Song: ${user.presence.activities[0].details} \nAlbum: ${user.presence.activities[0].state}\n` details = `Artist/Song: ${user.presence.activities[0].details} \nAlbum: ${user.presence.activities[0].state}\n`
break; break;
case "Spotify": }
case "Spotify": {
details = `Artist: ${user.presence.activities[0].state}\nAlbum: ${user.presence.activities[0].details}\nSong: ${user.presence.activities[0].assets.largeText}\n` details = `Artist: ${user.presence.activities[0].state}\nAlbum: ${user.presence.activities[0].details}\nSong: ${user.presence.activities[0].assets.largeText}\n`
break; break;
case "Custom Status": }
case "Custom Status": {
if(user.presence.activities[0].state !== null) if(user.presence.activities[0].state !== null)
details = `"${user.presence.activities[0].state}"\n` details = `"${user.presence.activities[0].state}"\n`
else else
details = ""; details = "";
break; break;
case "Code": }
case "Code": {
if(user.presence.activities[0].details != null) if(user.presence.activities[0].details != null)
if(user.presence.activities[0].details.slice(0, 7) === "Editing") if(user.presence.activities[0].details.slice(0, 7) === "Editing")
details = `Workspace: ${user.presence.activities[0].state}\nEditing: ${user.presence.activities[0].details.slice(8)}\n` details = `Workspace: ${user.presence.activities[0].state}\nEditing: ${user.presence.activities[0].details.slice(8)}\n`
else else
details = `Workspace: ${user.presence.activities[0].state}\n${user.presence.activities[0].details}\n` details = `Workspace: ${user.presence.activities[0].state}\n${user.presence.activities[0].details}\n`
break; break;
default: }
default: {
details = 0; details = 0;
break; break;
} }
}
return details return details
} }

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const getSubdirFiles = require('./getSubdirFiles'); const getSubdirFiles = require('./getSubdirFiles');
const commandPath = 'commands/' const commandPath = 'commands/'
const utilPath = 'util/' const utilPath = 'util/'

View file

@ -1,9 +1,9 @@
const getGuildInfo = require("./getGuildInfo") const getGuildCount = require("./getGuildCount")
const parseMS = require('./parseMS'); const parseMS = require('./parseMS');
const convertDateToISOString = require('./convertDateToISOString') const convertDateToISOString = require('./convertDateToISOString')
module.exports = function ({presenceText, presenceType, client}) { module.exports = function ({presenceText, presenceType, client}) {
const globalPrefix = client.settings.get("globalPrefix") const {globalPrefix} = require ('../data/config.json')
const guildInfo = getGuildInfo(client); let guildCount = getGuildCount(client)
let uptime = parseMS(client.uptime); let uptime = parseMS(client.uptime);
let uptimeFormat = ""; let uptimeFormat = "";
let uptimeSingularOrPlural; let uptimeSingularOrPlural;
@ -18,23 +18,10 @@ module.exports = function ({presenceText, presenceType, client}) {
uptimeFormat = `less than a minute` uptimeFormat = `less than a minute`
} }
let presenceVariables = { let regex = [/\${guilds}/g,/\${prefix}/g,/\${uptime}/g];
guilds: guildInfo.guildCount, let replaceValue = [guildCount, globalPrefix, uptimeFormat];
prefix: globalPrefix, for(let i = 0; i < regex.length; i++){
uptime: uptimeFormat, presenceText = presenceText.replace(regex[i], replaceValue[i]);
members: guildInfo.totalMembers,
uniqueMembers: guildInfo.uniqueMemberCount
}
const regex = /(?<=\${)(.*?)(?=})/g;
const matches = presenceText.match(regex);
if(matches){
matches.forEach(match => {
if(presenceVariables[match]){
presenceText = presenceText.replace(`\${${match}}`, presenceVariables[match]);
}
});
} }
try { try {

View file

@ -1,5 +1,4 @@
const sendTimerReminder = require('./sendTimerReminder') const sendTimerReminder = require('./sendTimerReminder')
const fs = require('fs')
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
module.exports = async function (client) { module.exports = async function (client) {
const checkTimer = require('./checkTimer') const checkTimer = require('./checkTimer')

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
module.exports = async function () { module.exports = async function () {
const timers = require('../../data/timers.json') const timers = require('../../data/timers.json')

View file

@ -1,26 +1,16 @@
const parseTime = require('./parseTime'); const parseTime = require('./parseTime');
const timeUntil = require('./timeUntil');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
module.exports = async function (message, args) { module.exports = async function (message, args, compatibility) {
const databasePath = 'data/database.db' const databasePath = 'data/database.db'
if (args.length < 2) if (args.length < 2)
return "Please specify a time, and a message to send after the timer has finished"; return message.channel.send("Please specify a time, and a message to send after the timer has finished");
let currentUnixTime = Math.floor(new Date() / 1000); let currentUnixTime = Math.floor(new Date() / 1000);
let timeInSeconds = compatibility ? parseTime(args[0], currentUnixTime) : parseTime(args[1], currentUnixTime);
let timeInSeconds;
if(!isNaN(Date.parse(args[0])) && isNaN(parseTime(args[0], currentUnixTime))){
timeInSeconds = timeUntil(args[0]).totalInSeconds;
if(timeInSeconds < 0){
return "The date must not be in the past."
}
}else {
timeInSeconds = parseTime(args[0], currentUnixTime);
}
if (isNaN(timeInSeconds)) { if (isNaN(timeInSeconds)) {
return "Please specify a time, and a message to send after the timer has finished" return message.channel.send("Please specify a time, and a message to send after the timer has finished")
} }
const customMessage = args.slice(1).join(" ") let customMessage = compatibility ? args.slice(1).join(" ") : args.slice(2).join(" ");
const reminderTime = currentUnixTime + timeInSeconds let reminderTime = currentUnixTime + timeInSeconds
let newTimerID; let newTimerID;
const db = new sqlite3.Database(databasePath) const db = new sqlite3.Database(databasePath)
let sendText = await new Promise((resolve, reject)=>{ let sendText = await new Promise((resolve, reject)=>{

View file

@ -1,7 +1,7 @@
module.exports = function(time, currentUnixTime){ module.exports = function(time, currentUnixTime){
let timeInSeconds = parseFloat(time) let timeInSeconds = parseFloat(time.slice(0, time.length - 1))
const letterCount = time.length - timeInSeconds.toString().length; let letter = time.slice(time.length - 1)
const letter = time.slice(time.length - letterCount); if(!isNaN(letter)) return parseFloat(time) * 60;
switch (letter.toUpperCase()) { switch (letter.toUpperCase()) {
case "H": case "H":
timeInSeconds = timeInSeconds * 3_600; timeInSeconds = timeInSeconds * 3_600;
@ -14,8 +14,7 @@ module.exports = function(time, currentUnixTime){
case "D": case "D":
timeInSeconds = timeInSeconds * 86_400; timeInSeconds = timeInSeconds * 86_400;
break; break;
case "TS": // Unix timestamp case "T": // TODO: Make it so that I can have multiple letters per case, so that "TS" would work here.
case "T":
timeInSeconds = timeInSeconds - currentUnixTime; timeInSeconds = timeInSeconds - currentUnixTime;
break; break;
case "W": case "W":
@ -23,6 +22,17 @@ module.exports = function(time, currentUnixTime){
break; break;
default: default:
timeInSeconds = NaN; timeInSeconds = NaN;
if(time.includes(':'))
timeInSeconds = getTime(time, currentUnixTime);
} }
return timeInSeconds; return timeInSeconds;
} }
// function getTime(time, currentUnixTime) {
// return timeInSeconds;
// }

View file

@ -1,20 +0,0 @@
module.exports = function(targetTime) {
const countDownDate = new Date(targetTime).getTime();
const now = new Date().getTime();
const distance = countDownDate - now;
let days = Math.floor(distance / (1000 * 60 * 60 * 24));
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
if (seconds < 0) { // Due to how the math above works, if the input time is in the past, the time will be off by 1.
days = days + 1;
hours = hours + 1;
minutes = minutes + 1;
seconds = seconds + 1;
}
const totalInSeconds = (days * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds;
return { days, hours, minutes, seconds, totalInSeconds };
}

View file

@ -1,8 +1,14 @@
const setPresence = require('./setPresence') const setPresence = require('./setPresence')
module.exports = function (client) { module.exports = function (client) {
const updatePresence = require('./updatePresence')
let presenceText = client.settings.get("presenceText") let presenceText = client.settings.get("presenceText")
let presenceType = client.settings.get("presenceType") let presenceType = client.settings.get("presenceType")
if(presenceText.includes("${guilds}") || presenceText.includes("${prefix}") || presenceText.includes("${uptime}")) {
setPresence({presenceText: presenceText, presenceType: presenceType, client: client}); setPresence({presenceText: presenceText, presenceType: presenceType, client: client});
}
setTimeout(updatePresence, 60000, client)
} }