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 IgnCore # noinspection PyUnusedLocal @instance() class AOUController: AOU_URL = "https://www.ao-universe.com/mobile/parser.php?bot=igncore" 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: IgnCore = 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))