Compare commits

..
53 changed files with 1746 additions and 3436 deletions

View file

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View file

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

View file

@ -1,8 +1,5 @@
# discord_bot
![CI tests](https://github.com/SileNce5k/discord_bot/actions/workflows/CI.yml/badge.svg)
A modular discord bot written in javascript, using the [discord.js](https://discord.js.org) library.
This is my second attempt at making a discord bot.
@ -27,9 +24,11 @@ You can also change the global prefix.
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.
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 file
Netload is a feature that enables the ability to upload .js files via discord to the bot.
If you want this feature, you have to enable it in ./data/config.json.
## Known issues
See [issues](https://github.com/SileNce5k/discord_bot/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
None at the moment.

View file

@ -1,7 +1,7 @@
module.exports = {
name: 'exampleName', // Keep it to one word
description: 'example description',
execute({message}) {
execute({message}) { //parameters you can use for netload: message, args, client, prefix
//code
}
};

View file

@ -1,4 +1,5 @@
const calculateReloaded = require("../../util/calculateReloaded.js");
const reloadNetModules = require("../../util/reloadNetModules.js");
module.exports = {
name: 'reload',
@ -8,6 +9,7 @@ module.exports = {
let reloadCommands = require("../../util/reloadCommands.js")
let beforeSize = client.commands.size;
reloadNetModules(client)
reloadCommands(client)
let sendText = calculateReloaded(beforeSize, client)
message.channel.send(sendText)

View file

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

View file

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

View file

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

View file

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

View file

@ -8,19 +8,26 @@ module.exports = {
description: 'List all available commands.',
moreHelp: ["Examples:","`<prefix>help [optional_page]` will return help with a small description for each command",
"`<prefix>help <another_command>` will return help with a more descriptive description",
"The descriptive description isn't available on all commands"
"The descriptive description isn't available on all commands",
"`<prefix>help netmodules` to display help for netmodules"
],
execute({ message, args, prefix, client }) {
let commands = ""
let commandFiles = getSubdirFiles('commands/')
let x = false;
let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`;
let iteration = 0;
let num_in_args = false;
let added_commands = 0;
let page = -1;
if (args[0] == "netmodules") {
commandFiles = fs.readdirSync('../../netload').filter(file => file.endsWith('.js'));
if (commandFiles.length == 0) {
message.channel.send("There are no netmodules currently loaded.")
x = true;
}
if(!isNaN(parseInt(args[0])) && parseInt(args[0])){
}else if(!isNaN(parseInt(args[0])) && parseInt(args[0])){
num_in_args = true;
iteration = ( parseInt(args[0]) - 1) * 10;
page = Math.round(parseInt(args[0]))
@ -31,6 +38,7 @@ module.exports = {
fieldName = fieldName.replace("[[page]]", page);
const max_commands = iteration + 10;
if (x) return;
const embed = new EmbedBuilder()
.setColor(15780145)
@ -58,7 +66,6 @@ module.exports = {
start++;
}
}else if(args[0] === command.name){
fieldName = "Description";
commands = commands + `${prefix}${command.name}\n`
embed.setTitle(command.name.charAt(0).toUpperCase() + command.name.slice(1))
if(command.moreHelp){
@ -66,6 +73,7 @@ module.exports = {
commands = commands + `${element}\n`
});
} else {
fieldName = "Description";
commands = commands + `${command.description}`;
}
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 guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096});
let globalPfp = user.user.avatarURL({format: 'png', dynamic: true, size: 4096});
let sendText = `${globalPfp}`;
let sendText = `Global pfp:\n${globalPfp}`;
if(guildPfp != null){
sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`;
sendText = `${sendText}\nGuild pfp:\n${guildPfp}`;
}
message.channel.send(sendText)

View file

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

View file

@ -1,4 +1,4 @@
const parseMS = require('../../util/parseMS')
const parseMS = require('parse-ms')
module.exports = {
name: 'uptime',

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

@ -4,12 +4,6 @@ const getCurrentCover = require("../../util/lastfm/getCurrentCover");
const getTopTracks = require("../../util/lastfm/getTopTracks");
const help = require("../info/help");
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 = {
name: 'fm',
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`)",
"Set username: `<prefix>fmset <lastfm_username>`",
"Get current scrobble: `<prefix>fm`",
"Get top artists `<prefix>fmas`",
"Get top albums: `<prefix>fmabl`",
"Get top tracks: `<prefix>fmtt`",
"Get album cover for current scrobble: `<prefix>fmcover`",
"Get a roast from an LLM using your top artists and albums: `<prefix>fmroast`"
],
async execute({ message, args, prefix, client }) {
let sendText = {text: "Something went wrong.", embed: null};
@ -37,46 +28,9 @@ module.exports = {
args.shift();
sendText = await getTopTracks(message.author.id, args, message.guild);
break;
case "topalbums":
case "topalbum":
case "abl":
args.shift();
sendText = await getTopAlbums(message.author.id, args, message.guild)
break;
case "topartists":
case "topartist":
case "ta":
case "as":
args.shift();
sendText = await getTopArtists(message.author.id, args, message.guild);
break;
case "cover":
sendText = await getCurrentCover(message.author.id, message.guild);
break;
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:
sendText.text = `${args[0]} is not a valid subcommand.\nSee \`${prefix}help fm\` for more info.`;
break;
@ -87,13 +41,11 @@ module.exports = {
if(sendText.embed != null){
let parse = parseMention(message.author.id, message.guild)
let user = message.guild.members.cache.get(parse);
if(!sendText.embed.data.color){
let roleColor = 15788778;
if (user.roles.color) {
roleColor = user.roles.color.color;
}
sendText.embed.setColor(roleColor);
let roleColor = 15788778;
if (user.roles.color) {
roleColor = user.roles.color.color;
}
sendText.embed.setColor(roleColor);
message.channel.send({embeds :[sendText.embed]})
}else{
message.channel.send(sendText.text.replaceAll("<prefix>", prefix));

View file

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

77
commands/misc/netload.js Normal file
View file

@ -0,0 +1,77 @@
const https = require('https');
const fs = require('fs')
const netloadDir = "../../netload"
const validUrl = require('valid-url');
module.exports = {
name: 'netload',
description: 'Load a module from the internet',
moreHelp: ["Examples:",
"Either provide a link to the module, or upload it",
"To get an example of how your module should look like, do:",
"<prefix>netload example",
"You have to be whitelisted to use this command.",
"The bot operator also has to have this enabled in the config."
],
execute({ message, args, prefix, client, owners }) {
if (!fs.existsSync('./data/netmoduleWhitelist.json')) {
fs.writeFileSync('./data/netmoduleWhitelist.json', "[]")
}
let json = fs.readFileSync('./data/netmoduleWhitelist.json', 'utf8');
let whitelist = JSON.parse(json)
if (json.indexOf(message.author.id.toString()) == -1) {
message.channel.send("You do not have permissions to use this command.");
return;
}
if (args[0] == "whitelist" && owners.indexOf(message.author.id.toString()) >= 0) {
whitelist.push(args[1])
fs.writeFileSync("./data/netmoduleWhitelist.json", JSON.stringify(whitelist, null, 4))
return;
}
if (!args[0] && message.attachments.size == 0) {
message.channel.send(`You have to either specify a url or upload a file via the command.\nTo get an example file, execute \`${prefix}netload example\``)
return;
} if (args[0] == "example") {
let example = fs.readFileSync('commands/.example')
message.channel.send(`\`\`\`js\n${example}\n\`\`\``)
return;
}
let url, fileName;
if (message.attachments.size > 0) {
url = message.attachments.first().url
fileName = message.attachments.first().name
} else {
url = args[0]
if (!validUrl.isUri(url)) {
message.channel.send("This does not look like a valid url")
return;
}
fileName = args[0].split("/")[args[0].split("/").length - 1]
}
if (fs.existsSync(`${netloadDir}/${fileName}`)) {
message.channel.send(`A module with this filename(${fileName}) already exists.`)
return;
}
https.get(url, (res) => {
res.on('data', (d) => {
fs.writeFileSync(`${netloadDir}/${fileName}`, d);
reloadNetModules(client);
});
}).on('error', (e) => {
message.channel.send("Error download file.");
console.log(e)
});
let reloadNetModules = require('../util/reloadNetModules');
reloadNetModules(client)
}
};

View file

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

View file

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

View file

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

View file

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

View file

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

3665
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,10 +5,13 @@
"main": "server.js",
"dependencies": {
"@zuzak/owo": "^1.14.1",
"discord.js": "^14.21.0",
"dotenv": "^17.2.1",
"discord.js": "^14.16.3",
"dotenv": "^16.4.5",
"mysql": "^2.18.1",
"prompt-sync": "^4.2.0",
"seedrandom": "^3.0.5",
"sqlite3": "^5.1.6"
"sqlite3": "^5.1.6",
"valid-url": "^1.0.9"
},
"scripts": {
"test": "jest"
@ -20,7 +23,7 @@
"author": "SileNce5k",
"license": "UNLICENSE",
"devDependencies": {
"@types/jest": "^30.0.0",
"jest": "^30.0.5"
"@types/jest": "^29.5.13",
"jest": "^29.7.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

View file

@ -1,8 +1,6 @@
const fs = require('fs');
const createInitialConfig = require("./util/createInitialConfig")
const convertJSONToSQL = require('./util/timer/convertJSONToSQL');
const executeCommand = require('./util/executeCommand.js');
const sqlite3 = require('sqlite3').verbose();
if(!fs.existsSync("./data/config.json")) {
createInitialConfig();
}
@ -20,8 +18,6 @@ async function checkAndConvertJSONToSQL(){
}
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');
@ -45,26 +41,19 @@ const {
client.settings = new Collection();
client.commands = new Collection();
client.serverPrefixes = new Collection();
client.whitelist = {
guild: new Collection(),
user: new Collection()
}
process.env.TZ = "UTC";
client.netmodules = new Collection();
createAndLoadWhitelistTable(client.whitelist);
client.settings.set("presenceType", presenceType);
client.settings.set("presenceText", presenceText);
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 reloadNetModules = require('./util/reloadNetModules');
const onMessage = require('./server/message');
const onReady = require('./server/ready');
reloadCommands(client)
reloadNetModules(client)
client.once('ready', () => {
onReady(client, enableLoginMessage, loginChannel, loginMessage)
@ -79,7 +68,7 @@ client.once('disconnect', () => {
});
client.on('messageCreate', async message => {
onMessage(client, owners, message);
onMessage(client, owners, message, globalPrefix);
});

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

6
server/input.js Normal file
View file

@ -0,0 +1,6 @@
const prompt = require("prompt-sync")();
module.exports = function(client, owners, message){
let input;
}

View file

@ -1,6 +1,8 @@
const isWhitelisted = require('../util/isWhitelisted')
module.exports = function(client, owners, message){
let prefix = client.settings.get("globalPrefix");
const fs = require('fs');
const customReplaceWithVariables = require('../util/custom_commands/customReplaceWithVariables');
module.exports = function(client, owners, message, globalPrefix){
let prefix = globalPrefix;
let serverPrefix = client.serverPrefixes.get(message.guild.id);
if (serverPrefix) {
prefix = serverPrefix;
@ -20,15 +22,35 @@ module.exports = function(client, owners, message){
const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName);
if(!command) return;
if(command.needsWhitelist && !isWhitelisted.isWhitelisted(command.name, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)){
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.`)
const netModule = client.netmodules.get(commandName);
if (!command){
if (netModule){
try {
netModule.execute({message: message, args: args, client: client, prefix: prefix})
}catch(e){
console.log(e)
}
return;
}
const customPath = './data/customCommands.json';
if(fs.existsSync(customPath)){
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
customCommands.forEach(function (customCommand) {
if (customCommand.customName === commandName) {
let customMessage = customReplaceWithVariables(customCommand.customMessage, message, prefix, globalPrefix)
message.channel.send(customMessage)
}
});
}
return;
}
if (command.admin && owners.indexOf(message.author.id.toString()) == -1) return;
try {
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})
let t0 = performance.now();
command.execute({ message: message, args: args, client: client, prefix: prefix, owners: owners, globalPrefix: globalPrefix})
let t1 = performance.now();
console.log(`${message.author.username}(id: ${message.author.id}) executed ${command.name} with '${args}' as arguments in ${(t1-t0).toFixed(2)} ms`)
} catch (error) {
let divider = "------------------------"
console.log(divider)

View file

@ -3,15 +3,7 @@ const checkTimer = require('../util/timer/checkTimer');
const updatePresence = require('../util/updatePresence');
module.exports = function(client, enableLoginMessage, loginChannel, loginMessage) {
updatePresence(client)
client.lastPresenceUpdate = Date.now()
setInterval(() => {
updatePresence(client)
client.lastPresenceUpdate = Date.now()
}, 60 * 1000);
console.log('Ready!');
if (enableLoginMessage === true)
try{

View file

@ -16,8 +16,11 @@ let expectedResults = [
]
if(beforeSizes.length != expectedResults.length) throw "Error: beforeSizes and expectedResults are unequal length"
test('calculateReloaded test', () => {
for(let i = 0; i < beforeSizes.length; i++){
for(let i = 0; i < beforeSizes.length; i++){
test('calculateReloaded test', () => {
expect(calculateReloaded(beforeSizes[i], client)).toBe(expectedResults[i])
}
});
});
}

View file

@ -3,9 +3,10 @@ const convertDateToISOString = require('../util/convertDateToISOString');
let dates = [new Date("2020-01-01"), new Date(0), new Date(5600000), new Date(1000200000000)]
let expectedDateStrings = ["2020-01-01 00:00:00", "1970-01-01 00:00:00", "1970-01-01 01:33:20", "2001-09-11 09:20:00"]
for (let i = 0; i < dates.length; i++) {
test('Converts to human readable date string', () => {
for (let i = 0; i < dates.length; i++) {
expect(convertDateToISOString(dates[i])).toBe(expectedDateStrings[i]);
}
});
test('Converts to human readable date string', () => {
expect(convertDateToISOString(dates[i])).toBe(expectedDateStrings[i]);
});
}

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

@ -6,7 +6,8 @@ module.exports = function () {
"enableLoginMessage": false,
"loginChannel" : "",
"loginMessage" : "Bot is online!",
"owners": []
"owners": [],
"allowNetload" : false
}
if(!fs.existsSync("./data/")) fs.mkdirSync("./data");

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

@ -2,11 +2,6 @@ const getNickname = require("../getNickname");
const parseMention = require("../parseMention");
const getFmUsername = require("./getFmUsername");
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();
module.exports = async function (userID, guild) {
@ -63,56 +58,21 @@ module.exports = async function (userID, guild) {
sendText.text = tracks.errorMsg;
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()
.setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setThumbnail(tracks[0].cover)
.setColor(color)
.addFields({
name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}`
},)
.setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setThumbnail(tracks[0].cover)
.setColor(15780145)
.addFields({
name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}`
},)
if (isCurrentScrobble === "Current") {
embed.addFields({
name: "Previous:", value: `${tracks[1].song}\n **${tracks[1].artist} • ** ${tracks[1].album}`
},)
}
sendText.embed = embed;
fs.rmSync(`${directory}`, {recursive: true})
} else {
sendText.text = "You haven't set your last.fm username yet. Use `<prefix>fm set <lastfm_username>` to set it.";
}
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`)
.then(response => response.json())
.then(data => {
const maxIterations = data.toptracks.track.length >= 10 ? 10 : data.toptracks.track.length;
for(let i = 0; i < maxIterations; i++){
for(let i = 0; i < 10; i++){
let track = {}
let currentTrack = data.toptracks.track[i];
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;
}

19
util/reloadNetModules.js Normal file
View file

@ -0,0 +1,19 @@
const fs = require('fs')
const netloadDir = 'netload/'
module.exports = function (client) {
if (!fs.existsSync(netloadDir)) fs.mkdirSync(netloadDir);
let commandFiles = fs.readdirSync(netloadDir).filter(file => file.endsWith('.js'));
if (client.netmodules.size != 0) {
for (const i of commandFiles) {
delete require.cache[require.resolve(`../${netloadDir}${i}`)];
}
}
client.netmodules.clear()
for (const file of commandFiles) {
const command = require(`../${netloadDir}${file}`);
client.netmodules.set(command.name, command);
}
}

View file

@ -1,9 +1,9 @@
const getGuildInfo = require("./getGuildInfo")
const parseMS = require('./parseMS');
const getGuildCount = require("./getGuildCount")
const parseMS = require('parse-ms');
const convertDateToISOString = require('./convertDateToISOString')
module.exports = function ({presenceText, presenceType, client}) {
const globalPrefix = client.settings.get("globalPrefix")
const guildInfo = getGuildInfo(client);
const {globalPrefix} = require ('../data/config.json')
let guildCount = getGuildCount(client)
let uptime = parseMS(client.uptime);
let uptimeFormat = "";
let uptimeSingularOrPlural;
@ -18,23 +18,10 @@ module.exports = function ({presenceText, presenceType, client}) {
uptimeFormat = `less than a minute`
}
let presenceVariables = {
guilds: guildInfo.guildCount,
prefix: globalPrefix,
uptime: uptimeFormat,
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]);
}
});
let regex = [/\${guilds}/g,/\${prefix}/g,/\${uptime}/g];
let replaceValue = [guildCount, globalPrefix, uptimeFormat];
for(let i = 0; i < regex.length; i++){
presenceText = presenceText.replace(regex[i], replaceValue[i]);
}
try {

View file

@ -1,26 +1,17 @@
const fs = require('fs');
const parseTime = require('./parseTime');
const timeUntil = require('./timeUntil');
const sqlite3 = require('sqlite3').verbose();
module.exports = async function (message, args) {
module.exports = async function (message, args, compatibility) {
const databasePath = 'data/database.db'
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 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);
}
let timeInSeconds = compatibility ? parseTime(args[0], currentUnixTime) : parseTime(args[1], currentUnixTime);
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(" ")
const reminderTime = currentUnixTime + timeInSeconds
let customMessage = compatibility ? args.slice(1).join(" ") : args.slice(2).join(" ");
let reminderTime = currentUnixTime + timeInSeconds
let newTimerID;
const db = new sqlite3.Database(databasePath)
let sendText = await new Promise((resolve, reject)=>{

View file

@ -1,7 +1,7 @@
module.exports = function(time, currentUnixTime){
let timeInSeconds = parseFloat(time)
const letterCount = time.length - timeInSeconds.toString().length;
const letter = time.slice(time.length - letterCount);
let timeInSeconds = parseFloat(time.slice(0, time.length - 1))
let letter = time.slice(time.length - 1)
if(!isNaN(letter)) return parseFloat(time) * 60;
switch (letter.toUpperCase()) {
case "H":
timeInSeconds = timeInSeconds * 3_600;
@ -14,8 +14,7 @@ module.exports = function(time, currentUnixTime){
case "D":
timeInSeconds = timeInSeconds * 86_400;
break;
case "TS": // Unix timestamp
case "T":
case "T": // TODO: Make it so that I can have multiple letters per case, so that "TS" would work here.
timeInSeconds = timeInSeconds - currentUnixTime;
break;
case "W":
@ -23,6 +22,17 @@ module.exports = function(time, currentUnixTime){
break;
default:
timeInSeconds = NaN;
if(time.includes(':'))
timeInSeconds = getTime(time, currentUnixTime);
}
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')
module.exports = function (client) {
const updatePresence = require('./updatePresence')
let presenceText = client.settings.get("presenceText")
let presenceType = client.settings.get("presenceType")
setPresence({presenceText: presenceText, presenceType: presenceType, client: client});
if(presenceText.includes("${guilds}") || presenceText.includes("${prefix}") || presenceText.includes("${uptime}")) {
setPresence({presenceText: presenceText, presenceType: presenceType, client: client});
}
setTimeout(updatePresence, 60000, client)
}