import re
import time
from xml.etree import ElementTree
import bbcode
import hjson
import requests
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Const, Int
from core.decorators import instance, command
from core.dict_object import DictObject
from core.translation_service import TranslationService
from core.igncore import Tyrbot
# noinspection PyUnusedLocal
@instance()
class AOUController:
AOU_URL = "https://www.ao-universe.com/mobile/parser.php?bot=tyrbot"
CACHE_GROUP = "aou"
CACHE_MAX_AGE = 604800
def __init__(self):
self.guide_id_regex = re.compile(r"pid=(\d+)", re.IGNORECASE)
# initialize bbcode parser
self.parser = bbcode.Parser(install_defaults=False, newline="\n", replace_links=False, replace_cosmetic=False,
drop_unrecognized=True)
self.parser.add_simple_formatter("i", "%(value)s")
self.parser.add_simple_formatter("b", "%(value)s")
self.parser.add_simple_formatter("ts_ts", " + ", standalone=True)
self.parser.add_simple_formatter("ts_ts2", " = ", standalone=True)
self.parser.add_simple_formatter("ct", " | ", standalone=True)
self.parser.add_simple_formatter("cttd", " | ", standalone=True)
self.parser.add_simple_formatter("cttr", "\n | ", standalone=True)
self.parser.add_simple_formatter("br", "\n", standalone=True)
self.parser.add_formatter("img", self.bbcode_render_image)
self.parser.add_formatter("url", self.bbcode_render_url)
self.parser.add_formatter("item", self.bbcode_render_item)
self.parser.add_formatter("itemname", self.bbcode_render_item)
self.parser.add_formatter("itemicon", self.bbcode_render_item)
self.parser.add_formatter("waypoint", self.bbcode_render_waypoint)
def inject(self, registry):
self.text = registry.get_instance("text")
self.bot: Tyrbot = registry.get_instance("bot")
self.items_controller = registry.get_instance("items_controller")
self.cache_service = registry.get_instance("cache_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
def start(self):
self.command_alias_service.add_alias("title", "aou 11")
self.command_alias_service.add_alias("totw", "macro aou 171|aou 172")
self.command_alias_service.add_alias("som", "macro aou 169|aou 383")
self.command_alias_service.add_alias("reck", "aou 629")
self.command_alias_service.add_alias("pets", "aou 2")
self.ts.register_translation("module/aou", self.load_aou_msg)
def load_aou_msg(self):
with open("modules/standard/aou/aou.msg", mode="r", encoding="utf-8") as f:
return hjson.load(f)
@command(command="aou", params=[Int("guide_id")], access_level="member",
description="Show an AO-Universe guide")
def aou_show_cmd(self, request, guide_id):
guide_info = self.retrieve_guide(guide_id)
if not guide_info:
return self.getresp("module/aou", "no_guide_id", {"id": guide_id})
obj = DictObject()
obj.id = self.text.make_chatcmd(guide_info.id,
f"/start https://www.ao-universe.com/main.php?site=knowledge&id="
f"{guide_info.id}")
obj.raw = self.text.make_chatcmd("Raw", "/start %s" % (self.AOU_URL + "&mode=view&id=" + str(guide_info.id)))
obj.updated = guide_info.updated
obj.profession = guide_info.profession
obj.faction = guide_info.faction
obj.level = guide_info.level
obj.author = self.format_bbcode_code(guide_info.author)
obj.aou = self.text.make_chatcmd("AO-Universe.com", "/start https://www.ao-universe.com")
obj.text = self.format_bbcode_code(guide_info.text)
self.bot.send_mass_message(request.sender.char_id,
ChatBlob(guide_info.name, self.getresp("module/aou", "guide", {**obj})))
@command(command="aou", params=[Const("all", is_optional=True), Any("search")], access_level="member",
description="Search for an AO-Universe guides")
def aou_search_cmd(self, request, include_all_matches, search):
include_all_matches = include_all_matches or False
r = requests.get(self.AOU_URL + "&mode=search&search=" + search, timeout=5)
xml = ElementTree.fromstring(r.content)
blob = ""
count = 0
for section in xml.iter("section"):
category = self.get_category(section)
found = False
for guide in self.get_guides(section):
if include_all_matches or self.check_matches(
category + " " + guide["name"] + " " + (guide["description"] or ""), search):
# don't show category unless we have at least one guide for it
if not found:
blob += "\n%s\n" % category
found = True
count += 1
blob += "%s - %s\n" % (
self.text.make_tellcmd(guide["name"], "aou %s" % guide["id"]), guide["description"])
blob += "\n\nPowered by %s" % self.text.make_chatcmd("AO-Universe.com", "/start https://www.ao-universe.com")
if count == 0:
return self.getresp("module/aou", "no_guide_search", {"search": search})
else:
self.bot.send_mass_message(request.sender.char_id, ChatBlob(
self.getresp("module/aou", "search_guide_title" + ("_all" if include_all_matches else ""),
{"search": search, "count": count}), blob))
def retrieve_guide(self, guide_id):
cache_key = "%d.xml" % guide_id
t = int(time.time())
# check cache for fresh value
cache_result = self.cache_service.retrieve(self.CACHE_GROUP, cache_key)
if cache_result and cache_result.last_modified > (t - self.CACHE_MAX_AGE):
result = ElementTree.fromstring(cache_result.data)
else:
response = requests.get(self.AOU_URL + "&mode=view&id=" + str(guide_id), timeout=5)
result = ElementTree.fromstring(response.content)
if result.findall("./error"):
result = None
if result:
# store result in cache
self.cache_service.store(self.CACHE_GROUP, cache_key, ElementTree.tostring(result, encoding="unicode"))
elif cache_result:
# check cache for any value, even expired
result = ElementTree.fromstring(cache_result.data)
if result:
return self.get_guide_info(result)
else:
return None
def get_guide_info(self, xml):
content = self.get_xml_child(xml, "section/content")
return DictObject({
"id": self.get_xml_child(content, "id").text,
"category": self.get_category(self.get_xml_child(xml, "section")),
"name": self.get_xml_child(content, "name").text,
"updated": self.get_xml_child(content, "update").text,
"profession": self.get_xml_child(content, "class").text,
"faction": self.get_xml_child(content, "faction").text,
"level": self.get_xml_child(content, "level").text,
"author": self.get_xml_child(content, "author").text,
"text": self.get_xml_child(content, "text").text
})
def check_matches(self, haystack, needle):
haystack = haystack.lower()
for n in needle.split():
if n in haystack:
return True
return False
def get_guides(self, section):
result = []
for guide in section.findall("./guidelist/guide"):
result.append({"id": guide[0].text, "name": guide[1].text, "description": guide[2].text})
return result
def get_category(self, section):
result = []
for folder_names in section.findall("./folderlist/folder/name"):
result.append(folder_names.text)
return " - ".join(reversed(result))
def get_xml_child(self, xml, child_tag):
return xml.findall("./%s" % child_tag)[0]
def format_bbcode_code(self, bbcode_str):
return self.parser.format(bbcode_str)
# BBCode formatters
def bbcode_render_image(self, tag_name, value, options, parent, context):
return self.text.make_chatcmd("Image", "/start https://www.ao-universe.com/" + value)
def bbcode_render_url(self, tag_name, value, options, parent, context):
url = options.get("url") or value
guide_id_match = self.guide_id_regex.search(url)
if guide_id_match:
return self.text.make_tellcmd(value, "aou " + guide_id_match.group(1))
else:
return self.text.make_chatcmd(value, "/start " + url)
def bbcode_render_item(self, tag_name, value, options, parent, context):
item = self.items_controller.get_by_item_id(value)
if not item:
return "Unknown Item(%s)" % value
else:
include_icon = tag_name == "item" or tag_name == "itemicon"
return self.text.format_item(item, with_icon=include_icon)
def bbcode_render_waypoint(self, tag_name, value, options, parent, context):
x_coord = options["x"]
y_coord = options["y"]
pf_id = options["pf"]
return self.text.make_chatcmd("%s (%sx%s)" % (value, x_coord, y_coord),
"/waypoint %s %s %s" % (x_coord, y_coord, pf_id))