@averagehelper/corde
Version:
A simple library for Discord bot tests. (Republished fork to demonstrate a bugfix)
331 lines (330 loc) • 12.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CordeBot = void 0;
const rxjs_1 = require("rxjs");
const events_1 = require("./events");
const cordeClientError_1 = require("../errors/cordeClientError");
const errors_1 = require("../errors");
const DEFAULT_TEST_TIMEOUT = 5000;
/**
* Encapsulation of Discord Client with all specific
* functions for corde test.
*/
class CordeBot extends events_1.Events {
/**
* Starts new instance of Discord client with its events.
*
* @param prefix Corde bot prefix.
* @param guildId Guild id where corde bot is located in.
* @param channelId Channel id where corde bot is located in.
* @param waitTimeOut Timeout for message wait.
* @param testBotId id of testing bot.
*/
constructor(prefix, guildId, channelId, waitTimeOut = DEFAULT_TEST_TIMEOUT, testBotId, client) {
super(client);
this._channelId = channelId;
this._prefix = prefix;
this._guildId = guildId;
this._waitTimeOut = waitTimeOut;
this._testBotId = testBotId;
this._onStart = new rxjs_1.BehaviorSubject(false);
this._reactionsObserved = new rxjs_1.BehaviorSubject(null);
this.loadClientEvents();
}
/**
* Observes if corde bot is **ready**.
* This is invoked after onReady in Discord.Client.
* Used to initialize some data for CordeBot.
* **Do not use onReady declared in Events if the intention is to ensure that
* cordebot is ready for usage**
*/
get onStart() {
return this._onStart.asObservable();
}
get guild() {
return this.textChannel.guild;
}
/**
* Authenticate Corde bot to the installed bot in Discord server.
*
* @param token Corde bot token
*
* @returns Promise resolve for success connection, or a promise
* rejection with a formatted message if there was found a error in
* connection attempt.
*/
login(token) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this._client.login(token);
}
catch (error) {
return Promise.reject(this.buildLoginErrorMessage(token, error));
}
});
}
/**
* Destroy client connection.
*/
logout() {
this._client.destroy();
}
/**
* Sends a pure message without prefix it.
* @param message Data to be send to channel
*/
sendMessage(message) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.textChannel.send(message);
});
}
/**
* Send a message to a channel defined in configs.
*
* @see Runtime
*
* @param message Message without prefix that will be sent to defined server's channel
* @description The message is concatenated with the stored **prefix** and is sent to the channel.
*
* @return Promise rejection if a testing bot does not send any message in the timeout value setted,
* or a resolve for the promise with the message returned by the testing bot.
*/
sendTextMessage(message) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
this.validateMessageAndChannel(message);
const formattedMessage = this._prefix + message;
const returnedMessage = yield this.textChannel.send(formattedMessage);
resolve(returnedMessage);
}
catch (error) {
reject(error);
}
}));
});
}
/**
* Observes for a message send by the testing bot after corde bot
* send it's message.
*/
awaitMessagesFromTestingBot() {
return __awaiter(this, void 0, void 0, function* () {
const msg = yield this.textChannel.awaitMessages((responseName) => this.responseAuthorIsTestingBot(responseName.author.id), this.createWatchResponseConfigs());
if (msg) {
return msg.first();
}
throw new cordeClientError_1.CordeClientError("No message was send");
});
}
waitForAddedReactions(message, reactions) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
const filter = this.createWaitForAdedReactionsFilter(reactions);
const maxReactionsToWait = this.defineMaxReactionsToWaitForAddedReactions(reactions);
const collectedReactions = yield message.awaitReactions(filter, this.createWatchResponseConfigs(maxReactionsToWait));
resolve(collectedReactions);
}
catch (error) {
reject(new errors_1.TimeoutError());
}
}));
});
}
createWaitForAdedReactionsFilter(reactions) {
if (reactions && Array.isArray(reactions)) {
return (reaction, user) => {
return reactions.includes(reaction.emoji.name) && this.responseAuthorIsTestingBot(user.id);
};
}
return (_reaction, user) => {
return this.responseAuthorIsTestingBot(user.id);
};
}
defineMaxReactionsToWaitForAddedReactions(reactions) {
if (reactions && Array.isArray(reactions)) {
return reactions.length;
}
if (typeof reactions === "number" && reactions > 0) {
return reactions;
}
return 1;
}
waitForRemovedReactions(message, take) {
let amount = 0;
const reactions = [];
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new errors_1.TimeoutError());
}, this._waitTimeOut ? this._waitTimeOut : DEFAULT_TEST_TIMEOUT);
this._reactionsObserved.subscribe((reaction) => {
if (reaction) {
if (reaction.message.id === message.id) {
amount++;
reactions.push(reaction);
}
if (amount >= take) {
resolve(reactions);
}
}
});
});
}
/**
* Checks if corde bot is connected
*/
isLoggedIn() {
return !!this._client && !!this._client.readyAt && this._onStart.value;
}
findMessage(data) {
return __awaiter(this, void 0, void 0, function* () {
const messageData = data;
if (messageData && messageData.text) {
return this._findMessage((m) => m.content === messageData.text);
}
if (messageData && messageData.id) {
return this._findMessage((m) => m.id === messageData.id);
}
if (data) {
return this._findMessage(data);
}
return null;
});
}
findPinnedMessage(messageData) {
return __awaiter(this, void 0, void 0, function* () {
const msgs = yield this.textChannel.messages.fetchPinned();
if (messageData && messageData.text) {
return msgs.find((m) => m.content === messageData.text);
}
if (messageData && messageData.id) {
return msgs.find((m) => m.id === messageData.id);
}
return null;
});
}
fetchRole(id) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.guild.roles.fetch(id, false, true);
});
}
hasRole(roleData) {
return __awaiter(this, void 0, void 0, function* () {
return !!(yield this.findRole(roleData));
});
}
findRole(roleData) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.guild.roles.fetch();
if (roleData.id) {
return data.cache.find((r) => r.id === roleData.id);
}
else if (roleData.name) {
return data.cache.find((r) => r.name === roleData.name);
}
return null;
});
}
getRoles() {
return this.guild.roles.cache;
}
/**
* Search for messages based in a filter query.
*/
_findMessage(fn) {
return __awaiter(this, void 0, void 0, function* () {
const collection = yield this.textChannel.messages.fetch();
if (collection) {
return collection.find(fn);
}
return null;
});
}
/**
* Get a channel based in the id stored in configs.
*
* @see Runtime
*/
loadChannel() {
const guild = this.findGuild(this._guildId);
const channel = this.findChannel(guild, this._channelId);
this.textChannel = this.convertToTextChannel(channel);
}
responseAuthorIsTestingBot(idAuthor) {
return idAuthor === this._testBotId;
}
createWatchResponseConfigs(max = 1) {
return {
max,
time: this._waitTimeOut ? this._waitTimeOut : DEFAULT_TEST_TIMEOUT,
errors: ["time"],
};
}
loadClientEvents() {
this.onReady(() => {
this.loadChannel();
this._onStart.next(true);
});
this.onMessageReactionRemoveEmoji((reaction) => {
this._reactionsObserved.next(reaction);
});
}
buildLoginErrorMessage(token, error) {
return `Error trying to login with token ${token}. \n` + error;
}
validateMessageAndChannel(message) {
if (!message) {
throw new cordeClientError_1.CordeClientError("No tests were declared");
}
if (!this.textChannel) {
throw new cordeClientError_1.CordeClientError("Channel not found");
}
}
findGuild(guildId) {
if (!this._client.guilds) {
throw new cordeClientError_1.CordeClientError(`corde bot isn't added in a guild. Please add it to the guild: ${guildId}`);
}
else if (!this._client.guilds.cache.has(guildId)) {
throw new cordeClientError_1.CordeClientError(`Guild ${guildId} doesn't belong to corde bot. change the guild id ` +
` in corde.config or add the bot to a valid guild`);
}
else {
const guild = this._client.guilds.cache.find((_guild) => _guild.id === guildId);
if (guild) {
return guild;
}
const availableGuildsIds = this._client.guilds.cache.map((guildAvailable) => guildAvailable.id);
throw new cordeClientError_1.CordeClientError(`Could not find the guild ${guildId}.` +
` this client has connections with the following guilds: ${availableGuildsIds}`);
}
}
findChannel(guild, channelId) {
if (!guild.channels) {
throw new cordeClientError_1.CordeClientError(`Guild '${guild.name}' do not have any channel.`);
}
else if (!guild.channels.cache.has(channelId)) {
throw new cordeClientError_1.CordeClientError(`channel ${channelId} doesn't appear to be a channel of guild ${guild.name}`);
}
else {
const channel = guild.channels.cache.find((ch) => ch.id === channelId);
if (channel === undefined) {
throw new cordeClientError_1.CordeClientError("There is no informed channel to start tests");
}
return channel;
}
}
convertToTextChannel(channel) {
return channel;
}
}
exports.CordeBot = CordeBot;