Initial Release of IGNCore version 2.5

This commit is contained in:
2021-08-09 13:18:56 +02:00
commit a83d98c47e
910 changed files with 224171 additions and 0 deletions
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
class AunoComment:
def __init__(self, author, date, content):
self.author = author
self.date = date
self.content = content
+179
View File
@@ -0,0 +1,179 @@
import html
import re
from typing import List
import requests
from bs4 import BeautifulSoup
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Int, Item
from core.decorators import instance, command
from core.text import Text
from modules.standard.items.auno_comment import AunoComment
from modules.standard.items.items_controller import ItemsController
@instance()
class AunoController:
def __init__(self):
pass
def inject(self, registry):
self.text: Text = registry.get_instance("text")
self.items_controller: ItemsController = registry.get_instance("items_controller")
@command(command="auno", params=[Int("item_id")], access_level="member",
description="Fetch comments for item from Auno by item id")
def auno_comments_item_id_cmd(self, _, item_id):
item = self.items_controller.get_by_item_id(item_id)
if item:
low_id = item.lowid
high_id = item.highid
name = item.name
else:
low_id = item_id
high_id = item_id
name = item_id
return self.get_combined_response(low_id, high_id, name)
@command(command="auno", params=[Item("item_link")], access_level="member",
description="Fetch comments for item from Auno by item link")
def auno_comments_item_link_cmd(self, _, item):
return self.get_combined_response(item.low_id, item.high_id, item.name)
@command(command="auno", params=[Any("search")], access_level="member",
description="Fetch comments for item from Auno by search")
def auno_comments_cmd(self, _, search):
items = self.items_controller.find_items(search)
count = len(items)
if count > 0:
if count > 1:
link_txt = f"Multiple search results for \"{search}\" ({count})"
return ChatBlob(link_txt, self.multiple_results_blob(items, search))
else:
return self.get_combined_response(items[0].lowid, items[0].highid, items[0].name)
else:
return "No items found matching <highlight>%s</highlight>." % search
def get_combined_response(self, low_id, high_id, name):
combined_response = self.get_auno_response(low_id, high_id)
if len(combined_response) > 0:
# high id comments
soup = BeautifulSoup(combined_response[0].text, features="html.parser")
comments: List[AunoComment] = self.find_comments(soup)
if len(combined_response) > 1:
# low id comments
soup = BeautifulSoup(combined_response[1].text, features="html.parser")
comments += self.find_comments(soup)
# sort the comments by date
comments.sort(key=lambda comment: comment.date)
if len(comments) > 0:
return ChatBlob(f"Comments for {name} ({len(comments)})",
self.build_comments_blob(comments, name, low_id, high_id))
else:
return f"No comments found for <highlight>{name}</highlight>."
else:
return "Error fetching comments from Auno.org."
def build_comments_blob(self, comments, name, low_id, high_id):
link_auno = self.text.make_chatcmd("Auno", f"/start {self.get_auno_request_url(high_id)}")
link_aoitems = self.text.make_chatcmd("AOItems", f"/start {self.get_aoitems_request_url(high_id)}")
item = self.items_controller.get_by_item_id(high_id)
blob = ""
if item:
ql = item.highql
blob += f"Item: {self.text.make_item(int(low_id), int(high_id), int(ql), name)}\n"
blob += f"Item links: [{link_auno}] [{link_aoitems}]\n\n"
blob += "<header2>Comments</header2>\n"
for comment in comments:
blob += html.unescape(comment.content) + "\n"
blob += f"<highlight>{comment.author}</highlight> [<grey>{comment.date}</grey>]\n"
blob += "\n<pagebreak>"
return blob
def multiple_results_blob(self, items, search):
max_multiple_results = 40
blob = f"Found <highlight>{len(items)}</highlight> items matching <highlight>\"{search}\"</highlight>\n"
if len(items) > max_multiple_results:
blob += "Results have been truncated to only show the first %s results...\n\n" % max_multiple_results
items = items[:max_multiple_results]
for i, item in enumerate(items):
itemref = self.text.make_item(item.lowid, item.highid, item.highql, item.name)
comments_link = self.text.make_tellcmd("Comments", f"auno {item.highid}")
auno_link_h = self.text.make_chatcmd("Auno", f"/start {self.get_auno_request_url(item.highid)}")
blob += f"{i + 1}. {itemref}\n | [{comments_link}] [{auno_link_h}]"
blob += "\n\n<pagebreak>"
return blob
def find_comments(self, soup):
comments: List[AunoComment] = []
brs = soup.find_all("br")
for br in brs:
br.replace_with("\n")
trs = soup.find_all(self.tr_contains_comment)
for tr in trs:
author, date = tr.find("span").string.split("@")
comment_content = tr.find("div")
comment = ""
for content in comment_content:
if type(content) is str:
comment += html.escape(content)
else:
comment += html.escape(content.string)
# when those halfwits think a billion linebreaks are necessary
# and because auno's comment output is ever so lovely...
# noinspection RegExpUnnecessaryNonCapturingGroup
comment = re.sub(r"(\n(?:\n)*(?:\s)*)", "\n", comment)
comments.append(AunoComment(author.strip(), date.strip(), comment.strip()))
return comments
def tr_contains_comment(self, tag):
if tag:
if 'id' in tag.attrs:
p = re.compile(r"aoc\d+")
return p.match(tag['id'])
def get_auno_response(self, low_id, high_id):
auno_request_low = self.get_auno_request_url(low_id)
auno_request_high = self.get_auno_request_url(high_id)
auno_response_h = requests.get(auno_request_high, timeout=5)
auno_response_l = None
if low_id != high_id:
auno_response_l = requests.get(auno_request_low, timeout=5)
combined_response = []
if auno_response_h:
if auno_response_h.status_code == 200:
combined_response.append(auno_response_h)
if auno_response_l:
if auno_response_l.status_code == 200:
combined_response.append(auno_response_l)
return combined_response
def get_auno_request_url(self, item_id):
return "https://auno.org/ao/db.php?id=%s" % item_id
def get_aoitems_request_url(self, item_id):
return "https://aoitems.com/item/%s/" % item_id
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+232
View File
@@ -0,0 +1,232 @@
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 <highlight>{item_id:d}</highlight>."
@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 <highlight>{ql:d}</highlight> items found matching <highlight>{search}</highlight>."
else:
return f"No items found matching <highlight>{search}</highlight>."
elif cnt == 1:
item = items[0]
return self.format_single_item([item], ql)
else:
blob = ""
# blob += "Version: <highlight>%s</highlight>\n" % "unknown"
if ql:
blob += f"Search: <highlight>QL {ql:d} {search}</highlight>\n"
else:
blob += f"Search: <highlight>{search}</highlight>\n"
blob += "\n"
if page > 1:
blob += " " + 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):
blob += " Page " + str(page)
blob += " " + 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 += "\n"
blob += self.format_items(items, ql)
# 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 += "<pagebreak>"
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 reversed(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 <EXTENDED_LIKE=%d> ?" % len(params)
params.append(name)
if ql:
sql += " AND lowql <= ? AND highql >= ?"
params.append(ql)
params.append(ql)
sql += " ORDER BY name ASC, 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 <myname> items {ql:d} {search} --page={page:d}"
else:
return f"/tell <myname> 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:
break
current_item = item
grouped.append(item)
self.current_index += 1
return grouped
+155
View File
@@ -0,0 +1,155 @@
# noinspection LongLineForFile
DROP TABLE IF EXISTS skills;
CREATE TABLE IF NOT EXISTS skills
(
id INT NOT NULL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
common_name VARCHAR(50) NOT NULL
);
INSERT INTO skills (id, name, common_name)
VALUES (318, '% Add. Nano Cost', ''),
(319, '% Add. Xp', ''),
(102, '1h Blunt', '1 handed blunt'),
(103, '1h Edged', '1 handed edged'),
(107, '2h Blunt', '2 handed blunt'),
(105, '2h Edged', '2 handed edged'),
(277, 'Add All Def.', ''),
(276, 'Add All Off.', ''),
(281, 'Add. Chem. Dam.', ''),
(311, 'Add. Cold Dam.', ''),
(280, 'Add. Energy Dam.', ''),
(316, 'Add. Fire Dam.', ''),
(279, 'Add. Melee Dam.', ''),
(315, 'Add. Nano Dam.', ''),
(317, 'Add. Poison Dam.', ''),
(278, 'Add. Proj. Dam.', ''),
(282, 'Add. Rad. Dam.', ''),
(137, 'Adventuring', ''),
(51, 'Aggdef', ''),
(201, 'Aggressiveness', ''),
(17, 'Agility', ''),
(151, 'Aimed Shot', ''),
(116, 'Assault Rif', 'Assault Rifle'),
(22, 'Attack rating', ''),
(128, 'Bio Metamor', 'Biological Metamorphosis'),
(152, 'Body Dev.', 'Body Development'),
(111, 'Bow', ''),
(121, 'Bow Spc Att', 'Bow Special Attack'),
(142, 'Brawling', ''),
(165, 'Break&Entry', 'Breaking and Entering'),
(148, 'Burst', ''),
(93, 'Chemical AC', ''),
(163, 'Chemistry', ''),
(95, 'Cold AC', ''),
(161, 'Comp. Liter', 'Computer Literacy'),
(164, 'Concealment', ''),
(391, 'Critical Decrease', ''),
(379, 'CriticalIncrease', ''),
(35, 'Damage to Pet', ''),
(39, 'Damage To Pet Damage Multiplier', ''),
(383, 'Decreased Nano-Interrupt Modifier %', ''),
(145, 'Deflect', ''),
(144, 'Dimach', ''),
(536, 'Direct Nano Damage Efficiency', ''),
(96, 'Disease AC', ''),
(154, 'Dodge-Rng', 'Dodge Range'),
(153, 'Duck-Exp', 'Duck Explosives'),
(126, 'Elec. Engi', 'Electrical Engineering'),
(92, 'Energy AC', ''),
(155, 'Evade-ClsC', 'Evade Close Combat'),
(147, 'Fast Attack', ''),
(97, 'Fire AC', ''),
(123, 'First Aid', ''),
(150, 'Fling Shot', ''),
(45, 'Free deck slot', ''),
(167, 'Full Auto', ''),
(109, 'Grenade', ''),
(689, 'Heal Reactivity', ''),
(343, 'HealDelta', ''),
(535, 'Healing Efficiency', ''),
(110, 'Heavy Weapons', ''),
(90, 'Imp/Proj AC', 'Projectile AC'),
(19, 'Intelligence', ''),
(100, 'Martial Arts', ''),
(127, 'Matt.Metam', 'Matter Metamorphosis'),
(130, 'Matter Crea', 'Matter Creation'),
(1, 'Max Health', ''),
(221, 'Max Nano', ''),
(181, 'Max NCU', ''),
(478, 'MaxReflectedChemicalDmg', ''),
(480, 'MaxReflectedColdDmg', ''),
(477, 'MaxReflectedEnergyDmg', ''),
(482, 'MaxReflectedFireDmg', ''),
(476, 'MaxReflectedMeleeDmg', ''),
(481, 'MaxReflectedNanoDmg', ''),
(483, 'MaxReflectedPoisonDmg', ''),
(475, 'MaxReflectedProjectileDmg', ''),
(479, 'MaxReflectedRadiationDmg', ''),
(125, 'Mech. Engi', 'Mechanical Engineering'),
(104, 'Melee Ener.', 'Melee Energy'),
(118, 'Melee. Init.', 'Melee Initiative'),
(91, 'Melee/ma AC', 'Melee/Martial Arts AC'),
(114, 'MG / SMG', ''),
(101, 'Mult. Melee', 'Multi Melee'),
(134, 'Multi Ranged', ''),
(132, 'Nano Pool', ''),
(160, 'Nano Progra', 'Nano Programming'),
(168, 'Nano Resist', ''),
(149, 'NanoC. Init.', 'Nano Cast Initiative'),
(364, 'NanoDelta', 'Nano Delta'),
(136, 'Perception', ''),
(159, 'Pharma Tech', ''),
(120, 'Physic. Init', 'Physical Initiative'),
(106, 'Piercing', ''),
(112, 'Pistol', ''),
(21, 'Psychic', ''),
(129, 'Psycho Modi', 'Pychological Modifications'),
(162, 'Psychology', ''),
(157, 'Quantum FT', ''),
(94, 'Radiation AC', ''),
(133, 'Ranged Ener', 'Ranged Energy'),
(119, 'Ranged. Init.', 'Ranged Initiative'),
(381, 'RangeInc. NF', 'Ranged Increase Nano'),
(380, 'RangeInc. Weapon', 'Ranged Increase Weapon'),
(208, 'ReflectChemicalAC', ''),
(217, 'ReflectColdAC', ''),
(207, 'ReflectEnergyAC', ''),
(219, 'ReflectFireAC', ''),
(206, 'ReflectMeleeAC', ''),
(218, 'ReflectNanoAC', ''),
(225, 'ReflectPoisonAC', ''),
(205, 'ReflectProjectileAC', ''),
(216, 'ReflectRadiationAC', ''),
(593, 'Regain XP Percentage', ''),
(113, 'Rifle', ''),
(143, 'Riposte', ''),
(156, 'Run Speed', ''),
(360, 'Scale', ''),
(20, 'Sense', ''),
(122, 'Sensory Impr', 'Sensory Improvement'),
(108, 'Sharp Obj', 'Sharp Objects'),
(229, 'ShieldChemicalAC', ''),
(231, 'ShieldColdAC', ''),
(228, 'ShieldEnergyAC', ''),
(233, 'ShieldFireAC', ''),
(227, 'ShieldMeleeAC', ''),
(232, 'ShieldNanoAC', ''),
(234, 'ShieldPoisonAC', ''),
(226, 'ShieldProjectileAC', ''),
(230, 'ShieldRadiationAC', ''),
(115, 'Shotgun', ''),
(382, 'SkillLockModifier', ''),
(146, 'Sneak Atck', 'Sneak Attack'),
(18, 'Stamina', ''),
(16, 'Strength', ''),
(138, 'Swimming', ''),
(131, 'Time&Space', ''),
(135, 'Trap Disarm.', ''),
(124, 'Treatment', ''),
(141, 'Tutoring', ''),
(180, 'Used NCU', ''),
(139, 'Vehicle Air', ''),
(166, 'Vehicle Ground', ''),
(117, 'Vehicle Water', ''),
(158, 'Weapon Smt', 'Weapon Smithing');
@@ -0,0 +1,144 @@
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Options
from core.db import DB
from core.decorators import instance, command
from core.text import Text
@instance()
class WhatBuffsController:
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 + "/" + "item_buffs.sql", pre_optimized=True)
self.db.create_view("item_buffs")
self.db.load_sql_file(self.module_dir + "/" + "item_types.sql", pre_optimized=True)
self.db.create_view("item_types")
self.db.load_sql_file(self.module_dir + "/" + "skills.sql", pre_optimized=True)
self.db.create_view("skills")
def start(self):
self.command_alias_service.add_alias("buffs", "whatbuffs")
@command(command="whatbuffs", params=[], access_level="member",
description="Find items or nanos that buff a skill (or ability)")
def whatbuffs_list_cmd(self, _):
data = self.db.query("SELECT name FROM skills ORDER BY name")
blob = ""
for row in data:
blob += self.text.make_tellcmd(row.name, f"whatbuffs {row.name}") + "\n"
blob += self.get_footer()
return ChatBlob("Whatbuffs Skill List", blob)
@command(command="whatbuffs",
params=[
Any("skill"),
Options(["arms", "back", "chest", "deck", "feet", "fingers",
"hands", "head", "hud", "legs", "nanoprogram",
"neck", "shoulders", "unknown", "util", "weapon", "wrists", "all"])],
access_level="member",
description="Find items or nanos that buff a skill (or ability) for a particular item type")
def whatbuffs_detail_cmd(self, _, skill_name, item_type):
item_type = item_type.capitalize()
return self.show_search_results(item_type, skill_name)
@command(command="whatbuffs", params=[Any("skill")], access_level="member",
description="Find items or nanos that buff a skill (or ability)")
def whatbuffs_skill_cmd(self, _, skill_name):
skills = self.search_for_skill(skill_name)
if len(skills) == 0:
return "Could not find skill <highlight>%s</highlight>." % skill_name
elif len(skills) == 1:
skill = skills.pop()
data = self.db.query("SELECT i.item_type, COUNT(1) AS cnt "
"FROM aodb "
"JOIN item_types i ON aodb.highid = i.item_id "
"JOIN item_buffs b ON aodb.highid = b.item_id "
"JOIN skills s ON b.attribute_id = s.id "
"WHERE s.id = ? "
"GROUP BY item_type "
"HAVING cnt > 0 "
"ORDER BY item_type", [skill.id])
blob = ""
total_count = 0
for row in data:
total_count += row.cnt
for row in data:
blob += f'{self.text.zfill(row.cnt, total_count)} - ' \
f'{self.text.make_tellcmd(row.item_type, f"whatbuffs {skill.name} {row.item_type}")}\n'
blob += self.get_footer()
return ChatBlob(f"Whatbuffs {skill.name} - Choose Type", blob)
else:
blob = "Choose a skill:\n\n"
for skill in skills:
blob += self.text.make_tellcmd(skill.name, f"whatbuffs {skill.name}") + "\n"
blob += self.get_footer()
return ChatBlob("Whatbuffs - Choose Skill", blob)
def search_for_skill(self, skill_name):
skill_name = skill_name.lower()
data = self.db.query(
"SELECT id, name FROM skills WHERE name <EXTENDED_LIKE=0> ? OR common_name <EXTENDED_LIKE=1> ?",
[skill_name, skill_name], extended_like=True)
# check for exact match first, in order to disambiguate between Bow and Bot Special Attack
for row in data:
if row.name.lower() == skill_name:
return [row]
return data
def show_search_results(self, item_type, skill_name):
skills = self.search_for_skill(skill_name)
if len(skills) == 0:
return "Could not find skill <highlight>%s</highlight>." % skill_name
elif len(skills) == 1:
skill = skills.pop()
return self.get_search_results(item_type, skill)
else:
blob = ""
for skill in skills:
blob += self.text.make_tellcmd(skill.name, "whatbuffs %s %s" % (skill.name, item_type)) + "\n"
return ChatBlob("Whatbuffs - Choose Skill", blob)
def get_search_results(self, item_type, skill):
data = self.db.query("SELECT aodb.*, b.amount, i.item_type "
"FROM aodb "
"JOIN item_types i ON aodb.highid = i.item_id "
"JOIN item_buffs b ON aodb.highid = b.item_id "
"JOIN skills s ON b.attribute_id = s.id "
"WHERE i.item_type LIKE ? AND s.id = ? "
"ORDER BY item_type, amount DESC", ["%" if item_type == "All" else item_type, skill.id])
if len(data) == 0:
return f"No items found of type <highlight>{item_type}</highlight> " \
f"that buff <highlight>{skill.name}</highlight>."
else:
current_item_type = ""
blob = ""
for row in data:
if current_item_type != row.item_type:
blob += f"\n\n<header2>{row.item_type}</header2>\n"
current_item_type = row.item_type
blob += f"{self.text.zfill(row.amount, data[0].amount)} - " \
f"{self.text.make_item(row.lowid, row.highid, row.highql, row.name)}\n"
blob += self.get_footer()
return ChatBlob(f"Whatbuffs - {skill.name} {item_type} ({len(data):d})", blob)
def get_footer(self):
return "\nItem DB Extraction Info provided by Unk"