""" Contains classes used for utilities. *Functions* ----------- Sanitize(data: str, lowerCaseValue: bool = false) -> dict *Classes* --------- Options() Credentials() DatabaseFuncs() """ import re # Used in getID import git # Used to pull when stopping import os # Used to test if files exist import json # Used to read the data about addmovie/addshow import time # Used to test how long it's been since commands were synced import discord # Used for type hints def sanitize(data: str, lowerCaseValue: bool = False): """ Sanitize and create a dictionary from a string. Each element is created from a line with a : in it. The key is left of the :, the value is right of it. *Parameters* ------------ data: str The string to create a dict from. lowerCaseValue: bool = False Whether the value of each element should be lowercase. *Returns* --------- dct: dict The sanitized dictionary of elements. """ data = data.splitlines() dct = {} for line in data: if line[0] != "#" and ":" in line: lineValues = line.split(":") lineValues[0] = lineValues[0].lower() lineValues[1] = lineValues[1].replace(" ", "") if lowerCaseValue: lineValues[1] = lineValues[1].lower() if lineValues[0] in ["testing guild ids", "admins"]: lineValues[1] = lineValues[1].split(",") if all(i.isnumeric() for i in lineValues[1]): lineValues[1] = [int(i) for i in lineValues[1]] if any(i == lineValues[1] for i in ["true", "false"]): lineValues[1] = (lineValues[1] == "true") dct[lineValues[0]] = lineValues[1] return dct class Options(): """Contains the options for the bot.""" def __init__(self): """Initialize the options.""" with open("options.txt", "r") as f: data = sanitize(f.read(), True) self.testing = data["testing"] self.guildIds = data["testing guild ids"] self.admins = data["admins"] class Credentials(): """Contains the credentials for the bot and apis.""" def __init__(self): """Initialize the credentials.""" with open("credentials.txt", "r") as f: data = sanitize(f.read()) self.token = data["bot token"] self.finnhubKey = data["finnhub api key"] self.wordnikKey = data["wordnik api key"] self.mongoDBUser = data["mongodb user"] self.mongoDBPassword = data["mongodb password"] self.wolfKey = data["wolframalpha appid"] self.radarrKey = data["radarr api key"] self.sonarrKey = data["sonarr api key"] class databaseFuncs(): """ Manages database functions. *Methods* --------- getName(userID: str) -> str getID(userName: str) -> str deleteGame(gameType: str, channel: str) wipeGames() connectFourReactionTest(message: discord.Message, user: discord.User) -> bool hangmanReactionTest(message: discord.Message, user: discord.User) -> bool BedreNetflixReactionTest(message: discord.Message, user: discord.User) -> bool, bool, list syncCommands() """ def __init__(self, bot): """Initialize the class.""" self.bot = bot def getName(self, userID: str): """ Get the name of a user you have the # id of. *Parameters: ------------ userID: str The id of the user you want the name of. The format is "#" + str(discord.User.id) *Returns* --------- userName: str The name of the user. If the user couldn't be found, returns the userID. """ user = self.bot.database["users"].find_one({"_id": userID}) if userID == f"#{self.bot.user.id}": return "Gwendolyn" elif user is not None: return user["user name"] else: self.bot.log(f"Couldn't find user {userID}") return userID def getID(self, userName: str): """ Get the id of a user you have the username of. *Parameters: ------------ userName: str The name of the user you want the id of. *Returns* --------- userID: str The id of the user in the format "#" + str(discord.User.id). If the user couldn't be found, returns the userName. """ userSearch = {"user name": re.compile(userName, re.IGNORECASE)} user = self.bot.database["users"].find_one(userSearch) if user is not None: return user["_id"] else: self.bot.log("Couldn't find user "+userName) return None def deleteGame(self, gameType: str, channel: str): """ Remove a game from the database. *Parameters* ------------ gameType: str The name of the collection the game is in, like "hangman games", "blackjack games" etc. channel: str The channel id of the channel the game is on as a string. """ self.bot.database[gameType].delete_one({"_id": channel}) def wipeGames(self): """Delete all running games and pull from git.""" self.bot.database["trivia questions"].delete_many({}) self.bot.database["blackjack games"].delete_many({}) self.bot.database["connect 4 games"].delete_many({}) self.bot.database["hangman games"].delete_many({}) self.bot.database["hex games"].delete_many({}) if not self.bot.options.testing: g = git.cmd.Git("") g.pull() def connectFourReactionTest(self, message: discord.Message, user: discord.User): """ Test if the given message is the current connect four game. Also tests if the given user is the one who's turn it is. *Parameters* ------------ message: discord.Message The message to test. user: discord.User The user to test. *Returns* --------- : bool Whether the given message is the current connect four game and if the user who reacted is the user who's turn it is. """ channel = message.channel channelSearch = {"_id": str(channel.id)} game = self.bot.database["connect 4 games"].find_one(channelSearch) filePath = f"resources/games/oldImages/connectFour{channel.id}" with open(filePath, "r") as f: oldImage = int(f.read()) if message.id == oldImage: self.bot.log("They reacted to the connectFour game") turn = game["turn"] if user == game["players"][turn]: return True else: self.bot.log("It wasn't their turn") return False else: return False def hangmanReactionTest(self, message: discord.Message, user: discord.User): """ Test if the given message is the current hangman game. Also tests if the given user is the one who's playing hangman. *Parameters* ------------ message: discord.Message The message to test. user: discord.User The user to test. *Returns* --------- : bool Whether the given message is the current hangman game and if the user who reacted is the user who's playing hangman. """ channel = message.channel filePath = f"resources/games/oldImages/hangman{channel.id}" if os.path.isfile(filePath): with open(filePath, "r") as f: oldMessages = f.read().splitlines() else: return False gameMessage = False for oldMessage in oldMessages: oldMessageID = int(oldMessage) if message.id == oldMessageID: database = self.bot.database["hangman games"] channelSearch = {"_id": str(channel.id)} game = database.find_one(channelSearch) if user == game["player"]: gameMessage = True break return gameMessage def bedreNetflixReactionTest(self, message: discord.Message): """ Test if the given message is the response to a plex request. *Parameters* ------------ message: discord.Message The message to test. *Returns* --------- : bool Whether the message is the response to a plex request. : bool Whether it was a movie request (false for a show request) : list A list of ids or names of the shows or movies that Gwendolyn presented after the request. """ channel = message.channel filePath = f"resources/bedreNetflix/oldMessage{str(channel.id)}" if os.path.isfile(filePath): with open(filePath, "r") as f: data = json.load(f) else: return False, None, None if data["messageID"] == message.id: if "imdbIds" in data: return True, True, data["imdbIds"] else: return True, False, data["imdbNames"] else: return False, None, None async def syncCommands(self): """Sync the slash commands with the discord API.""" collection = self.bot.database["last synced"] lastSynced = collection.find_one() now = time.time() if lastSynced["last synced"] < now - 86400: slashCommandList = await self.bot.slash.to_dict() self.bot.log(f"Updating commands: {slashCommandList}") await self.bot.slash.sync_all_commands() idNumber = lastSynced["_id"] queryFilter = {"_id": idNumber} update = {"$set": {"last synced": now}} collection.update_one(queryFilter, update)