akinator-discord.js
Version:
A literal fork of the npm package discord.js-akinator, except with very minor changes(WIP). The only reason I'm publishing this is for private use. But anyone is free to use it if they wish.
378 lines (331 loc) • 19.6 kB
JavaScript
const Discord = require("discord.js");
const { Aki } = require("aki-api");
const fs = require("fs");
const translate = require("./translate");
const awaitInput = require("./input");
const games = new Set();
const attemptingGuess = new Set();
// this simply gets the user's reply from a button interaction (that is, if the user has chosen to enable buttons)
function getButtonReply(interaction) {
interaction = interaction.customId;
if (interaction === "✅") { //yes
return "y"
}
else if (interaction === "❌") { //no
return "n"
}
else if (interaction === "❓") { //don't know
return "i"
}
else if (interaction === "👍") { //probably
return "p"
}
else if (interaction === "👎") { //probably not
return "pn"
}
else if (interaction === "⏪") { //back
return "b"
}
else if (interaction === "🛑") { //stop game
return "s"
}
else return null;
}
/**
* Play a Game of Akinator.
*
* Simply pass in the Discord `Message` or `CommandInteraction` Sent by the User to Setup the Game.
*
* __Game Options__
*
* - `language` - The Language of the Game.
* - `childMode` - Whether to use Akinator's Child Mode.
* - `gameType` - The Type of Akinator Game to Play. (`animal`, `character` or `object`)
* - `useButtons` - Whether to use Discord's Buttons.
*
* @param {Discord.Message | Discord.CommandInteraction} input The Message Sent by the User.
* @param {object} options The Options for the Game.
* @param {string} [options.language="en"] The Language of the Game. Defaults to "en".
* @param {boolean} [options.childMode=false] Whether to use Akinator's Child Mode. Defaults to "false".
* @param {"character" | "animal" | "object"} [options.gameType="character"] The Type of Akinator Game to Play. Defaults to "character".
* @param {boolean} [options.useButtons=false] Whether to use Discord's Buttons. Defaults to "false".
* @returns {Promise<Discord.Message>} Discord.js Akinator Game
* @example
* const { Client, Intents } = require("discord.js");
* const akinator = require("discord.js-akinator");
* const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
*
* client.on("ready", () => {
* console.log("Bot is Online")
* });
*
* const PREFIX = "!";
*
* //Example options
*
* const language = "en"; //The Language of the Game
* const childMode = false; //Whether to use Akinator's Child Mode
* const gameType = "character"; //The Type of Akinator Game to Play. ("animal", "character" or "object")
* const useButtons = true; //Whether to use Discord's Buttons
*
* client.on("messageCreate", async message => {
* if(message.content.startsWith(`${PREFIX}akinator`)) {
* akinator(message, {
* language: language, //Defaults to "en"
* childMode: childMode, //Defaults to "false"
* gameType: gameType, //Defaults to "character"
* useButtons: useButtons //Defaults to "false"
* });
* }
* });
*/
module.exports = async function (input, options = {}) {
let inputData = {};
try {
// configuring game options if not specified
options.language = options.language || "en";
options.childMode = options.childMode || false;
options.gameType = options.gameType || "character";
options.useButtons = options.useButtons || false;
options.language = options.language.toLowerCase();
options.gameType = options.gameType.toLowerCase();
// error handling
if (!input) return console.log("Discord.js Akinator Error: Message or CommandInteraction was not Provided.\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'");
// if the input is not a Discord.Message or CommandInteraction, return an error
if (!input.client) return console.log("Discord.js Akinator Error: Message or CommandInteration Provided was Invalid.\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'");
if (!input.guild) return console.log("Discord.js Akinator Error: Cannot be used in Direct Messages.\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'");
if (!fs.existsSync(`${__dirname}/translations/${options.language}.json`)) return console.log(`Discord.js Akinator Error: Language "${options.language}" Not Found. Examples are: "en" or "fr" or "es".\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'`);
if (!["animal", "character", "object"].includes(options.gameType)) return console.log(`Discord.js Akinator Error: Game Type "${options.gameType}" Not Found. Choose from: "animal", "character" or "object".\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'`);
try {
inputData.client = input.client,
inputData.guild = input.guild,
inputData.author = input.author ? input.author : input.user,
inputData.channel = input.channel
} catch {
return console.log("Discord.js Akinator Error: Failed to Parse Input for Use.\nJoin Our Discord Server for Support at 'https://discord.gg/P2g24jp'");
}
// defining for easy use
let usertag = inputData.author.tag
let avatar = inputData.author.displayAvatarURL()
// check if a game is being hosted by the player
if (games.has(inputData.author.id)) {
let alreadyPlayingEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`❌ ${await translate("You're already playing!", options.language)}`)
.setDescription(`**${await translate(`You're already playing a game of Akinator. ${!options.useButtons ? `Type \`S\` or \`Stop\`` : `Press the \`Stop\` button on the previous game's message`} to cancel your game.`, options.language)}**`)
.setColor("RED")
if (input.commandName && (!input.replied || !input.deferred)) { // check if it's a slash command and see if it's already been replied or deferred
input.reply({ embeds: [alreadyPlayingEmbed] })
} else {
input.channel.send({ embeds: [alreadyPlayingEmbed] })
}
return;
}
// adding the player into the game
games.add(inputData.author.id)
let startingEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`${await translate("Starting Game...", options.language)}`)
.setDescription(`**${await translate("The game will start in a few seconds...", options.language)}**`)
.setColor("RANDOM")
let startingMessage;
if (input.commandName && (!input.replied || !input.deferred)) { // check if it's a slash command and hasn't been replied or deferred
startingMessage = await input.reply({ embeds: [startingEmbed] })
} else {
startingMessage = await input.channel.send({ embeds: [startingEmbed] })
}
// get translation object for the language
let translations = require(`${__dirname}/translations/${options.language}.json`);
// starts the game
let gameTypeRegion = options.gameType == "animal" ? "en_animals" : options.gameType == "character" ? "en" : "en_objects";
let aki = new Aki({ region: gameTypeRegion, childMode: options.childMode });
await aki.start();
let notFinished = true;
let stepsSinceLastGuess = 0;
let hasGuessed = false;
let noResEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(translations.gameEnded)
.setDescription(`**${inputData.author.username}, ${translations.gameEndedDesc}**`)
.setColor("RANDOM")
let akiEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`${translations.question} ${aki.currentStep + 1}`)
.setDescription(`**${translations.progress}: 0%\n${await translate(aki.question, options.language)}**`)
.setFooter(translations.stopTip)
.setColor("RANDOM")
if (!options.useButtons) akiEmbed.addField(translations.pleaseType, `**Y** or **${translations.yes}**\n**N** or **${translations.no}**\n**I** or **IDK**\n**P** or **${translations.probably}**\n**PN** or **${translations.probablyNot}**\n**B** or **${translations.back}**`)
if (input.user) await input.deleteReply();
else await startingMessage.delete();
let akiMessage = await inputData.channel.send({ embeds: [akiEmbed] });
// if message was deleted, quit the player from the game
inputData.client.on("messageDelete", async deletedMessage => {
if (deletedMessage.id == akiMessage.id) {
notFinished = false;
games.delete(inputData.author.id)
attemptingGuess.delete(inputData.guild.id)
await aki.win()
return;
}
})
// repeat while the game is not finished
while (notFinished) {
if (!notFinished) return;
stepsSinceLastGuess = stepsSinceLastGuess + 1
if (((aki.progress >= 95 && (stepsSinceLastGuess >= 10 || hasGuessed == false)) || aki.currentStep >= 78) && (!attemptingGuess.has(inputData.guild.id))) {
attemptingGuess.add(inputData.guild.id)
await aki.win();
stepsSinceLastGuess = 0;
hasGuessed = true;
let guessEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`${await translate(`I'm ${Math.round(aki.progress)}% sure your character is...`, options.language)}`)
.setDescription(`**${await translate(aki.answers[0].name, options.language)}**\n${await translate(aki.answers[0].description, options.language)}\n\n${translations.isThisYourCharacter} ${!options.useButtons ? `**(Type Y/${translations.yes} or N/${translations.no})**` : ""}`)
.addField(translations.ranking, `**#${aki.answers[0].ranking}**`, true)
.addField(translations.noOfQuestions, `**${aki.currentStep}**`, true)
.setImage(aki.answers[0].absolute_picture_path)
.setColor("RANDOM")
await akiMessage.edit({ embeds: [guessEmbed] });
akiMessage.embeds[0] = guessEmbed;
await awaitInput(options.useButtons, inputData, akiMessage, true, translations, options.language)
.then(async response => {
if (response === null) {
notFinished = false;
games.delete(inputData.author.id)
akiMessage.edit({ embeds: [noResEmbed], components: [] })
return;
}
let reply = getButtonReply(response) || response
const guessAnswer = reply.toLowerCase();
attemptingGuess.delete(inputData.guild.id)
// if they answered yes
if (guessAnswer == "y" || guessAnswer == translations.yes.toLowerCase()) {
const yesClickedButton = new Discord.MessageButton()
.setCustomId('iwdidjsksjf')
.setLabel('✅ Yes')
.setStyle('SUCCESS')
.setDisabled(true);
const noClickedButton = new Discord.MessageButton()
.setCustomId('iwdijdjddjsjf')
.setLabel('❌ No')
.setStyle('SECONDARY')
.setDisabled(true);
const whatDidYaPick = new Discord.MessageActionRow()
.addComponents(yesClickedButton)
.addComponents(noClickedButton)
let finishedGameCorrect = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(translations.wellPlayed)
.setDescription(`**${inputData.author.username}, ${translations.guessedRightOneMoreTime}**`)
.addField(translations.character, `**${await translate(aki.answers[0].name, options.language)}**`, true)
.addField(translations.ranking, `**#${aki.answers[0].ranking}**`, true)
.addField(translations.noOfQuestions, `**${aki.currentStep}**`, true)
.setColor("RANDOM")
if (options.useButtons) {await response.update({ embeds: [guessEmbed], components: [whatDidYaPick] })
input.channel.send({embeds: [finishedGameCorrect], components: []})
notFinished = false;
games.delete(inputData.author.id)
return; } else { await akiMessage.edit({ embeds: [guessEmbed], components: [whatDidYaPick] })
input.channel.send({embeds : [finishedGameCorrect], components: []})
notFinished = false;
games.delete(inputData.author.id)
return; }
// otherwise
} else if (guessAnswer == "n" || guessAnswer == translations.no.toLowerCase()) {
if (aki.currentStep >= 78) {
let finishedGameDefeated = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`Well Played!`)
.setDescription(`**${inputData.author.username}, ${translations.defeated}**`)
.setColor("RANDOM")
if (options.useButtons) await response.update({ embeds: [finishedGameDefeated], components: [] })
else await akiMessage.edit({ embeds: [finishedGameDefeated], components: [] })
notFinished = false;
games.delete(inputData.author.id)
} else {
if (options.useButtons) await response.update({ embeds: [guessEmbed], components: [] })
else await akiMessage.edit({ embeds: [guessEmbed], components: [] })
aki.progress = 50
}
}
});
}
if (!notFinished) return;
if (aki.currentStep !== 0) {
let updatedAkiEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`${translations.question} ${aki.currentStep + 1}`)
.setDescription(`**${translations.progress}: ${Math.round(aki.progress)}%\n${await translate(aki.question, options.language)}**`)
.setFooter(translations.stopTip)
.setColor("RANDOM")
if (!options.useButtons) updatedAkiEmbed.addField(translations.pleaseType, `**Y** or **${translations.yes}**\n**N** or **${translations.no}**\n**I** or **IDK**\n**P** or **${translations.probably}**\n**PN** or **${translations.probablyNot}**\n**B** or **${translations.back}**`)
await akiMessage.edit({ embeds: [updatedAkiEmbed] })
akiMessage.embeds[0] = updatedAkiEmbed
}
await awaitInput(options.useButtons, inputData, akiMessage, false, translations, options.language)
.then(async response => {
if (response === null) {
await aki.win()
notFinished = false;
games.delete(inputData.author.id)
return akiMessage.edit({ embeds: [noResEmbed], components: [] })
}
let reply = getButtonReply(response) || response
const answer = reply.toLowerCase();
// assign points for the possible answers given
const answers = {
"y": 0,
"yes": 0,
"n": 1,
"no": 1,
"i": 2,
"idk": 2,
"dont know": 2,
"don't know": 2,
"i": 2,
"p": 3,
"probably": 3,
"pn": 4,
"probably not": 4,
}
let thinkingEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(`${translations.question} ${aki.currentStep + 1}`)
.setDescription(`**${translations.progress}: ${Math.round(aki.progress)}%\n${await translate(aki.question, options.language)}**`)
.setFooter(translations.thinking)
.setColor("RANDOM")
if (!options.useButtons) thinkingEmbed.addField(translations.pleaseType, `**Y** or **${translations.yes}**\n**N** or **${translations.no}**\n**I** or **IDK**\n**P** or **${translations.probably}**\n**PN** or **${translations.probablyNot}**\n**B** or **${translations.back}**`)
if (options.useButtons) await response.update({ embeds: [thinkingEmbed], components: [] })
else await akiMessage.edit({ embeds: [thinkingEmbed], components: [] })
akiMessage.embeds[0] = thinkingEmbed
if (answer == "b" || answer == translations.back.toLowerCase()) {
if (aki.currentStep >= 1) {
await aki.back();
}
// stop the game if the user selected to stop
} else if (answer == "s" || answer == translations.stop.toLowerCase()) {
games.delete(inputData.author.id)
let stopEmbed = new Discord.MessageEmbed()
.setAuthor(usertag, avatar)
.setTitle(translations.gameEnded)
.setDescription(`**${inputData.author.username}, ${translations.gameForceEnd}**`)
.setColor("RANDOM")
await aki.win()
await akiMessage.edit({ embeds: [stopEmbed], components: [] })
notFinished = false;
} else {
await aki.step(answers[answer]);
}
if (!notFinished) return;
});
}
} catch (e) {
// log any errors that come
attemptingGuess.delete(inputData.guild.id)
games.delete(inputData.guild.id)
if (e == "DiscordAPIError: Unknown Message") return;
else if (e == "DiscordAPIError: Cannot send an empty message") return console.log("Discord.js Akinator Error: Discord.js v13 or Higher is Required.\nNeed Help? Join Our Discord Server at 'https://discord.gg/P2g24jp'");
console.log("Discord.js Akinator Error:")
console.log(e);
}
}