import html from core.chat_blob import ChatBlob from core.command_param_types import Int, Any, NamedParameters from core.db import DB from core.decorators import instance, command from core.text import Text @instance() class ItemsController: PAGE_SIZE = 30 def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.command_alias_service = registry.get_instance("command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "aodb.sql", pre_optimized=True) self.db.create_view("aodb") def start(self): self.command_alias_service.add_alias("item", "items") self.command_alias_service.add_alias("i", "items") @command(command="items", params=[Int("item_id")], access_level="member", description="Search for an item by item id") def items_id_cmd(self, _, item_id): item = self.get_by_item_id(item_id) if item: return self.format_items_response(None, str(item_id), [item], 0, 1) else: return f"Could not find item with ID {item_id:d}." @command(command="items", params=[Int("ql", is_optional=True), Any("search"), NamedParameters(["page"])], access_level="member", description="Search for an item") def items_search_cmd(self, _, ql, search, named_params): page = int(named_params.page or "1") search = html.unescape(search) offset = (page - 1) * self.PAGE_SIZE all_items = self.find_items(search, ql) return self.format_items_response(ql, search, all_items, offset, page) def format_items_response(self, ql, search, all_items, offset, page): items = self.sort_items(search, all_items)[offset:offset + self.PAGE_SIZE] cnt = len(items) if cnt == 0: if ql: return f"No QL {ql:d} items found matching {search}." else: return f"No items found matching {search}." elif cnt == 1: item = items[0] return self.format_single_item([item], ql) else: blob = "" # blob += "Version: %s\n" % "unknown" if ql: blob += f"Search: QL {ql:d} {search}\n" else: blob += f"Search: {search}\n" head_foot = "" if page > 1: head_foot += " " + self.text.make_chatcmd(f"«« Page {page - 1:d}", self.get_chat_command(ql, search, page - 1)) if offset + self.PAGE_SIZE < len(all_items): head_foot += " Page " + str(page) head_foot += " " + self.text.make_chatcmd(f"Page {page + 1:d} »»", self.get_chat_command(ql, search, page + 1)) if self.PAGE_SIZE < len(all_items): blob += "\n" blob += head_foot + "\n\n" blob += self.format_items(items, ql) blob += head_foot # noinspection LongLine blob += f"\nItem DB rips created using the {self.text.make_chatcmd('Budabot Items Extractor', '/start https://github.com/Budabot/ItemsExtractor')} tool." return ChatBlob( f"Item Search Results ({offset + 1:d} - {min(offset + self.PAGE_SIZE, len(all_items)):d} " f"of {len(all_items):d})", blob) def format_items(self, items, ql=None): blob = "" for item_group in ItemIter(items): blob += "" blob += self.text.make_image(item_group[0].icon) + "\n" blob += self.format_single_item(item_group, ql) blob += "\n\n" return blob def format_single_item(self, item_group, ql): msg = "" msg += item_group[0].name for item in item_group: if ql: if item.lowql != item.highql: msg += f" {self.text.make_item(item.lowid, item.highid, ql, ql)}" msg += f" [{self.text.make_item(item.lowid, item.highid, item.lowql, item.lowql)} - " \ f"{self.text.make_item(item.lowid, item.highid, item.highql, item.highql)}]" elif item.lowql == item.highql: msg += f" [{self.text.make_item(item.lowid, item.highid, item.highql, item.highql)}]" elif item.lowql == item.highql: msg += f" [{self.text.make_item(item.lowid, item.highid, item.highql, item.highql)}]" else: msg += f" [{self.text.make_item(item.lowid, item.highid, item.lowql, item.lowql)} " \ f"- {self.text.make_item(item.lowid, item.highid, item.highql, item.highql)}]" return msg def find_items(self, name, ql=None): params = [name] sql = "SELECT * FROM aodb WHERE name LIKE ? " if ql: sql += " AND lowql <= ? AND highql >= ?" params.append(ql) params.append(ql) sql += " UNION SELECT * FROM aodb WHERE name ?" % len(params) params.append(name) if ql: sql += " AND lowql <= ? AND highql >= ?" params.append(ql) params.append(ql) sql += " ORDER BY name, highid, highql DESC" return self.db.query(sql, params, extended_like=True) def sort_items(self, search, items): search = search.lower() search_parts = search.split(" ") # if item name matches search exactly (case-insensitive) then priority = 0 # if item name contains every whole word from search (case-insensitive) then priority = 1 # +1 priority for each whole word from search that item name does not contain for row in items: row.priority = 0 row_name = row.name.lower() if row_name != search: row.priority += 1 row_parts = row_name.split(" ") for search_part in search_parts: if search_part not in row_parts: row.priority += 1 items.sort(key=lambda x: x.priority, reverse=False) return items def get_by_item_id(self, item_id, ql=None): if ql: return self.db.query_single("SELECT * FROM aodb " "WHERE (highid = ? OR lowid = ?) " "AND (lowql <= ? AND highql >= ?) " "ORDER BY highid = ? DESC " "LIMIT 1", [item_id, item_id, ql, ql, item_id]) else: return self.db.query_single("SELECT * FROM aodb " "WHERE highid = ? OR lowid = ? " "ORDER BY highid = ? DESC " "LIMIT 1", [item_id, item_id, item_id]) def find_by_name(self, name, ql=None): if ql: return self.db.query_single("SELECT * FROM aodb " "WHERE name = ? " "AND lowql <= ? " "AND highql >= ? " "ORDER BY highid DESC " "LIMIT 1", [name, ql, ql]) else: return self.db.query_single("SELECT * FROM aodb " "WHERE name = ? " "ORDER BY highql DESC, " "highid DESC " "LIMIT 1", [name]) def get_chat_command(self, ql, search, page): if ql: return f"/tell items {ql:d} {search} --page={page:d}" else: return f"/tell items {search} --page={page:d}" class ItemIter: """Iterator that groups items with the same name and icon together.""" def __init__(self, items): self.items = items self.current_index = 0 def __iter__(self): return self def __next__(self): num_items = len(self.items) if self.current_index >= num_items: raise StopIteration else: grouped = [] item = self.items[self.current_index] self.current_index += 1 grouped.append(item) current_item = item while self.current_index < num_items: item = self.items[self.current_index] if item.name != current_item.name \ or item.icon != current_item.icon \ or item.highql == current_item.highql \ or item.highql < current_item.highql: break current_item = item grouped.append(item) self.current_index += 1 return grouped