Skip to content

Commit

Permalink
Merge pull request #164 from utarwyn/issue-159-duel-request-buttons
Browse files Browse the repository at this point in the history
GH-159: Request a duel using buttons
  • Loading branch information
utarwyn committed Jan 28, 2022
2 parents 6f6a09d + e388401 commit 29691a2
Show file tree
Hide file tree
Showing 18 changed files with 246 additions and 44 deletions.
1 change: 1 addition & 0 deletions config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"allowedRoleIds": [],
"requestExpireTime": 60,
"requestCooldownTime": 0,
"requestReactions": false,
"simultaneousGames": false,
"gameExpireTime": 30,
"gameBoardReactions": false,
Expand Down
6 changes: 5 additions & 1 deletion config/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Zum Annehmen reagieren.",
"expire": ":x: `{invited}` ist nicht zum Match angetreten.",
"reject": ":x: `{invited}` hat das Match abgelehnt.",
"unknown-user": "Du kannst diesen User nicht herausfordern."
"unknown-user": "Du kannst diesen User nicht herausfordern.",
"button": {
"accept": "Akzeptieren",
"decline": "Ablehnen"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "React to this message to accept or decline the duel.",
"expire": ":x: `{invited}` did not rise up to the challenge.",
"reject": ":x: `{invited}` has rejected the duel.",
"unknown-user": "you cannot challenge that user."
"unknown-user": "you cannot challenge that user.",
"button": {
"accept": "Accept",
"decline": "Decline"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Reacciona a este mensaje para aceptar o rechazar el duelo.",
"expire": ":x: `{invited}` no se puso a la altura del desafío.",
"reject": ":x: `{invited}` ha rechazado el duelo.",
"unknown-user": "no pudiste desafiar a ese usuario."
"unknown-user": "no pudiste desafiar a ese usuario.",
"button": {
"accept": "Aceptar",
"decline": "Rechazar"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Réagis à ce message pour accepter ou décliner le duel.",
"expire": ":x: `{invited}` n'a pas répondu à la demande dans les temps.",
"reject": ":x: `{invited}` a rejeté votre proposition de duel.",
"unknown-user": "vous ne pouvez pas défier ce membre."
"unknown-user": "vous ne pouvez pas défier ce membre.",
"button": {
"accept": "Accepter",
"decline": "Décliner"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Reagisci a questo messaggio per accettare o no la sfida.",
"expire": ":x: `{invited}` non ha risposto.",
"reject": ":x: `{invited}` non ha accettato.",
"unknown-user": "non puoi sfidare questo utente."
"unknown-user": "non puoi sfidare questo utente.",
"button": {
"accept": "Accettare",
"decline": "Rifiutare"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Reageer op dit bericht om het spel te accepteren of te weigeren.",
"expire": ":x: `{invited}` heeft te laat gereageerd!",
"reject": ":x: `{invited}` heeft het spel geweigerd!",
"unknown-user": "Je kan dit lid niet uitdagen!"
"unknown-user": "Je kan dit lid niet uitdagen!",
"button": {
"accept": "Accepteren",
"decline": "Refuse"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Zareaguj odpowiednią emotką do tej wiadomości, aby zaakceptować lub odrzucić zaproszenie.",
"expire": ":x: `{invited}` nie odpowiedział na zaproszenie do pojedynku.",
"reject": ":x: `{invited}` odrzucił zaproszenie do pojedynku.",
"unknown-user": "nie możesz wyzwać na pojedynek tego użytkownika."
"unknown-user": "nie możesz wyzwać na pojedynek tego użytkownika.",
"button": {
"accept": "Akceptuj",
"decline": "Odmawiający"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Reaja esta mensagem para aceitar ou recusar este duelo.",
"expire": ":x: `{invited}` não apareceu para o duelo.",
"reject": ":x: `{invited}` rejeitou o duelo.",
"unknown-user": "você não pode desafiar este usuário."
"unknown-user": "você não pode desafiar este usuário.",
"button": {
"accept": "Accept",
"decline": "Decline"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "Нажмите на реакции ниже для принятия или отклонения приглашения!",
"expire": ":x: `{invited}` Не ответил на Ваше предложение.",
"reject": ":x: `{invited}` Отклонил вызов на игру.",
"unknown-user": "Вы не можете вызвать данного игрока **{username}**. Пожалуйста линканите другого пользователя."
"unknown-user": "Вы не можете вызвать данного игрока **{username}**. Пожалуйста линканите другого пользователя.",
"button": {
"accept": "Принять",
"decline": "отказывать"
}
},
"game": {
"title": ":game_die: `{player1}` **Против** `{player2}`",
Expand Down
6 changes: 5 additions & 1 deletion config/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"action": "React vào tin nhắn này để chấp nhận hoặc từ chối lời mời",
"expire": ":x: `{invited}` đã không phản hồi gì về lời mời của bạn",
"reject": ":x: `{invited}` đã từ chối lời mời của bạn.",
"unknown-user": "Bạn không thể thách đấu **{username}**. Hãy mention một người khác."
"unknown-user": "Bạn không thể thách đấu **{username}**. Hãy mention một người khác.",
"button": {
"accept": "Accept",
"decline": "Decline"
}
},
"game": {
"title": ":game_die: `{player1}` **VS** `{player2}`",
Expand Down
4 changes: 2 additions & 2 deletions src/bot/command/GameCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import InteractionMessagingTunnel from '@bot/messaging/InteractionMessagingTunnel';
import CommandInteractionMessagingTunnel from '@bot/messaging/CommandInteractionMessagingTunnel';
import MessagingTunnel from '@bot/messaging/MessagingTunnel';
import TextMessagingTunnel from '@bot/messaging/TextMessagingTunnel';
import GameStateManager from '@bot/state/GameStateManager';
Expand Down Expand Up @@ -69,7 +69,7 @@ export default class GameCommand {
(noTrigger || interaction.commandName === this.config.command)
) {
// Retrieve the inviter and create an interaction tunnel
const tunnnel = new InteractionMessagingTunnel(interaction);
const tunnnel = new CommandInteractionMessagingTunnel(interaction);

// Retrieve invited user from options if provided
const mentionned = interaction.options.getMember(
Expand Down
122 changes: 95 additions & 27 deletions src/bot/entity/DuelRequest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import ComponentInteractionMessagingTunnel from '@bot/messaging/ComponentInteractionMessagingTunnel';
import MessagingTunnel from '@bot/messaging/MessagingTunnel';
import GameStateManager from '@bot/state/GameStateManager';
import localize from '@i18n/localize';
import {
Collection,
GuildMember,
Message,
MessageActionRow,
MessageButton,
MessageComponentInteraction,
MessageOptions,
MessageReaction,
Snowflake
Expand All @@ -26,10 +30,6 @@ export default class DuelRequest {
* Global game state manager.
*/
private readonly manager: GameStateManager;
/**
* Tunnel that initiated the duel request.
*/
private readonly tunnel: MessagingTunnel;
/**
* Member who has been invited to play
*/
Expand All @@ -38,6 +38,14 @@ export default class DuelRequest {
* Expiration time of a request message
*/
private readonly expireTime: number;
/**
* Interact with reactions instead of buttons
*/
private readonly useReactions: boolean;
/**
* Tunnel that initiated the duel request.
*/
private tunnel: MessagingTunnel;

/**
* Constructs a new duel request based on a message.
Expand All @@ -46,17 +54,20 @@ export default class DuelRequest {
* @param tunnel messaging tunnel that created the request
* @param invited invited member object
* @param expireTime expiration time of the mesage, undefined for default
* @param useReactions interact with reactions instead of buttons
*/
constructor(
manager: GameStateManager,
tunnel: MessagingTunnel,
invited: GuildMember,
expireTime?: number
expireTime?: number,
useReactions?: boolean
) {
this.manager = manager;
this.tunnel = tunnel;
this.invited = invited;
this.expireTime = expireTime ?? 60;
this.useReactions = useReactions ?? false;
}

/**
Expand All @@ -75,6 +86,22 @@ export default class DuelRequest {

return {
allowedMentions: { parse: [] },
components: !this.useReactions
? [
new MessageActionRow().addComponents(
new MessageButton({
style: 'SUCCESS',
customId: 'yes',
label: localize.__('duel.button.accept')
}),
new MessageButton({
style: 'DANGER',
customId: 'no',
label: localize.__('duel.button.decline')
})
)
]
: [],
embeds: [
{
color: 2719929, // #2980B9
Expand All @@ -92,39 +119,78 @@ export default class DuelRequest {
* @param message discord.js message object to attach
*/
public async attachTo(message: Message): Promise<void> {
for (const reaction of DuelRequest.REACTIONS) {
await message.react(reaction);
if (this.useReactions) {
for (const reaction of DuelRequest.REACTIONS) {
await message.react(reaction);
}

message
.awaitReactions({
filter: (reaction, user) =>
reaction.emoji.name != null &&
DuelRequest.REACTIONS.includes(reaction.emoji.name) &&
user.id === this.invited.id,
max: 1,
time: this.expireTime * 1000,
errors: ['time']
})
.then(this.challengeEmojiAnswered.bind(this))
.catch(this.challengeExpired.bind(this));
} else {
message
.createMessageComponentCollector({
filter: interaction => interaction.user.id === this.invited.id,
max: 1,
time: this.expireTime * 1000
})
.on('collect', this.challengeButtonAnswered.bind(this))
.on('end', async (_, reason) => {
if (reason !== 'limit') {
await this.challengeExpired();
}
});
}
}

message
.awaitReactions({
filter: (reaction, user) =>
reaction.emoji.name != null &&
DuelRequest.REACTIONS.includes(reaction.emoji.name) &&
user.id === this.invited.id,
max: 1,
time: this.expireTime * 1000,
errors: ['time']
})
.then(this.challengeAnswered.bind(this))
.catch(this.challengeExpired.bind(this));
/**
* Called when the invited user answered to the request using a button.
*
* @param interaction interaction that has operated challenge answer
* @private
*/
private async challengeButtonAnswered(interaction: MessageComponentInteraction): Promise<void> {
// now that an interaction using buttons has been operated on message, use it
this.tunnel = new ComponentInteractionMessagingTunnel(interaction);
return this.challengeAnswered(interaction.customId === 'yes');
}

/**
* Called when the invited user answered to the request.
* Called when the invited user answered to the request using an emoji.
*
* @param collected collection with all reactions added
*/
private async challengeAnswered(
private async challengeEmojiAnswered(
collected: Collection<Snowflake, MessageReaction>
): Promise<void> {
if (collected.first()!.emoji.name === DuelRequest.REACTIONS[0]) {
return this.challengeAnswered(collected.first()!.emoji.name === DuelRequest.REACTIONS[0]);
}

/**
* Called when the invited user answered to the request.
*
* @param accepted true if user accepted the request, false otherwise
* @param rejectFunc function called to reject the duel request
*/
private async challengeAnswered(accepted: boolean): Promise<void> {
if (accepted) {
await this.tunnel.end();
await this.manager.createGame(this.tunnel, this.invited);
return this.manager.createGame(this.tunnel, this.invited);
} else {
await this.tunnel.end({
return this.tunnel.end({
allowedMentions: { parse: [] },
content: localize.__('duel.reject', { invited: this.invited.displayName })
components: [],
content: localize.__('duel.reject', { invited: this.invited.displayName }),
embeds: []
});
}
}
Expand All @@ -133,9 +199,11 @@ export default class DuelRequest {
* Called if the challenge has expired without answer.
*/
private async challengeExpired(): Promise<void> {
await this.tunnel.end({
return this.tunnel.end({
allowedMentions: { parse: [] },
content: localize.__('duel.expire', { invited: this.invited.displayName })
components: [],
content: localize.__('duel.expire', { invited: this.invited.displayName }),
embeds: []
});
}
}
2 changes: 1 addition & 1 deletion src/bot/entity/GameBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export default class GameBoard {
/**
* Called when a player has selected a valid move button.
*
* @param collected collected data from discordjs
* @param interaction interaction that has operated move request
* @private
*/
private async onButtonMoveSelected(interaction: ButtonInteraction): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CommandInteraction, GuildMember, Message, TextChannel } from 'discord.j
* @author Utarwyn
* @since 2.2.0
*/
export default class InteractionMessagingTunnel extends MessagingTunnel {
export default class CommandInteractionMessagingTunnel extends MessagingTunnel {
/**
* Interaction object retrieved from the Discord API
* @private
Expand Down Expand Up @@ -85,8 +85,7 @@ export default class InteractionMessagingTunnel extends MessagingTunnel {
public async end(reason?: MessagingAnswer): Promise<void> {
if (this.reply) {
try {
await this.editReply(reason ?? { content: '.' });
await this.reply.suppressEmbeds(true);
await this.editReply(reason ?? { content: '.', components: [], embeds: [] });
await this.reply.reactions.removeAll();
} catch {
// ignore api error
Expand Down

0 comments on commit 29691a2

Please sign in to comment.