diff options
author | nea <romangraef@gmail.com> | 2022-03-12 01:57:57 +0100 |
---|---|---|
committer | nea <romangraef@gmail.com> | 2022-03-12 01:57:57 +0100 |
commit | e7caa7a9ba0202c44ad02ea9fd37c27bd4336c26 (patch) | |
tree | 03bce4042c47e475a99dfe90bc1e7a31afe25358 /sbdata | |
download | sbdata-e7caa7a9ba0202c44ad02ea9fd37c27bd4336c26.tar.gz sbdata-e7caa7a9ba0202c44ad02ea9fd37c27bd4336c26.tar.bz2 sbdata-e7caa7a9ba0202c44ad02ea9fd37c27bd4336c26.zip |
Initial commit
Diffstat (limited to 'sbdata')
-rw-r--r-- | sbdata/__init__.py | 1 | ||||
-rw-r--r-- | sbdata/__main__.py | 36 | ||||
-rw-r--r-- | sbdata/repo.py | 61 | ||||
-rw-r--r-- | sbdata/task.py | 64 | ||||
-rw-r--r-- | sbdata/tasks.py | 55 | ||||
-rw-r--r-- | sbdata/util.py | 13 | ||||
-rw-r--r-- | sbdata/wiki.py | 15 |
7 files changed, 245 insertions, 0 deletions
diff --git a/sbdata/__init__.py b/sbdata/__init__.py new file mode 100644 index 0000000..572b454 --- /dev/null +++ b/sbdata/__init__.py @@ -0,0 +1 @@ +from sbdata import tasks as _ diff --git a/sbdata/__main__.py b/sbdata/__main__.py new file mode 100644 index 0000000..596a51f --- /dev/null +++ b/sbdata/__main__.py @@ -0,0 +1,36 @@ +import dataclasses +import json +import sys +from typing import Any + +import questionary + +from sbdata.repo import Item +from sbdata.task import Arguments, tasks + + +class ObjectEncoder(json.JSONEncoder): + + def default(self, o: Any) -> Any: + if isinstance(o, Item): + return o.internalname + if dataclasses.is_dataclass(o): + return o.__dict__ + return super().default(o) + + +def main(): + args = Arguments(sys.argv) + task = args.get_value( + 'Task', tasks.get(args.task), + questionary.select('Which task do you want to execute?', choices=[ + questionary.Choice(task.label, task) for task in tasks.values() + ])) + print("Selected task: " + task.label) + data = task.run(args) + if args.has_flag('json'): + print(json.dumps(data, cls=ObjectEncoder)) + + +if __name__ == '__main__': + main() diff --git a/sbdata/repo.py b/sbdata/repo.py new file mode 100644 index 0000000..475f2a6 --- /dev/null +++ b/sbdata/repo.py @@ -0,0 +1,61 @@ +import dataclasses +import json +import os +import pathlib +import re +import typing + +item_list = {} +repo_dir = pathlib.Path(os.environ.get('REPO_DIR', 'NotEnoughUpdates-REPO')) + + +@dataclasses.dataclass +class Item: + displayname: str + itemid: str + internalname: str + lore: list[str] + + +def unformat_name(name: str) -> str: + return re.sub('ยง.', '', name) + + +def bare_name(name: str) -> str: + name = unformat_name(name).replace("'", '').lower() + if name.startswith('ultimate'): + name = name[8:] + return name.strip() + + +def load_item(item: pathlib.Path): + with item.open('r') as fp: + data = json.load(fp) + item_list[data['internalname']] = Item(data['displayname'], data['itemid'], data['internalname'], data['lore']) + + +def find_item_by_name(name: str) -> typing.Optional[Item]: + name = bare_name(name) + pot = [item for item in item_list.values() + if item.internalname.casefold() == name + or bare_name(item.displayname) in name + or (item.itemid == 'minecraft:enchanted_book' + and name in bare_name(item.lore[0]))] + if pot: + return pot[0] + return None + + +def load_items(): + item_dir = repo_dir / 'items' + for item in item_dir.iterdir(): + if item.name.endswith('.json'): + load_item(item) + + +def load_repo_data(): + item_list.clear() + load_items() + + +load_repo_data() diff --git a/sbdata/task.py b/sbdata/task.py new file mode 100644 index 0000000..7011ca2 --- /dev/null +++ b/sbdata/task.py @@ -0,0 +1,64 @@ +import dataclasses +import os +import sys +import typing + +import questionary + +_T = typing.TypeVar('_T') + + +class Arguments: + + def __init__(self, args: list[str]): + self.prog = args[0] + self.args: typing.Dict[str, str] = {} + self.flags: list[str] = [] + self.no_prompt = os.environ.get('PROMPT') == 'NO_PROMPT' + self.task: typing.Optional[str] + self.task = None + last_arg = None + for arg in args: + if last_arg is None: + if arg.startswith('--'): + last_arg = arg[2:] + elif arg.startswith('-'): + self.flags.append(arg[1:]) + elif arg.startswith(':'): + self.task = arg[1:] + else: + print("Unknown arg: " + arg) + else: + self.args[last_arg] = arg + last_arg = None + + def get_value(self, label: str, value: _T, question: questionary.Question) -> _T: + if value is None: + if self.no_prompt: + print('No argument present for ' + label) + sys.exit(1) + return question.ask() + return value + + def has_flag(self, param: str) -> bool: + return param in self.flags + + +@dataclasses.dataclass +class Task: + label: str + name: str + run: typing.Callable + + +tasks = {} + +TASK_TYPE = typing.Callable[[Arguments], None] + + +def register_task(label: str) -> typing.Callable[[TASK_TYPE], TASK_TYPE]: + def d(func: TASK_TYPE) -> TASK_TYPE: + tasks[func.__name__] = Task(label, func.__name__, func) + return func + + return d diff --git a/sbdata/tasks.py b/sbdata/tasks.py new file mode 100644 index 0000000..8ac1f8c --- /dev/null +++ b/sbdata/tasks.py @@ -0,0 +1,55 @@ +import ast +import dataclasses +import json +import re +import sys +import typing + +from sbdata.repo import find_item_by_name, Item +from sbdata.task import register_task, Arguments +from sbdata.wiki import get_wiki_sources_by_title + + +@dataclasses.dataclass +class DungeonDrop: + item: Item + floor: int + chest: str + drop_chances: dict[str, str] + + def get_drop_chance(self, has_s_plus: bool, talisman_level: int, boss_luck: int): + drop_identifier = "S" + ('+' if has_s_plus else '') + 'ABCD'[talisman_level] + str(len([i for i in [0, 1, 3, 5, 10] if i >= boss_luck])) + return self.drop_chances.get(drop_identifier) + + +@register_task("Fetch Dungeon Loot") +def fetch_dungeon_loot(args: Arguments): + items = [] + for floor in get_wiki_sources_by_title(*[f'Template:Catacombs Floor {f} Loot Master' for f in ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']]).values(): + for template in floor.filter_templates(): + if template.name.strip() == 'Dungeon Chest Table/Row': + item = None + ifloor = None + chest = None + drop_chances = {} + + for param in template.params: + attr_name = param.name.nodes[0].strip() + attr_value = param.value.nodes[0].strip() + if attr_name == 'item': + if item is None: + item = find_item_by_name(attr_value) + elif attr_name == 'customlink': + if item is None: + item = find_item_by_name(attr_value.split('#')[-1]) + elif attr_name == 'chest': + chest = attr_value + elif attr_name == 'floor': + ifloor = int(attr_value) + elif attr_name.startswith("S"): + drop_chances[attr_name] = attr_value + if item is None or ifloor is None or chest is None: + print('WARNING: Missing data for item: ' + str(template)) + else: + items.append(DungeonDrop(item, ifloor, chest, drop_chances)) + return items diff --git a/sbdata/util.py b/sbdata/util.py new file mode 100644 index 0000000..87a3098 --- /dev/null +++ b/sbdata/util.py @@ -0,0 +1,13 @@ +import functools +from typing import TypeVar, Generator, ParamSpec, Callable + +_Param = ParamSpec('_Param') +_RetType = TypeVar('_RetType') + + +def no_generator(func: Callable[_Param, Generator[_RetType, None, None]]) -> Callable[_Param, list[_RetType]]: + @functools.wraps(func) + def wrapper(*args, **kwargs) -> list[_RetType]: + return list(func(*args, **kwargs)) + + return wrapper diff --git a/sbdata/wiki.py b/sbdata/wiki.py new file mode 100644 index 0000000..7e029a5 --- /dev/null +++ b/sbdata/wiki.py @@ -0,0 +1,15 @@ +import urllib.parse +import requests +import mwparserfromhell + + +def get_wiki_sources_by_title(*page_titles: str, wiki_host: str = 'wiki.hypixel.net') -> dict[str, mwparserfromhell.wikicode.Wikicode]: + prepared_titles = "|".join(map(urllib.parse.quote, page_titles)) + api_data = requests.get(f'https://{wiki_host}/api.php?action=query&prop=revisions&titles={prepared_titles}&rvprop=content&format=json&rvslots=main').json() + if "batchcomplete" not in api_data: + print(f'Batch data not present in wiki response for: {page_titles}') + + return { + page['title']: mwparserfromhell.parse(page['revisions'][0]['slots']['main']['*']) + for _, page in api_data["query"]["pages"].items() + } |