From 06b5d881ea0c224f407a3c40a2d23d8ae767851b Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Thu, 15 Apr 2021 15:16:56 +0200 Subject: [PATCH] :twisted_rightwards_arrows: Merging with master --- .gitignore | 4 +- Gwendolyn.py | 137 +++++++++++++++------ cogs/EventCog.py | 20 ++-- cogs/GameCogs.py | 112 ++++++++++------- cogs/LookupCog.py | 18 ++- cogs/MiscCog.py | 53 +++++---- cogs/StarWarsCog.py | 31 ++--- resources/longStrings.json | 4 + utils/__init__.py | 9 +- utils/eventHandlers.py | 184 ++++++++++++++++++---------- utils/helperClasses.py | 238 ++++++++++++++++++++++++++++++++----- utils/utilFunctions.py | 178 ++++++++++++++++++++++----- 12 files changed, 729 insertions(+), 259 deletions(-) create mode 100644 resources/longStrings.json diff --git a/.gitignore b/.gitignore index aa5010b..e10cdc3 100644 --- a/.gitignore +++ b/.gitignore @@ -153,11 +153,11 @@ static token.txt credentials.txt options.txt -resources/starWars/destinyPoints.txt +resources/star_wars/destinyPoints.txt resources/bedreNetflix/ resources/games/hilo/ resources/games/blackjackTables/ -resources/games/oldImages/ +resources/games/old_images/ resources/games/connect4Boards/ resources/games/hexBoards/ resources/games/hangmanBoards/ diff --git a/Gwendolyn.py b/Gwendolyn.py index 5a48420..20342c5 100644 --- a/Gwendolyn.py +++ b/Gwendolyn.py @@ -1,63 +1,138 @@ -import os, finnhub, platform, asyncio, discord +""" +Contains the Gwendolyn class, and runs it when run as script. -from discord.ext import commands -from discord_slash import SlashCommand -from pymongo import MongoClient +*Classes* +--------- + Gwendolyn(discord.ext.commands.Bot) +""" +import os # Used for loading cogs in Gwendolyn.addCogs +import finnhub # Used to add a finhub client to the bot +import platform # Used to test if the bot is running on windows, in +# order to fix a bug with asyncio +import asyncio # used to set change the loop policy if the bot is +# running on windows +import discord # Used for discord.Intents and discord.Status +import discord_slash # Used to initialized SlashCommands object + +from discord.ext import commands # Used to inherit from commands.bot +from pymongo import MongoClient # Used for database management from funcs import Money, StarWars, Games, Other, LookupFuncs -from utils import Options, Credentials, logThis, makeFiles, databaseFuncs, EventHandler, ErrorHandler +from utils import (Options, Credentials, logThis, makeFiles, databaseFuncs, + EventHandler, ErrorHandler, longStrings) + class Gwendolyn(commands.Bot): + """ + A multifunctional Discord bot. + + *Methods* + --------- + log(messages: Union[str, list], channel: str = "", + level: int = 20) + stop(ctx: discord_slash.context.SlashContext) + defer(ctx: discord_slash.context.SlashContext) + """ + def __init__(self): + """Initialize the bot.""" + intents = discord.Intents.default() + intents.members = True + initParams = { + "command_prefix": " ", + "case_insensitive": True, + "intents": intents, + "status": discord.Status.dnd + } + super().__init__(**initParams) + + self._addClientsAndOptions() + self._addUtilClasses() + self._addFunctionContainers() + self._addCogs() + + def _addClientsAndOptions(self): + """Add all the client, option and credentials objects.""" + self.longStrings = longStrings() self.options = Options() self.credentials = Credentials() - self.finnhubClient = finnhub.Client(api_key = self.credentials.finnhubKey) - self.MongoClient = MongoClient(f"mongodb+srv://{self.credentials.mongoDBUser}:{self.credentials.mongoDBPassword}@gwendolyn.qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority") + finnhubKey = self.credentials.finnhubKey + self.finnhubClient = finnhub.Client(api_key=finnhubKey) + mongoUser = self.credentials.mongoDBUser + mongoPassword = self.credentials.mongoDBPassword + mongoLink = f"mongodb+srv://{mongoUser}:{mongoPassword}@gwendolyn" + mongoLink += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority" + dataBaseClient = MongoClient(mongoLink) if self.options.testing: self.log("Testing mode") - self.database = self.MongoClient["Gwendolyn-Test"] + self.database = dataBaseClient["Gwendolyn-Test"] else: - self.database = self.MongoClient["Gwendolyn"] + self.database = dataBaseClient["Gwendolyn"] + def _addUtilClasses(self): + """Add all the classes used as utility.""" + self.databaseFuncs = databaseFuncs(self) + self.eventHandler = EventHandler(self) + self.errorHandler = ErrorHandler(self) + slashParams = { + "sync_commands": True, + "sync_on_cog_reload": True, + "override_type": True + } + self.slash = discord_slash.SlashCommand(self, **slashParams) + + def _addFunctionContainers(self): + """Add all the function containers used for commands.""" self.starWars = StarWars(self) self.other = Other(self) self.lookupFuncs = LookupFuncs(self) self.games = Games(self) self.money = Money(self) - self.databaseFuncs = databaseFuncs(self) - self.eventHandler = EventHandler(self) - self.errorHandler = ErrorHandler(self) - intents = discord.Intents.default() - intents.members = True + def _addCogs(self): + """Load cogs.""" + for filename in os.listdir("./cogs"): + if filename.endswith(".py"): + self.load_extension(f"cogs.{filename[:-3]}") - super().__init__(command_prefix=" ", case_insensitive=True, intents = intents, status = discord.Status.dnd) - - def log(self, messages, channel : str = "", level : int = 20): + def log(self, messages, channel: str = "", level: int = 20): + """Log a message. Described in utils/utilFunctions.py.""" logThis(messages, channel, level) - async def stop(self, ctx): + async def stop(self, ctx: discord_slash.context.SlashContext): + """ + Stop the bot, and stop running games. + + Only stops the bot if the user in ctx.author is one of the + admins given in options.txt. + + *parameters* + ------------ + ctx: discord_slash.context.SlashContext + The context of the "/stop" slash command. + """ if f"#{ctx.author.id}" in self.options.admins: await ctx.send("Pulling git repo and restarting...") - await self.change_presence(status = discord.Status.offline) + await self.change_presence(status=discord.Status.offline) - self.databaseFuncs.stopServer() + self.databaseFuncs.wipeGames() - self.log("Logging out", level = 25) + self.log("Logging out", level=25) await self.close() else: - self.log(f"{ctx.author.display_name} tried to stop me! (error code 201)",str(ctx.channel_id)) - await ctx.send(f"I don't think I will, {ctx.author.display_name} (error code 201)") + logMessage = f"{ctx.author.display_name} tried to stop me!" + self.log(logMessage, str(ctx.channel_id)) + await ctx.send(f"I don't think I will, {ctx.author.display_name}") - async def defer(self, ctx): + async def defer(self, ctx: discord_slash.context.SlashContext): + """Send a "Gwendolyn is thinking" message to the user.""" try: await ctx.defer() - except: + except discord_slash.error.AlreadyResponded: self.log("defer failed") - if __name__ == "__main__": if platform.system() == "Windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @@ -67,15 +142,9 @@ if __name__ == "__main__": # Creates the Bot bot = Gwendolyn() - bot.slash = SlashCommand(bot, sync_commands=True, sync_on_cog_reload=True, override_type=True) - - #Loads cogs - for filename in os.listdir("./cogs"): - if filename.endswith(".py"): - bot.load_extension(f"cogs.{filename[:-3]}") try: # Runs the whole shabang bot.run(bot.credentials.token) - except: - bot.log("Could not log in. Remember to write your bot token in the credentials.txt file") \ No newline at end of file + except Exception: + bot.log(bot.longStrings["Can't log in"]) diff --git a/cogs/EventCog.py b/cogs/EventCog.py index 7b72f6b..50fc3b9 100644 --- a/cogs/EventCog.py +++ b/cogs/EventCog.py @@ -1,34 +1,40 @@ -from discord.ext import commands +"""Contains the EventCog, which runs code for specific bot events.""" +from discord.ext import commands # Has the cog class + class EventCog(commands.Cog): + """Handles bot events.""" + def __init__(self, bot): + """Initialize the bot.""" self.bot = bot self.bot.on_error = self.on_error - # Syncs commands, sets the game, and logs when the bot logs in @commands.Cog.listener() async def on_ready(self): + """Log and set bot status when bot logs in.""" await self.bot.eventHandler.on_ready() - # Logs when user sends a command @commands.Cog.listener() async def on_slash_command(self, ctx): + """Log when a slash command is run.""" await self.bot.eventHandler.on_slash_command(ctx) - # Logs if a command experiences an error @commands.Cog.listener() async def on_slash_command_error(self, ctx, error): + """Log when a slash error occurs.""" await self.bot.errorHandler.on_slash_command_error(ctx, error) - # Logs if on error occurs async def on_error(self, method, *args, **kwargs): + """Log when an error occurs.""" await self.bot.errorHandler.on_error(method) - # If someone reacted to a message, checks if it's a reaction it's - # Gwendolyn has been waiting for, and then does something @commands.Cog.listener() async def on_reaction_add(self, reaction, user): + """Handle when someone reacts to a message.""" await self.bot.eventHandler.on_reaction_add(reaction, user) + def setup(bot): + """Add the eventcog to the bot.""" bot.add_cog(EventCog(bot)) diff --git a/cogs/GameCogs.py b/cogs/GameCogs.py index 4714437..c813e24 100644 --- a/cogs/GameCogs.py +++ b/cogs/GameCogs.py @@ -1,155 +1,177 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains all the cogs that deal with game commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class GamesCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains miscellaneous game commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Checks user balance @cog_ext.cog_slash(**params["balance"]) async def balance(self, ctx): + """Check user balance.""" await self.bot.money.sendBalance(ctx) - # Gives another user an amount of GwendoBucks @cog_ext.cog_slash(**params["give"]) async def give(self, ctx, user, amount): + """Give another user an amount of GwendoBucks.""" await self.bot.money.giveMoney(ctx, user, amount) - # Invest GwendoBucks in the stock market @cog_ext.cog_slash(**params["invest"]) - async def invest(self, ctx, parameters = "check"): + async def invest(self, ctx, parameters="check"): + """Invest GwendoBucks in the stock market.""" await self.bot.games.invest.parseInvest(ctx, parameters) - # Runs a game of trivia @cog_ext.cog_slash(**params["trivia"]) - async def trivia(self, ctx, answer = ""): + async def trivia(self, ctx, answer=""): + """Run a game of trivia.""" await self.bot.games.trivia.triviaParse(ctx, answer) class BlackjackCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains the blackjack commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of blackjack @cog_ext.cog_subcommand(**params["blackjackStart"]) async def blackjackStart(self, ctx): + """Start a game of blackjack.""" await self.bot.games.blackjack.start(ctx) @cog_ext.cog_subcommand(**params["blackjackBet"]) async def blackjackBet(self, ctx, bet): + """Enter the game of blackjack with a bet.""" await self.bot.games.blackjack.playerDrawHand(ctx, bet) @cog_ext.cog_subcommand(**params["blackjackStand"]) - async def blackjackStand(self, ctx, hand = ""): + async def blackjackStand(self, ctx, hand=""): + """Stand on your hand in blackjack.""" await self.bot.games.blackjack.stand(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHit"]) - async def blackjackHit(self, ctx, hand = 0): + async def blackjackHit(self, ctx, hand=0): + """Hit on your hand in blackjack.""" await self.bot.games.blackjack.hit(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackDouble"]) - async def blackjackDouble(self, ctx, hand = 0): + async def blackjackDouble(self, ctx, hand=0): + """Double in blackjack.""" await self.bot.games.blackjack.double(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackSplit"]) - async def blackjackSplit(self, ctx, hand = 0): + async def blackjackSplit(self, ctx, hand=0): + """Split your hand in blackjack.""" await self.bot.games.blackjack.split(ctx, hand) @cog_ext.cog_subcommand(**params["blackjackHilo"]) async def blackjackHilo(self, ctx): + """Get the hilo value for the deck in blackjack.""" await self.bot.games.blackjack.hilo(ctx) @cog_ext.cog_subcommand(**params["blackjackShuffle"]) async def blackjackShuffle(self, ctx): + """Shuffle the blackjack game.""" await self.bot.games.blackjack.shuffle(ctx) @cog_ext.cog_subcommand(**params["blackjackCards"]) async def blackjackCards(self, ctx): + """Get the amount of cards left in the blackjack deck.""" await self.bot.games.blackjack.cards(ctx) class ConnectFourCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the connect four commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Start a game of connect four against a user @cog_ext.cog_subcommand(**params["connectFourStartUser"]) async def connectFourStartUser(self, ctx, user): + """Start a game of connect four against another user.""" await self.bot.games.connectFour.start(ctx, user) - # Start a game of connect four against gwendolyn @cog_ext.cog_subcommand(**params["connectFourStartGwendolyn"]) - async def connectFourStartGwendolyn(self, ctx, difficulty = 3): + async def connectFourStartGwendolyn(self, ctx, difficulty=3): + """Start a game of connect four against Gwendolyn.""" await self.bot.games.connectFour.start(ctx, difficulty) - # Stop the current game of connect four @cog_ext.cog_subcommand(**params["connectFourSurrender"]) async def connectFourSurrender(self, ctx): + """Surrender the game of connect four.""" await self.bot.games.connectFour.surrender(ctx) class HangmanCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" + """Contains all the hangman commands.""" + + def __init__(self, bot): + """Initialize the cog.""" self.bot = bot - # Starts a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStart"]) async def hangmanStart(self, ctx): + """Start a game of hangman.""" await self.bot.games.hangman.start(ctx) - # Stops a game of Hangman @cog_ext.cog_subcommand(**params["hangmanStop"]) async def hangmanStop(self, ctx): + """Stop the current game of hangman.""" await self.bot.games.hangman.stop(ctx) class HexCog(commands.Cog): - def __init__(self,bot): - """Runs game stuff.""" - self.bot = bot + """Contains all the hex commands.""" + + def __init__(self, bot): + """Initialize the cog.""" + self.bot = bot + self.hex = self.bot.games.hex - # Start a game of Hex against another user @cog_ext.cog_subcommand(**params["hexStartUser"]) async def hexStartUser(self, ctx, user): - await self.bot.games.hex.start(ctx, user) + """Start a game of hex against another player.""" + await self.hex.start(ctx, user) - # Start a game of Hex against Gwendolyn @cog_ext.cog_subcommand(**params["hexStartGwendolyn"]) - async def hexStartGwendolyn(self, ctx, difficulty = 2): - await self.bot.games.hex.start(ctx, difficulty) + async def hexStartGwendolyn(self, ctx, difficulty=2): + """Start a game of hex against Gwendolyn.""" + await self.hex.start(ctx, difficulty) - # Place a piece in the hex game @cog_ext.cog_subcommand(**params["hexPlace"]) async def hexPlace(self, ctx, coordinates): - await self.bot.games.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") + """Place a piece in the hex game.""" + await self.hex.placeHex(ctx, coordinates, f"#{ctx.author.id}") - # Undo your last hex move @cog_ext.cog_subcommand(**params["hexUndo"]) async def hexUndo(self, ctx): - await self.bot.games.hex.undo(ctx) + """Undo your last hex move.""" + await self.hex.undo(ctx) - # Perform a hex swap @cog_ext.cog_subcommand(**params["hexSwap"]) async def hexSwap(self, ctx): - await self.bot.games.hex.swap(ctx) + """Perform a hex swap.""" + await self.hex.swap(ctx) - # Surrender the hex game @cog_ext.cog_subcommand(**params["hexSurrender"]) async def hexSurrender(self, ctx): - await self.bot.games.hex.surrender(ctx) + """Surrender the hex game.""" + await self.hex.surrender(ctx) def setup(bot): + """Add all the cogs to the bot.""" bot.add_cog(GamesCog(bot)) bot.add_cog(BlackjackCog(bot)) bot.add_cog(ConnectFourCog(bot)) bot.add_cog(HangmanCog(bot)) - bot.add_cog(HexCog(bot)) \ No newline at end of file + bot.add_cog(HexCog(bot)) diff --git a/cogs/LookupCog.py b/cogs/LookupCog.py index 1b23cd1..f41d38b 100644 --- a/cogs/LookupCog.py +++ b/cogs/LookupCog.py @@ -1,24 +1,32 @@ -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the LookupCog, which deals with the lookup commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() + class LookupCog(commands.Cog): + """Contains the lookup commands.""" + def __init__(self, bot): - """Runs lookup commands.""" + """Initialize the cog.""" self.bot = bot # Looks up a spell @cog_ext.cog_slash(**params["spell"]) async def spell(self, ctx, query): + """Look up a spell.""" await self.bot.lookupFuncs.spellFunc(ctx, query) # Looks up a monster @cog_ext.cog_slash(**params["monster"]) async def monster(self, ctx, query): + """Look up a monster.""" await self.bot.lookupFuncs.monsterFunc(ctx, query) + def setup(bot): - bot.add_cog(LookupCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(LookupCog(bot)) diff --git a/cogs/MiscCog.py b/cogs/MiscCog.py index 136c0fb..ab6e146 100644 --- a/cogs/MiscCog.py +++ b/cogs/MiscCog.py @@ -1,94 +1,99 @@ -import discord, codecs, string, json -from discord.ext import commands -from discord_slash import cog_ext +"""Contains the MiscCog, which deals with miscellaneous commands.""" +from discord.ext import commands # Has the cog class +from discord_slash import cog_ext # Used for slash commands -from utils import getParams # pylint: disable=import-error +from utils import getParams # pylint: disable=import-error params = getParams() + class MiscCog(commands.Cog): + """Contains the miscellaneous commands.""" + def __init__(self, bot): - """Runs misc commands.""" + """Initialize the cog.""" self.bot = bot self.bot.remove_command("help") self.generators = bot.other.generators self.bedreNetflix = bot.other.bedreNetflix self.nerdShit = bot.other.nerdShit - # Sends the bot's latency @cog_ext.cog_slash(**params["ping"]) async def ping(self, ctx): + """Send the bot's latency.""" await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") - # Restarts the bot @cog_ext.cog_slash(**params["stop"]) async def stop(self, ctx): + """Stop the bot.""" await self.bot.stop(ctx) - # Gets help for specific command @cog_ext.cog_slash(**params["help"]) - async def helpCommand(self, ctx, command = ""): + async def helpCommand(self, ctx, command=""): + """Get help for commands.""" await self.bot.other.helpFunc(ctx, command) - # Lets you thank the bot @cog_ext.cog_slash(**params["thank"]) async def thank(self, ctx): + """Thank the bot.""" await ctx.send("You're welcome :blush:") - # Sends a friendly message @cog_ext.cog_slash(**params["hello"]) async def hello(self, ctx): + """Greet the bot.""" await self.bot.other.helloFunc(ctx) - # Rolls dice @cog_ext.cog_slash(**params["roll"]) - async def roll(self, ctx, dice = "1d20"): + async def roll(self, ctx, dice="1d20"): + """Roll dice.""" await self.bot.other.rollDice(ctx, dice) - # Sends a random image @cog_ext.cog_slash(**params["image"]) async def image(self, ctx): + """Get a random image from Bing.""" await self.bot.other.imageFunc(ctx) - # Finds a random movie @cog_ext.cog_slash(**params["movie"]) async def movie(self, ctx): + """Get a random movie from the Plex server.""" await self.bot.other.movieFunc(ctx) - # Generates a random name @cog_ext.cog_slash(**params["name"]) async def name(self, ctx): + """Generate a random name.""" await self.generators.nameGen(ctx) - # Generates a random tavern name @cog_ext.cog_slash(**params["tavern"]) async def tavern(self, ctx): + """Generate a random tavern name.""" await self.generators.tavernGen(ctx) - # Finds a page on the Senkulpa wiki @cog_ext.cog_slash(**params["wiki"]) - async def wiki(self, ctx, wikiPage = ""): + async def wiki(self, ctx, wikiPage=""): + """Get a page on a fandom wiki.""" await self.bot.other.findWikiPage(ctx, wikiPage) - #Searches for movie and adds it to Bedre Netflix @cog_ext.cog_slash(**params["addMovie"]) async def addMovie(self, ctx, movie): + """Search for a movie and add it to the Plex server.""" await self.bedreNetflix.requestMovie(ctx, movie) - #Searches for show and adds it to Bedre Netflix @cog_ext.cog_slash(**params["addShow"]) async def addShow(self, ctx, show): + """Search for a show and add it to the Plex server.""" await self.bedreNetflix.requestShow(ctx, show) - #Returns currently downloading torrents @cog_ext.cog_slash(**params["downloading"]) - async def downloading(self, ctx, parameters = "-d"): + async def downloading(self, ctx, parameters="-d"): + """Get the current downloading torrents.""" await self.bedreNetflix.downloading(ctx, parameters) - #Looks up on Wolfram Alpha @cog_ext.cog_slash(**params["wolf"]) async def wolf(self, ctx, query): + """Perform a search on Wolfram Alpha.""" await self.nerdShit.wolfSearch(ctx, query) + def setup(bot): + """Add the cog to the bot.""" bot.add_cog(MiscCog(bot)) diff --git a/cogs/StarWarsCog.py b/cogs/StarWarsCog.py index 57e39f0..72516d4 100644 --- a/cogs/StarWarsCog.py +++ b/cogs/StarWarsCog.py @@ -1,37 +1,40 @@ -import discord, string, json +"""Contains the StarWarsCog, which deals with Star Wars commands.""" from discord.ext import commands from discord_slash import cog_ext -from utils import getParams +from utils import getParams # pylint: disable=import-error params = getParams() -class starWarsCog(commands.Cog): + +class StarWarsCog(commands.Cog): + """Contains the Star Wars commands.""" def __init__(self, bot): - """Runs star wars commands.""" + """Initialize the cog.""" self.bot = bot - # Rolls star wars dice @cog_ext.cog_slash(**params["starWarsRoll"]) - async def starWarsRoll(self, ctx, dice = ""): + async def starWarsRoll(self, ctx, dice=""): + """Roll Star Wars dice.""" await self.bot.starWars.roll.parseRoll(ctx, dice) - # Controls destiny points @cog_ext.cog_slash(**params["starWarsDestiny"]) - async def starWarsDestiny(self, ctx, parameters = ""): + async def starWarsDestiny(self, ctx, parameters=""): + """Control Star Wars destiny points.""" await self.bot.starWars.destiny.parseDestiny(ctx, parameters) - # Rolls for critical injuries @cog_ext.cog_slash(**params["starWarsCrit"]) - async def starWarsCrit(self, ctx, severity : int = 0): + async def starWarsCrit(self, ctx, severity: int = 0): + """Roll for critical injuries.""" await self.bot.starWars.roll.critRoll(ctx, severity) - # Accesses and changes character sheet data with the parseChar function - # from funcs/starWarsFuncs/starWarsCharacter.py @cog_ext.cog_slash(**params["starWarsCharacter"]) - async def starWarsCharacter(self, ctx, parameters = ""): + async def starWarsCharacter(self, ctx, parameters=""): + """Access and change Star Wars character sheet data.""" await self.bot.starWars.character.parseChar(ctx, parameters) + def setup(bot): - bot.add_cog(starWarsCog(bot)) \ No newline at end of file + """Add the cog to the bot.""" + bot.add_cog(StarWarsCog(bot)) diff --git a/resources/longStrings.json b/resources/longStrings.json new file mode 100644 index 0000000..a7b418f --- /dev/null +++ b/resources/longStrings.json @@ -0,0 +1,4 @@ +{ + "missing parameters" : "Missing command parameters. Try using `!help [command]` to find out how to use the command.", + "Can't log in": "Could not log in. Remember to write your bot token in the credentials.txt file" +} \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 7d2a46c..43fa506 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,7 +1,10 @@ -"""A collections of utilities used by Gwendolyn and her functions""" +"""A collections of utilities used by Gwendolyn and her functions.""" -__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", "ErrorHandler", "getParams", "logThis", "cap", "makeFiles", "replaceMultiple", "emojiToCommand"] +__all__ = ["Options", "Credentials", "databaseFuncs", "EventHandler", + "ErrorHandler", "getParams", "logThis", "cap", "makeFiles", + "replaceMultiple", "emojiToCommand"] from .helperClasses import Options, Credentials, databaseFuncs from .eventHandlers import EventHandler, ErrorHandler -from .utilFunctions import getParams, logThis, cap, makeFiles, replaceMultiple, emojiToCommand \ No newline at end of file +from .utilFunctions import (getParams, logThis, cap, makeFiles, + replaceMultiple, emojiToCommand, longStrings) diff --git a/utils/eventHandlers.py b/utils/eventHandlers.py index 491846b..1ec9810 100644 --- a/utils/eventHandlers.py +++ b/utils/eventHandlers.py @@ -1,13 +1,52 @@ -import discord, traceback, discord_slash, sys -from discord.ext import commands +""" +Classes used to handle bot events and errors. + +*Classes* +--------- + EventHandler + ErrorHandler +""" +import discord # Used to init discord.Game and discord.Status, as well +# as compare errors to discord errors and as typehints +import traceback # Used to get the traceback of errors +import sys # Used to get traceback when the specific error is not +# available +from discord.ext import commands # Used to compare errors with command +# errors + +from discord_slash.context import SlashContext +from utils.utilFunctions import emojiToCommand -from .utilFunctions import emojiToCommand class EventHandler(): + """ + Handles bot events. + + *Methods* + --------- + on_ready() + on_slash_command(ctx: discord_slash.context.SlashContext) + on_reaction_add(ctx: discord_slash.context.SlashContext) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command(self, ctx): + async def on_ready(self): + """Log and sets status when it logs in.""" + await self.bot.databaseFuncs.syncCommands() + name = self.bot.user.name + userid = str(self.bot.user.id) + loggedInMessage = f"Logged in as {name}, {userid}" + self.bot.log(loggedInMessage, level=25) + game = discord.Game("Use /help for commands") + + onlineStatus = discord.Status.online + await self.bot.change_presence(activity=game, status=onlineStatus) + + async def on_slash_command(self, ctx: SlashContext): + """Log when a slash command is given.""" if ctx.subcommand_name is not None: subcommand = f" {ctx.subcommand_name} " else: @@ -21,100 +60,117 @@ class EventHandler(): args = " ".join([str(i) for i in ctx.args]) fullCommand = f"/{ctx.command}{subcommand}{subcommandGroup}{args}" logMessage = f"{ctx.author.display_name} ran {fullCommand}" - self.bot.log(logMessage, str(ctx.channel_id), level = 25) + self.bot.log(logMessage, str(ctx.channel_id), level=25) - async def on_ready(self): - await self.bot.databaseFuncs.syncCommands() - self.bot.log("Logged in as "+self.bot.user.name+", "+str(self.bot.user.id), level = 25) - game = discord.Game("Use /help for commands") - await self.bot.change_presence(activity=game, status = discord.Status.online) - - async def on_reaction_add(self, reaction : discord.Reaction, user): - if user.bot == False: + async def on_reaction_add(self, reaction: discord.Reaction, + user: discord.User): + """Take action if the reaction is on a command message.""" + if not user.bot: + tests = self.bot.databaseFuncs message = reaction.message channel = message.channel - self.bot.log(f"{user.display_name} reacted to a message",str(channel.id)) - try: - connectFourTheirTurn, piece = self.bot.databaseFuncs.connectFourReactionTest(channel,message,"#"+str(user.id)) - except: - connectFourTheirTurn = False + reactedMessage = f"{user.display_name} reacted to a message" + self.bot.log(reactedMessage, str(channel.id)) + plexData = tests.bedreNetflixReactionTest(message) + # plexData is a list containing 3 elements: whether it was + # the addshow/addmovie command message the reaction was to + # (bool), whether it's a movie (bool) (if false, it's a + # show), and the imdb ids/names for the for the movies or + # shows listed in the message (list). - bedreNetflixMessage, addMovie, imdbIds = self.bot.databaseFuncs.bedreNetflixReactionTest(channel, message) + reactionTestParams = [message, f"#{str(user.id)}"] - if connectFourTheirTurn: + if tests.connectFourReactionTest(*reactionTestParams): column = emojiToCommand(reaction.emoji) - await self.bot.games.connectFour.placePiece(message, f"#{user.id}", column-1) - elif bedreNetflixMessage and addMovie: - moviePick = emojiToCommand(reaction.emoji) - if moviePick == "none": - imdbID = None - else: - imdbID = imdbIds[moviePick-1] + params = [message, f"#{user.id}", column-1] + await self.bot.games.connectFour.placePiece(*params) - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addMovie(message, imdbID, False) - else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addMovie(message, imdbID) - elif bedreNetflixMessage and not addMovie: - showPick = emojiToCommand(reaction.emoji) - if showPick == "none": - imdbName = None - else: - imdbName = imdbIds[showPick-1] + if plexData[0]: + plexFuncs = self.bot.other.bedreNetflix + if plexData[1]: + moviePick = emojiToCommand(reaction.emoji) + if moviePick == "none": + imdbID = None + else: + imdbID = plexData[2][moviePick-1] - if isinstance(channel, discord.DMChannel): - await message.delete() - await self.bot.other.bedreNetflix.addShow(message, imdbName, False) + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addMovie(message, imdbID, False) + else: + await message.clear_reactions() + await plexFuncs.addMovie(message, imdbID) else: - await message.clear_reactions() - await self.bot.other.bedreNetflix.addShow(message, imdbName) + showPick = emojiToCommand(reaction.emoji) + if showPick == "none": + imdbName = None + else: + imdbName = plexData[2][showPick-1] - elif self.bot.databaseFuncs.hangmanReactionTest(channel, message, f"#{user.id}"): + if isinstance(channel, discord.DMChannel): + await message.delete() + await plexFuncs.addShow(message, imdbName, False) + else: + await message.clear_reactions() + await plexFuncs.addShow(message, imdbName) + + elif tests.hangmanReactionTest(*reactionTestParams): self.bot.log("They reacted to the hangman message") - if ord(reaction.emoji) in range(127462,127488): + if ord(reaction.emoji) in range(127462, 127488): + # The range is letter-emojis guess = chr(ord(reaction.emoji)-127397) - await self.bot.games.hangman.guess(message, f"#{user.id}", guess) + # Converts emoji to letter + params = [message, f"#{user.id}", guess] + await self.bot.games.hangman.guess(*params) else: self.bot.log("Bot they didn't react with a valid guess") + class ErrorHandler(): + """ + Handles errors. + + *Methods* + --------- + on_slash_command_error(ctx: discord_slash.context.SlashContext, + error: Exception) + on_error(method: str) + """ + def __init__(self, bot): + """Initialize the handler.""" self.bot = bot - async def on_slash_command_error(self, ctx, error): + async def on_slash_command_error(self, ctx: SlashContext, + error: Exception): + """Log when there's a slash command.""" if isinstance(error, commands.CommandNotFound): - await ctx.send("That's not a command (error code 001)") + await ctx.send("That's not a command") elif isinstance(error, discord.errors.NotFound): self.bot.log("Deleted message before I could add all reactions") elif isinstance(error, commands.errors.MissingRequiredArgument): - self.bot.log(f"{error}",str(ctx.channel_id)) - await ctx.send("Missing command parameters (error code 002). Try using `!help [command]` to find out how to use the command.") + self.bot.log(f"{error}", str(ctx.channel_id)) + await ctx.send(self.bot.longStrings["missing parameters"]) else: - exception = traceback.format_exception(type(error), error, error.__traceback__) - stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" - if stopAt in exception: - index = exception.index(stopAt) - exception = exception[:index] + params = [type(error), error, error.__traceback__] + exception = traceback.format_exception(*params) exceptionString = "".join(exception) - self.bot.log([f"exception in /{ctx.name}", f"{exceptionString}"],str(ctx.channel_id), 40) + logMessages = [f"exception in /{ctx.name}", f"{exceptionString}"] + self.bot.log(logMessages, str(ctx.channel_id), 40) if isinstance(error, discord.errors.NotFound): - self.bot.log("Context is non-existant", level = 40) + self.bot.log("Context is non-existant", level=40) else: await ctx.send("Something went wrong (error code 000)") - async def on_error(self, method): + async def on_error(self, method: str): + """Log when there's an error.""" errorType = sys.exc_info()[0] if errorType == discord.errors.NotFound: self.bot.log("Deleted message before I could add all reactions") else: exception = traceback.format_exc() - stopAt = "\nThe above exception was the direct cause of the following exception:\n\n" - if stopAt in exception: - index = exception.index(stopAt) - exception = exception[:index] exceptionString = "".join(exception) - self.bot.log([f"exception in {method}", f"{exceptionString}"], level = 40) \ No newline at end of file + logMessages = [f"exception in {method}", f"{exceptionString}"] + self.bot.log(logMessages, level=40) diff --git a/utils/helperClasses.py b/utils/helperClasses.py index b643071..ca3ef41 100644 --- a/utils/helperClasses.py +++ b/utils/helperClasses.py @@ -1,6 +1,44 @@ -import re, git, os, json, time +""" +Contains classes used for utilities. -def sanitize(data : str, options : bool = False): +*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: @@ -8,7 +46,7 @@ def sanitize(data : str, options : bool = False): lineValues = line.split(":") lineValues[0] = lineValues[0].lower() lineValues[1] = lineValues[1].replace(" ", "") - if options: + if lowerCaseValue: lineValues[1] = lineValues[1].lower() if lineValues[0] in ["testing guild ids", "admins"]: @@ -23,18 +61,26 @@ def sanitize(data : str, options : bool = False): return dct + class Options(): + """Contains the options for the bot.""" + def __init__(self): - with open("options.txt","r") as f: + """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): - with open("credentials.txt","r") as f: + """Initialize the credentials.""" + with open("credentials.txt", "r") as f: data = sanitize(f.read()) self.token = data["bot token"] @@ -46,34 +92,99 @@ class Credentials(): 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): - user = self.bot.database["users"].find_one({"_id":userID}) + 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 != None: + 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): - user = self.bot.database["users"].find_one({"user name":re.compile(userName, re.IGNORECASE)}) + def getID(self, userName: str): + """ + Get the id of a user you have the username of. - if user != None: + *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, channel): - self.bot.database[gameType].delete_one({"_id":channel}) + def deleteGame(self, gameType: str, channel: str): + """ + Remove a game from the database. - def stopServer(self): + *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({}) @@ -84,35 +195,80 @@ class databaseFuncs(): g = git.cmd.Git("") g.pull() - def connectFourReactionTest(self,channel,message,user): - game = self.bot.database["connect 4 games"].find_one({"_id":str(channel.id)}) + def connectFourReactionTest(self, message: discord.Message, + user: discord.User): + """ + Test if the given message is the current connect four game. - with open("resources/games/oldImages/connectFour"+str(channel.id), "r") as f: + 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, turn+1 + return True else: self.bot.log("It wasn't their turn") - return False, 0 + return False else: - return False, 0 + return False - def hangmanReactionTest(self, channel, message, user): - try: - with open("resources/games/oldImages/hangman"+str(channel.id), "r") as f: + 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() - except: + else: return False gameMessage = False for oldMessage in oldMessages: oldMessageID = int(oldMessage) if message.id == oldMessageID: - game = self.bot.database["hangman games"].find_one({"_id":str(channel.id)}) + database = self.bot.database["hangman games"] + channelSearch = {"_id": str(channel.id)} + game = database.find_one(channelSearch) if user == game["player"]: gameMessage = True @@ -120,9 +276,30 @@ class databaseFuncs(): return gameMessage - def bedreNetflixReactionTest(self, channel, message): - if os.path.isfile(f"resources/bedreNetflix/oldMessage{str(channel.id)}"): - with open("resources/bedreNetflix/oldMessage"+str(channel.id),"r") as f: + 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 @@ -136,6 +313,7 @@ class databaseFuncs(): 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() @@ -144,6 +322,6 @@ class databaseFuncs(): 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}} + queryFilter = {"_id": idNumber} + update = {"$set": {"last synced": now}} collection.update_one(queryFilter, update) diff --git a/utils/utilFunctions.py b/utils/utilFunctions.py index 1d0600b..6fea627 100644 --- a/utils/utilFunctions.py +++ b/utils/utilFunctions.py @@ -1,3 +1,18 @@ +""" +Contains utility functions used by parts of the bot. + +*Functions* +----------- + longstrings() -> dict + getParams() -> dict + logThis(messages: Union[str, list], channel: str = "", + level: int = 20) + cap(s: str) -> str + makeFiles() + replaceMultiple(mainString: str, toBeReplaced: list, + newString: str) -> str + emojiToCommand(emoji: str) -> str +""" import json import logging import os @@ -5,22 +20,55 @@ import sys import imdb from .helperClasses import Options + +# All of this is logging configuration FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s" PRINTFORMAT = "%(asctime)s - %(message)s" DATEFORMAT = "%Y-%m-%d %H:%M:%S" logging.addLevelName(25, "PRINT") -logging.basicConfig(format=FORMAT, datefmt=DATEFORMAT, level=logging.INFO, filename="gwendolyn.log") +loggingConfigParams = { + "format": FORMAT, + "datefmt": DATEFORMAT, + "level": logging.INFO, + "filename": "gwendolyn.log" +} +logging.basicConfig(**loggingConfigParams) logger = logging.getLogger("Gwendolyn") printer = logging.getLogger("printer") handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(logging.Formatter(fmt = PRINTFORMAT, datefmt=DATEFORMAT)) +handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT)) printer.addHandler(handler) printer.propagate = False -imdb._logging.setLevel("CRITICAL") +imdb._logging.setLevel("CRITICAL") # Basically disables imdbpy +# logging, since it's printed to the terminal. + + +def longStrings(): + """ + Get the data from resources/longStrings.json. + + *Returns* + --------- + data: dict + The long strings and their keys. + """ + with open("resources/longStrings.json", "r") as f: + data = json.load(f) + + return data + def getParams(): + """ + Get the slash command parameters. + + *Returns* + --------- + params: dict + The parameters for every slash command. + """ with open("resources/slashParameters.json", "r") as f: params = json.load(f) @@ -32,8 +80,25 @@ def getParams(): return params -def logThis(messages, channel : str = "", level : int = 20): - channel = channel.replace("Direct Message with ","") + +def logThis(messages, channel: str = "", level: int = 20): + """ + Log something in Gwendolyn's logs. + + *Parameters* + ------------ + messages: Union[str, list] + A string or list of strings to be logged. If there are + multiple strings and the level is PRINT (25) or higher, + only the first string will be printed. + channel: str = "" + The channel the event to be logged occurred in. Will be + logged along with the message(s). + level: int = 20 + The level to log the message(s) at. If PRINT (25) or + higher, the first message will be printed to the console. + """ + channel = channel.replace("Direct Message with ", "") if type(messages) is str: messages = [messages] @@ -41,9 +106,11 @@ def logThis(messages, channel : str = "", level : int = 20): for x, msg in enumerate(messages): if channel != "": - messages[x] = f"{msg} - ({channel})" + messages[x] = f"{msg} - ({channel})" # Adds channel to log + # messages - if len(messages) > 1: + if len(messages) > 1: # Tells user to check the log if there are + # more messages there printMessage += " (details in log)" if level >= 25: @@ -52,10 +119,24 @@ def logThis(messages, channel : str = "", level : int = 20): for logMessage in messages: logger.log(level, logMessage) -# Capitalizes all words except some of them -def cap(s): - no_caps_list = ["of","the"] - # Capitalizes a strink like a movie title + +def cap(s: str): + """ + Capitalize a string like a movie title. + + That means "of" and "the" are not capitalized. + + *Parameters* + ------------ + s: str + The string to capitalized. + + *Returns* + --------- + res: str + The capitalized string. + """ + no_caps_list = ["of", "the"] word_number = 0 lst = s.split() res = '' @@ -67,22 +148,25 @@ def cap(s): res = res[:-1] return res -def makeFiles(): - def makeJsonFile(path,content): - # Creates json file if it doesn't exist - if not os.path.isfile(path): - logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: - json.dump(content,f,indent = 4) - def makeTxtFile(path,content): - # Creates txt file if it doesn't exist +def makeFiles(): + """Create all the files and directories needed by Gwendolyn.""" + def makeJsonFile(path, content): + """Create json file if it doesn't exist.""" if not os.path.isfile(path): logThis(path.split("/")[-1]+" didn't exist. Making it now.") - with open(path,"w") as f: + with open(path, "w") as f: + json.dump(content, f, indent=4) + + def makeTxtFile(path, content): + """Create txt file if it doesn't exist.""" + if not os.path.isfile(path): + logThis(path.split("/")[-1]+" didn't exist. Making it now.") + with open(path, "w") as f: f.write(content) def directory(path): + """Create directory if it doesn't exist.""" if not os.path.isdir(path): os.makedirs(path) logThis("The "+path.split("/")[-1]+" directory didn't exist") @@ -90,27 +174,58 @@ def makeFiles(): with open("resources/startingFiles.json") as f: data = json.load(f) + for path, content in data["json"].items(): + makeJsonFile(path, content) + + for path, content in data["txt"].items(): + makeTxtFile(path, content) + for path in data["folder"]: directory(path) - for path, content in data["json"].items(): - makeJsonFile(path,content) - for path, content in data["txt"].items(): - makeTxtFile(path,content) +def replaceMultiple(mainString: str, toBeReplaced: list, newString: str): + """ + Replace multiple substrings in a string with the same substring. -# Replaces multiple things with the same thing -def replaceMultiple(mainString, toBeReplaces, newString): + *Parameters* + ------------ + mainString: str + The string to replace substrings in. + toBeReplaced: list + The substrings to replace. + newString: str + The string to replace the substrings with. + + *Returns* + --------- + mainString: str + The string with the substrings replaced. + """ # Iterate over the strings to be replaced - for elem in toBeReplaces : + for elem in toBeReplaced: # Check if string is in the main string - if elem in mainString : + if elem in mainString: # Replace the string mainString = mainString.replace(elem, newString) return mainString -def emojiToCommand(emoji): + +def emojiToCommand(emoji: str): + """ + Convert emoji to text. + + *Parameters* + ------------ + emoji: str + The emoji to decipher. + + *Returns* + --------- + : str + The deciphered string. + """ if emoji == "1️⃣": return 1 elif emoji == "2️⃣": @@ -131,4 +246,5 @@ def emojiToCommand(emoji): return "none" elif emoji == "✔️": return 1 - else: return "" + else: + return ""