diff --git a/.dockerignore b/.dockerignore index cb75162..ca4f185 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .git config.json node_modules +db.json diff --git a/.gitignore b/.gitignore index 7198fa5..030d8ca 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,5 @@ typings/ .env # custom -config.json \ No newline at end of file +config.json +db.json diff --git a/Dockerfile b/Dockerfile index 26377b3..dfe1cdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,6 @@ RUN npm install COPY . ./ -VOLUME [ "/app/config.json" ] +VOLUME [ "/app/config.json", "/app/db.json" ] CMD ["sh", "entrypoint.sh"] diff --git a/package-lock.json b/package-lock.json index 0d73932..558366f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "discord-modals": "github:jonbarrow/discord-modals", "discord.js": "^13.6.0", "lodash.clonedeep": "^4.5.0", + "simple-json-db": "^2.0.0", "slash-create": "^3.0.1" }, "devDependencies": { @@ -1654,6 +1655,14 @@ "node": ">=8" } }, + "node_modules/simple-json-db": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-json-db/-/simple-json-db-2.0.0.tgz", + "integrity": "sha512-oTh7gFQzqAe0E8RN3EkisPo0CojkzcKCKibTcJncg0yt47hWTaNwwjX/FsxfXSTDxfMjBFXFVnZe/EskAlJr7w==", + "engines": { + "node": ">=10.0" + } + }, "node_modules/slash-create": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/slash-create/-/slash-create-3.5.0.tgz", @@ -3230,6 +3239,11 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "simple-json-db": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-json-db/-/simple-json-db-2.0.0.tgz", + "integrity": "sha512-oTh7gFQzqAe0E8RN3EkisPo0CojkzcKCKibTcJncg0yt47hWTaNwwjX/FsxfXSTDxfMjBFXFVnZe/EskAlJr7w==" + }, "slash-create": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/slash-create/-/slash-create-3.5.0.tgz", diff --git a/package.json b/package.json index e3af79c..6f7d829 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "discord-modals": "github:jonbarrow/discord-modals", "discord.js": "^13.6.0", "lodash.clonedeep": "^4.5.0", + "simple-json-db": "^2.0.0", "slash-create": "^3.0.1" }, "devDependencies": { diff --git a/src/bot.js b/src/bot.js index f5b8687..9d8fc38 100644 --- a/src/bot.js +++ b/src/bot.js @@ -8,6 +8,10 @@ const messageCreateHandler = require('./events/messageCreate'); const modalSubmitHandler = require('./events/modalSubmit'); const config = require('../config.json'); +if (!config.guild_id) { + throw new Error("No guild id set in config, exiting"); +} + const client = new Discord.Client({ intents: [ Discord.Intents.FLAGS.GUILDS, diff --git a/src/commands/settings.js b/src/commands/settings.js new file mode 100644 index 0000000..e4069ec --- /dev/null +++ b/src/commands/settings.js @@ -0,0 +1,128 @@ +const Discord = require('discord.js'); +const db = require('../db'); +const { guild_id } = require('../../config.json'); +const { SlashCommandBuilder } = require('@discordjs/builders'); + +const editableOptions = [ + "mod-applications.channel.log", + "report.channel.log", + "joinmsg.channels.readme", + "joinmsg.channels.rules", + "stats.channels.members", + "stats.channels.people", + "stats.channels.bots", +] + +async function isValidkey(interaction) { + const key = interaction.options.getString("key"); + if (!editableOptions.includes(key)) { + await interaction.reply({ + content: "Cannot edit this setting - not a valid setting", + ephemeral: true, + }) + return false; + } + return true; +} + +/** + * + * @param {Discord.CommandInteraction} interaction + */ +async function settingsHandler(interaction) { + if (interaction.guildId !== guild_id) { + await interaction.reply({ + content: "Cannot edit this setting - this guild is not whitelisted", + ephemeral: true, + }) + return; + } + + const key = interaction.options.getString("key"); + if (interaction.options.getSubcommand() === "get") { + if (!await isValidkey(interaction)) + return; + // this is hellish string concatenation, I know + await interaction.reply({ + content: "```\n" + key + "=" + '"' + `${db.getDB().get(key)}` +'"' + "\n```", + ephemeral: true, + allowedMentions: { + parse: [], // dont allow tagging anything + } + }) + return; + } + + if (interaction.options.getSubcommand() === "set") { + if (!await isValidkey(interaction)) + return; + db.getDB().set(key, interaction.options.getString("value")) + await interaction.reply({ + content: `setting \`${key}\` has been saved successfully`, + ephemeral: true, + allowedMentions: { + parse: [], // dont allow tagging anything + } + }) + return; + } + + if (interaction.options.getSubcommand() === "which") { + await interaction.reply({ + content: `**possible settings**:\n${editableOptions.map(v=>`\`${v}\``).join("\n")}`, + ephemeral: true, + allowedMentions: { + parse: [], // dont allow tagging anything + } + }) + return; + } + + throw new Error("unhandled subcommand"); +} + +const command = new SlashCommandBuilder(); + +command.setDefaultPermission(false); +command.setName('settings'); +command.setDescription('Setup the bot'); +command.addSubcommand(cmd => { + cmd.setName("set"); + cmd.setDescription("Change a settings key"); + cmd.addStringOption(option => { + option.setName('key'); + option.setDescription('Key to modify'); + option.setRequired(true); + return option; + }); + cmd.addStringOption(option => { + option.setName('value'); + option.setDescription('value to set the setting to'); + option.setRequired(true); + return option; + }); + return cmd; +}) +command.addSubcommand(cmd => { + cmd.setName("get"); + cmd.setDescription("Get value of settings key"); + cmd.addStringOption(option => { + option.setName('key'); + option.setDescription('Key to modify'); + option.setRequired(true); + return option; + }); + return cmd; +}) +command.addSubcommand(cmd => { + cmd.setName("which"); + cmd.setDescription("which settings are valid?"); + return cmd; +}) + +module.exports = { + name: command.name, + help: 'Change settings of the bot', + handler: settingsHandler, + deploy: command.toJSON() +}; diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..7b80381 --- /dev/null +++ b/src/db.js @@ -0,0 +1,11 @@ +const JSONdb = require('simple-json-db'); +const path = require('path'); +const db = new JSONdb(path.join(__dirname, "../db.json")); + +function getDB() { + return db; +} + +module.exports = { + getDB +}; diff --git a/src/events/guildMemberAdd.js b/src/events/guildMemberAdd.js index 636d267..1843a87 100644 --- a/src/events/guildMemberAdd.js +++ b/src/events/guildMemberAdd.js @@ -1,5 +1,6 @@ const Discord = require('discord.js'); const util = require('../util'); +const db = require('../db'); /** * @@ -8,22 +9,26 @@ const util = require('../util'); async function guildMemberAddHandler(member) { const guild = member.guild; const channels = await guild.channels.fetch(); - const readmeChannel = channels.find(channel => channel.type === 'GUILD_TEXT' && channel.name === 'readme'); - const rulesChannel = channels.find(channel => channel.type === 'GUILD_TEXT' && channel.name === 'rules'); + const readmeChannel = channels.find(channel => channel.id === db.getDB().get("joinmsg.channels.readme")); + const rulesChannel = channels.find(channel => channel.id === db.getDB().get("joinmsg.channels.rules")); - - const welcomeEmbed = new Discord.MessageEmbed(); - - welcomeEmbed.setColor(0x1B1F3B); - welcomeEmbed.setTitle('Welcome to Pretendo Network :tada:'); - welcomeEmbed.setURL('https://pretendo.network'); - welcomeEmbed.setDescription(`**Thank you for joining the Pretendo Network Discord server! Be sure to refer to the <#${readmeChannel.id}> and <#${rulesChannel.id}> channels for detailed information about the server**\n\n_**Links**_:\nWebsite - https://pretendo.network\nGitHub - https://github.com/PretendoNetwork\nPatreon - https://patreon.com/PretendoNetwork\nTwitter - https://twitter.com/PretendoNetwork\nTwitch - https://twitch.tv/PretendoNetwork\nYouTube - https://youtube.com/c/PretendoNetwork`); - welcomeEmbed.setThumbnail('https://i.imgur.com/8clyKqx.png'); - welcomeEmbed.setImage('https://i.imgur.com/CF7qgW1.png'); - - await member.send({ - embeds: [welcomeEmbed], - }); + if (readmeChannel && rulesChannel) { + const welcomeEmbed = new Discord.MessageEmbed(); + + welcomeEmbed.setColor(0x1B1F3B); + welcomeEmbed.setTitle('Welcome to Pretendo Network :tada:'); + welcomeEmbed.setURL('https://pretendo.network'); + welcomeEmbed.setDescription(`**Thank you for joining the Pretendo Network Discord server! Be sure to refer to the <#${readmeChannel.id}> and <#${rulesChannel.id}> channels for detailed information about the server**\n\n_**Links**_:\nWebsite - https://pretendo.network\nGitHub - https://github.com/PretendoNetwork\nPatreon - https://patreon.com/PretendoNetwork\nTwitter - https://twitter.com/PretendoNetwork\nTwitch - https://twitch.tv/PretendoNetwork\nYouTube - https://youtube.com/c/PretendoNetwork`); + welcomeEmbed.setThumbnail('https://i.imgur.com/8clyKqx.png'); + welcomeEmbed.setImage('https://i.imgur.com/CF7qgW1.png'); + + // caught because user could have dm's disabled + try { + await member.send({ + embeds: [welcomeEmbed], + }); + } catch {} + } await util.updateMemberCountChannels(member.guild); } diff --git a/src/modals/mod-application.js b/src/modals/mod-application.js index 4d10c02..23e2303 100644 --- a/src/modals/mod-application.js +++ b/src/modals/mod-application.js @@ -1,4 +1,5 @@ const Discord = require('discord.js'); +const db = require('../db'); const { Modal, TextInputComponent, ModalSubmitInteraction } = require('discord-modals'); const { button: acceptButton } = require('../buttons/mod-application-accept'); const { button: denyButton } = require('../buttons/mod-application-deny'); @@ -59,7 +60,15 @@ async function modApplicationHandler(interaction) { const guild = await interaction.guild.fetch(); const channels = await guild.channels.fetch(); - const channel = channels.find(channel => channel.type === 'GUILD_TEXT' && channel.name === 'mod-applications'); + const channel = channels.find(channel => channel.id === db.getDB().get("mod-applications.channel.log")); + + if (!channel) { + await interaction.editReply({ + content: 'application failed to submit - channel not setup!', + ephemeral: true + }); + return; + } const modApplicationEmbed = new Discord.MessageEmbed(); diff --git a/src/modals/report-user.js b/src/modals/report-user.js index d726ff0..d0666b4 100644 --- a/src/modals/report-user.js +++ b/src/modals/report-user.js @@ -1,4 +1,5 @@ const Discord = require('discord.js'); +const db = require('../db'); const { Modal, TextInputComponent, ModalSubmitInteraction } = require('discord-modals'); const discordTranscripts = require('discord-html-transcripts'); @@ -43,7 +44,15 @@ async function reportUserHandler(interaction) { } const channels = await interaction.guild.channels.fetch(); - const reportsChannel = channels.find(channel => channel.type === 'GUILD_TEXT' && channel.name === 'reports'); + const reportsChannel = channels.find(channel => channel.id === db.getDB().get("report.channel.log")); + + if (!reportsChannel) { + await interaction.editReply({ + content: 'Report failed to submit - channel not setup', + ephemeral: true + }); + return; + } const reportEmbed = new Discord.MessageEmbed(); diff --git a/src/util.js b/src/util.js index fb352b8..337794e 100644 --- a/src/util.js +++ b/src/util.js @@ -1,16 +1,18 @@ const Discord = require('discord.js'); +const db = require('./db'); /** - * + * * @param {Discord.Guild} guild */ - async function updateMemberCountChannels(guild) { + // TODO this should really done on interval, a bot raid will ratelimit the bot so it cant take any more actions for a while + // (this is on global ratelimit iirc) const channels = await guild.channels.fetch(); - const membersChannel = channels.find(channel => channel.type === 'GUILD_VOICE' && channel.name.startsWith('Members')); - const peopleChannel = channels.find(channel => channel.type === 'GUILD_VOICE' && channel.name.startsWith('People')); - const botsChannel = channels.find(channel => channel.type === 'GUILD_VOICE' && channel.name.startsWith('Bots')); + const membersChannel = channels.find(channel => channel.id === db.getDB().get("stats.channels.members")); + const peopleChannel = channels.find(channel => channel.id === db.getDB().get("stats.channels.people")); + const botsChannel = channels.find(channel => channel.id === db.getDB().get("stats.channels.bots")); const members = await guild.members.fetch(); const membersCount = guild.memberCount; @@ -26,9 +28,9 @@ async function updateMemberCountChannels(guild) { } }); - await membersChannel.setName(`Members - ${membersCount}`); - await peopleChannel.setName(`People - ${peopleCount}`); - await botsChannel.setName(`Bots - ${botsCount}`); + if (membersChannel) await membersChannel.setName(`Members - ${membersCount}`); + if (peopleChannel) await peopleChannel.setName(`People - ${peopleCount}`); + if (botsChannel) await botsChannel.setName(`Bots - ${botsCount}`); } module.exports = {