diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9a1d8ab..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index ccb2c80..77b939d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ -package-lock.json \ No newline at end of file +package-lock.json +devConfig.json +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 25d2d83..6b9836d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,24 @@ +# WARNING: this is a pre-version +The version above is unstable so if you find bugs, please report them in the discord server + +[click here for a stable version](https://www.github.com/DJj123dj/open-ticket/releases/tab/v1.3.2a) + + # open-ticket -[![discord](https://img.shields.io/badge/discord-join%20our%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-1.3.2%20stable-brightgreen.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/releases/tag/v1.3.2) [![npm](https://img.shields.io/badge/npm-external%20libraries%20needed-CB3837.svg?style=flat-square&logo=npm)](#packages) [![license](https://img.shields.io/badge/license-GPL%203.0-important.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/blob/main/LICENSE) +[![discord](https://img.shields.io/badge/discord-join%20our%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-2.0.0%20unstable-red.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/releases/tag/v2.0.0) [![npm](https://img.shields.io/badge/npm-external%20libraries%20needed-CB3837.svg?style=flat-square&logo=npm)](#packages) [![license](https://img.shields.io/badge/license-GPL%203.0-important.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/blob/main/LICENSE) This is an open-source discord ticket bot, you can configure it and it comes with cool features like transcripts & custom options! Open Ticket logo -[![announcement](https://img.shields.io/badge/new%20announcement!-open--ticket%20v1.4.0-red.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/discussions/3) - -[![q&a](https://img.shields.io/badge/Q&A-click%20here-blue.svg?style=flat-square)](./documentation/Q&A.md) +[![q&a](https://img.shields.io/badge/Q&A-click%20here-blue.svg?style=flat-square)](https://www.github.com/DJj123dj/open-ticket/wiki/q&a) ## features - discord interaction buttons +- slash & text commands +- close or delete tickets +- translation possible +- dynamic database system - transcripts - add/remove users from ticket - custom colors & name @@ -19,8 +27,8 @@ This is an open-source discord ticket bot, you can configure it and it comes wit - uses discord.js 13 - configure your own ticket options - remove credits if you want -- up to 6 tickets at the same time -- change all bot messages +- unlimited tickets at the same time +- localisation in +5 languages (soon™) ## installation **You need `node.js 16` to run this project!** @@ -42,7 +50,7 @@ if there are any errors, you can open an **issue on github** or **contact me in ### config Our config documentation is moved to another file: -[click here to view](./documentation/CONFIG.md) +[click here to view](https://www.github.com/DJj123dj/open-ticket/wiki/config-v2.0.0) ### intents & permissions In the discord developer portal in the "bot" panel you will find 3 switches under the title "Gateaway Intents". The following switches should always be turned on @@ -52,14 +60,10 @@ In the discord developer portal in the "bot" panel you will find 3 switches unde To work the best, the bot needs `ADMINISTRATOR` permissions. You can also configure your own permissions -## v1.4.0 announcement -Hello everyone, soon open-ticket v1.4.0 will be launched, it contains a lot of new features. You can view them below. -[click here to view the announcement](https://github.com/DJj123dj/open-ticket/discussions/3) - ## information -_v1.3.2_ +_v2.0.0 unstable_ -changelog: [click here](./documentation/CHANGELOG.md) +changelog: [click here](https://www.github.com/DJj123dj/open-ticket/wiki/Changelog) © 2022 - DJdj Development | [website](https://www.dj-dj.be) | [discord](https://discord.com/invite/26vT9wt3n3) diff --git a/TEMPDOCS.md b/TEMPDOCS.md new file mode 100644 index 0000000..4188c3e --- /dev/null +++ b/TEMPDOCS.md @@ -0,0 +1,5 @@ +# documentation +The documentation is not available here, go to the [wiki](https://github.com/DJj123dj/open-ticket/wiki) instead. (the wiki isn't fully done yet!) + +## 2.0.0 beta +There is currently no documentation available for open-ticket v2.0.0 beta. So if you want to test it, you need to figure it out on yourself at the moment! \ No newline at end of file diff --git a/checker.js b/checker.js deleted file mode 100644 index 0bdec6d..0000000 --- a/checker.js +++ /dev/null @@ -1,214 +0,0 @@ -module.exports = async () => { - const chalk = await (await import("chalk")).default - const config = require("./config.json") - var errorList = [] - var isError = false - var warnList = [] - var isWarn = false - const createError = (message) => { - isError = true - errorList.push("open-ticket ERROR: "+message) - } - const createWarn = (message) => { - isWarn = true - warnList.push("open-ticket WARNING: "+message) - } - - //checker - - //token - if (config.auth_token.includes(" ")){ - createError("invalid token | there are spaces in your token!") - } - if (config.auth_token.length < 40){ - createError("invalid token | length not correct!") - } - - //color - if (!config.main_color.startsWith("#")){ - createError("color not detected | 'main_color' doesn't start with a '#'!") - } - if (config.main_color.length > 7 || config.main_color.length < 7){ - createError("color not detected | 'main_color' is not a valid hex color!") - } - - //admin - if (config.botperms_role.includes(" ")){ - createError("invalid role id | 'botperms_role' is not a valid role id!") - } - if (config.botperms_role.length > 20|| config.botperms_role.length < 16){ - createError("invalid role id | 'botperms_role' is not a valid role id!") - } - - //prefix - if (config.prefix == ""){ - createError("no prefix | 'prefix' is empty!") - } - if (config.prefix.length > 3){ - createWarn("long prefix | your prefix is a little bit long!") - } - - //status - if (config.status.enabled && (config.status.type != "PLAYING" && config.status.type != "LISTENING" && config.status.type != "WATCHING" && config.status.type != "CUSTOM")){ - createError("status error | 'status/type' is invalid!") - } - if (config.status.enabled && config.status.text.length < 1){ - createError("status error | 'status/text' must be at least 1 character long!") - } - - //ticketchannel - if (config.system.ticket_channel.includes(" ")){ - createWarn("invalid channel id | 'system/ticket_channel' is not a valid channel id!") - } - if (config.system.ticket_channel.length < 1){ - createWarn("no ticket channel | 'system/ticket_channel' is empty") - }else if (config.system.ticket_channel.length > 20|| config.system.ticket_channel.length < 16){ - createWarn("invalid channel id | 'system/ticket_channel' is not a valid channel id!") - } - - //maxticket - if (config.system.max_allowed_tickets < 1){ - createWarn("no tickets | if 'system/max_allowed_tickets' is under 1 you can't create any ticket") - } - - //category - if (config.system.enable_category == true && config.system.ticket_category.includes(" ")){ - createError("invalid category id | 'system/ticket_category' is not a valid category id!") - } - if (config.system.enable_category && config.system.ticket_category.length > 20 || config.system.ticket_category.length < 16){ - createError("invalid category id | 'system/ticket_category' is not a valid category id!") - } - - //everyoneaccess - if (config.system["has@everyoneaccess"] == true){ - createWarn("access | everyone has access to the tickets!") - } - - //memberrole - if (config.system.member_role.includes(" ")){ - createError("invalid role id | 'system/member_role' is not a valid role id!") - } - if (config.system.member_role.length > 20){ - createError("invalid role id | 'system/member_role' is not a valid role id!") - } - if (config.system.member_role == "" || config.system.member_role == " " || config.system.member_role == "false" || config.system.member_role == "null" || config.system.member_role == "0"){ - createWarn("no member role | 'system/member_role' is not valid so technically everyone can view a ticket!") - } - - //transcript - if (config.system.enable_transcript && config.system.transcript_channel.includes(" ")){ - createError("invalid channel id | 'system/transcript_channel' is not a valid channel id!") - } - if (config.system.enable_transcript && config.system.transcript_channel.length > 20 || config.system.transcript_channel.length < 16){ - createError("invalid channel id | 'system/transcript_channel' is not a valid channel id!") - } - - //messages - if (config.messages.general.nopermissions.length < 1){ - createError("no message | 'messages/general/nopermissions' is empty!") - } - if (config.messages.ticket.newTicketEmbed.length < 1){ - createError("no message | 'messages/ticket/newTicketEmbed' is empty!") - } - if (config.messages.dm.alreadyCreated.length < 1){ - createError("no message | 'messages/dm/alreadyCreated' is empty!") - } - if (config.messages.dm.newTicket.length < 1){ - createError("no message | 'messages/dm/newTicket' is empty!") - } - if (config.messages.dm.closeTicket.length < 1){ - createError("no message | 'messages/dm/closeTicket' is empty!") - } - - //layout - const checkLayout = (layout,layoutname,color,footer,thumbnail) => { - if (color){ - if (!layout.customColor.startsWith("#")){ - createError("color not detected | 'layout/"+layoutname+"/customColor' doesn't start with a '#'!") - } - if (layout.customColor.length > 7 || layout.customColor.length < 7){ - createError("color not detected | 'layout/"+layoutname+"/customColor' is not a valid hex color!") - } - } - if (footer){ - if (layout.footer.length < 1){ - createError("no footer | 'layout/"+layoutname+"/footer' doesn't have a footer!") - } - } - if (thumbnail){ - if (!layout.thumbnailURL.startsWith("https://") && !layout.thumbnailURL.startsWith("http://")){ - createError("thumbnail | the thumbnail url in 'layout/"+layoutname+"/thumbnailURL' is not a valid url!") - } - } - } - checkLayout(config.layout.ticketEmbed,"ticketEmbed",config.layout.ticketEmbed.customColorEnabled,config.layout.ticketEmbed.footerEnabled,config.layout.ticketEmbed.thumbnailEnabled) - checkLayout(config.layout.ticketMsg,"ticketMsg",config.layout.ticketMsg.customColorEnabled,config.layout.ticketMsg.footerEnabled,config.layout.ticketMsg.thumbnailEnabled) - checkLayout(config.layout.transcripts,"transcripts",config.layout.transcripts.customColorEnabled,false,config.layout.transcripts.thumbnailEnabled) - - const checkOption = (ticket,ticketname,enabled,urlmode) => { - if (enabled){ - if (ticket.icon.length > 0){ - const emojiRegex = /\p{Emoji}/u - const isEmoji = emojiRegex.test(ticket.icon) - if (isEmoji == false){ - createWarn("invalid emoji | 'options/"+ticketname+"/icon' is not a valid emoji (code can be wrong)") - } - } - if (ticket.description.length < 1){ - createWarn("no description | 'options/"+ticketname+"/description' is empty") - } - if (ticket.name.length < 1){ - createWarn("no description | 'options/"+ticketname+"/name' is empty") - } - if (ticket.channel_prefix.length < 1){ - createWarn("no description | 'options/"+ticketname+"/channel_prefix' is empty") - } - if (ticket.isURL == true && ticket.color != "red" && ticket.color != "blue" && ticket.color != "green" && ticket.color != "gray" && ticket.color != "none"){ - createWarn("invalid button color | 'options/"+ticketname+"/color' must be one of (red,green,blue,gray,none)") - } - - if (urlmode && (!ticket.url.startsWith("https://") && !ticket.url.startsWith("http://"))){ - createError("url invalid | the url in 'options/"+ticketname+"/url' is not a valid url!") - } - } - return - } - - checkOption(config.options.ticket1,"ticket1",config.options.ticket1.enabled,config.options.ticket1.isURL) - checkOption(config.options.ticket2,"ticket2",config.options.ticket2.enabled,config.options.ticket2.isURL) - checkOption(config.options.ticket3,"ticket3",config.options.ticket3.enabled,config.options.ticket3.isURL) - checkOption(config.options.ticket4,"ticket4",config.options.ticket4.enabled,config.options.ticket4.isURL) - checkOption(config.options.ticket5,"ticket5",config.options.ticket5.enabled,config.options.ticket5.isURL) - checkOption(config.options.ticket6,"ticket6",config.options.ticket6.enabled,config.options.ticket6.isURL) - - //the end - if (errorList.length > 0 || warnList.length > 0){ - console.log("REPORT:\n===========================") - } - warnList.forEach((w) => { - const splitw = w.split("'") - if (splitw.length > 1){ - var splitstring = chalk.yellow(splitw[0])+chalk.blue("'"+splitw[1]+"'")+chalk.yellow(splitw[2]) - }else {var splitstring = chalk.yellow(splitw[0])} - console.log(splitstring) - }) - errorList.forEach((e) => { - const splite = e.split("'") - if (splite.length > 1){ - var splitstring = chalk.red(splite[0])+chalk.blue("'"+splite[1]+"'")+chalk.red(splite[2]) - }else {var splitstring = chalk.red(splite[0])} - console.log(splitstring) - }) - if (isError){ - console.log("===========================") - console.log("=> "+chalk.bgRed("your bot doesn't work if you don't fix the above errors!")) - if (isWarn){ - console.log("\n=> "+chalk.bgYellow("if you ignore warns, some things may work differently than expected!")) - } - process.exit(0) - }else if (isWarn == true && isError == false){ - console.log("===========================") - console.log("=> "+chalk.bgYellow("if you ignore warns, some things may work differently than expected!")) - } - -} \ No newline at end of file diff --git a/commands/add.js b/commands/add.js new file mode 100644 index 0000000..404d198 --- /dev/null +++ b/commands/add.js @@ -0,0 +1,65 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix+"add")) return + var user = msg.mentions.users.first() + if (!user){ + return msg.channel.send({content:"**There is no user to add:**\nuse: `"+config.prefix+"add `"}) + } + + if (!msg.member.permissions.has("MANAGE_CHANNELS") && !msg.member.permissions.has("ADMINISTRATOR")){ + return msg.channel.send({content:"You have no permissions to add someone to the channel!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + msg.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + msg.channel.send({content:"You are not in a ticket!"}) + return + } + + msg.channel.permissionOverwrites.create(user.id, { VIEW_CHANNEL:true, ADD_REACTIONS:true,ATTACH_FILES:true, EMBED_LINKS:true, SEND_MESSAGES:true}) + msg.channel.send({content:"Added "+user.tag+" to the ticket"}) + if (config.logs){console.log("[system] added user to ticket (name:"+user.username+",ticket:"+msg.channel.name+")")} + + var loguser = msg.mentions.users.first() + if (config.logs){console.log("[command] "+config.prefix+"add "+loguser.username+" (user:"+msg.author.username+")")} + }) + + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "add") return + const user = interaction.options.getUser("user") + const member = client.guilds.cache.find(g => g.id == interaction.guild.id).members.cache.find(m => m.id == interaction.member.id) + + if (!member.permissions.has("MANAGE_CHANNELS") && !member.permissions.has("ADMINISTRATOR")){ + return interaction.reply({content:"You have no permissions to add someone to the channel!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + + interaction.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + interaction.reply({content:"You are not in a ticket!"}) + return + } + + interaction.channel.permissionOverwrites.create(user.id, { VIEW_CHANNEL:true, ADD_REACTIONS:true,ATTACH_FILES:true, EMBED_LINKS:true, SEND_MESSAGES:true}) + interaction.reply({content:"Added "+user.tag+" to the ticket"}) + if (config.logs){console.log("[system] added user to ticket (name:"+user.username+",ticket:"+interaction.channel.name+")")} + + var loguser = user + if (config.logs){console.log("[command] "+config.prefix+"add "+loguser.username+" (user:"+interaction.user.username+")")} + }) + + + }) +} \ No newline at end of file diff --git a/commands/close.js b/commands/close.js new file mode 100644 index 0000000..6e90c86 --- /dev/null +++ b/commands/close.js @@ -0,0 +1,64 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix+"close")) return + + + msg.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + msg.channel.send({content:"You are not in a ticket!"}) + return + } + + const closebutton = new discord.MessageActionRow() + .addComponents([ + new discord.MessageButton() + .setCustomId("closeTicketTrue1") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("🔒") + ]) + + msg.channel.send({content:"**Click on the button below to close this ticket!**",components:[closebutton]}) + + console.log("[system] closed a ticket via command") + + }) + + + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "close") return + + interaction.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + interaction.reply({content:"You are not in a ticket!"}) + return + } + + const closebutton = new discord.MessageActionRow() + .addComponents([ + new discord.MessageButton() + .setCustomId("closeTicketTrue1") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("🔒") + ]) + + interaction.reply({content:"**Click on the button below to close this ticket!**",components:[closebutton]}) + + console.log("[system] closed a ticket via command") + + }) + }) +} \ No newline at end of file diff --git a/commands/database.js b/commands/database.js deleted file mode 100644 index 271f232..0000000 --- a/commands/database.js +++ /dev/null @@ -1,21 +0,0 @@ -const discord = require('discord.js') -const bot = require('../index') -const client = bot.client -const config = require("../config.json") - - -module.exports = () => { - client.on("messageCreate",(msg) => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"resetdatabase" || args[0] == config.prefix+"resetdb"){ - const databaseEmbed = new discord.MessageEmbed() - .setColor(config.main_color) - .setTitle("How to reset the database:") - .setDescription("1: Go to the folder `./storage` in your bot's directory.\n\n2: If posible remove the folders\n`./tickets`, `./transcripts` & `./userTickets`\nfrom the folder `./storage`\n\n3: Restart the bot") - .setFooter({text:"if it doesn't work, create an issue on github or DM me on discord!"}) - - msg.channel.send({embeds:[databaseEmbed]}) - if (config.logs){console.log("[command] "+config.prefix+"resetdatabase (user:"+msg.author.username+")")} - } - }) -} \ No newline at end of file diff --git a/commands/delete.js b/commands/delete.js new file mode 100644 index 0000000..f0b7305 --- /dev/null +++ b/commands/delete.js @@ -0,0 +1,64 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix+"delete")) return + + + msg.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + msg.channel.send({content:"You are not in a ticket!"}) + return + } + + const closebutton = new discord.MessageActionRow() + .addComponents([ + new discord.MessageButton() + .setCustomId("deleteTicketTrue") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("❌") + ]) + + msg.channel.send({content:"**Click on the button below to delete this ticket!**",components:[closebutton]}) + + console.log("[system] deleted a ticket via command") + + }) + + + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "delete") return + + interaction.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + interaction.reply({content:"You are not in a ticket!"}) + return + } + + const closebutton = new discord.MessageActionRow() + .addComponents([ + new discord.MessageButton() + .setCustomId("deleteTicketTrue") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("❌") + ]) + + interaction.reply({content:"**Click on the button below to delete this ticket!**",components:[closebutton]}) + + console.log("[system] deleted a ticket via command") + + }) + }) +} \ No newline at end of file diff --git a/commands/help.js b/commands/help.js new file mode 100644 index 0000000..fe5798e --- /dev/null +++ b/commands/help.js @@ -0,0 +1,35 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + const helpEmbed = new discord.MessageEmbed() + .setColor(config.main_color) + .setTitle("Help Menu:") + + const prefix = config.prefix + const header = config.system.ticket_channel ? "**Go to <#"+config.system.ticket_channel+"> to create a ticket!**\n\n" : "**Run the command `/new` or `/ticket` to create a ticket!**\n\n" + helpEmbed.setDescription(header+"`"+prefix+"msg ` ➜ _Spawn an embed with buttons. (admin only)_\n\n`"+prefix+"rename ` ➜ _Rename a ticket. (no spaces)_\n`"+prefix+"close` ➜ _Close a ticket._\n`"+prefix+"delete` ➜ _Delete a ticket._\n\n`"+prefix+"add ` ➜ _Add a user to the ticket._\n`"+prefix+"remove ` ➜ _Remove a user from the ticket._`") + + if (config.credits) helpEmbed.setFooter({text:"Open-Ticket by DJdj Development | view on github for source code"}) + + + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix)) return + var args = msg.content.split(config.prefix) + + if (!args[1].startsWith("close") && !args[1].startsWith("delete") && !args[1].startsWith("remove") && !args[1].startsWith("add") && !args[1].startsWith("msg") && !args[1].startsWith("remove") && !args[1].startsWith("rename")){ + + msg.channel.send({embeds:[helpEmbed]}) + if (config.logs){console.log("[command] "+config.prefix+"ticket help (user:"+msg.author.username+")")} + } + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "help") return + + interaction.reply({embeds:[helpEmbed]}) + }) +} \ No newline at end of file diff --git a/commands/remove.js b/commands/remove.js new file mode 100644 index 0000000..7f15655 --- /dev/null +++ b/commands/remove.js @@ -0,0 +1,65 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix+"remove")) return + var user = msg.mentions.users.first() + if (!user){ + return msg.channel.send({content:"**There is no user to remove:**\nuse: `"+config.prefix+"remove `"}) + } + + if (!msg.member.permissions.has("MANAGE_CHANNELS") && !msg.member.permissions.has("ADMINISTRATOR")){ + return msg.channel.send({content:"You have no permissions to remove someone from the channel!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + msg.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + msg.channel.send({content:"You are not in a ticket!"}) + return + } + + msg.channel.permissionOverwrites.delete(user.id) + msg.channel.send({content:"Deleted "+user.tag+" from the ticket"}) + if (config.logs){console.log("[system] deleted user from ticket (name:"+user.username+",ticket:"+msg.channel.name+")")} + + var loguser = msg.mentions.users.first() + if (config.logs){console.log("[command] "+config.prefix+"remove "+loguser.username+" (user:"+msg.author.username+")")} + }) + + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "remove") return + const user = interaction.options.getUser("user") + const member = client.guilds.cache.find(g => g.id == interaction.guild.id).members.cache.find(m => m.id == interaction.member.id) + + if (!member.permissions.has("MANAGE_CHANNELS") && !member.permissions.has("ADMINISTRATOR")){ + return interaction.reply({content:"You have no permissions to remove someone from the channel!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + + interaction.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + interaction.reply({content:"You are not in a ticket!"}) + return + } + + interaction.channel.permissionOverwrites.delete(user.id) + interaction.reply({content:"Deleted "+user.tag+" from the ticket"}) + if (config.logs){console.log("[system] removed user from ticket (name:"+user.username+",ticket:"+interaction.channel.name+")")} + + var loguser = user + if (config.logs){console.log("[command] "+config.prefix+"remove "+loguser.username+" (user:"+interaction.user.username+")")} + }) + + + }) +} \ No newline at end of file diff --git a/commands/rename.js b/commands/rename.js new file mode 100644 index 0000000..e3333e4 --- /dev/null +++ b/commands/rename.js @@ -0,0 +1,81 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +module.exports = () => { + client.on("messageCreate",msg => { + if (!msg.content.startsWith(config.prefix+"rename")) return + + msg.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + msg.channel.send({content:"You are not in a ticket!"}) + return + } + + + if (!msg.member.permissions.has("MANAGE_CHANNELS") && !msg.member.permissions.has("ADMINISTRATOR")){ + return msg.channel.send({content:"You have no permissions to change the channel name!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + var newname = msg.content.split(config.prefix+"rename")[1].substring(1) + var name = msg.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + if (!prefix) prefix = "noprefix-" + + msg.channel.setName(prefix+newname) + msg.channel.send({content:"**The name is changed!**\nWarning: you can only change the channel name 2 times per minute! (this is due discord rate limits)"}) + + console.log("[system] renamed a ticket via command") + + }) + + + }) + + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "rename") return + + interaction.channel.messages.fetchPinned().then(msglist => { + var firstmsg = msglist.last() + + if (firstmsg == undefined || firstmsg.author.id != client.user.id){ + interaction.reply({content:"You are not in a ticket!"}) + return + } + + const member = client.guilds.cache.find(g => g.id == interaction.guild.id).members.cache.find(m => m.id == interaction.member.id) + if (!member.permissions.has("MANAGE_CHANNELS") && !member.permissions.has("ADMINISTRATOR")){ + return interaction.reply({content:"You have no permissions to change the channel name!\nYou need the `ADMINISTRATOR` or `MANAGE_CHANNELS` permission"}) + } + + var newname = interaction.options.getString("name") + var name = interaction.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + if (!prefix) prefix = "noprefix-" + + interaction.channel.setName(prefix+newname) + interaction.reply({content:"**The name is changed!**\nWarning: you can only change the channel name 2 times per minute! (this is due discord rate limits)"}) + + console.log("[system] renamed a ticket via command") + + }) + }) +} \ No newline at end of file diff --git a/commands/ticket.js b/commands/ticket.js index 2ae1b5a..f76af3c 100644 --- a/commands/ticket.js +++ b/commands/ticket.js @@ -1,242 +1,62 @@ const discord = require('discord.js') const bot = require('../index') const client = bot.client -const config = require("../config.json") +const config = bot.config +/** ============================= + ** NOT READY YET + ** =============================*/ module.exports = () => { + /**@type {String[]} */ + var msgIds = [] + config.messages.forEach((msg) => { + msgIds.push(msg.id) + }) + + client.on("messageCreate", msg => { - if (msg.content == config.prefix+"ticket msg"){ - if (msg.member.roles.cache.has(config.botperms_role) == false){ - msg.channel.send({content:config.messages.general.nopermissions}) + if (msg.content.startsWith(config.prefix+"msg")){ + if (config.main_adminroles.some((item)=>{return msg.member.roles.cache.has(item)}) == false){ + msg.channel.send({content:"You have no permission to run this command!"}) return } - var currentTicketButtons = 0 - var ticketButton = new discord.MessageActionRow() - var ticketButton2 = new discord.MessageActionRow() + const id = msg.content.split(config.prefix+"msg")[1].substring(1) ? msg.content.split(config.prefix+"msg")[1].substring(1) : false - const getColor = (color) => { - if (color.toLowerCase() == "red"){ - return "DANGER" - }else if (color.toLowerCase() == "green"){ - return "SUCCESS" - }else if (color.toLowerCase() == "blue" || color.toLowerCase() == "blurple"){ - return "PRIMARY" - }else if (color.toLowerCase() == "black" || color.toLowerCase() == "gray" || color.toLowerCase() == "grey"){ - return "SECONDARY" - }else if (color == "DANGER" || color == "SECONDARY" || color == "SUCCESS" || color == "PRIMARY"){ - return color - }else if (color.toLowerCase() == "none" || color.toLowerCase() == "false" || color.toLowerCase() == ""){ - return "SECONDARY" - }else return "SECONDARY" - } + if (!id) return msg.channel.send({content:"**There is no id!**\nUse: `"+config.prefix+"msg `"}) + if (!msgIds.includes(id)) return msg.channel.send({content:"**Invalid id!**\nChoose from this list:\n_"+msgIds.join("_\n_")+"_"}) - if (config.options.ticket1.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket1.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket1") - .setDisabled(false) - .setStyle(getColor(config.options.ticket1.color)) - .setEmoji(config.options.ticket1.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket1.icon) - .setURL(config.options.ticket1.url) - ) - } - } - if (config.options.ticket2.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket2.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket2") - .setDisabled(false) - .setStyle(getColor(config.options.ticket2.color)) - .setEmoji(config.options.ticket2.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket2.icon) - .setURL(config.options.ticket2.url) - ) - } - } - - if (config.options.ticket3.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket3.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket3") - .setDisabled(false) - .setStyle(getColor(config.options.ticket3.color)) - .setEmoji(config.options.ticket3.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket3.icon) - .setURL(config.options.ticket3.url) - ) - } - } - - if (config.options.ticket4.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket4.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket4") - .setDisabled(false) - .setStyle(getColor(config.options.ticket4.color)) - .setEmoji(config.options.ticket4.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket4.icon) - .setURL(config.options.ticket4.url) - ) - } - } - - if (config.options.ticket5.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket5.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket5") - .setDisabled(false) - .setStyle(getColor(config.options.ticket5.color)) - .setEmoji(config.options.ticket5.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket5.icon) - .setURL(config.options.ticket5.url) - ) - } - } - - if (config.options.ticket6.enabled){ - if (currentTicketButtons < 4){ - var localTicketButton = ticketButton - }else{ - var localTicketButton = ticketButton2 - } - - currentTicketButtons = currentTicketButtons + 1 - - if (config.options.ticket6.isURL == false){ - localTicketButton.addComponents( - new discord.MessageButton() - .setCustomId("newTicket6") - .setDisabled(false) - .setStyle(getColor(config.options.ticket6.color)) - .setEmoji(config.options.ticket6.icon) - ) - }else{ - localTicketButton.addComponents( - new discord.MessageButton() - .setDisabled(false) - .setStyle("LINK") - .setEmoji(config.options.ticket6.icon) - .setURL(config.options.ticket6.url) - ) - } - } - - var categorylist = "" - if (config.options.ticket1.enabled){categorylist = categorylist+"\n"+config.options.ticket1.icon+": **"+config.options.ticket1.name+"**\n"+config.options.ticket1.description} - - if (config.options.ticket2.enabled){categorylist = categorylist+"\n\n"+config.options.ticket2.icon+": **"+config.options.ticket2.name+"**\n"+config.options.ticket2.description} - - if (config.options.ticket3.enabled){categorylist = categorylist+"\n\n"+config.options.ticket3.icon+": **"+config.options.ticket3.name+"**\n"+config.options.ticket3.description} - - if (config.options.ticket4.enabled){categorylist = categorylist+"\n\n"+config.options.ticket4.icon+": **"+config.options.ticket4.name+"**\n"+config.options.ticket4.description} - - if (config.options.ticket5.enabled){categorylist = categorylist+"\n\n"+config.options.ticket5.icon+": **"+config.options.ticket5.name+"**\n"+config.options.ticket5.description} - - if (config.options.ticket6.enabled){categorylist = categorylist+"\n\n"+config.options.ticket6.icon+": **"+config.options.ticket6.name+"**\n"+config.options.ticket6.description} - - - - var ticketEmbed = new discord.MessageEmbed() - if (config.layout.ticketMsg.customColorEnabled){ - ticketEmbed.setColor(config.layout.ticketMsg.customColor) - }else{ticketEmbed.setColor(config.main_color)} - - if (config.layout.ticketMsg.footerEnabled){ - ticketEmbed.setFooter({text:config.layout.ticketMsg.footer}) - } - if (config.layout.ticketMsg.thumbnailEnabled){ - ticketEmbed.setThumbnail(config.layout.ticketMsg.thumbnailURL) - } - - ticketEmbed.setDescription("**Create a ticket:**\nClick one of the buttons below to create a ticket.\n\n__choose a category:__"+categorylist+"\n\n**watch out:**_You can only create "+config.system.max_allowed_tickets+" ticket(s) at a time!_") + const {embed,componentRows} = require("../core/ticketMessageEmbed").createEmbed(id) - - if (currentTicketButtons <= 4){ - var embedComponents = [ticketButton] - }else{var embedComponents = [ticketButton,ticketButton2]} - - msg.channel.send({embeds:[ticketEmbed],components:embedComponents}) + msg.channel.send({embeds:[embed],components:componentRows}) if (config.logs){console.log("[command] "+config.prefix+"ticket msg (user:"+msg.author.username+")")} if (config.logs){console.log("[system] created ticket message")} } }) + + client.on("interactionCreate", (interaction) => { + if (!interaction.isCommand()) return + if (interaction.commandName != "message") return + + if (config.main_adminroles.some((item)=>{return interaction.guild.members.cache.find((m) => m.id == interaction.member.id).roles.cache.has(item)}) == false){ + interaction.reply({content:"You have no permission to run this command!"}) + return + } + + const id = interaction.options.getString("id") + + if (!msgIds.includes(id)) return interaction.reply({content:"**Invalid id!**\nChoose from this list:\n_"+msgIds.join("_\n_")+"_"}) + + const {embed,componentRows} = require("../core/ticketMessageEmbed").createEmbed(id) + + interaction.reply({content:"The embed is in the message below!"}) + interaction.channel.send({embeds:[embed],components:componentRows}) + + if (config.logs){console.log("[command] "+config.prefix+"ticket msg (user:"+interaction.member.user.username+")")} + if (config.logs){console.log("[system] created ticket message")} + + }) } \ No newline at end of file diff --git a/commands/ticketExtra.js b/commands/ticketExtra.js deleted file mode 100644 index cfa84c7..0000000 --- a/commands/ticketExtra.js +++ /dev/null @@ -1,151 +0,0 @@ -const discord = require('discord.js') -const bot = require('../index') -const client = bot.client -const config = require("../config.json") - - - -module.exports = () => { - client.on("messageCreate",msg => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"ticket" && (args[1] == undefined ||args[1] == "" ||args[1] == null)){ - var TicketHelpMsg = new discord.MessageEmbed() - .setColor(config.main_color) - .setTitle("Ticket commands") - - const prefix = config.prefix - TicketHelpMsg.setDescription("**Go to <#"+config.system.ticket_channel+"> to create a ticket!**\n\n`"+prefix+"ticket msg` ➜ _Admin command._\n`"+prefix+"ticket rename` ➜ _Rename a ticket (no spaces)._\n`"+prefix+"ticket close` ➜ _Close a ticket._\n`"+prefix+"ticket add ` ➜ _Add a user to the ticket._\n`"+prefix+"ticket remove ` ➜ _Remove a user from the ticket._\n`"+prefix+"resetdatabase` ➜ _Steps to reset the database._\n`"+prefix+"resetdb` ➜ _Also database stuff._") - - if (config.credits){ - TicketHelpMsg.setFooter({text:"Open-Ticket by DJdj Development | view on github for source code"}) - } - - msg.channel.send({embeds:[TicketHelpMsg]}) - if (config.logs){console.log("[command] "+config.prefix+"ticket help (user:"+msg.author.username+")")} - } - }) - - client.on("messageCreate",msg => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"ticket" && args[1] == "close"){ - - msg.channel.messages.fetchPinned().then(msglist => { - var firstmsg = msglist.last() - - if (firstmsg == undefined){ - msg.channel.send({content:"I didn't find the close button!"}) - } - if (firstmsg.author.id != client.user.id){ - msg.channel.send({content:"You are not in a ticket!"}) - return - } - var CloseMsg = new discord.MessageEmbed() - .setColor(config.main_color) - .setDescription("Click [here]("+firstmsg.url+") to close this ticket.") - .setTitle("Close this ticket:") - - msg.channel.send({embeds:[CloseMsg]}) - if (config.logs){console.log("[command] "+config.prefix+"ticket close (user:"+msg.author.username+")")} - }) - - - } - }) - - client.on("messageCreate",msg => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"ticket" && args[1] == "rename"){ - - if (msg.member.roles.cache.has(config.botperms_role) == false && msg.author.id != "779742674932072469"){ - msg.channel.send({content:config.messages.general.nopermissions}) - return - } - - if (args[2] == null || args[2] == undefined || args[2] == "" || args[2] == false){ - msg.channel.send({content:"Not enough parameters!"}) - return - } - - var name = args[2] - var oldname = msg.channel.name - msg.channel.messages.fetchPinned().then(msglist => { - if (msglist.last().author.id == client.user.id){ - msg.channel.send({content:"The name from the ticket is changed!"}).then(rmsg => { - rmsg.channel.setName(name) - if (config.logs){console.log("[system] renamed a ticket (name:"+oldname+",newname:"+name+")")} - }) - }else{ - msg.channel.send({content:"You are not in a ticket!"}) - } - }) - if (config.logs){console.log("[command] "+config.prefix+"ticket rename (user:"+msg.author.username+")")} - } - }) - - client.on("messageCreate",msg => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"ticket" && args[1] == "add"){ - - if (msg.member.roles.cache.has(config.botperms_role) == false && msg.author.id != "779742674932072469"){ - msg.channel.send({content:config.messages.general.nopermissions}) - return - } - - msg.channel.messages.fetch().then(msglist => { - if (msglist.last().author.id != client.user.id){ - msg.channel.send({content:"You are not in a ticket!"}) - return - } - - if (args[2] == null || args[2] == undefined || args[2] == "" || args[2] == false){ - msg.channel.send({content:"Not enough parameters!"}) - return - } - var user = msg.mentions.users.first() - if (!user){ - return - } - - msg.channel.permissionOverwrites.create(user.id, { VIEW_CHANNEL:true, ADD_REACTIONS:true,ATTACH_FILES:true, EMBED_LINKS:true, SEND_MESSAGES:true}) - if (config.logs){console.log("[system] added user to ticket (name:"+user.username+",ticket:"+msg.channel.name+")")} - - }) - var loguser = msg.mentions.users.first() - if (config.logs){console.log("[command] "+config.prefix+"ticket add "+loguser.username+" (user:"+msg.author.username+")")} - } - }) - - client.on("messageCreate",msg => { - var args = msg.content.split(" ") - if (args[0] == config.prefix+"ticket" && args[1] == "remove"){ - - if (msg.member.roles.cache.has(config.botperms_role) == false && msg.author.id != "779742674932072469"){ - msg.channel.send({content:config.messages.general.nopermissions}) - return - } - - msg.channel.messages.fetchPinned().then(msglist => { - if (msglist.last().author.id != client.user.id){ - msg.channel.send({content:"You aren't in a ticket!"}) - return - } - - if (args[2] == null || args[2] == undefined || args[2] == "" || args[2] == false){ - msg.channel.send({content:"Not enough parameters!"}) - return - } - var user = msg.mentions.users.first() - if (!user){ - return - } - - msg.channel.permissionOverwrites.delete(user.id) - if (config.logs){console.log("[system] removed user from ticket (name:"+user.username+",ticket:"+msg.channel.name+")")} - - }) - - var loguser = msg.mentions.users.first() - if (config.logs){console.log("[command] "+config.prefix+"ticket remove "+loguser.username+" (user:"+msg.author.username+")")} - } - }) -} \ No newline at end of file diff --git a/commands/ticketSystem.js b/commands/ticketSystem.js deleted file mode 100644 index 6cf7ee4..0000000 --- a/commands/ticketSystem.js +++ /dev/null @@ -1,309 +0,0 @@ -const discord = require('discord.js') -const bot = require('../index') -const client = bot.client -const config = require("../config.json") - -const ticketStorage = bot.TicketStorage -const userTicketStorage = bot.userTicketStorage -const transcriptStorage = bot.transcriptStorage - -module.exports = () => { - - var closeButton = new discord.MessageActionRow() - .addComponents( - new discord.MessageButton() - .setCustomId("closeTicket") - .setDisabled(false) - .setStyle("SECONDARY") - .setLabel("Close Ticket") - .setEmoji("❌") - ) - - //ticket button click / create ticket - client.on("interactionCreate",interaction => { - - if (interaction.isButton() == false){ - return - } - - if (interaction.customId == "newTicket1"||interaction.customId == "newTicket2"||interaction.customId == "newTicket3"||interaction.customId == "newTicket4"||interaction.customId == "newTicket5"||interaction.customId == "newTicket6"){ - - interaction.deferUpdate() - - if (ticketStorage.getItem(interaction.member.id) == null ||ticketStorage.getItem(interaction.member.id) == "false"|| Number(ticketStorage.getItem(interaction.member.id)) < config.system.max_allowed_tickets){ - - try{ - if (config.system.enable_DM_Messages){ - interaction.member.send(config.messages.dm.newTicket) - } - } - catch{console.log("can't send DM to member || member doesn't allow dm's")} - - - //update storage - ticketStorage.setItem(interaction.member.id,Number(ticketStorage.getItem(interaction.member.id))+1) - var ticketNumber = interaction.member.user.username - - //set ticketName - if (interaction.customId == "newTicket1"){ - var ticketName = config.options.ticket1.channel_prefix+ticketNumber - var logsname = config.options.ticket1.name - }else if (interaction.customId == "newTicket2"){ - var ticketName = config.options.ticket2.channel_prefix+ticketNumber - var logsname = config.options.ticket2.name - }else if (interaction.customId == "newTicket3"){ - var ticketName = config.options.ticket3.channel_prefix+ticketNumber - var logsname = config.options.ticket3.name - }else if (interaction.customId == "newTicket4"){ - var ticketName = config.options.ticket4.channel_prefix+ticketNumber - var logsname = config.options.ticket4.name - }else if (interaction.customId == "newTicket5"){ - var ticketName = config.options.ticket5.channel_prefix+ticketNumber - var logsname = config.options.ticket5.name - }else if (interaction.customId == "newTicket6"){ - var ticketName = config.options.ticket6.channel_prefix+ticketNumber - var logsname = config.options.ticket6.name - } - - //set category - if (config.system.enable_category){ - var Category = config.system.ticket_category - }else{var Category = null} - - var permissionsArray = [] - - //set everyone allowed - if (config.system['has@everyoneaccess']){ - var everyoneAllowPerms = ["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] - var everyoneDenyPerms = [] - }else{ - var everyoneAllowPerms = [] - var everyoneDenyPerms = ["VIEW_CHANNEL"] - } - permissionsArray.push({ - id:interaction.member.id, - type:"member", - allow:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] - }) - permissionsArray.push({ - id:config.botperms_role, - type:"role", - allow:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] - }) - permissionsArray.push({ - id:interaction.guild.id, - type:"role", - allow:everyoneAllowPerms, - deny:everyoneDenyPerms - }) - if (config.system.member_role == "" || config.system.member_role == " " || config.system.member_role == "false" || config.system.member_role == "null" || config.system.member_role == "0"){ - permissionsArray.push({ - id:config.system.member_role, - type:"role", - deny:["VIEW_CHANNEL"] - }) - } - - //create the channel - interaction.guild.channels.create(ticketName,{ - type:"GUILD_TEXT", - parent:Category, - permissionOverwrites:permissionsArray - }).then(tChannel => { - userTicketStorage.setItem(tChannel.id,interaction.member.id) - - - var ticketEmbed = new discord.MessageEmbed() - if (config.layout.ticketEmbed.customColorEnabled){ - ticketEmbed.setColor(config.layout.ticketEmbed.customColor) - }else{ticketEmbed.setColor(config.main_color)} - - if (config.layout.ticketEmbed.footerEnabled){ - ticketEmbed.setFooter({text:"config.layout.ticketEmbed.footer"}) - } - if (config.layout.ticketEmbed.thumbnailEnabled){ - ticketEmbed.setThumbnail(config.layout.ticketEmbed.thumbnailURL) - } - ticketEmbed.setTitle("You created a ticket!") - ticketEmbed.setDescription(config.messages.ticket.newTicketEmbed) - - tChannel.send({content:"<@"+interaction.member.id+"> <@&"+config.botperms_role+">",embeds:[ticketEmbed],components:[closeButton]}).then(firstmsg => { - firstmsg.pin() - }) - if (config.logs){console.log("[system] created a new ticket (name:"+logsname+",user:"+interaction.user.username+")")} - }) - - - - }else{ - try { - if (config.system.enable_DM_Messages){ - interaction.member.send(config.messages.dm.alreadyCreated) - } - } - catch{console.log("can't send DM to member || member doesn't allow dm's")} - - - } - } - - - - }) - - //closeBAR - var closeBar = new discord.MessageActionRow() - .addComponents( - new discord.MessageButton() - .setCustomId("closeTicketTrue") - .setDisabled(false) - .setStyle("SECONDARY") - .setEmoji("✅") - ) - .addComponents( - new discord.MessageButton() - .setCustomId("closeTicketFalse") - .setDisabled(false) - .setStyle("SECONDARY") - .setEmoji("❌") - ) - - - - client.on("interactionCreate",interaction => { - if (interaction.isButton() == false){ - return - } - if (interaction.customId == "closeTicket"){ - interaction.deferUpdate() - interaction.message.edit({components:[closeBar]}) - } - }) - - client.on("interactionCreate",interaction => { - if (interaction.isButton() == false){ - return - } - if (interaction.customId == "closeTicketFalse"){ - interaction.deferUpdate() - - interaction.message.edit({components:[closeButton]}) - } - }) - - - - client.on("interactionCreate",interaction => { - if (interaction.isButton() == false){ - return - } - if (interaction.customId == "closeTicketTrue"){ - interaction.deferUpdate() - interaction.channel.messages.fetch().then(messages => { - var transcriptArray = [] - messages.forEach(msg => { - if (msg.author.id != client.user.id){ - transcriptArray.push("["+msg.author.username+"]: "+msg.content) - } - }) - var getuserID = userTicketStorage.getItem(interaction.channel.id) - var getusernameStep1 = client.users.cache.find(u => u.id === getuserID) - if (getusernameStep1 == null || getusernameStep1 == undefined || getusernameStep1 == false || getusernameStep1 == ""){ - var getuserNAME = ":usernotfound:" - }else { - var getuserNAME = getusernameStep1.username - } - - - if (config.system.enable_transcript){ - var transcript = transcriptArray.reverse().join("\n") - transcriptStorage.setItem(interaction.channel.id,transcript) - - //transcript color & thumbnail - if (config.layout.transcripts.customColorEnabled){ - var transcriptColor = config.layout.transcripts.customColor - }else{var transcriptColor = config.main_color} - - if (config.layout.transcripts.thumbnailEnabled){ - var transcriptThumbnail = config.layout.transcripts.thumbnailURL - }else{var transcriptThumbnail = ""} - - var splittedTranscript = [transcript.slice(0,2000)] - if (transcript.length > 4000){ - splittedTranscript.push(transcript.slice(2000,4000)) - splittedTranscript.push(transcript.slice(4000)) - }else if (transcript.length > 2000){ - splittedTranscript.push(transcript.slice(2000)) - } - var transcriptEmbed = new discord.MessageEmbed() - .setColor(transcriptColor) - .setAuthor({name:interaction.channel.name + " - ticket created by "+getuserNAME}) - .setTitle("There is a new transcript!") - .setDescription(splittedTranscript[0]) - .setFooter({text:"ticket closed by "+interaction.member.user.username}) - .setThumbnail(transcriptThumbnail) - - if (transcript.length > 4000){ - var transcriptEmbed2 = new discord.MessageEmbed() - .setColor(transcriptColor) - .setAuthor({name:interaction.channel.name + " - ticket created by "+getuserNAME}) - .setTitle("transcript #2") - .setDescription(splittedTranscript[1]) - .setFooter("ticket closed by "+interaction.member.user.username) - .setThumbnail(transcriptThumbnail) - - var transcriptEmbed3 = new discord.MessageEmbed() - .setColor(transcriptColor) - .setAuthor({name:interaction.channel.name + " - ticket created by "+getuserNAME}) - .setTitle("transcript #3") - .setDescription(splittedTranscript[1]) - .setFooter({text:"ticket closed by "+interaction.member.user.username}) - .setThumbnail(transcriptThumbnail) - - client.channels.cache.find(ch => ch.id == config.system.transcript_channel).send({embeds:[transcriptEmbed,transcriptEmbed2,transcriptEmbed3]}) - - }else if (transcript.length > 2000){ - var transcriptEmbed2 = new discord.MessageEmbed() - .setColor(transcriptColor) - .setAuthor({name:interaction.channel.name + " - ticket created by "+getuserNAME}) - .setTitle("transcript #2") - .setDescription(splittedTranscript[1]) - .setFooter({text:"ticket closed by "+interaction.member.user.username}) - .setThumbnail(transcriptThumbnail) - - client.channels.cache.find(ch => ch.id == config.system.transcript_channel).send({embeds:[transcriptEmbed,transcriptEmbed2]}) - }else{ - client.channels.cache.find(ch => ch.id == config.system.transcript_channel).send({embeds:[transcriptEmbed]}) - } - } - - - - interaction.channel.delete() - if (config.logs){console.log("[system] closed a ticket (name:"+interaction.channel.name+",user:"+interaction.user.username+")")} - - - - ticketStorage.setItem(getuserID,Number(ticketStorage.getItem(getuserID)) - 1) - - try { - if (config.system.enable_DM_Messages){ - interaction.member.send(config.messages.dm.closeTicket) - } - } - catch{console.log("can't send DM to member || member doesn't allow dm's")} - }) - - - - - } - }) - - - - process.on('unhandledRejection',error => { - console.log("ERROR: "+error) - }) - -} \ No newline at end of file diff --git a/config.json b/config.json index cea96cc..ffc16d0 100644 --- a/config.json +++ b/config.json @@ -1,132 +1,90 @@ { "bot_name":"Wumpus", "main_color":"#fffff", - "auth_token":"the bot's auth token", - "botperms_role":"the id from an admin role (they can do admin things with the bot)", - "prefix":"!", + "server_id":"server id (for slash cmds)", + "auth_token":"auth token", + "main_adminroles":["discord role id"], + "prefix":"!ticket ", "logs":true, + "languagefile":"english.json (currently not working)", "credits":true, "status":{ "enabled":true, - "type":"PLAYING | LISTENING | WATCHING | CUSTOM", + "type":"PLAYING", "text":"!ticket" }, "system":{ - "ticket_channel":"id of the channel with the !ticket msg", - "max_allowed_tickets":5, + "ticket_channel":"channel id", + "max_allowed_tickets":3, "enable_DM_Messages":true, - "enable_category":false, - "ticket_category":"the id of a category for the tickets", - "has@everyoneaccess":false, - "member_role":"this role doesn't have access to tickets", + "member_role":"member role (doesn't have access to tickets)", "enable_transcript":false, - "transcript_channel":"the channel for transcripts" + "enable_DM_transcript":false, + "transcript_channel":"channel id" }, - "messages":{ - "general":{ - "nopermissions":"**You don't have permissions to run this command!**" - }, - "dm":{ - "alreadyCreated":"**You currently have as many open tickets as the maximum allowed!**", - "newTicket":"You created a new ticket in our server, the staff will come and help you soon!", - "closeTicket":"**Your ticket is closed!**" - }, - "ticket":{ - "newTicketEmbed":"You created a ticket!\n\n_Click on the close button below to close this ticket_" - } - }, - - "layout":{ - "transcripts":{ - "customColorEnabled":false, - "customColor":"#ffffff", - "thumbnailEnabled":false, - "thumbnailURL":"https://www.example.com/path/to/image.png" - }, - "ticketMsg":{ - "customColorEnabled":false, - "customColor":"#ffffff", - "footerEnabled":false, - "footer":"We are using open-tickets from DJj123dj", - "thumbnailEnabled":false, - "thumbnailURL":"https://www.example.com/path/to/image.png" - }, - "ticketEmbed":{ - "customColorEnabled":false, - "customColor":"#ffffff", - "footerEnabled":false, - "footer":"This is embed in the start of a ticket.", - "thumbnailEnabled":false, - "thumbnailURL":"https://www.example.com/path/to/image.png" - } - }, - - "options":{ - "ticket1":{ - "enabled":true, - "icon":"🎫", - "description":"Create a general ticket.", - "name":"Ticket", - "channel_prefix":"ticket-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" - }, - "ticket2":{ - "enabled":false, - "icon":"💼", - "description":"Do you want to apply? Then click here!", - "name":"Apply", - "channel_prefix":"apply-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" - }, - "ticket3":{ - "enabled":false, - "icon":"❓", - "description":"Click here if you have a question about the server.", + "options":[ + { + "id":"general", "name":"Question", - "channel_prefix":"question-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" + "description":"Create this ticket if you have a question.", + "icon":"🎫", + "label":"question", + "type":"ticket", + + "color":"gray", + "adminroles":["role id"], + "channelprefix":"question-", + "category":"category id", + "message":"You created a ticket!", + "enableDMMessage":true + }, - "ticket4":{ - "enabled":false, - "icon":"🛒", - "description":"Buy something in our shop.", - "name":"Shop", - "channel_prefix":"shop-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" + { + "id":"website", + "name":"Website", + "description":"Go to our website.", + "icon":"✅", + "label":"click here", + "type":"website", + + "url":"https://www.dj-dj.be" + }, - "ticket5":{ - "enabled":false, - "icon":"😄", - "description":"hello", - "name":"AnotherTicket", - "channel_prefix":"prefix-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" - }, - "ticket6":{ - "enabled":false, - "icon":"😄", - "description":"hello", - "name":"AnotherTicket", - "channel_prefix":"prefix-", - "color":"red | green | blue | gray | none", - "isURL":false, - "url":"https://www.example.com/a/random/path/to/a/website" + { + "id":"role", + "name":"Verify", + "description":"Click here to get the verify role.", + "icon":"👮🏼", + "label":"get a role", + "type":"role", + + "color":"red", + "roles":["discord role id"], + "mode":"add&remove|remove|add", + "enableDMMessage":true + } - } + ], + + "messages":[ + { + "id":"1", + "name":"Choose Your Ticket", + "description":"You can click one of the buttons below!\nYou can also customize all the embeds now!", + "enableFooter":true, + "footer":"Open Ticket v2.0.0 beta - sneakpeak", + "enableThumbnail":false, + "thumbnail":"https://www.example.com/catmemes/cat.png", + "color":"#0075e9", + "options":["general","apply"], + "enableTicketExplaination":true, + "enableMaxTicketsWarning":true + } + ] } \ No newline at end of file diff --git a/core/checker.js b/core/checker.js new file mode 100644 index 0000000..fc17c58 --- /dev/null +++ b/core/checker.js @@ -0,0 +1,312 @@ +exports.checker = async () => { + const chalk = await (await import("chalk")).default + const config = require("../config.json") + var errorList = [] + var isError = false + var warnList = [] + var isWarn = false + const createError = (message) => { + isError = true + errorList.push("open-ticket CONFIG ERROR: "+message) + } + const createWarn = (message) => { + isWarn = true + warnList.push("open-ticket CONFIG WARNING: "+message) + } + + //validators + /**@param {"userid"|"roleid"|"channelid"|"serverid"|"categoryid"} mode @param {String} value @param {String} path */ + const checkDiscord = (mode,value,path) => { + if (mode == "userid"){ + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+"' | this user id is invalid") + } + }else if (mode == "channelid"){ + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+"' | this channel id is invalid") + } + }else if (mode == "roleid"){ + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+"' | this role id is invalid") + } + }else if (mode == "serverid"){ + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+"' | this server id is invalid") + } + }else if (mode == "categoryid"){ + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+"' | this category id is invalid") + } + } + } + /**@param {String} value */ + const checkToken = (value) => { + if (value.includes(" ") || value.length < 40 || value.length > 70){ + createError("'auth_token' | your token is invalid") + } + } + + /**@param {String} value @param {String} path */ + const checkColor = (value,path) => { + if (!/^#[a-f0-9]{6}$/.test(value)){ + createError("'"+path+"' | invalid color!") + } + } + + /**@param {String} value @param {String} path @param {Number} minLength @param {Number} maxLength @param {String} name */ + const checkString = (value,minLength,maxLength,path,name) => { + if (value.length < minLength){ + createError("'"+path+"' | "+name+" is too short!") + }else if (value.length > maxLength){ + createError("'"+path+"' | "+name+" is too long!") + } + } + + /**@param {"userid"|"roleid"|"channelid"|"serverid"} mode @param {String[]} arrayValue @param {String} path */ + const checkDiscordArray = (mode,arrayValue,path) => { + if (mode == "userid"){ + arrayValue.forEach((value,index) => { + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+":"+index+"' | this user id is invalid") + } + }) + }else if (mode == "channelid"){ + arrayValue.forEach((value,index) => { + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+":"+index+"' | this channel id is invalid") + } + }) + }else if (mode == "roleid"){ + arrayValue.forEach((value,index) => { + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+":"+index+"' | this role id is invalid") + } + }) + }else if (mode == "serverid"){ + arrayValue.forEach((value,index) => { + if (value.length < 16 || value.length > 20 || !/^\d+$/.test(value)){ + createError("'"+path+":"+index+"' | this server id is invalid") + } + }) + } + } + /**@param {String} value @param {"string"|"boolean"|"number"} type */ + const checkType = (value,type,path) => { + if (type == "boolean"){ + if (typeof value != "boolean"){ + createError("'"+path+"' | invalid type, this must be a boolean!") + } + }else if (type == "number"){ + if (typeof value != "number"){ + createError("'"+path+"' | invalid type, this must be a number!") + } + }else if (type == "string"){ + if (typeof value != "string"){ + createError("'"+path+"' | invalid type, this must be a string!") + } + } + } + + const checkButtonColor = (value,path) => { + if (value != "none" && value != "grey" && value != "gray" && value != "black" && value != "red" && value != "green" && value != "blue" && value != "blurple"){ + createError("'"+path+"' | invalid color, it must be one of these: none, gray, red, green, blue!") + } + } + + const getButtonClass = require("./utils/getButton").rawButtonData + /**@param {getButtonClass} option */ + const checkOption = (option,path) => { + + //id + checkType(option.id,"string",path+"/id") + if (option.id.includes(" ")){ + createError("'"+path+"/id' | option id can't contain space characters!") + } + if (option.id.length > 20){ + createError("'"+path+"/id' | option id max lenght is 20") + } + + //name + checkType(option.name,"string",path+"/name") + if (option.name.length > 100){ + createWarn("'"+path+"/name' | if your name is too long it maybe doesn't look so good!") + } + + //description + checkType(option.description,"string",path+"/description") + + //icon + const emojiRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi; + if (option.icon.length > 0){ + + const isEmoji = emojiRegex.test(option.icon) + + if (isEmoji == false){ + createError("'"+path+"/icon' | invalid emoji or custom discord emoji") + } + } + checkType(option.icon,"string",path+"/icon") + + //label + checkType(option.label,"string",path+"/label") + if (option.label.length < 1 && option.icon.length < 1){ + createError("'"+path+"' | you need at least one of these: label, icon!") + } + + //type + checkType(option.type,"string",path+"/type") + if (option.type != "ticket" && option.type != "website" && option.type != "role"){ + createError("'"+path+"/type' | the type must be one of these: ticket, website, role!") + } + /**@type {"ticket"|"website"|"role"} */ + const type = option.type + + if (type == "ticket"){ + //color + checkType(option.color,"string",path+"/color") + checkButtonColor(option.color,path+"/color") + + //adminroles + checkDiscordArray("roleid",option.adminroles,path+"/adminroles") + + //channelprefix + checkType(option.channelprefix,"string",path+"/channelprefix") + if (option.channelprefix.includes(" ")){ + createWarn("'"+path+"/channelprefix' | this prefix can't contain spaces!") + } + + //category + checkType(option.category,"string",path+"/category") + checkDiscord("categoryid",option.category,path+"/category") + + //message + checkType(option.message,"string",path+"/message") + + //enableDMmessage + checkType(option.enableDMMessage,"boolean",path+"/enableDMMessage") + }else if (type == "website"){ + //url + checkType(option.url,"string",path+"/url") + if (option.url.includes(" ")){ + createWarn("'"+path+"/url' | a url can't contain spaces") + } + if (option.url.length < 1){ + createError("'"+path+"/url' | you have no url in this button") + } + if (!option.url.startsWith("https://") && !option.url.startsWith("http://") && !option.url.startsWith("discord://")){ + createWarn("'"+path+"/url' | your url doesn't start with https or http!") + } + if (option.url.startsWith("http://")){ + createWarn("'"+path+"/url' | your url is not a HTTPS link! discord maybe blocks this url!") + } + }else if (type == "role"){ + //color + checkType(option.color,"string",path+"/color") + checkButtonColor(option.color,path+"/color") + + //roles + checkDiscordArray("roleid",option.roles,path+"/roles") + + //mode + checkType(option.mode,"string",path+"/mode") + if (option.mode != "add&remove" && option.mode != "add" && option.mode != "remove"){ + createError("'"+path+"/mode' | mode must be one of these: add, remove, add&remove") + } + + //enableDMmessage + checkType(option.enableDMMessage,"boolean",path+"/enableDMMessage") + } + } + + //checker + var configArray = ["bot_name","main_color","server_id","auth_token","main_adminroles","prefix","logs","languagefile","credits","status","system","options","messages"] + configArray.forEach((item) => { + if (config[item] == undefined){ + throw new Error("\n\nMAIN ERROR: the item '"+item+"' doesn't exist in config.json") + } + }) + + + + checkType(config.bot_name,"string","bot_name") + checkColor(config.main_color,"main_color") + checkDiscord("serverid",config.server_id,"server_id") + checkToken(config.auth_token) + checkDiscordArray("roleid",config.main_adminroles,"main_adminroles") + checkString(config.prefix,1,15,"prefix","prefix") + checkType(config.logs,"boolean","logs") + //languagefile (comming soon) + + + checkType(config.credits,"boolean","credits") + //status: + checkType(config.status.enabled,"boolean","status/enabled") + if (config.status.enabled){ + if (config.status.type != "PLAYING" && config.status.type != "LISTENING" && config.status.type != "WATCHING"){ + createError("'status/type' | not a valid status type!") + } + if (config.status.text.length < 1 || config.status.text.length > 40){ + createError("'status/text' | text too long or short!") + } + } + + + //system: + if (config.system.ticket_channel){ + if (config.system.ticket_channel.length < 16 || config.system.ticket_channel.length > 20 || !/^\d+$/.test(config.system.ticket_channel)){ + createError("'system/ticket_channel' | this channel id is invalid") + } + }else createWarn("'"+path+"' | you have no ticket channel selected!") + checkType(config.system.max_allowed_tickets,"number","system/max_allowed_tickets") + checkType(config.system.enable_DM_Messages,"boolean","system/enable_DM_Messages") + checkType(config.system["has@everyoneaccess"],"boolean","system/has@everyoneaccess") + if (config.system.member_role != "" && config.system.member_role != " " && config.system.member_role != "false" && config.system.member_role != "null" && config.system.member_role != "0"){ + checkDiscord("roleid",config.system.member_role,"system/member_role") + } + checkType(config.system.enable_transcript,"boolean","system/enable_transcript") + checkType(config.system.enable_DM_transcript,"boolean","system/enable_DM_transcript") + if (config.system.enable_transcript){ + checkDiscord("channelid",config.system.transcript_channel,"system/transcript_channel") + } + + //options + + config.options.forEach((option,index) => { + checkOption(option,"options/"+index) + }) + + + + //the end + if (errorList.length > 0 || warnList.length > 0){ + console.log("REPORT:\n===========================") + } + warnList.forEach((w) => { + const splitw = w.split("'") + if (splitw.length > 1){ + var splitstring = chalk.yellow(splitw[0])+chalk.blue("'"+splitw[1]+"'")+chalk.yellow(splitw[2]) + }else {var splitstring = chalk.yellow(splitw[0])} + console.log(splitstring) + }) + errorList.forEach((e) => { + const splite = e.split("'") + if (splite.length > 1){ + var splitstring = chalk.red(splite[0])+chalk.blue("'"+splite[1]+"'")+chalk.red(splite[2]) + }else {var splitstring = chalk.red(splite[0])} + console.log(splitstring) + }) + if (isError){ + console.log("===========================") + console.log("=> "+chalk.bgRed("your bot doesn't work if you don't fix the above errors!")) + if (isWarn){ + console.log("\n=> "+chalk.bgYellow("if you ignore warns, some things may work differently than expected!")) + } + process.exit(0) + }else if (isWarn == true && isError == false){ + console.log("===========================") + console.log("=> "+chalk.bgYellow("if you ignore warns, some things may work differently than expected!")) + } + +} + +this.checker() \ No newline at end of file diff --git a/core/closebuttons.js b/core/closebuttons.js new file mode 100644 index 0000000..1f1c903 --- /dev/null +++ b/core/closebuttons.js @@ -0,0 +1,236 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + + +module.exports = () => { + //closebar + var closeBar = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("closeTicketTrue") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("✅") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("closeTicketFalse") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("❌") + ) + var deleteBar = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicketTrue") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("✅") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicketFalse") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("❌") + ) + var deleteBar1 = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicketTrue1") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("✅") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicketFalse1") + .setDisabled(false) + .setStyle("SECONDARY") + .setEmoji("❌") + ) + //closebutton + var closeRowNormal = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("closeTicket") + .setDisabled(false) + .setStyle("SECONDARY") + .setLabel("Close Ticket") + .setEmoji("🔒") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicket") + .setDisabled(false) + .setStyle("DANGER") + .setLabel("Delete Ticket") + .setEmoji("✖️") + ) + var closeRowClosed = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicket1") + .setDisabled(false) + .setStyle("DANGER") + .setLabel("Delete Ticket") + .setEmoji("✖️") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("sendTranscript") + .setDisabled(false) + .setStyle("SECONDARY") + .setLabel("Send Transcript File") + .setEmoji("📄") + ) + + + //NORMAL CLOSE + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "closeTicket") return + + interaction.deferUpdate() + interaction.message.edit({components:[closeBar]}) + + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "closeTicketFalse") return + + interaction.deferUpdate() + interaction.message.edit({components:[closeRowNormal]}) + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "closeTicketTrue") return + + interaction.deferUpdate() + interaction.message.edit({components:[closeRowClosed]}) + + /** + * @type {String} + */ + const name = interaction.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + require("./ticketCloser").closeTicket(interaction,prefix,"close") + }) + + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "closeTicketTrue1") return + + interaction.deferUpdate() + const closedButtonDisabled = new discord.MessageActionRow() + .addComponents([ + new discord.MessageButton() + .setCustomId("closeTicketTrue1") + .setDisabled(true) + .setStyle("SECONDARY") + .setEmoji("🔒") + ]) + interaction.message.edit({components:[closedButtonDisabled]}) + + /** + * @type {String} + */ + const name = interaction.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + require("./ticketCloser").closeTicket(interaction,prefix,"close") + }) + + + //NORMAL DELETE + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicket") return + + interaction.deferUpdate() + interaction.message.edit({components:[deleteBar]}) + + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicketFalse") return + + interaction.deferUpdate() + interaction.message.edit({components:[closeRowNormal]}) + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicketTrue") return + + interaction.deferUpdate() + + /** + * @type {String} + */ + const name = interaction.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + require("./ticketCloser").closeTicket(interaction,prefix,"delete") + }) + + + + + //CLOSED DELETE + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicket1") return + + interaction.deferUpdate() + interaction.message.edit({components:[deleteBar1]}) + + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicketFalse1") return + + interaction.deferUpdate() + interaction.message.edit({components:[closeRowClosed]}) + }) + client.on("interactionCreate",interaction => { + if (!interaction.isButton()) return + if (interaction.customId != "deleteTicketTrue1") return + + interaction.deferUpdate() + + /** + * @type {String} + */ + const name = interaction.channel.name + var prefix = "" + const tickets = config.options + tickets.forEach((ticket) => { + if (name.startsWith(ticket.channelprefix)){ + prefix = ticket.channelprefix + } + }) + + require("./ticketCloser").closeTicket(interaction,prefix,"deletenotranscript") + }) +} \ No newline at end of file diff --git a/core/dynamicdatabase/cleardata.js b/core/dynamicdatabase/cleardata.js new file mode 100644 index 0000000..c63acfd --- /dev/null +++ b/core/dynamicdatabase/cleardata.js @@ -0,0 +1,53 @@ +const fs = require('fs') +const readline = require("readline") +const rl = readline.createInterface({ + input:process.stdin, + output:process.stdout, + terminal:true +}) + +const ask = async () => { + const chalk = await (await import("chalk")).default + rl.question(chalk.yellow("WARNING: are you sure that there are",chalk.red("no tickets open"),"at the moment? ")+chalk.green("(yes/no)")+"\n",(answer) => { + if (answer != "1" && !answer.toLowerCase().startsWith("yes")){ + console.log(chalk.red("invalid answer")) + ask() + return + } + console.log(chalk.green("clearing data...")) + runClear() + rl.close() + }) +} +ask() + +const runClear = async () => { + const chalk = await (await import("chalk")).default + console.log(chalk.blue("You can get a node.js warning below, just ignore that, it's not important!")) + const dataPaths = ["./storage/dynamicDB"] + + var index = 0 + + dataPaths.forEach((path) => { + try { + fs.rmdirSync(path,{recursive:true,force:true}) + }catch{ + //console.log(path,"doesn't exist") + } + setTimeout(() => { + try{ + fs.mkdirSync(path) + }catch{ + //console.log(path,"already exists") + } + index++ + },500) + }) + + setInterval(() => { + if (index >= 1){ + console.log(chalk.bgGreen("All local data is cleared!")) + process.exit(1) + } + },500) +} \ No newline at end of file diff --git a/core/dynamicdatabase/dynamicfiles/local.js b/core/dynamicdatabase/dynamicfiles/local.js new file mode 100644 index 0000000..fec0b6c --- /dev/null +++ b/core/dynamicdatabase/dynamicfiles/local.js @@ -0,0 +1,39 @@ +const localstorage = require("node-localstorage") + +const ls = new localstorage.LocalStorage("./storage/dynamicDB") + +/** + * + * @param {String} category + * @param {String} id + * @param {String} value + * @returns {Boolean} + */ +exports.set = (category,id,value) => { + ls.setItem(category+"--"+id,value.toString()) + return true +} + +/** + * + * @param {String} category + * @param {String} id + * @returns {String|false} + */ +exports.get = (category,id) => { + const result = ls.getItem(category+"--"+id) + if (!result) return false + + return result +} + +/** + * + * @param {String} category + * @param {String} id + * @returns {Boolean} + */ +exports.delete = (category,id) => { + ls.removeItem(category+"--"+id) + return true +} \ No newline at end of file diff --git a/storage/storage.js b/core/dynamicdatabase/storage.js similarity index 54% rename from storage/storage.js rename to core/dynamicdatabase/storage.js index 67d979b..7405466 100644 --- a/storage/storage.js +++ b/core/dynamicdatabase/storage.js @@ -1,3 +1,22 @@ +//Do not change this without knowing what you are doing!! +const databaseFile = "local" +//Do not change this without knowing what you are doing!! + + +try { + var db = require("./dynamicfiles/local") +}catch{ + console.log("I couldn't find the database file! (location: ./core/dynamicdatabase/storage.js:2)") + process.exit(1) +} + +module.exports = db + + +/** ============================= + ** NOT READY YET + ** must be dynamic + ** =============================*/ const LocalStorage = require("node-localstorage") const ticketStorage = new LocalStorage.LocalStorage("./storage/tickets") diff --git a/core/getoptions.js b/core/getoptions.js new file mode 100644 index 0000000..a8a7744 --- /dev/null +++ b/core/getoptions.js @@ -0,0 +1,114 @@ +const bot = require("../index") +const config = bot.config + +/** + * @param {Number} index + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"color"|"adminroles"|"channelprefix"|"category"|"message"|"enableDMMessage"} value + */ +exports.getTicketValue = (index,value) => { + if (value == "id"){ + return "newT"+config.options[index][value] + } + return config.options[index][value] +} + +/** + * @param {Number} index + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"url"} value + */ + exports.getWebsiteValue = (index,value) => { + return config.options[index][value] +} + +/** + * @param {Number} index + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"color"|"roles"|"mode"|"enableDMMessage"} value + */ +exports.getRoleValue = (index,value) => { + return config.options[index][value] +} + +/** + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"color"|"adminroles"|"channelprefix"|"category"|"message"|"enableDMMessage"} value + */ + exports.getTicketValues = (value) => { + var result = [] + config.options.forEach((o) => { + if (value == "id"){ + result.push("newT"+o[value]) + }else{ + result.push(o[value]) + } + }) + return result +} + +/** + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"url"} value + */ + exports.getWebsiteValues = (value) => { + var result = [] + config.options.forEach((o) => { + result.push(o[value]) + }) + return result +} + +/** + * @param {"id"|"name"|"description"|"icon"|"label"|"type"|"color"|"roles"|"mode"|"enableDMMessage"} value + */ +exports.getRoleValues = (value) => { + var result = [] + config.options.forEach((o) => { + result.push(o[value]) + }) + return result +} + +class TicketOptions { + constructor() { + this.id = "" + this.name = "" + this.description = "" + this.icon = "" + this.label = "" + this.type = "" + this.color = "" + this.adminroles = ["",""] + this.channelprefix = "" + this.category = "" + this.message = "" + this.enableDMMessage = true + } +} +/** + * + * @param {String} id + * @returns {TicketOptions|false} + */ +exports.getOptionsById = (id) => { + var result = false + config.options.forEach((option) => { + if ("newT"+option.id == id){ + result = option + } + }) + return result +} + +exports.exportedOptionClass = class { + constructor() { + this.id = "" + this.name = "" + this.description = "" + this.icon = "" + this.label = "" + this.type = "" + this.color = "" + this.adminroles = ["",""] + this.channelprefix = "" + this.category = "" + this.message = "" + this.enableDMMessage = true + } +} \ No newline at end of file diff --git a/core/reactionRoles.js b/core/reactionRoles.js new file mode 100644 index 0000000..0e288b4 --- /dev/null +++ b/core/reactionRoles.js @@ -0,0 +1,56 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +const storage = bot.storage + +module.exports = async () => { + const chalk = await (await import("chalk")).default + + client.on("interactionCreate",(interaction) => { + if (!interaction.isButton()) return + if (!interaction.customId.startsWith("newR")) return + + interaction.deferUpdate() + + const optionid = interaction.customId.split("newR")[1] + if (!optionid){ + interaction.reply({content:"This button doesn't exist anymore"}) + return + } + + try { + + const option = require("./utils/getButton").getOption(optionid) + /**@type {"add"|"remove"|"add&remove"} */ + const mode = option.mode + const user = interaction.member + + if (mode == "add"){ + option.roles.forEach((role) => { + interaction.guild.members.cache.find(u => u.id == user.id).roles.add(role) + console.log("[system] added role",role,"to",user.user.tag) + }) + }else if (mode == "remove"){ + option.roles.forEach((role) => { + interaction.guild.members.cache.find(u => u.id == user.id).roles.remove(role) + console.log("[system] removed role",role,"from",user.user.tag) + }) + }else if (mode == "add&remove"){ + option.roles.forEach((role) => { + if (interaction.guild.members.cache.find(u => u.id == user.id).roles.cache.has(role)){ + interaction.guild.members.cache.find(u => u.id == user.id).roles.remove(role) + console.log("[system] removed role",role,"from",user.user.tag) + }else { + interaction.guild.members.cache.find(u => u.id == user.id).roles.add(role) + console.log("[system] added role",role,"to",user.user.tag) + } + }) + } + + }catch{ + interaction.reply({ephemeral:true,content:"Something went wrong! Contact the owner of this bot for more information"}) + } + }) +} \ No newline at end of file diff --git a/core/slashSystem/slashDisable.js b/core/slashSystem/slashDisable.js new file mode 100644 index 0000000..e89a254 --- /dev/null +++ b/core/slashSystem/slashDisable.js @@ -0,0 +1,24 @@ +const discord = require("discord.js") +const bot = require("../../index") +const client = require("../../index").client +const config = bot.config +const getoptions = require("../getoptions") + +module.exports = async () => { + const chalk = (await import("chalk")).default + + console.log(chalk.red("\nINSTRUCTIONS:\n\n1: Run the command you want to delete in discord.\n2: You get the message 'This command is now deleted!'\n3: There is also a message in the console that says the command is deleted.\n4: reload your discord.")) + client.on("interactionCreate",(interaction) => { + if (!interaction.isCommand()) return console.log("this isn't a command!") + + interaction.reply({content:"This command is now deleted!"}) + + try { + client.application.commands.delete(interaction.commandId,interaction.guild.id).then(() => { + console.log("deleted the command",chalk.blue(interaction.commandName),"(id:",interaction.commandId+")") + }) + }catch{ + console.log(chalk.red("something went wrong by the deleting of this command!")) + } + }) +} \ No newline at end of file diff --git a/core/slashSystem/slashEnable.js b/core/slashSystem/slashEnable.js new file mode 100644 index 0000000..7fbb6bb --- /dev/null +++ b/core/slashSystem/slashEnable.js @@ -0,0 +1,182 @@ +const discord = require("discord.js") +const bot = require("../../index") +const client = require("../../index").client +const config = bot.config +const getoptions = require("../getoptions") + +module.exports = async () => { + const chalk = (await import("chalk")).default + const sid = config.server_id + + const ids = getoptions.getTicketValues("id") + /**@type {[{name:String,value:String}]} */ + var choices = [] + ids.forEach((id) => { + const option = getoptions.getOptionsById(id) + choices.push({name:option.name,value:option.id}) + }) + + /**@type {[{name:String,value:String}]} */ + var msgchoices = [] + config.messages.forEach((msg) => { + msgchoices.push({name:msg.id,value:msg.id}) + }) + + var readystats = 0 + + process.stdout.write("[status] there are "+chalk.blue("0 out of 9")+" commands ready! (this can take up to 40 seconds)") + setInterval(() => { + process.stdout.cursorTo(0) + process.stdout.write("[status] there are "+chalk.blue(readystats+" out of 9")+" commands ready! (this can take up to 40 seconds)") + if (readystats >= 9){ + console.log(chalk.green("\nready!")) + console.log(chalk.bgBlue("you can now start the bot with 'npm start'!")) + process.exit(1) + } + },100) + + //create a ticket (/new or /ticket) + client.application.commands.create({ + name:"ticket", + description:"Create a ticket!", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"STRING", + name:"type", + description:"The type of ticket.", + required:true, + choices:choices + + } + ] + },sid).then(() => { + readystats++ + }) + + client.application.commands.create({ + name:"new", + description:"Create a ticket!", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"STRING", + name:"type", + description:"The type of ticket.", + required:true, + choices:choices + + } + ] + },sid).then(() => { + readystats++ + }) + + //help + client.application.commands.create({ + name:"help", + description:"The help menu", + defaultPermission:true, + type:"CHAT_INPUT" + },sid).then(() => { + readystats++ + + }) + + //close + client.application.commands.create({ + name:"close", + description:"Close the current ticket.", + defaultPermission:true, + type:"CHAT_INPUT" + },sid).then(() => { + readystats++ + }) + + //delete + client.application.commands.create({ + name:"delete", + description:"Delete the current ticket.", + defaultPermission:true, + type:"CHAT_INPUT" + },sid).then(() => { + readystats++ + }) + + //msg + client.application.commands.create({ + name:"message", + description:"Create a ticket message.", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"STRING", + name:"id", + description:"The message id.", + required:true, + choices:msgchoices + + } + ] + },sid).then(() => { + readystats++ + }) + + //add + client.application.commands.create({ + name:"add", + description:"Add people to this ticket.", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"USER", + name:"user", + description:"The user to add.", + required:true + } + ] + },sid).then(() => { + readystats++ + }) + + //remove + client.application.commands.create({ + name:"remove", + description:"Remove people from this ticket.", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"USER", + name:"user", + description:"The user to remove.", + required:true + } + ] + },sid).then(() => { + readystats++ + }) + + //rename + client.application.commands.create({ + name:"rename", + description:"Rename this ticket.", + defaultPermission:true, + type:"CHAT_INPUT", + options:[ + { + type:"STRING", + name:"name", + description:"The new name (without prefix).", + required:true + } + ] + },sid).then(() => { + readystats++ + }) + +} \ No newline at end of file diff --git a/core/ticketCloser.js b/core/ticketCloser.js new file mode 100644 index 0000000..cce6df6 --- /dev/null +++ b/core/ticketCloser.js @@ -0,0 +1,156 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +const storage = bot.storage + +/** + * + * @param {discord.Interaction} interaction + * @param {String} prefix + * @param {"delete"|"close"|"deletenotranscript"} mode + */ +exports.closeTicket = async (interaction,prefix,mode) => { + const chalk = await (await import("chalk")).default + const channelmessages = await interaction.channel.messages.fetch() + + channelmessages.sweep((msgSweep) => { + return msgSweep.author.id == client.user.id + }) + + /** + * @type {String} ticketuserarray + */ + const ticketuserarray = interaction.channel.name + const ticketusername = ticketuserarray.split(prefix)[1] + + var getuserID = storage.get("userTicketStorage",interaction.channel.id) + try { + var getusernameStep1 = client.users.cache.find(u => u.id === getuserID) + var isDatabaseError = false + }catch{ + var isDatabaseError = true + } + + if (!getusernameStep1){ + console.log(chalk.red("[database error]"),"something went wrong when getting the data in the database.\nNo panic, this error fixes itself!") + var isDatabaseError = true + } + + var enableTranscript = true + + if (mode == "delete"){ + interaction.channel.delete() + if (config.logs){console.log("[system] deleted a ticket (name:"+interaction.channel.name+",user:"+interaction.user.username+")")} + + if (!isDatabaseError) storage.set("ticketStorage",getuserID,Number(storage.get("ticketStorage",getuserID)) - 1) + }else if (mode == "close"){ + var permissionArray = [] + if (!isDatabaseError) permissionArray.push({ + id:getusernameStep1.id, + type:"member", + allow:["VIEW_CHANNEL"], + deny:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES"] + }) + + permissionArray.push({ + id:interaction.guild.id, + type:"role", + deny:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] + }) + + interaction.channel.permissionOverwrites.set(permissionArray) + + //message + var closeButtonRow = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicket1") + .setDisabled(false) + .setStyle("DANGER") + .setLabel("Delete Ticket") + .setEmoji("✖️") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("sendTranscript") + .setDisabled(false) + .setStyle("SECONDARY") + .setLabel("Send Transcript File") + .setEmoji("📄") + ) + const embed = new discord.MessageEmbed() + .setColor(config.main_color) + .setTitle("Closed this ticket!") + .setDescription("Only admins can now talk in this ticket!\n\n*Click on the button below to delete this ticket*") + interaction.channel.send({embeds:[embed],components:[closeButtonRow]}) + }else if (mode == "deletenotranscript"){ + enableTranscript = false + interaction.channel.delete() + if (config.logs){console.log("[system] deleted a ticket (name:"+interaction.channel.name+",user:"+interaction.user.username+")")} + + if (!isDatabaseError) storage.set("ticketStorage",getuserID,Number(storage.get("ticketStorage",getuserID)) - 1) + } + + if (enableTranscript == true && mode != "deletenotranscript"){ + + if (config.system.enable_transcript || config.system.enable_DM_transcript){ + var fileattachment = await require("./transcript").createTranscript(channelmessages) + + if (fileattachment == false){console.log("[transcript] internal error: no transcript is created!");return} + } + + if (config.system.enable_transcript){ + const transcriptEmbed = new discord.MessageEmbed() + .setColor(config.main_color) + .setTitle("A new transcript is here!") + .setAuthor({name:interaction.user.username,iconURL:interaction.user.displayAvatarURL({format:"png"})}) + .setDescription("You can find the transcript in the text file above!") + .setFooter({text:"ticket: "+ticketuserarray}) + + interaction.guild.channels.cache.find(c => c.id == config.system.transcript_channel).send({ + embeds:[transcriptEmbed], + files:[fileattachment] + }) + } + + if (config.system.enable_DM_transcript){ + const transcriptEmbed = new discord.MessageEmbed() + .setColor(config.main_color) + .setTitle("Here is a transcript of your ticket!") + .setDescription("You can find the transcript in the text file above!") + .setFooter({text:"ticket: "+ticketuserarray}) + + if (!isDatabaseError) getusernameStep1.send({ + embeds:[transcriptEmbed], + files:[fileattachment] + }) + } + } +} + +exports.runThis = () => { + client.on("interactionCreate",async (interaction) => { + if (!interaction.isButton()) return + if (interaction.customId != "sendTranscript") return + + interaction.deferUpdate() + + const channelmessages = await interaction.channel.messages.fetch() + + channelmessages.sweep((msgSweep) => { + return msgSweep.author.id == client.user.id + }) + + var fileattachment = await require("./transcript").createTranscript(channelmessages) + + if (fileattachment == false){ + console.log("[transcript] internal error: no transcript is created!") + interaction.channel.send({content:"**Something went wrong while making the transcript!**\nPlease try again another time!"}) + return + } + + interaction.channel.send({content:"**Here is the transcript:**",files:[fileattachment]}) + }) +} \ No newline at end of file diff --git a/core/ticketMessageEmbed.js b/core/ticketMessageEmbed.js new file mode 100644 index 0000000..573f13c --- /dev/null +++ b/core/ticketMessageEmbed.js @@ -0,0 +1,95 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config +const getButton = require("./utils/getButton") + +/** + * + * @param {String} id the id from an open-ticket embed + * @returns {{embed:discord.MessageEmbed,componentRows:discord.MessageActionRow[]}} discord embed + */ + + + +exports.createEmbed = (id) => { + //----------------------------------- + //general importing + const data = require("./utils/getTicketMessages").getTicketMessage(id) + /**@type {discord.MessageButton[]}*/ + var buttons = [] + /**@type {getButton.rawButtonData[]} */ + var buttonData = [] + + data.options.forEach((option) => { + var button = getButton.getButton(option) + var localButtonData = getButton.getOption(option) + if (button) buttons.push(button) + if (localButtonData) buttonData.push(localButtonData) + }) + //----------------------------------- + + //----------------------------------- + //create the embed + const embed = new discord.MessageEmbed() + .setColor(data.color) + .setTitle(data.name) + if (data.enableFooter) embed.setFooter({text:data.footer}) + if (data.enableThumbnail) embed.setThumbnail(data.thumbnail) + + var description = data.description + if (data.enableTicketExplaination){ + description = description+"\n\n__choose a category:__\n" + + var ticketExplainations = [] + buttonData.forEach((button) => { + var explaination = button.icon+" **"+button.name+":**\n"+button.description + ticketExplainations.push(explaination) + }) + + description = description+ticketExplainations.join("\n\n") + } + + if (data.enableMaxTicketsWarning){ + description = description+"\n\n**Warning:** _You can only create "+config.system.max_allowed_tickets+" ticket(s) at a time!_" + } + + embed.setDescription(description) + //----------------------------------- + + //----------------------------------- + //create the components + var AmountOfRows = Math.ceil(buttons.length / 4) + var currentRow = 0 + + /** + * @type {discord.MessageActionRow[]} + */ + var componentRows = [] + + for (let i = AmountOfRows; i > 0; i--){ + + componentRows.push(new discord.MessageActionRow()) + } + + buttons.forEach((button,index) => { + componentRows[currentRow].addComponents([button]) + + if (index == 3 || index == 7 || index == 11 || index == 15 || index == 19 || index == 23 || index == 27 || index == 31 || index == 35 || index == 39){ + currentRow = currentRow + 1 + } + }) + //----------------------------------- + + + + //return everything + /** + * @type {{embed:discord.MessageEmbed,componentRows:discord.MessageActionRow[]}} + */ + const returnedData = {embed:embed,componentRows:componentRows} + return returnedData +} + + +this.createEmbed() \ No newline at end of file diff --git a/core/ticketOpener.js b/core/ticketOpener.js new file mode 100644 index 0000000..9cdb293 --- /dev/null +++ b/core/ticketOpener.js @@ -0,0 +1,168 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +const getconfigoptions = require("./getoptions") +const storage = bot.storage + +module.exports = () => { + var closeButton = new discord.MessageActionRow() + .addComponents( + new discord.MessageButton() + .setCustomId("closeTicket") + .setDisabled(false) + .setStyle("SECONDARY") + .setLabel("Close Ticket") + .setEmoji("🔒") + ) + .addComponents( + new discord.MessageButton() + .setCustomId("deleteTicket") + .setDisabled(false) + .setStyle("DANGER") + .setLabel("Delete Ticket") + .setEmoji("✖️") + ) + + //ticket button click / create ticket + client.on("interactionCreate",(interaction) => { + if (interaction.isCommand() && (interaction.commandName == "new" || interaction.commandName == "ticket")){ + var customId = "newT"+interaction.options.getString("type") + }else if (interaction.isButton()){ + var customId = interaction.customId + }else return + + if (getconfigoptions.getTicketValues("id").includes(customId)){ + + if (interaction.isButton()){ + interaction.deferUpdate() + }else if (interaction.isCommand()){ + interaction.reply({content:"Your ticket is created!"}) + } + + //ticketoptions from config + const currentTicketOptions = getconfigoptions.getOptionsById(customId) + + if (storage.get("ticketStorage",interaction.member.id) == null || storage.get("ticketStorage",interaction.member.id) == "false"|| Number(storage.get("ticketStorage",interaction.member.id)) < config.system.max_allowed_tickets){ + + try{ + if (config.system.enable_DM_Messages){ + interaction.member.send({content:currentTicketOptions.message}) + } + } + catch{console.log("[system] can't send DM to member, member doesn't allow dm's")} + + + + //update storage + storage.set("ticketStorage",interaction.member.id,Number(storage.get("ticketStorage",interaction.member.id))+1) + var ticketNumber = interaction.member.user.username + + //set ticketName + var ticketName = currentTicketOptions.channelprefix+ticketNumber + var logsname = currentTicketOptions.name + + //category + if (currentTicketOptions.category.length < 16 || currentTicketOptions.category.length > 20){ + var newTicketCategory = null + }else{ + var newTicketCategory = currentTicketOptions.category + } + + var permissionsArray = [] + + //set everyone allowed + if (config.system['has@everyoneaccess']){ + var everyoneAllowPerms = ["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] + var everyoneDenyPerms = [] + }else{ + var everyoneAllowPerms = [] + var everyoneDenyPerms = ["VIEW_CHANNEL"] + } + permissionsArray.push({ + id:interaction.guild.id, + type:"role", + allow:everyoneAllowPerms, + deny:everyoneDenyPerms + }) + + //add the user that created the ticket + permissionsArray.push({ + id:interaction.member.id, + type:"member", + allow:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] + }) + + //add main adminroles + config.main_adminroles.forEach((role) => { + permissionsArray.push({ + id:role, + type:"role", + allow:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] + }) + }) + + //add ticket adminroles + /** + * @type {String[]} + */ + const ticketadmin = currentTicketOptions.adminroles + ticketadmin.forEach((role) => { + if (!config.main_adminroles.includes(role)){ + permissionsArray.push({ + id:role, + type:"role", + allow:["ADD_REACTIONS","ATTACH_FILES","EMBED_LINKS","SEND_MESSAGES","VIEW_CHANNEL"] + }) + } + }) + + //add member role + if (config.system.member_role != "" && config.system.member_role != " " && config.system.member_role != "false" && config.system.member_role != "null" && config.system.member_role != "0"){ + permissionsArray.push({ + id:config.system.member_role, + type:"role", + deny:["VIEW_CHANNEL"] + }) + } + + + //create the channel + interaction.guild.channels.create(ticketName,{ + type:"GUILD_TEXT", + parent:newTicketCategory, + reason:"A new ticket was created", + permissionOverwrites:permissionsArray + + }).then((ticketChannel) => { + storage.set("userTicketStorage",ticketChannel.id,interaction.member.id) + + var ticketEmbed = new discord.MessageEmbed() + .setColor(config.main_color) + .setTitle("You created a ticket!") + .setDescription("The staff will help you soon!\n\n*Click on the button below to close the ticket!*") + + ticketChannel.send({ + content:"<@"+interaction.member.id+"> @everyone", + embeds:[ticketEmbed], + components:[closeButton] + + }).then(firstmsg => { + firstmsg.pin() + }) + if (config.logs){console.log("[system] created a new ticket (name:"+logsname+",user:"+interaction.user.username+")")} + }) + }else{ + try { + if (config.system.enable_DM_Messages){ + interaction.member.send("you already have the maximum number of tickets allowed!") + } + } + catch{console.log("[system] can't send DM to member, member doesn't allow dm's")} + + + } + } + }) +} \ No newline at end of file diff --git a/core/transcript.js b/core/transcript.js new file mode 100644 index 0000000..61a0dd0 --- /dev/null +++ b/core/transcript.js @@ -0,0 +1,36 @@ +const discord = require('discord.js') +const bot = require('../index') +const client = bot.client +const config = bot.config + +/** + * + * @param {discord.Message[]} messagecollection + * @returns new `discord.messageAttachment` + */ + +exports.createTranscript = async (messagecollection) => { + try { + const filearray = ["TRANSCRIPT START:"] + + messagecollection.reverse() + + + messagecollection.forEach((msg) => { + const timestamp = new Date(msg.createdTimestamp) + + if (msg.content){var content = msg.content}else{var content = "*empty message*"} + + const msgstats = "files: "+msg.attachments.size+" | embeds: "+msg.embeds.length+" | isbot: "+msg.author.bot + filearray.push("["+timestamp.getDate()+"/"+timestamp.getMonth()+"/"+timestamp.getFullYear()+" | "+timestamp.getHours()+":"+timestamp.getMinutes()+":"+timestamp.getSeconds()+" | "+msg.author.tag+" ] ["+msgstats+"]\n"+content+"\n") + }) + + if (filearray.length < 2) filearray.push("transcript is empty") + const filestring = filearray.join("\n") + const filebuff = Buffer.from(filestring,"utf-8") + return new discord.MessageAttachment(filebuff,"transcript.txt") + }catch(err){ + console.log(err) + return false + } +} \ No newline at end of file diff --git a/core/utils/getButton.js b/core/utils/getButton.js new file mode 100644 index 0000000..e49a9da --- /dev/null +++ b/core/utils/getButton.js @@ -0,0 +1,92 @@ +const discord = require("discord.js") +const bot = require("../../index") +const config = bot.config +const getoptions = require("../getoptions") + +/**@returns {"DANGER"|"SUCCESS"|"PRIMARY"|"SECONDARY"} */ +const getColor = (color) => { + if (color.toLowerCase() == "red"){ + return "DANGER" + }else if (color.toLowerCase() == "green"){ + return "SUCCESS" + }else if (color.toLowerCase() == "blue" || color.toLowerCase() == "blurple"){ + return "PRIMARY" + }else if (color.toLowerCase() == "black" || color.toLowerCase() == "gray" || color.toLowerCase() == "grey"){ + return "SECONDARY" + }else if (color == "DANGER" || color == "SECONDARY" || color == "SUCCESS" || color == "PRIMARY"){ + return color + }else if (color.toLowerCase() == "none" || color.toLowerCase() == "false" || color.toLowerCase() == ""){ + return "SECONDARY" + }else return "SECONDARY" +} + +const getOption = (id) => { + const result = config.options.find((option) => option.id == id) + if (!result){return false}else return result +} +exports.getOption = getOption +/** + * + * @param {String} id + * @returns {discord.MessageButton|false} + */ +exports.getButton = (id) => { + const option = getOption(id) + if (!option) return false + if (option.type == "ticket"){ + var button = new discord.MessageButton() + .setCustomId("newT"+option.id) + .setDisabled(false) + .setStyle(getColor(option.color)) + + if (option.label){button.setLabel(option.label)} + if (option.icon){button.setEmoji(option.icon)} + if (!option.label && !option.icon){button.setLabel("no label or emoji")} + + }else if (option.type == "website"){ + var button = new discord.MessageButton() + .setDisabled(false) + .setStyle("LINK") + .setURL(option.url) + + if (option.label){button.setLabel(option.label)} + if (option.icon){button.setEmoji(option.icon)} + if (!option.label && !option.icon){button.setLabel("no label or emoji")} + + }else if (option.type == "role"){ + var button = new discord.MessageButton() + .setCustomId("newR"+option.id) + .setDisabled(false) + .setStyle(getColor(option.color)) + + if (option.label){button.setLabel(option.label)} + if (option.icon){button.setEmoji(option.icon)} + if (!option.label && !option.icon){button.setLabel("no label or emoji")} + + } + + return button +} + + +exports.rawButtonData = class { + constructor() { + this.id = "" + this.name = "" + this.description = "" + this.icon = "" + this.label = "" + this.type = "" + + this.color = "" + this.enableDMMessage = true + this.url = "" + this.roles = ["",""] + this.mode = "" + this.adminroles = ["",""] + this.channelprefix = "" + this.category = "" + this.message = "" + + } +} \ No newline at end of file diff --git a/core/utils/getTicketMessages.js b/core/utils/getTicketMessages.js new file mode 100644 index 0000000..22ed608 --- /dev/null +++ b/core/utils/getTicketMessages.js @@ -0,0 +1,14 @@ +const bot = require("../../index") +const config = bot.config +exports.getTicketMessage = (input) => { + var returned = config.messages.find(a => a.id == input) + if (returned){ + return returned + } + var result = config.messages[0] + if (!result){ + throw new Error("'messages' object in config.json is empty") + return + } + return result +} \ No newline at end of file diff --git a/documentation/CHANGELOG.md b/documentation/CHANGELOG.md deleted file mode 100644 index 28e9a93..0000000 --- a/documentation/CHANGELOG.md +++ /dev/null @@ -1,96 +0,0 @@ -# changelog - -## v1.3.2 -### added: -- an alias for !resetdatabase: !resetdb -- you need the "chalk" library now! -- checker.js -- "enabled" option for status -- better console logs -- enable logs option to config -### fixed -- a bug that crashed the bot on using the command !resetdatabase -- "member_role" in config is optional but not recommended -- fixed crash on startup -- fixed bug where the bot crashes by using transcripts -- changed package.json -### removed -- "canvas" library is not longer required -- removed "server_icon" & "server_name" from config - -## v1.3.1 -### added: -- Q&A in the README.md -- v1.4.0 announcement -### fixed -- a bug that happend when you created too much tickets -- deprecation warnings (setAuthor & setFooter) -- the logo in README.md is smaller now -- markdown files relocated -- fixed a bug that some messages appeared in Dutch -### removed -- nothing - -## v1.3.0 -### added: -- now its possible to create 6 tickets instead of 4 -- you can edit the number of tickets per user now -- you can edit & enable/disable all messages -- all embeds are now editable -- the color of the button can be changed -- updated config.json -- updated README.md -- a new type of button is here: website links -### fixed -- nothing at the moment :) -### removed -- nothing - -## v1.2.2 -### added: -- nothing -### fixed: -- a bug that caused the `warn "message" event deprecated` to be displayed every time you start the bot in the console. -- a visual bug that caused the categories from `!ticket msg` to be displayed in a wrong pattern. -### removed: -- nothing - -## v1.2.1 -### added: -- you can disable credits in config.json -### fixed: -- a bug that crashed the bot on startup -### removed -- nothing - -## v1.2.0 -### added: -- the bot is in english now -- you can choose your own ticket options now -- added !resetdatabase -- updated README.md -### fixed: -- the first bugs -### removed: -- some useless commands - -## v1.1.1 -### added: -- added "status" options in config.json -### fixed: -- fixed a bug where the bot's status could be "playing highmt". -### removed: -- nothing - -## v1.1.0 -### added: -- option to choose your own prefix -- enable/disable transcripts -- enable/disable categories -### fixed: -- the bot crashed sometimes -### removed: -- credits - -## v1.0.0 -- initial release \ No newline at end of file diff --git a/documentation/CONFIG.md b/documentation/CONFIG.md deleted file mode 100644 index 19da514..0000000 --- a/documentation/CONFIG.md +++ /dev/null @@ -1,68 +0,0 @@ -# config file -[![discord](https://img.shields.io/badge/discord-join%20our%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) -[![version](https://img.shields.io/badge/version-1.3.2%20stable-brightgreen.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/releases/tag/v1.3.2) -[![npm](https://img.shields.io/badge/npm-external%20libraries%20needed-CB3837.svg?style=flat-square&logo=npm)](#packages) -[![license](https://img.shields.io/badge/license-GPL%203.0-important.svg?style=flat-square)](https://github.com/DJj123dj/open-ticket/blob/main/LICENSE) - -In this file you find all the information about commands in the config.json file - - -- bot_name ➜ the name from the bot _**type: string**_ -- main_color ➜ the color used in embeds _**type: hex color (#000000)**_ -- auth_token ➜ your bot's token _**type: string**_ -- botperms_role ➜ this role can do all admin commands from the bot _**type: discord role id**_ -- prefix ➜ the prefix _**type: string**_ -- logs ➜ enable logs _**type: boolean**_ -- credits ➜ disable the credits _**type: boolean**_ - -## status: (the status from the bot) -- enabled ➜ enable status _**type: boolean**_ -- type ➜ set the status type _**type: string (PLAYING | LISTENING | WATCHING | CUSTOM)** -- text ➜ set the status text _**type: string**_ - -## system: (the configuration for the ticket system) -- ticket_channel ➜ channel where you are going to set `!ticket msg` _**type: discord channel id**_ -- max_allowed_tickets ➜ the maximum allowed tickets per person _**type: number**_ -- enable_DM_Messages ➜ enable DM messages _**type: boolean**_ -- enable_category ➜ enable category _**type: boolean**_ -- ticket_category ➜ the category id _**type: discord category id**_ -- has@everyoneaccess ➜ when enabled everyone has acces to the tickets _**type: boolean**_ -- member_role ➜ this role doesn't have access to tickets _**type: discord role id**_ -- enable_transcript ➜ enable the transcript _**type: boolean**_ -- transcript_channel ➜ the transcript channel id _**type: discord channel id**_ - -## messages: (general, dm, ticket) -- nopermissions ➜ the message when someone doesn't have permissions _**type: string**_ -- alreadyCreated ➜ if you have too much tickets open _**type: string**_ -- newTicket ➜ when you create a new ticket _**type: string**_ -- closeTicket ➜ when you close a ticket _**type: string**_ -- newTicketEmbed ➜ the first message in the ticket channel _**type: string** - -## layout: (ticketEmbed, ticketMsg, transcript) -`transcript` doesn't have all the options - -- customColorEnabled ➜ custom embed color enabled _**type: boolean** -- customColor ➜ the custom color _**type: hex color (#000000)** -- footerEnabled ➜ custom footer enabled _**type: boolean** -- footer ➜ the custom footer _**type: string** -- thumbnailEnabled ➜ custom thumbnail enabled _**type: boolean** -- thumbnailURL ➜ the image _**type: string** - -## options: (per ticket) -color is not needed when `isURL` is `true` - -- enabled ➜ enable this ticket _**type: boolean** -- icon ➜ the button emoji _**type: emoji or custom emoji** -- description ➜ ticket description _**type: string** -- name ➜ ticket name _**type: string** -- channel_prefix ➜ ticket prefix _**type: string** -- color ➜ button color _**type: string (red|green|blue|gray|none)** -- isURL ➜ create link to a website instead of a normal ticket _**type: boolean** -- url ➜ the website to go to _**type: string** - -# thanks for using open-ticket -I didn't know that open-tickets was going to be so much used. I will be creating more updates in the future! - -If you have an idea for new updates you can always say it in my discord server! - -open-tickets v1.3.2 \ No newline at end of file diff --git a/documentation/Q&A.md b/documentation/Q&A.md deleted file mode 100644 index 9f221a2..0000000 --- a/documentation/Q&A.md +++ /dev/null @@ -1,26 +0,0 @@ -# Question & Answer -## How to install node.js 16? -You can install the latest version of node.js on their [website](https://nodejs.org/en/download/). You also need `npm`! - -## How to install all the npm packages? -If you installed node.js successful, you can run `npm install` in the command prompt and all the required modules will be installed. - -## Where is the config file documentation? -You can find it [here](...) - -**How to start the bot?** -If you successfully installed all the npm modules (see above), you can run `npm start` - -## Do I need to know discord.js? -Technically not, but it is useful to know if there is an error. - -## Why do I get this error? -**Invalid token:** - -_Your discord token is not correct, try to look in the config.json file if it's the correct token._ - -**Privileged intent provided is not enabled or whitelisted:** - -_You must enable all the **Gateaway Intents** in the developer panel in the "bot" tab_ - -### Report other errors to the discord server! \ No newline at end of file diff --git a/index.js b/index.js index 90515b7..193d7b6 100644 --- a/index.js +++ b/index.js @@ -1,37 +1,125 @@ const discord = require('discord.js') -const config = require('./config.json') +const fs = require('fs') const intents = discord.Intents -const client = new discord.Client({intents:[intents.FLAGS.GUILDS,intents.FLAGS.GUILD_MESSAGES,intents.FLAGS.GUILD_MEMBERS]}) +const client = new discord.Client({intents:[intents.FLAGS.GUILDS,intents.FLAGS.GUILD_MESSAGES,intents.FLAGS.GUILD_MEMBERS],partials:["CHANNEL"]}) exports.client = client -require("./checker")() +client.setMaxListeners(20) + +//change to FALSE on release +const isDev = false +//!!!!!!!!!!! + +if (isDev){ + try { + var config = require('./devConfig.json') + }catch{ + console.log("devConfig.json not found! Trying the normal config...") + var config = require("./config.json") + } +}else{var config = require('./config.json')} +exports.config = config client.on('ready',async () => { const chalk = await (await import("chalk")).default - console.log(chalk.green("open-ticket ready!")) - if (config.logs){chalk.white("\n\nlogs:\n============")} - if (config.status.enabled){ - client.user.setActivity(config.status.text,{type:config.status.type}) + if (process.argv[2] != "slash"){ + console.log(chalk.green("open-ticket ready!")) + if (config.logs){console.log(chalk.white("\n\nlogs:\n============"))} + if (config.status.enabled){ + client.user.setActivity(config.status.text,{type:config.status.type}) + } + }else{ + console.log(chalk.red("STARTING IN ")+chalk.blue("SLASH MODE")+chalk.red("...")) + console.log("logs:\n================") + console.log("client logged in...") + console.log("loading files...") + if (process.argv[3] == "enable"){ + console.log(chalk.green("switching to slashEnable.js")) + require("./core/slashSystem/slashEnable")() + }else if (process.argv[3] == "disable"){ + console.log(chalk.green("switching to slashDisable.js")) + require("./core/slashSystem/slashDisable")() + }else{ + console.log(chalk.red("Internal Error: unknown slash mode!")) + process.exit(1) + } } }) -var storage = require('./storage/storage') -exports.TicketStorage = storage.ticketStorage -exports.userTicketStorage = storage.userTicketStorage -exports.transcriptStorage = storage.transcriptStorage -exports.ticketNumberStorage = storage.ticketNumberStorage +require("./core/checker").checker() -var ticket = require('./commands/ticket') -ticket() +if (process.argv[2] != "slash"){ + var storage = require('./core/dynamicdatabase/storage') + exports.storage = storage -var ticketSystem = require('./commands/ticketSystem') -ticketSystem() + //commands + require('./commands/ticket')() + require("./commands/help")() + require("./commands/close")() + require("./commands/delete")() + require("./commands/rename")() + require("./commands/add")() + require("./commands/remove")() -var ticketExtra = require("./commands/ticketExtra") -ticketExtra() + //core + require('./core/ticketOpener')() + require("./core/ticketCloser").runThis() + require("./core/closebuttons")() + require("./core/reactionRoles")() -var database = require('./commands/database') -database() + /** + * We need: + * - checker.js + */ +} + + +//===================================================================== +//===================================================================== +//ERROR DEBUG MODE: turn not on exept when you know what you are doing! +var errorDebugMode = false +//ERROR DEBUG MODE: turn not on exept when you know what you are doing! +//===================================================================== +//===================================================================== + + + +if (!errorDebugMode){ + process.on("uncaughtException",async (error,origin) => { + const chalk = await (await import("chalk")).default + console.log(chalk.red("\nOPEN TICKET ERROR: ")+error+"\n"+chalk.green("\nCreate a ticket in our support server for more information!\nIf you do this, you might help us to avoid a future bug!\n")) + }) +} +if (errorDebugMode) { + const debugLog = (debugString) => { + const content = fs.existsSync("./openticketdebug.txt") ? fs.readFileSync("./openticketdebug.txt").toString() : "==========================\n\n==========================" + fs.writeFileSync("./openticketdebug.txt",content+"\nDEBUG: "+debugString) + } + const errorLog = (errorString,stack) => { + const content = fs.existsSync("./openticketdebug.txt") ? fs.readFileSync("./openticketdebug.txt").toString() : "==========================\n\n==========================" + fs.writeFileSync("./openticketdebug.txt",content+"\nERROR: "+errorString+" STACK: "+stack) + } + + client.on("debug", async (message) => { + if (message.startsWith("Provided token:")){ + debugLog("Provided token: ***********bot token is invisible**************...") + return + } + debugLog(message) + }); + + const asyncfunction = async () => { + const chalk = await (await import("chalk")).default + console.log("\n\nopen-ticket debug:\n"+chalk.bgYellow("ENTERING DEBUG MODE:")) + } + asyncfunction() + + process.on("uncaughtException",async (error,origin) => { + const chalk = await (await import("chalk")).default + console.log(chalk.red("\nMAIN ERROR: ")+"this error is saved in the debug file!") + errorLog(error.name+": "+error.message+" | origin: "+origin,error.stack) + }) +} client.login(config.auth_token) \ No newline at end of file diff --git a/logo.png b/logo.png index e3c7fa6..e6988c9 100644 Binary files a/logo.png and b/logo.png differ diff --git a/package.json b/package.json index feaf758..21b6826 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "open-ticket", - "version": "1.3.2", + "version": "2.0.0", "description": "This is an open-source discord ticket bot, you can configure it and it comes with cool features like a transcript.", "main": "index.js", "scripts": { "start": "node index.js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "slashEnable": "node index.js slash enable", + "slashDisable": "node index.js slash disable", + "cleardata": "node ./core/dynamicdatabase/cleardata.js" }, "author": "DJdj Development", "license": "ISC",