Files
igncore/modules/standard/recipe/recipe_controller.py
T
Minidodo c04f76c0db Added the option to !opt-in/opt-out [onlinebot only]
Fixed command & event threading
Events are now threaded by event_type (i.e. all buddy_logon events get ran in the same one)
Added default preferences
Fixed recipe loading for multiple installs (i.e. on different machines)
2021-08-27 13:58:47 +02:00

292 lines
12 KiB
Python

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 <highlight>{recipe_id:d}</highlight>."
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: <highlight>{recipe.id:d}</highlight>\n"
blob += f"Author: <highlight>{recipe.author or 'Unknown'}</highlight>\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("<a href='chatcmd://\\2'>\\1</a>", 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 <myname> 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 = "<font color=#FFFF00>------------------------------</font>\n"
content += "<font color=#FF0000>Ingredients</font>\n"
content += "<font color=#FFFF00>------------------------------</font>\n\n"
for _, ingredient in ingredients:
content += self.text.make_image(ingredient["icon"]) + "<tab>"
content += self.text.make_item(ingredient["lowid"], ingredient["highid"], ingredient["ql"],
ingredient["name"]) + "\n"
return content
def format_steps(self, items, steps):
content = ""
content += "<font color=#FFFF00>------------------------------</font>\n"
content += "<font color=#FF0000>Recipe</font>\n"
content += "<font color=#FFFF00>------------------------------</font>\n\n"
for step in steps:
source = items[step["source"]]
target = items[step["target"]]
result = items[step["result"]]
content += f"<a href='itemref://{source['lowid']:d}/{source['highid']:d}/{source['ql']:d}'>" \
f"{self.text.make_image(source['icon'])}</a>" + ""
content += "<font color=#FFFFFF><tab>+<tab></font> "
content += f"<a href='itemref://{target['lowid']:d}/{target['highid']:d}/{target['ql']:d}'>" \
f"{self.text.make_image(target['icon'])}</a>" + ""
content += "<font color=#FFFFFF><tab>=<tab></font> "
content += f"<a href='itemref://{result['lowid']:d}/{result['highid']:d}/{result['ql']:d}'>" \
f"{self.text.make_image(result['icon'])}</a>"
content += "\n<tab><tab>" + self.text.make_item(source["lowid"],
source["highid"], source["ql"], source["name"])
content += "\n + <tab>" + self.text.make_item(target["lowid"],
target["highid"], target["ql"], target["name"])
content += "\n = <tab>" + self.text.make_item(result["lowid"],
result["highid"], result["ql"], result["name"]) + "\n"
if "skills" in step:
content += f"<font color=#FFFF00>Skills: | {step['skills']} |</font>\n"
content += "\n\n"
return content
def format_details(self, details):
content = ""
content += "<font color=#FFFF00>------------------------------</font>\n"
content += "<font color=#FF0000>Details</font>\n"
content += "<font color=#FFFF00>------------------------------</font>\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 += "<font color=#009B00>%s</font>" % \
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"<font color=#FFFFFF>{detail['text']}</font>\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