Discord/databaseWrapper.js

/**
 * Wrapper for database functions, provides safety checks to some functions and automatically looks up the object linked to an ID for others
 * @module databaseWrapper
 */
const databaseManager = require("../databaseManager.js");
let guild;

/**
 * The actual exports of the file. Exports are assigned to module.exports after startup is completed
 * @type {any}
 */
const realExports = {
	// In case the code attempts to start up a second time. A no-op that returns module exports
	startup: () => realExports,
	messageMap,
	locateMessageMaps,
	initiated: true
};
module.exports = { startup };

/**
 * A function that resolves once the database and DiscordManager have successfully started up. Duplicate calls will result in no-ops<br>
 * Must be called before using any other function exported by this file (Other functions are inaccessible until this resolves)
 * @async
 * @param {Discord.Guild} loggingGuild Discord server/guild object
 * @return {Promise} Resolves once startup is complete and functions become accessible
 */
async function startup(loggingGuild) {
	if(!guild) {
		guild = loggingGuild;
	}

	await databaseManager.startup;

	// Allow access to the actual module exports
	Object.assign(module.exports, realExports);

	return module.exports;
}

/**
 * Wrapper for databaseManager.messageMap. Adds error handling to messageMap to prevent the program from crashing after being unable to add an entry
 * @param {Object} passthroughObj Object to pass to databaseManager.messageMap
 * @return {Promise<void>}
 */
async function messageMap(passthroughObj) {
	try {
		await databaseManager.messageMap(passthroughObj);
		console.log(`Mapped Slack ${passthroughObj.SlackMessageID} to Discord ${passthroughObj.DiscordMessageID}`);
	} catch(err) {
		console.warn(`MAP ERROR:\n${err}`)
	}
}

/**
 * Will return an array of all maps. Selecting textOnly will return one or no results only
 * @async
 * @param {string} SlackMessageID The id of a Slack message to look up
 * @param {boolean} [textOnly = false] Whether to filter out message maps that don't contain the main text content
 * @param {boolean} [messageLookup = true] Whether to automatically look up the message map on Discord
 * @return {Object[]|Discord.Message[]} Array of maps or Discord message objects. May be empty
 */
async function locateMessageMaps(SlackMessageID, textOnly = false, messageLookup = true) {
	const maps = (await databaseManager.locateMessageMaps(SlackMessageID));

	if(maps.length === 0) {
		console.warn(`Message Map(s) Not Found For [${SlackMessageID}]`);
	}

	// TODO: Improve error handling
	const results = maps
		.filter(map => !textOnly || map["PurelyText"])
		.map(map => !messageLookup ? map : messageMapToMessage(map).catch(err => {
			console.warn(`Unable To Find Discord Message For ${map.DiscordMessageID} (${map.SlackMessageID})`);
			return false;
		}));

	if(results.length === 0) {
		console.warn(`Message Map(s) Not Found For [${SlackMessageID}] [TEXT ONLY]`);
	}

	return (await Promise.all(results)).filter(result => result !== false);
}

/**
 * Converts a Message Map into a Discord Message object
 * @async
 * @param {Object} map Message map to look up
 * @return {Message|undefined} Resolves to the located message or nothing if not found
 */
async function messageMapToMessage(map) {
	if(!map) {
		return;
	}

	let channel = await guild.channels.fetch(map.DiscordChannelID);
	if(map.DiscordThreadID !== "Main") {
		channel = await channel.threads.fetch(map.DiscordThreadID);
		if(channel.archived) {
			await channel.setArchived(false, "Unarchived Thread for Incoming Slack Events");
		}
	}
	return channel.messages.fetch(map.DiscordMessageID);
}