From ae60732836af84e7ec266c4e35f04fb3bac8636d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Gro=C3=9F?= Date: Thu, 18 Aug 2022 19:24:44 +0200 Subject: [PATCH] WIP basic card dropping --- commands/collection.js | 2 +- commands/debug.js | 27 +++-- commands/debugDrop.js | 48 ++++++++ commands/drop.js | 104 +++++++++++++----- events/interactionCreate.js | 17 +-- ...0220818130258-make-card-userid-nullable.js | 18 +++ models/band.js | 1 - models/character.js | 1 - util/cards.js | 23 ++++ util/db.js | 2 + util/index.js | 25 +++++ util/reply.js | 27 +++++ util/users.js | 28 +++++ 13 files changed, 265 insertions(+), 58 deletions(-) create mode 100644 commands/debugDrop.js create mode 100644 migrations/20220818130258-make-card-userid-nullable.js create mode 100644 util/cards.js create mode 100644 util/index.js create mode 100644 util/reply.js create mode 100644 util/users.js diff --git a/commands/collection.js b/commands/collection.js index 5e06d27..363df6b 100644 --- a/commands/collection.js +++ b/commands/collection.js @@ -39,7 +39,7 @@ module.exports = { message += `------------------------ \n`; } interaction.reply({ - content: message, + content: message.substring(0, 1500), ephemeral: false }); diff --git a/commands/debug.js b/commands/debug.js index c747d68..bb2d7cb 100644 --- a/commands/debug.js +++ b/commands/debug.js @@ -1,5 +1,7 @@ -const { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); +const { SlashCommandBuilder, ComponentType, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { customAlphabet } = require("nanoid"); +const { Card, User, Character } = require("../models"); +const Util = require("../util/cards"); module.exports = { data: new SlashCommandBuilder() @@ -9,20 +11,12 @@ module.exports = { option .setName("feature") .setDescription("The command to debug") - .setRequired(true) + .setRequired(false) ), async execute(interaction) { - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId('primary') - .setLabel('Primary') - .setStyle(ButtonStyle.Primary), - ); - - await interaction.reply({ content: 'Pong!', components: [row] }); - return; + const identifier = Util.generateIdentifier(); + switch (interaction.options.getString("feature")) { case "ping": interaction.reply({ @@ -42,6 +36,15 @@ module.exports = { ephemeral: false }); break; + case "clear_cards": + const cards = await Card.findAll(); + for (let i = 0; i < cards.length; i++) { + await cards[i].destroy(); + } + interaction.reply({ + content: `Cleared ${cards.length} cards`, + ephemeral: false + }); } } } \ No newline at end of file diff --git a/commands/debugDrop.js b/commands/debugDrop.js new file mode 100644 index 0000000..3eacf58 --- /dev/null +++ b/commands/debugDrop.js @@ -0,0 +1,48 @@ +const { SlashCommandBuilder } = require("discord.js"); +const { Card, User } = require("../models"); +const { customAlphabet } = require("nanoid"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("debug_drop") + .setDescription("Drop a card") + .addIntegerOption((option) => + option + .setName("id") + .setDescription("The id of the character to drop") + .setRequired(true) + ), + + async execute(interaction) { + //get user id from database given the userID + const user = await User.findOne({ + where: { + discordId: interaction.member.id + } + }); + + //create new card with the given character id, and the user id + const nanoid = customAlphabet('23456789ABCDEFGHJKLMNPRSTUVWXYZ',6); //Up to 887.503.681 + const identifier = nanoid(); + const existingCharacterCount = await Card.count({ + where: { + characterId: interaction.options.getInteger("id") + } + }); + + const card = await Card.create({ + characterId: interaction.options.getInteger("id"), + identifier: identifier, + quality: 1, + printNr: existingCharacterCount + 1, + userId: user.id + }); + + //reply with the new card id + interaction.reply({ + content: `Dropped card ${card.id}`, + ephemeral: false + }); + + } +} \ No newline at end of file diff --git a/commands/drop.js b/commands/drop.js index a8c28c8..de380d3 100644 --- a/commands/drop.js +++ b/commands/drop.js @@ -1,47 +1,93 @@ -const { SlashCommandBuilder } = require("discord.js"); -const { Card, User } = require("../models"); +const { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js"); +const { Card, User, Character } = require("../models"); const { customAlphabet } = require("nanoid"); +const { CardUtils, UserUtils, ReplyUtils } = require("../util"); +const card = require("../models/card"); module.exports = { data: new SlashCommandBuilder() .setName("drop") - .setDescription("Drop a card") - .addIntegerOption((option) => - option - .setName("id") - .setDescription("The id of the character to drop") - .setRequired(true) - ), + .setDescription("Drop a card"), async execute(interaction) { - //get user id from database given the userID const user = await User.findOne({ where: { discordId: interaction.member.id } }); - //create new card with the given character id, and the user id - const nanoid = customAlphabet('23456789ABCDEFGHJKLMNPRSTUVWXYZ',6); //Up to 887.503.681 - const identifier = nanoid(); - const existingCharacterCount = await Card.count({ - where: { - characterId: interaction.options.getInteger("id") + //Generate 3 cards, each is persisted with an initial userId of NULL + const cards = []; + for (let i = 0; i < 3; i++) { + //get number of characters in database + const characterId = Math.floor(Math.random() * await CardUtils.getCharacterCount()) + 1; + console.log(`characterId: ${characterId}`); + + let newCard = await Card.create({ + characterId: characterId, + identifier: CardUtils.generateIdentifier(), + quality: 1, + printNr: await CardUtils.getNextPrintNumber(characterId), + + }); + cards.push(newCard); + } + + let reply = "You have dropped the following cards: \n"; + + const row = new ActionRowBuilder(); + + for (const [i, card] of cards.entries()) { + let character = await Character.findOne({ + where: { + id: card.characterId + } + }); + reply += `IID: ${card.id} - ID:${card.identifier} \nP: ${card.printNr} Q: ${card.quality} \nC: ${character.name} \n----------\n`; + + //Add claim button for each card + row.addComponents( + new ButtonBuilder() + .setCustomId(`claim-${i}-${card.identifier}`) + .setLabel(`Claim ${i+1}`) + .setStyle(ButtonStyle.Primary), + ); + } + + const message = await interaction.reply({ content: reply, components: [row], fetchReply: true }); + + const filter = m => m.author.id === interaction.user.id; + const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 15000 }); + + collector.on('collect', async i => { + let cardId = i.customId.split("-")[1]; + if (await cards[cardId].userId) { i.reply({ content: "This card has already been claimed!", ephemeral: true }); return; } + + let claimUser = await UserUtils.getUserByDiscordId(i.user.id); + if (claimUser) { + //Update card with the user id + cards[cardId].userId = claimUser.id; + await cards[cardId].save(); + //fetch character name from database given the character id + let character = await Character.findOne({ + attributes: ["name"], + where: { + id: cards[cardId].characterId + } + }); + i.reply({ content: `${i.user} (${claimUser.id}) claimed ${character.name}`, ephemeral: false }); + let newRow = ReplyUtils.recreateComponents(i.message.components); + newRow.components[cardId].setLabel("Claimed"); + newRow.components[cardId].setStyle(ButtonStyle.Success); + newRow.components[cardId].setDisabled(true); + + message.edit({ components: [newRow] }); } }); - - const card = await Card.create({ - characterId: interaction.options.getInteger("id"), - identifier: identifier, - quality: 1, - printNr: existingCharacterCount + 1, - userId: user.id - }); - - //reply with the new card id - interaction.reply({ - content: `Dropped card ${card.id}`, - ephemeral: false + + collector.on('end', collected => { + console.log(`Collected ${collected.size} interactions.`); + message.interaction.editReply({ components: [], deferred: true }); }); } diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 614d143..7a5b1f0 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -2,10 +2,12 @@ require("dotenv").config(); const { REST } = require("@discordjs/rest"); const { Routes } = require("discord-api-types/v10") const { Guild, User } = require("../models"); +const { UserUtils } = require("../util"); module.exports = { name: "interactionCreate", async execute (interaction) { + if (!UserUtils.registrationCheck(interaction)) return; if (!interaction.isCommand()) return; const guild = await interaction.guild; @@ -30,19 +32,6 @@ module.exports = { }); } - //check if the user exists in the database, if not tell him to use the /register command - let user = await User.findOne({ - where: { - discordId: interaction.member.id - } - }); - if (!user && interaction.commandName !== "register") { - interaction.reply({ - content: `You are not registered, use the /register command`, - ephemeral: false - }); - return; - } const command = interaction.client.commands.get(interaction.commandName); @@ -53,7 +42,7 @@ module.exports = { } catch (err) { if (err) console.log(err); await interaction.reply({ - content: `An error occured processing the command :(\n \`\`\`${JSON.stringify(err, null, 2)}\`\`\``, + content: `An error occured processing the command :(\n \`\`\`${err.stack}\`\`\``, ephemeral: false }); } diff --git a/migrations/20220818130258-make-card-userid-nullable.js b/migrations/20220818130258-make-card-userid-nullable.js new file mode 100644 index 0000000..f106567 --- /dev/null +++ b/migrations/20220818130258-make-card-userid-nullable.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = { + async up (queryInterface, Sequelize) { + await queryInterface.changeColumn('Cards', 'userId', { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + }); + }, + + async down (queryInterface, Sequelize) { + await queryInterface.changeColumn('Cards', 'userId', { + type: Sequelize.INTEGER, + allowNull: false + }); + } +}; diff --git a/models/band.js b/models/band.js index bc66f81..70529ef 100644 --- a/models/band.js +++ b/models/band.js @@ -15,7 +15,6 @@ module.exports = (sequelize, DataTypes) => { } } Band.init({ - uniqueId: DataTypes.INTEGER, name: DataTypes.STRING, description: DataTypes.TEXT, imageURL: DataTypes.STRING, diff --git a/models/character.js b/models/character.js index 064def9..38a8a3d 100644 --- a/models/character.js +++ b/models/character.js @@ -10,7 +10,6 @@ module.exports = (sequelize, DataTypes) => { * The `models/index` file will call this method automatically. */ static associate(models) { - Character.belongsToMany(models.Card, { through: 'CardCharacter' }); Character.belongsTo(models.Band, { foreignKey: 'bandId', }); } } diff --git a/util/cards.js b/util/cards.js new file mode 100644 index 0000000..ecaf68a --- /dev/null +++ b/util/cards.js @@ -0,0 +1,23 @@ +const { customAlphabet } = require("nanoid"); +const { Card, Character } = require("../models"); + +module.exports = { + name: "CardUtils", + generateIdentifier: function() { + const nanoid = customAlphabet('6789BCDFGHJKLMNPQRTW',6); + return nanoid(); + }, + + getNextPrintNumber: async function(characterId) { + let count = await Card.count({ + where: { + characterId: characterId + } + }); + return count + 1; + }, + + getCharacterCount: async function(characterId) { + return await Character.count(); + } +} diff --git a/util/db.js b/util/db.js index ebb6cd4..ea9f282 100644 --- a/util/db.js +++ b/util/db.js @@ -14,7 +14,9 @@ async function syncDb() { } + module.exports = { + name: "DbUtils", getDb, syncDb } \ No newline at end of file diff --git a/util/index.js b/util/index.js new file mode 100644 index 0000000..3f7e6c6 --- /dev/null +++ b/util/index.js @@ -0,0 +1,25 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const basename = path.basename(__filename); +const utils = {}; + +fs + .readdirSync(__dirname) + .filter(file => { + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + }) + .forEach(file => { + const util = require(path.join(__dirname, file)); + utils[util.name] = util; + console.log(`Registered util: ${util.name}`); + }); + +Object.keys(utils).forEach(modelName => { + if (utils[modelName].associate) { + utils[modelName].associate(utils); + } +}); + +module.exports = utils; \ No newline at end of file diff --git a/util/reply.js b/util/reply.js new file mode 100644 index 0000000..2ba3be7 --- /dev/null +++ b/util/reply.js @@ -0,0 +1,27 @@ +const { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js"); + +module.exports = { + name: "ReplyUtils", + recreateComponents: function(components) { + console.log("Recreating components"); + for (let i = 0; i < components.length; i++) { + let row = new ActionRowBuilder(); + for (let j = 0; j < components[i].components.length; j++) { + console.log(components[i].components[j]); + console.log(`Recreating button ${components[i].components[j].customId}`); + let button = new ButtonBuilder(); + button.setCustomId(components[i].components[j].customId); + button.setLabel(components[i].components[j].label); + button.setStyle(components[i].components[j].style); + if (components[i].components[j].emoji) { + button.setEmoji(components[i].components[j].emoji); + } + if (components[i].components[j].disabled) { + button.setDisabled(components[i].components[j].disabled); + } + row.addComponents(button); + } + return row; + } + }, +} diff --git a/util/users.js b/util/users.js new file mode 100644 index 0000000..c464ac4 --- /dev/null +++ b/util/users.js @@ -0,0 +1,28 @@ +const { User } = require("../models"); + +module.exports = { + name: "UserUtils", + getUserByDiscordId: async function(discordId) { + return await User.findOne({ + where: { + discordId: discordId + } + }); + }, + + registrationCheck: async function(interaction) { + let user = await this.getUserByDiscordId(interaction.member.id); + if (user) { + return true; + } + if (!interaction.isButton() && interaction.commandName === "register") { + return true; + } + interaction.reply({ + content: `${interaction.member} You are not registered, use the /register command`, + ephemeral: false + }); + + return false; + } +}