diff --git a/default-locales.json b/default-locales.json index 98c29cd6..17bb77c2 100644 --- a/default-locales.json +++ b/default-locales.json @@ -50,6 +50,28 @@ "reload-failed-message": "**FAILED**\n```%r```\n**Please read your log to find more information**\nThe bot will kill itself now, bye :wave:", "command-description": "Reloads the configuration" }, + "hunt-the-code": { + "admin-command-description": "Manage the current Code-Hunt", + "create-code-description": "Create a new code for the current code-hunt", + "display-name-description": "Name of the code that will be displayed to user when they redeem the code", + "code-description": "Set the code that will be used to redeem it (default: randomly generated)", + "code-created": "Code \"%displayName\" successfully created: \"%code\"", + "error-creating-code": "Error creating code \"{{displayName}}\". Maybe the entered code is already in the database?", + "successful-reset": "Successfully ended the current Code-Hunt-Game - [here](%url)'s your report - save the URL if you want to access it later.", + "end-description": "Ends the current Code-Hunt (will clear users and codes and generates a report)", + "command-description": "Redeem or see data about the current Code-Hunt", + "redeem-description": "Redeem a code you found", + "code-redeem-description": "The code you want to redeem", + "leaderboard-description": "See the current leaderboard", + "profile-description": "See your current count of found codes", + "no-codes-found": "No codes redeemed yet ):", + "no-users": "No users redeemed codes yet ):", + "report-header": "Report for the Hunt-The-Code game on %s", + "user-header": "Participating users", + "code-header": "Codes", + "report-description": "Generates a report", + "report": "You can find the report [here](%url)." + }, "config": { "checking-config": "Checking configurations...", "done-with-checking": "Done with checking. Out of %totalModules modules, %enabled were enabled, %configDisabled were disabled because their configuration was wrong.", diff --git a/modules/hunt-the-code/commands/hunt-the-code-admin.js b/modules/hunt-the-code/commands/hunt-the-code-admin.js new file mode 100644 index 00000000..c8a4ac4e --- /dev/null +++ b/modules/hunt-the-code/commands/hunt-the-code-admin.js @@ -0,0 +1,120 @@ +const {localize} = require('../../../src/functions/localize'); +const {randomString, postToSCNetworkPaste} = require('../../../src/functions/helpers'); + +module.exports.subcommands = { + 'create-code': function (interaction) { + interaction.client.models['hunt-the-code']['Code'].create({ + code: (interaction.options.getString('code') || (randomString(3) + '-' + randomString(3) + '-' + randomString(3))).toUpperCase(), + displayName: interaction.options.getString('display-name') + }).then((codeObject) => { + interaction.reply({ + ephemeral: true, + content: '✅ ' + localize('hunt-the-code', 'code-created', { + displayName: interaction.options.getString('display-name'), + code: codeObject.code + }) + }); + }).catch(() => { + interaction.reply({ + ephemeral: true, + content: '⚠ ' + localize('hunt-the-code', 'error-creating-code', {displayName: interaction.options.getString('display-name')}) + }); + }); + }, + 'end': async function (interaction) { + await interaction.deferReply({ephemeral: true}); + const url = await generateReport(interaction.client); + await interaction.client.models['hunt-the-code']['Code'].destroy({ + truncate: true + }); + await interaction.client.models['hunt-the-code']['User'].destroy({ + truncate: true + }); + await interaction.editReply({ + content: '✅ ' + localize('hunt-the-code', 'successful-reset', {url}) + }); + }, + 'report': async function (interaction) { + await interaction.deferReply({ephemeral: true}); + const url = await generateReport(interaction.client); + await interaction.editReply({ + content: localize('hunt-the-code', 'report', {url}) + }); + } +}; + +/** + * Generate a report of the current Code-Hunt-Session + * @param {Client} client Client + * @returns {Promise} URL to Report + */ +async function generateReport(client) { + let reportString = `# ${localize('hunt-the-code', 'report-header', {s: client.guild.name})}\n`; + const codes = await client.models['hunt-the-code']['Code'].findAll({ + order: [ + ['foundCount', 'DESC'] + ] + }); + const users = await client.models['hunt-the-code']['User'].findAll({ + order: [ + ['foundCount', 'DESC'] + ] + }); + reportString = reportString + `\n## ${localize('hunt-the-code', 'user-header')}\n| Rank | Tag | ID | Amount found | Codes |\n| --- | --- | --- | --- | --- |\n`; + for (const i in users) { + const user = users[i]; + const u = await client.users.fetch(user.id); + reportString = reportString + `| ${parseInt(i) + 1}. | ${u.tag} | ${u.id} | ${user.foundCount} | ${user.foundCodes.join(', ')} |\n`; + } + reportString = reportString + `\n## ${localize('hunt-the-code', 'code-header')}\n| Rank | Code | Display-Name | Times found |\n| --- | --- | --- | --- |\n`; + for (const i in codes) { + const code = codes[i]; + reportString = reportString + `| ${parseInt(i) + 1}. | ${code.code} | ${code.displayName} | ${code.foundCount} |\n`; + } + reportString = reportString + `\n



Generated at ${new Date().toLocaleString(client.locale)}.`; + return await postToSCNetworkPaste(reportString, { + expire: '1month', + burnafterreading: 0, + opendiscussion: 1, + textformat: 'markdown', + output: 'text', + compression: 'zlib' + }); +} + +module.exports.config = { + name: 'hunt-the-code-admin', + description: localize('hunt-the-code', 'admin-command-description'), + defaultPermission: false, + options: [ + { + type: 'SUB_COMMAND', + name: 'create-code', + description: localize('hunt-the-code', 'create-code-description'), + options: [ + { + type: 'STRING', + name: 'display-name', + required: true, + description: localize('hunt-the-code', 'display-name-description') + }, + { + type: 'STRING', + name: 'code', + required: false, + description: localize('hunt-the-code', 'code-description') + } + ] + }, + { + type: 'SUB_COMMAND', + name: 'end', + description: localize('hunt-the-code', 'end-description') + }, + { + type: 'SUB_COMMAND', + name: 'report', + description: localize('hunt-the-code', 'report-description') + } + ] +}; \ No newline at end of file diff --git a/modules/hunt-the-code/commands/hunt-the-code.js b/modules/hunt-the-code/commands/hunt-the-code.js new file mode 100644 index 00000000..d062a710 --- /dev/null +++ b/modules/hunt-the-code/commands/hunt-the-code.js @@ -0,0 +1,114 @@ +const {localize} = require('../../../src/functions/localize'); +const {embedType} = require('../../../src/functions/helpers'); +const {MessageEmbed} = require('discord.js'); + +module.exports.subcommands = { + 'redeem': async function (interaction) { + const moduleStrings = interaction.client.configurations['hunt-the-code']['strings']; + const codeObject = await interaction.client.models['hunt-the-code']['Code'].findOne({ + where: { + code: interaction.options.getString('code').toUpperCase() + } + }); + if (!codeObject) return interaction.reply(embedType(moduleStrings.codeNotFoundMessage, {}, {ephemeral: true})); + const [user] = await interaction.client.models['hunt-the-code']['User'].findOrCreate({ + where: { + id: interaction.user.id + } + }); + if (user.foundCodes.includes(codeObject.code)) return interaction.reply(embedType(moduleStrings.codeAlreadyRedeemed, { + '%userCodesCount%': user.foundCount, + '%displayName%': codeObject.displayName, + '%codeUseCount%': codeObject.foundCount + }, {ephemeral: true})); + user.foundCount++; + user.foundCodes = [...user.foundCodes, codeObject.code]; + await user.save(); + codeObject.foundCount++; + interaction.reply(embedType(moduleStrings.codeRedeemed, { + '%displayName%': codeObject.displayName, + '%codeUseCount%': codeObject.foundCount, + '%userCodesCount%': user.foundCount + }, {ephemeral: true})); + await codeObject.save(); + }, + 'profile': async function (interaction) { + const [user] = await interaction.client.models['hunt-the-code']['User'].findOrCreate({ + where: { + id: interaction.user.id + } + }); + const codes = await interaction.client.models['hunt-the-code']['Code'].findAll({ + attributes: ['displayName', 'code'] + }); + let foundCodes = ''; + for (const code of user.foundCodes) { + const codeObject = codes.find(c => c.code === code); + if (!codeObject) continue; + foundCodes = foundCodes + `\n• ${codeObject.displayName}`; + } + if (!foundCodes) foundCodes = localize('hunt-the-code', 'no-codes-found'); + interaction.reply(embedType(interaction.client.configurations['hunt-the-code']['strings'].profileMessage, { + '%username%': interaction.user.username, + '%foundCount%': user.foundCount, + '%allCodesCount%': codes.length, + '%foundCodes%': foundCodes + }, {ephemeral: true})); + }, + 'leaderboard': async function (interaction) { + const moduleStrings = interaction.client.configurations['hunt-the-code']['strings']; + const users = await interaction.client.models['hunt-the-code']['User'].findAll({ + attributes: ['id', 'foundCount'], + order: [ + ['foundCount', 'DESC'] + ], + limit: 20 + }); + let userString = ''; + for (const user of users) { + userString = userString + `\n<@${user.id}>: ${user.foundCount}`; + } + if (userString === '') userString = localize('hunt-the-code', 'no-users'); + const embed = new MessageEmbed() + .setDescription(userString) + .setTitle(moduleStrings.leaderboardMessage.title) + .setImage(moduleStrings.leaderboardMessage.image || null) + .setThumbnail(moduleStrings.leaderboardMessage.thumbnail || null) + .setColor(moduleStrings.leaderboardMessage.color); + interaction.reply({ + ephemeral: true, + embeds: [embed] + }); + } +}; + +module.exports.config = { + name: 'hunt-the-code', + description: localize('hunt-the-code', 'command-description'), + defaultPermission: true, + options: [ + { + type: 'SUB_COMMAND', + name: 'redeem', + description: localize('hunt-the-code', 'redeem-description'), + options: [ + { + type: 'STRING', + name: 'code', + required: true, + description: localize('hunt-the-code', 'code-redeem-description') + } + ] + }, + { + type: 'SUB_COMMAND', + name: 'leaderboard', + description: localize('hunt-the-code', 'leaderboard-description') + }, + { + type: 'SUB_COMMAND', + name: 'profile', + description: localize('hunt-the-code', 'profile-description') + } + ] +}; \ No newline at end of file diff --git a/modules/hunt-the-code/models/Code.js b/modules/hunt-the-code/models/Code.js new file mode 100644 index 00000000..39fc1580 --- /dev/null +++ b/modules/hunt-the-code/models/Code.js @@ -0,0 +1,27 @@ +const {DataTypes, Model} = require('sequelize'); + +module.exports = class HuntTheCodeCode extends Model { + static init(sequelize) { + return super.init({ + code: { + type: DataTypes.STRING, + primaryKey: true + }, + creatorID: DataTypes.STRING, + displayName: DataTypes.STRING, + foundCount: { + type: DataTypes.INTEGER, + defaultValue: 0 + } + }, { + tableName: 'hunt-the-code_Code', + timestamps: true, + sequelize + }); + } +}; + +module.exports.config = { + 'name': 'Code', + 'module': 'hunt-the-code' +}; \ No newline at end of file diff --git a/modules/hunt-the-code/models/User.js b/modules/hunt-the-code/models/User.js new file mode 100644 index 00000000..929872c0 --- /dev/null +++ b/modules/hunt-the-code/models/User.js @@ -0,0 +1,29 @@ +const {DataTypes, Model} = require('sequelize'); + +module.exports = class HuntTheCodeUser extends Model { + static init(sequelize) { + return super.init({ + id: { + type: DataTypes.STRING, + primaryKey: true + }, + foundCount: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + foundCodes: { + type: DataTypes.JSON, + defaultValue: [] + } + }, { + tableName: 'hunt-the-code_User', + timestamps: true, + sequelize + }); + } +}; + +module.exports.config = { + 'name': 'User', + 'module': 'hunt-the-code' +}; \ No newline at end of file diff --git a/modules/hunt-the-code/module.json b/modules/hunt-the-code/module.json new file mode 100644 index 00000000..180c8c2a --- /dev/null +++ b/modules/hunt-the-code/module.json @@ -0,0 +1,20 @@ +{ + "name": "hunt-the-code", + "humanReadableName-en": "Hunt the code", + "humanReadableName-de": "Sammel die Codes", + "author": { + "scnxOrgID": "1", + "name": "SCDerox (SC Network Team)", + "link": "https://github.com/SCDerox" + }, + "openSourceURL": "https://github.com/SCNetwork/CustomDCBot/tree/main/modules/hunt-the-code", + "description-en": "Hide codes and let your users collect them", + "description-de": "Verstecke Codes und lasse sie von deinen Nutzern sammeln", + "commands-dir": "/commands", + "models-dir": "/models", + "fa-icon": "fa-solid fa-tags", + "config-example-files": [ + "strings.json" + ], + "tags": ["community"] +} \ No newline at end of file diff --git a/modules/hunt-the-code/strings.json b/modules/hunt-the-code/strings.json new file mode 100644 index 00000000..2d8a2cac --- /dev/null +++ b/modules/hunt-the-code/strings.json @@ -0,0 +1,190 @@ +{ + "filename": "strings.json", + "humanname-de": "Nachrichten", + "humanname-en": "Messages", + "description-en": "Edit the messages and strings of the module here", + "description-de": "Stelle hier die Nachrichten des Modules ein", + "content": [ + { + "field_name": "codeNotFoundMessage", + "humanname-en": "Code-not-found Message", + "humanname-de": "Code-nicht-gefunden Nachricht", + "default-en": "⚠ Sorry, this code is invalid ):", + "default-de": "⚠ Dieser Code ist leider ungültig", + "type": "string", + "allowEmbed": true, + "description-en": "This message gets send, when an invalid code is being redeemed", + "description-de": "Diese Nachricht wird verschickt, wenn ein ungültiger Code eingelöst wird" + }, + { + "field_name": "codeAlreadyRedeemed", + "humanname-en": "Code-already-Redeemed Message", + "humanname-de": "Code-bereits-eingelöst Nachricht", + "default-en": "Good news, you have already redeemed this code", + "default-de": "Gute Nachrichten, du hast diesen Code bereits eingelöst", + "type": "string", + "allowEmbed": true, + "description-en": "This message gets send, when a user tries to redeem a code that is already in their inventory", + "description-de": "Diese Nachricht wird verschickt, wenn ein Nutzer einen Code einlösen will, den er bereits eingelöst hat", + "params-en": [ + { + "name": "%displayName%", + "description": "Display-Name of the code that the user wants to redeem" + }, + { + "name": "%codeUseCount%", + "description": "Count of times this code has already been redeemed" + }, + { + "name": "%userCodesCount%", + "description": "Count of codes this user already has redeemed" + } + ], + "params-de": [ + { + "name": "%displayName%", + "description": "Anzeige-Name des Codes, denn der Nutzer einlösen möchte" + }, + { + "name": "%userCodesCount%", + "description": "Anzahl der Codes, die dieser Nutzer bereits eingelöst hat" + }, + { + "name": "%codeUseCount%", + "description": "Anzahl von Nutzer, die diesen Code bereits eingelöst haben" + } + ] + }, + { + "field_name": "codeRedeemed", + "humanname-en": "Code-Redeemed Message", + "humanname-de": "Code-eingelöst Nachricht", + "default-en": "Good job, you have successfully redeemed the code **%displayName%**", + "default-de": "Gute Arbeit, du hast erfolgreich den Code **%displayName%** eingelöst.", + "type": "string", + "allowEmbed": true, + "description-en": "This message gets send, when a user tries redeems a code", + "description-de": "Diese Nachricht wird verschickt, wenn ein Nutzer einen Code einlöst", + "params-en": [ + { + "name": "%displayName%", + "description": "Display-Name of the code" + }, + { + "name": "%codeUseCount%", + "description": "Count of times this code has already been redeemed" + }, + { + "name": "%userCodesCount%", + "description": "Count of codes this user already has redeemed" + } + ], + "params-de": [ + { + "name": "%displayName%", + "description": "Anzeige-Name des Codes, denn der Nutzer einlöst" + }, + { + "name": "%userCodesCount%", + "description": "Anzahl der Codes, die dieser Nutzer bereits eingelöst hat" + }, + { + "name": "%codeUseCount%", + "description": "Anzahl von Nutzer, die diesen Code bereits eingelöst haben" + } + ] + }, + { + "field_name": "profileMessage", + "humanname-en": "Profile-Message", + "humanname-de": "Profil-Nachricht", + "default-en": { + "title": "Your profile, %username%!", + "fields": [ + { + "name": "Found codes", + "value": "%foundCodes%", + "inline": true + }, + { + "name": "Progress", + "value": "%foundCount%/%allCodesCount% found", + "inline": true + } + ] + }, + "default-de": { + "title": "Dein Profil %username%!", + "fields": [ + { + "name": "Gefunde Codes", + "value": "%foundCodes%", + "inline": true + }, + { + "name": "Fortschritt", + "value": "%foundCount%/%allCodesCount% gefunden", + "inline": true + } + ] + }, + "type": "string", + "allowEmbed": true, + "description-en": "This message gets send, when a user opens their profile", + "description-de": "Diese Nachricht wird versendet, wenn ein Nutzer sein Profil öffnet", + "params-en": [ + { + "name": "%foundCodes%", + "description": "All codes that this user has found" + }, + { + "name": "%username%", + "description": "Username of the user running the command" + }, + { + "name": "%foundCount%", + "description": "Count of found codes" + }, + { + "name": "%allCodesCount%", + "description": "Count of all available codes" + } + ], + "params-de": [ + { + "name": "%foundCodes%", + "description": "Alle Codes, die der Nutzer gefunden hat" + }, + { + "name": "%foundCount%", + "description": "Anzahl aller gefunden Codes" + }, + { + "name": "%username%", + "description": "Nutzername des Nutzers, der den Befehl ausführt" + }, + { + "name": "%allCodesCount%", + "description": "Anzahl aller verfügbaren Codes" + } + ] + }, + { + "field_name": "leaderboardMessage", + "humanname-en": "Leaderboard-Message", + "humanname-de": "Leaderboard-Nachricht", + "type": "keyed", + "disableKeyEdits": true, + "content": { + "key": "string", + "value": "string" + }, + "default": { + "title": "Leaderboard", + "color": "GREEN", + "thumbnail": "", + "image": "" + } + } + ] +} \ No newline at end of file