Skip to content

Commit

Permalink
Merge pull request #84 from tarolling/clubs
Browse files Browse the repository at this point in the history
(feat) add improved club functionality
  • Loading branch information
tarolling authored Jul 27, 2024
2 parents 6bba463 + 6f03a90 commit e62b25d
Show file tree
Hide file tree
Showing 20 changed files with 303 additions and 108 deletions.
191 changes: 158 additions & 33 deletions commands/rps/club.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const { SlashCommandBuilder } = require('discord.js');
const { createClub, joinClub, leaveClub, viewClub } = require('../../src/db/clubs');
const { club: clubEmbed } = require('../../src/embeds');
const { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, ComponentType, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
const { createClub, joinClub, leaveClub, fetchClub, fetchClubs, fetchPlayerClub } = require('../../src/db/clubs');
const { clubInfo: clubInfoEmbed, clubList } = require('../../src/embeds');


const MAX_PAGES = 5;
const MAX_CLUBS = 50;


module.exports = {
Expand Down Expand Up @@ -57,47 +61,168 @@ module.exports = {
.setDescription('Specify what club you would like to view.')
.setMinLength(3)
.setMaxLength(32)
.setRequired(true)
)
),
async execute(interaction) {
try {
await interaction.deferReply({ ephemeral: true })
.catch(console.error);
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case 'join': {
await interaction.deferReply({ ephemeral: true }).catch(console.error);
return joinClub(interaction);
}
case 'leave': {
await interaction.deferReply({ ephemeral: true }).catch(console.error);
return leaveClub(interaction);
}
case 'create': {
const clubCheck = await fetchPlayerClub(interaction.user.id);
if (clubCheck) {
return interaction.reply({ content: `You are already a member of ${clubCheck.name}!` }).catch(console.error);
}

const subcommand = interaction.options.getSubcommand();
const modal = new ModalBuilder()
.setCustomId('create-club')
.setTitle('Create New Club');

switch (subcommand) {
case 'join': {
await joinClub(interaction);
break;
}
case 'leave': {
await leaveClub(interaction);
break;
}
case 'create': {
await createClub(interaction);
break;
const clubName = new TextInputBuilder()
.setCustomId('club-name')
.setLabel('Club Name')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Pittsburgh Steelers')
.setMinLength(3)
.setMaxLength(32)
.setRequired(true)

const abbreviation = new TextInputBuilder()
.setCustomId('club-abbr')
.setLabel('Club Abbreviation')
.setStyle(TextInputStyle.Short)
.setPlaceholder('PIT')
.setMinLength(2)
.setMaxLength(4)
.setRequired(true)

const actionRowOne = new ActionRowBuilder().addComponents(clubName);
const actionRowTwo = new ActionRowBuilder().addComponents(abbreviation);

modal.addComponents(actionRowOne, actionRowTwo);
await interaction.showModal(modal).catch(console.error);
const submittedInfo = await interaction.awaitModalSubmit({
time: 120_000,
filter: i => i.user.id === interaction.user.id
}).catch(console.error);

if (submittedInfo) {
return createClub(submittedInfo);
}
case 'view': {
const club = await viewClub(interaction);
if (!club) {
break;
}
case 'view': {
await interaction.deferReply({ ephemeral: true }).catch(console.error);

const clubName = interaction.options.getString('name') ?? null;
if (clubName !== null) {
const clubDocs = await fetchClub(clubName);
if (clubDocs === null || clubDocs === undefined) {
return interaction.editReply({ content: 'This club could not be found!', ephemeral: true })
.catch(console.error);
}
await interaction.editReply({ embeds: [clubEmbed(club)], ephemeral: true })
.catch(console.error);
break;

const leaderInfo = await interaction.client.users.fetch(clubDocs.leader);
const newMembers = [];
for (const memberId of clubDocs.members) {
const memberInfo = await interaction.client.users.fetch(memberId);
newMembers.push({
username: memberInfo.username,
id: memberId
});
}

const newClubInfo = {
leader: {
username: leaderInfo.username,
id: clubDocs.leader
},
name: clubDocs.name,
abbreviation: clubDocs.abbreviation,
members: newMembers
};
return interaction.editReply({ embeds: [clubInfoEmbed(newClubInfo)], ephemeral: true }).catch(console.error);
}

const clubDocs = await fetchClubs(MAX_CLUBS);
clubDocs.sort((a, b) => b.members.length - a.members.length);

let clubInfo = [];
const lazyLoad = async (startIndex, endIndex) => {
for (let i = startIndex; i < endIndex; i++) {
clubInfo.push({
name: clubDocs[i].name,
abbreviation: clubDocs[i].abbreviation,
members: clubDocs[i].members.length
});
}
};

const numPerPage = 10;
let currentPageIndex = 0;

try {
await lazyLoad(currentPageIndex * numPerPage, Math.min(clubDocs.length - 1, (currentPageIndex * numPerPage) + numPerPage));
} catch (e) {
console.error(`lazyLoad: ${e}`);
return;
}
default: break;

const backButton = new ButtonBuilder()
.setCustomId('back')
.setLabel('Back')
.setStyle(ButtonStyle.Secondary);
const forwardButton = new ButtonBuilder()
.setCustomId('forward')
.setLabel('Forward')
.setStyle(ButtonStyle.Secondary);
let row = new ActionRowBuilder()
.addComponents(forwardButton);

const message = await interaction.editReply({
embeds: [clubList(clubInfo.slice(currentPageIndex * numPerPage, Math.min(clubDocs.length - 1, (currentPageIndex * numPerPage) + numPerPage)))],
components: [row]
}).catch(console.error);

const buttonFilter = i => i.user.id === interaction.user.id;
const collector = message.createMessageComponentCollector({ filter: buttonFilter, componentType: ComponentType.Button, idle: 60_000 });

collector.on('collect', async i => {
i.deferUpdate().catch(console.error);
if (i.customId === 'back') {
currentPageIndex = Math.max(0, currentPageIndex - 1);
row.components = (currentPageIndex === 0) ? [forwardButton] : [backButton, forwardButton];
} else if (i.customId === 'forward') {
currentPageIndex = Math.min(MAX_PAGES - 1, currentPageIndex + 1, (clubDocs.length / numPerPage) - 1);
if (clubInfo.length === currentPageIndex * numPerPage) {
await lazyLoad(currentPageIndex * numPerPage, Math.min(clubDocs.length - 1, (currentPageIndex * numPerPage) + numPerPage));
}
row.components = (currentPageIndex === Math.min(MAX_PAGES - 1, (clubDocs.length / numPerPage) - 1)) ? [backButton] : [backButton, forwardButton];
}
interaction.editReply({
embeds: [
clubList(clubInfo.slice(currentPageIndex * numPerPage,
Math.min(clubDocs.length - 1, (currentPageIndex * numPerPage) + numPerPage)
))
],
components: [row]
}).catch(console.error);
collector.resetTimer({ idle: 60_000 });
});

collector.on('end', () => {
interaction.editReply({ components: [] }).catch(console.error);
});

break;
}
} catch (err) {
console.error(err);
return interaction.editReply({
content: 'An error occurred while trying to join/create a club.',
ephemeral: true
}).catch(console.error);
default: return;
}
}
};
52 changes: 23 additions & 29 deletions commands/rps/leaderboard.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
const { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, ComponentType } = require('discord.js');
const { fetchLeaderboards } = require('../../src/db');
const { leaderboard } = require('../../src/embeds');
const { fetchPlayerLeaderboard } = require('../../src/db');
const { playerLeaderboard } = require('../../src/embeds');

const MAX_PAGES = 5;
const MAX_PLAYERS = 50;
const MAX_PLAYERS = 25;


module.exports = {
data: new SlashCommandBuilder()
.setName('lb')
.setDescription('View the global leaderboard.'),
.setDescription('View the global leaderboards.'),
async execute(interaction) {
await interaction.deferReply()
await interaction.deferReply({ ephemeral: true })
.catch(console.error);

const { client } = interaction;

let players;
try {
players = await fetchLeaderboards(MAX_PLAYERS);
players = await fetchPlayerLeaderboard(MAX_PLAYERS);
if (!players) {
return interaction.editReply({
content: 'Huh. I guess there are no active players.',
Expand All @@ -33,26 +30,20 @@ module.exports = {
}).catch(console.error);
}

const numPerPage = 10;
const maxPageIndex = Math.ceil(MAX_PLAYERS / numPerPage) - 1;
let playerInfo = [];
let currentPageIndex = 0;

const lazyLoad = async (startIndex, endIndex) => {
for (let i = startIndex; i < endIndex; i++) {
await (async () => {
for (let i = 0; i < players.length; i++) {
const player = await interaction.client.users.fetch(players[i].user_id);
playerInfo.push({
player: await client.users.fetch(players[i].user_id),
player: player,
elo: players[i].elo
});
}
};

const numPerPage = MAX_PLAYERS / MAX_PAGES;
let currentPageIndex = 0;

try {
await lazyLoad(currentPageIndex * numPerPage, (currentPageIndex * numPerPage) + numPerPage);
} catch (e) {
console.error(`lazyLoad: ${e}`);
return;
}
})();

const backButton = new ButtonBuilder()
.setCustomId('back')
Expand All @@ -65,9 +56,8 @@ module.exports = {
let row = new ActionRowBuilder()
.addComponents(forwardButton);


const message = await interaction.editReply({
embeds: [leaderboard(playerInfo.slice(currentPageIndex * numPerPage, (currentPageIndex * numPerPage) + numPerPage))],
embeds: [playerLeaderboard(playerInfo.slice(currentPageIndex * numPerPage, Math.min(MAX_PLAYERS, (currentPageIndex * numPerPage) + numPerPage)))],
components: [row]
}).catch(console.error);

Expand All @@ -80,12 +70,16 @@ module.exports = {
currentPageIndex = Math.max(0, currentPageIndex - 1);
row.components = (currentPageIndex === 0) ? [forwardButton] : [backButton, forwardButton];
} else if (i.customId === 'forward') {
currentPageIndex = Math.min(MAX_PAGES - 1, currentPageIndex + 1);
if (playerInfo.length === currentPageIndex * numPerPage) await lazyLoad(currentPageIndex * numPerPage, (currentPageIndex * numPerPage) + numPerPage);
row.components = (currentPageIndex === MAX_PAGES - 1) ? [backButton] : [backButton, forwardButton];
currentPageIndex = Math.min(currentPageIndex + 1, maxPageIndex);
row.components = (currentPageIndex === maxPageIndex) ? [backButton] : [backButton, forwardButton];
}
interaction.editReply({
embeds: [leaderboard(playerInfo.slice(currentPageIndex * numPerPage, (currentPageIndex * numPerPage) + numPerPage))],
embeds: [
playerLeaderboard(playerInfo.slice(
currentPageIndex * numPerPage,
Math.min(MAX_PLAYERS, (currentPageIndex * numPerPage) + numPerPage)
))
],
components: [row]
}).catch(console.error);
collector.resetTimer({ idle: 60_000 });
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"scripts": {
"start": "node -r dotenv/config index.js",
"dev": "node -r dotenv/config index.js dev",
"reload": "node -r dotenv/config deploy-commands.js",
"reload-dev": "node -r dotenv/config deploy-commands.js dev",
"reload": "node -r dotenv/config ./scripts/deploy-commands.js",
"reload-dev": "node -r dotenv/config ./scripts/deploy-commands.js dev",
"lint": "eslint **/*.js",
"fix": "eslint **/*.js --fix",
"precommit": "npm run lint && npm run test"
Expand Down
2 changes: 1 addition & 1 deletion deploy-commands.js → scripts/deploy-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const clientId = (process.argv.findIndex(s => s === 'dev') == -1) ? process.env.

const commands = [];
// Grab all the command folders from the commands directory you created earlier
const foldersPath = path.join(__dirname, 'commands');
const foldersPath = path.join(__dirname, '../commands');
const commandFolders = fs.readdirSync(foldersPath);

for (const folder of commandFolders) {
Expand Down
17 changes: 5 additions & 12 deletions src/db/clubs/createClub.js → src/db/clubs/create-club.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ module.exports = async (interaction) => {
}
});
const userId = interaction.user.id;
const playerQuery = { members: userId };

const clubName = interaction.options.getString('name');
const clubAbbr = interaction.options.getString('abbreviation').toUpperCase();
const clubName = interaction.fields.getTextInputValue('club-name');
const clubAbbr = interaction.fields.getTextInputValue('club-abbr').toUpperCase();
const clubQuery = { name: { $regex: clubName, $options: 'i' } };

const doc = { leader: userId, name: clubName, abbreviation: clubAbbr, members: [userId] };
Expand All @@ -22,20 +21,14 @@ module.exports = async (interaction) => {
await dbClient.connect();
const clubCollection = dbClient.db('rps').collection('clubs');

let validation = await clubCollection.findOne(playerQuery);
const validation = await clubCollection.findOne(clubQuery);
if (validation) {
interaction.editReply({ content: `You are already a member of ${validation.name}!` }).catch(console.error);
return;
}

validation = await clubCollection.findOne(clubQuery);
if (validation) {
interaction.editReply({ content: 'This club already exists.' }).catch(console.error);
interaction.reply({ content: 'This club already exists.', ephemeral: true }).catch(console.error);
return;
}

await clubCollection.insertOne(doc);
interaction.editReply({ content: `You have successfully created ${clubName}!` }).catch(console.error);
interaction.reply({ content: `You have successfully created ${clubName}!`, ephemeral: true }).catch(console.error);
} catch (err) {
console.error(err);
} finally {
Expand Down
3 changes: 1 addition & 2 deletions src/db/clubs/viewClub.js → src/db/clubs/fetch-club.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
const { MongoClient, ServerApiVersion } = require('mongodb');


module.exports = async (interaction) => {
module.exports = async (clubName) => {
const dbClient = new MongoClient(process.env.DB_URI, {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true
}
});
const clubName = interaction.options.getString('name');
const query = { name: { $regex: clubName, $options: 'i' } };

try {
Expand Down
Loading

0 comments on commit e62b25d

Please sign in to comment.