import json import os import re from core.chat_blob import ChatBlob from core.command_param_types import Any, Int, NamedParameters from core.decorators import instance, command from core.logger import Logger @instance() class RecipeController: def __init__(self): self.logger = Logger(__name__) self.recipe_name_regex = re.compile(r"(\d+)\.(txt|json)") self.recipe_item_regex = re.compile(r"#L \"([^\"]+)\" \"([\d+]+)\"") self.recipe_link_regex = re.compile(r"#L \"([^\"]+)\" \"([^\"]+)\"") def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.items_controller = registry.get_instance("items_controller") self.command_alias_service = registry.get_instance("command_alias_service") def start(self): self.command_alias_service.add_alias("r", "recipe") self.command_alias_service.add_alias("tradeskill", "recipe") self.db.exec("CREATE TABLE IF NOT EXISTS recipe (" "id INT NOT NULL PRIMARY KEY, " "name VARCHAR(50) NOT NULL, " "author VARCHAR(50) NOT NULL, " "recipe TEXT NOT NULL, " "dt INT NOT NULL DEFAULT 0)") self.db.create_view("recipe") recipe_dir = os.path.dirname(os.path.realpath(__file__)) + "/recipes/" recipes = self.db.query("SELECT id, dt FROM recipe") for file in os.listdir(recipe_dir): if file.startswith("_"): continue m = self.recipe_name_regex.match(file) if m: recipe_id = m.group(1) file_type = m.group(2) dt = int(os.path.getmtime(recipe_dir + file)) recipe = self.find_recipe(recipe_id, recipes) if recipe: recipes.remove(recipe) if recipe.dt == dt: continue self.update_recipe(recipe_dir, recipe_id, file_type, dt) else: raise Exception(f"Unknown recipe format for '{file}'") @command(command="recipe", params=[Int("recipe_id")], access_level="member", description="Show a recipe") def recipe_show_cmd(self, _, recipe_id): recipe = self.get_recipe(recipe_id) if not recipe: return f"Could not find recipe with ID {recipe_id:d}." return self.format_recipe(recipe) @command(command="recipe", params=[Any("search"), NamedParameters(["page"])], access_level="member", description="Search for a recipe") def recipe_search_cmd(self, _, search, named_params): page = int(named_params.page or "1") page_size = 30 offset = (page - 1) * page_size search = self.text.strip_html_tags(search) data = self.db.query("SELECT * FROM recipe " "WHERE name LIKE ? " "or recipe LIKE ? " "ORDER BY name", [f"%{search}%", f"%{search}%"]) count = len(data) paged_data = data[offset:offset + page_size] if count == 1: return self.format_recipe(data[0]) blob = "" if count > page_size: if page > 1 and len(paged_data) > 0: blob += " " + self.text.make_chatcmd(f"«« Page {page - 1:d}", self.get_chat_command(search, page - 1)) if offset + page_size < len(data): blob += " Page " + str(page) blob += " " + self.text.make_chatcmd(f"Page {page + 1:d} »»", self.get_chat_command(search, page + 1)) blob += "\n\n" for row in paged_data: blob += self.text.make_tellcmd(row.name, f"recipe {row.id:d}") + "\n" return ChatBlob( f"Recipes Matching '{search}' ({offset + 1:d} - {min(offset + page_size, count):d} of {count:d})", blob) def get_recipe(self, recipe_id): return self.db.query_single("SELECT * FROM recipe WHERE id = ?", [recipe_id]) def format_recipe(self, recipe): blob = f"Recipe ID: {recipe.id:d}\n" blob += f"Author: {recipe.author or 'Unknown'}\n\n" blob += self.format_recipe_text(recipe.recipe) return ChatBlob(f"Recipe for '{recipe.name}'", blob) def format_recipe_text(self, recipe_text): recipe_text = recipe_text.replace("\\n", "\n") recipe_text = self.recipe_item_regex.sub(self.lookup_item, recipe_text) recipe_text = self.recipe_link_regex.sub("\\1", recipe_text) return recipe_text def lookup_item(self, m): name = m.group(1) item_id = m.group(2) item = self.items_controller.get_by_item_id(item_id) if item: return self.text.make_item(item.lowid, item.highid, item.highql, item.name) else: return name def get_chat_command(self, search, page): return f"/tell recipe {search} --page={page:d}" def find_recipe(self, recipe_id, recipes): for row in recipes: if str(row.id) == recipe_id: return row return None def update_recipe(self, recipe_dir, recipe_id, _, dt): with open(recipe_dir + recipe_id + ".json", mode="r", encoding="UTF-8") as f: recipe = json.load(f) name = recipe["name"] author = recipe["author"] if "raw" in recipe: content = recipe["raw"] else: content = self.format_json_recipe(recipe_id, recipe) self.db.exec("REPLACE INTO recipe (id, name, author, recipe, dt) " "VALUES (?, ?, ?, ?, ?)", [recipe_id, name, author, content, dt]) def format_json_recipe(self, recipe_id, recipe): items = {} for i in recipe["items"]: item = self.items_controller.get_by_item_id(i["item_id"], i.get("ql")) if not item: raise Exception(f"Could not find recipe item '{i['item_id']:d}' for recipe id {recipe_id}") item.ql = i.get("ql") or (item.highql if i["item_id"] == item.highid else item.lowql) items[i["alias"]] = item content = "" ingredients = items.copy() for step in recipe["steps"]: del ingredients[step["result"]] content += self.format_ingredients(ingredients.items()) content += "\n" content += self.format_steps(items, recipe["steps"]) if "details" in recipe: content += self.format_details(recipe["details"]) return content def format_ingredients(self, ingredients): content = "------------------------------\n" content += "Ingredients\n" content += "------------------------------\n\n" for _, ingredient in ingredients: content += self.text.make_image(ingredient["icon"]) + "" content += self.text.make_item(ingredient["lowid"], ingredient["highid"], ingredient["ql"], ingredient["name"]) + "\n" return content def format_steps(self, items, steps): content = "" content += "------------------------------\n" content += "Recipe\n" content += "------------------------------\n\n" for step in steps: source = items[step["source"]] target = items[step["target"]] result = items[step["result"]] content += f"" \ f"{self.text.make_image(source['icon'])}" + "" content += "+ " content += f"" \ f"{self.text.make_image(target['icon'])}" + "" content += "= " content += f"" \ f"{self.text.make_image(result['icon'])}" content += "\n" + self.text.make_item(source["lowid"], source["highid"], source["ql"], source["name"]) content += "\n + " + self.text.make_item(target["lowid"], target["highid"], target["ql"], target["name"]) content += "\n = " + self.text.make_item(result["lowid"], result["highid"], result["ql"], result["name"]) + "\n" if "skills" in step: content += f"Skills: | {step['skills']} |\n" content += "\n\n" return content def format_details(self, details): content = "" content += "------------------------------\n" content += "Details\n" content += "------------------------------\n\n" last_type = "" for detail in details: if "item" in detail: last_type = "item" i = detail["item"] if "ql" in i: item = self.items_controller.get_by_item_id(i["id"], i["ql"]) else: item = self.items_controller.get_by_item_id(i["id"]) item["ql"] = item["highql"] content += "%s" % \ self.text.make_item(item['lowid'], item['highid'], item['ql'], item['name']) if "comment" in i: content += " - " + i["comment"] content += "\n" elif "text" in detail: if last_type == "item": content += "\n" last_type = "text" content += f"{detail['text']}\n" return content def convert_to_json(self, recipe_dir, recipe_id, file): with open(recipe_dir + file, mode="r", encoding="UTF-8") as f: lines = f.readlines() recipe = { "name": lines.pop(0).strip()[6:], "author": lines.pop(0).strip()[8:], "items": list(), "steps": list(), "details": list(), "raw": None } content = "".join(lines) items = {} matches = self.recipe_item_regex.findall(content) for item_name, item_id in matches: item = self.items_controller.get_by_item_id(item_id) if not item: self.logger.warning(f"Could not find recipe item '{item_id} - {item_name}' for recipe id {recipe_id}") else: items[item.highid] = {"alias": item.name, "item_id": item.highid} recipe["items"].extend(items.values()) recipe["raw"] = content with open(recipe_dir + recipe_id + ".json", mode="w", encoding="UTF-8") as f: f.write(json.dumps(recipe, indent=4)) # delete file os.remove(recipe_dir + file) return True