So in my free time, I made a discord bot to play around with and learn a bit of python. I'm mainly a java developer. The bot has a few commands that allow it to find and output recipe for cocktails and a few other things revolving around cocktails.
In the background, there are 2 JSON files. My bot mainly reads and filters these JSON files and returns what it finds. In java, I am used to streams which I don't have in python so I just had to make do with what I know but I get a feeling that some things can definitely be done cleaner/shorter/better/ and I'm not sure everything follows python standards so I would be grateful for any feedback.
[ !! cocktails can be exchanged with -c, ingredients with -i !! ] [ <KW> find cocktails <search criteria1> <search criteria2> ... Find cocktail. ] [ <KW> find ingredients <search criteria1> <search criteria2> ... Find ingredient. ] [ <KW> cocktails List all cocktails. ] [ <KW> ingredients List all ingredients. ] [ <KW> categories List all cocktail categories. ] [ <KW> list <category> List all cocktails by category ] [ <KW> count cocktails How many cocktails? ] [ <KW> count ingredients How many ingredients? ] [ <KW> with <ingredient> <ingredient2> ... Cocktails with ingredient. ] [ <KW> recipe <cocktail> How to make a cocktail. ]
The possible commands are as above <KW> is the trigger word for the bot that a different component handles. bartender.py receives the content with this cut off.
bartender.py
import json class Bartender: with open("recipes.json") as recipes_file: recipes = json.load(recipes_file) with open("ingredients.json") as ingredients_file: ingredients = json.load(ingredients_file) default = "This command does not exist or you mistyped something" error = "There was a problem with processing the command" def handle(self, message): command_prefix = message.content.strip().lower() answer = self.default if self.starts_with_ingredients_prefix(command_prefix): answer = self.get_all_ingredients() elif self.starts_with_cocktails_prefix(command_prefix): answer = self.get_all_cocktails() elif command_prefix == "categories": answer = self.get_all_categories() elif command_prefix.startswith("count"): answer = self.get_count(command_prefix.removeprefix("count").strip()) elif command_prefix.startswith("recipe"): answer = self.get_recipe(command_prefix.removeprefix("recipe").strip()) elif command_prefix.startswith("list"): answer = self.get_cocktails_by_category(command_prefix.removeprefix("list").strip()) elif command_prefix.startswith("find"): answer = self.find(command_prefix.removeprefix("find").strip()) elif command_prefix.startswith("with"): answer = self.get_cocktails_with(command_prefix.removeprefix("with").strip()) return answer def get_all_ingredients(self): """Returns a string containing all of the ingredients the bot knows.""" answer = "" for key, value in self.ingredients.items(): answer += f"{key} ({value.get('abv')}%)\n" return answer def get_all_cocktails(self): """Returns a string containing the names of all of the cocktails the bot knows.""" answer = "" for cocktail in self.recipes: answer += f"{cocktail.get('name')}\n" return answer def get_all_categories(self): """Returns a string containing all the cocktail categories the bot knows.""" answer = "" categories_list = [] for cocktail in self.recipes: categories_list.append(cocktail.get("category")) # Remove duplicates categories_list = list(set(categories_list)) categories_list.sort(key=str) for category in categories_list: answer += f"{category}\n" return answer def get_count(self, param): """Returns the amount of ingredients or cocktails the bot knows.""" answer = self.error if self.starts_with_ingredients_prefix(param): answer = len(self.ingredients) elif self.starts_with_cocktails_prefix(param): answer = len(self.recipes) return answer def get_recipe(self, param): """Returns the full recipe for the passed cocktail name.""" answer = f"There is no recipe for a cocktail called {param}. To see all cocktails with a recipe " \ f"type '$bt cocktails'" for cocktail in self.recipes: if param == cocktail.get("name").lower(): formatted_ingredients = self.get_formatted_ingredients(cocktail.get("ingredients")) garnish = self.get_garnisch(cocktail) return f"__**{cocktail.get('name')}**__\n" \ f"**Ingriedients:**\n" \ f"{formatted_ingredients}" \ f"{garnish}" \ f"**Preparation:**\n" \ f"{cocktail.get('preparation')} \n" return answer def get_formatted_ingredients(self, ingredients): """Returns a string of ingredients formatted as list for the cocktails including the special ones if it has any.""" formatted_ingredients = "" special_ingredients = "" for ingredient in ingredients: if ingredient.get("special") is not None: special_ingredients += f" - {ingredient.get('special')}\n" else: formatted_ingredients += f" - {ingredient.get('amount')} {ingredient.get('unit')} {ingredient.get('ingredient')} " if ingredient.get("label") is not None: f"({ingredient.get('label')})" formatted_ingredients += "\n" return formatted_ingredients + special_ingredients def get_garnisch(self, cocktail): """Returns the garnish for the cocktail if it has one.""" if cocktail.get("garnish") is not None: return f"**Garnish:**\n" \ f" - {cocktail.get('garnish')} \n" else: return "" def get_cocktails_by_category(self, category): """Returns all cocktails in the given category.""" answer = "" for cocktail in self.recipes: if category == str(cocktail.get("category")).lower(): answer += f"{cocktail.get('name')}\n" return answer if len(answer) > 0 else f"There is no category called {category} or it contains no cocktails" def starts_with_cocktails_prefix(self, param): """Returns true if passed string starts with the cocktails prefix (-c or cocktails).""" return param.startswith("-c") or param.startswith("cocktails") def remove_cocktails_prefix(self, param): """Returns a string with the cocktails prefix (-c or cocktails) removed. If the string does not start with the cocktails prefix it will return the original string.""" if param.startswith("-c"): param = param.removeprefix("-c") elif param.startswith("cocktails"): param = param.removeprefix("cocktails") return param def starts_with_ingredients_prefix(self, param): """Returns true if passed string starts with the ingredient prefix (-i or ingredients).""" return param.startswith("-i") or param.startswith("ingredients") def remove_ingredients_prefix(self, param): """Returns a string with the ingredient prefix (-i or ingredients) removed. If the string does not start with the ingredients prefix it will return the original string.""" if param.startswith("-i"): param = param.removeprefix("-i") elif param.startswith("ingredients"): param = param.removeprefix("ingredients") return param def find(self, param): """Returns all ingredients or cocktails containing the criteria in the parameter separated by commas.""" answer = "" if self.starts_with_cocktails_prefix(param): param = self.remove_cocktails_prefix(param) for criteria in param.strip().split(): answer += f"**Criteria: {criteria}**\n" answer += self.get_cocktails_containing(criteria) elif self.starts_with_ingredients_prefix(param): param = self.remove_ingredients_prefix(param) for criteria in param.strip().split(): answer += f"**Criteria: {criteria}**\n" answer += self.get_ingredients_containing(criteria) return answer if len(answer) > 0 else "Nothing was found matching your criteria" def get_cocktails_containing(self, criteria): """Returns all cocktails containing the criteria in its name.""" answer = "" for cocktail in self.recipes: if criteria in str(cocktail.get("name")).lower(): answer += f"{cocktail.get('name')}\n" return answer if len(answer) > 0 else "Nothing was found matching your criteria" def get_ingredients_containing(self, criteria): """Returns all ingredients containing the criteria in its name.""" answer = "" for ingredient in self.ingredients.keys(): if criteria in ingredient.lower(): answer += f"{ingredient}\n" return answer if len(answer) > 0 else "Nothing was found matching your criteria" def get_cocktails_with(self, param): """Returns all cocktails containing the searched for ingredients in the parameter separated by commas.""" answer = "" for ingredient in param.strip().split(","): for cocktail in self.recipes: cocktail_ingredients = cocktail.get("ingredients") answer += self.does_cocktail_contain(cocktail, cocktail_ingredients, ingredient.strip()) return answer if len(answer) > 0 else "Nothing was found matching your criteria" def does_cocktail_contain(self, cocktail, cocktail_ingredients, ingredient): """Returns the name of the cocktail if the cocktail contains the searched for ingredient.""" for cocktail_ingredient in cocktail_ingredients: if cocktail_ingredient.get("ingredient") is not None and ingredient in cocktail_ingredient.get( "ingredient").lower(): return f"{cocktail.get('name')}\n" return ""
recipes.json
[ { "name": "Vesper", "glass": "martini", "category": "Before Dinner Cocktail", "ingredients": [ { "unit": "cl", "amount": 6, "ingredient": "Gin" }, { "unit": "cl", "amount": 1.5, "ingredient": "Vodka" }, { "unit": "cl", "amount": 0.75, "ingredient": "Lillet Blonde" }, { "special": "3 dashes Strawberry syrup" } ], "garnish": "Lemon twist", "preparation": "Shake and strain into a chilled cocktail glass." }, ... ]
Not all cocktails have the glass attribute and a cocktail can have 0 to n "special" ingredients.
ingredients.json
{ "Absinthe": { "abv": 40, "taste": null }, "Aperol": { "abv": 11, "taste": "bitter" }, ... }