diff options
-rw-r--r-- | .editorconfig | 7 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
m--------- | NotEnoughUpdates-REPO | 0 | ||||
-rw-r--r-- | Pipfile | 16 | ||||
-rw-r--r-- | Pipfile.lock | 178 | ||||
-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 |
13 files changed, 453 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9155a4f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.py] +max_line_length = 180 +end_of_line = lf +insert_final_newline = true +charset = utf-8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe3edea --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.mypy_cache +__pycache__ +build/ +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cb24a04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "NotEnoughUpdates-REPO"] + path = NotEnoughUpdates-REPO + url = https://github.com/NotEnoughUpdates/NotEnoughUpdates-REPO/ diff --git a/NotEnoughUpdates-REPO b/NotEnoughUpdates-REPO new file mode 160000 +Subproject d93e124a64dd4da6dcae45f8c1857b2c4ad6a1d @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" +mwparserfromhell = "*" +click = "*" +questionary = "*" + +[dev-packages] +mypy = "*" + +[requires] +python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..9480071 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,178 @@ +{ + "_meta": { + "hash": { + "sha256": "2f97b2115fdfc808a8b0d6e47b772ed9a39aa5e4a87cf2c2846103de8d070412" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "markers": "python_version >= '3'", + "version": "==2.0.12" + }, + "click": { + "hashes": [ + "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1", + "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb" + ], + "index": "pypi", + "version": "==8.0.4" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "mwparserfromhell": { + "hashes": [ + "sha256:0519497b8a7472298324ef92e1e82c1ab5cab85b4d64462d7ae46c4464c8b872", + "sha256:0c2bbb36110410b5f6d6d8b2f35f65f8ec8f57c0477609d35bcaac4784a59e5a", + "sha256:18dac5162471c38e5bbf6ee3698c49d2753e8dde372864112fdaf81047ce89d3", + "sha256:1c908c9738c5c6bce04b825b3f95592d971ff439ace294a86fc758070afc6d0c", + "sha256:203ad9cd78dec7480fde45c9f49d0bc2a2eaa28fa1b585461fb9f56f6587f46c", + "sha256:2d6e124396ee41c35ea12017a66c560abb1f7f51bee04e631a149318adaf15e2", + "sha256:2d983914c19dee5c2a13298b1ccd3a1ed2b65c81d322b7e7df99cd5386a460c6", + "sha256:3e5f4bb96b68557acd14c4baa62cbe440b6e6d0f5263cb4860d37e1ceeada2a7", + "sha256:42eb94a6bad20b7f8845fd2900a45373cb4d414d5a357b27457c7c6c259115c5", + "sha256:511ff847cddb8e7014b6afb0af5dbdb5cf05ada67e31fc39efa34fbbdccb8e8b", + "sha256:54d9819c11530fc00b8a70fa3508898109b3df72336f7b8e52f8faffbe03ee88", + "sha256:5dfd7f57fa3d516b21790ef7f6094119082baa2e6072cef78fb9f999b77e674f", + "sha256:60d86c8d3501edc1331b37df72b74689ee392da077c36a8b453460b8e3714cdd", + "sha256:7c822985760b9e82857ecfb99dbb60ac35d0ebf7b2977a0215c7c56fe70c2b68", + "sha256:7f5682ab9e1a55b20e9fb669582493d196c76a512276456848153c39d726d7d2", + "sha256:82010e5b5da130cbcb002747f5592ffca73488e0e9cf1ebdfef6e8559c535c41", + "sha256:92bec9528ae34d272893ccaf2b527df85c314ff28cfbb3056340467b095d834c", + "sha256:a344ceabde013aa2f9b23494e73af11b99795f63a30124e955c185de2c8ae397", + "sha256:abae1052b9c12a8814c76dd26ec9cfdd71102e7f89c28fb58a5fba7ee55ad1bc", + "sha256:b7f19e7d064c467f32e0704becd81c841a807335934134d6aa859d98d01c7cf3", + "sha256:cd0a74474ed6e85808c874511d28a253ffd2d1e5a3abe915705a25804212ac73", + "sha256:d521a2e3787c83ecf607a7806ae655d32f3c3884b2dcf35a388183c6028ddce4", + "sha256:d63d76f576e133c14a29f1ad2f3fc2afa17b74945ebc017e8d7d3bcb59f5243c", + "sha256:da2568e2a492dcc913f1c026434371af7a24e05dc56450c3ab063d9e580b48f2", + "sha256:dd81220d66bf829664a6f911b3f58e7af3061fd7fdee68c0fc9731f5bcd7519d", + "sha256:e0c3d3bc409f8ac1221639ee2dab0dc830711d9a56a39014aad2824c2c98b3e2", + "sha256:f8c450c39ef647678831ecf9a1f8236521d369afc4ae59a9c601d07f298eda35", + "sha256:fc4f5718e761a3f5ad76eb9089e0792ed3a6786095abe098e37e7ac7af76afef" + ], + "index": "pypi", + "version": "==0.6.4" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:30129d870dcb0b3b6a53efdc9d0a83ea96162ffd28ffe077e94215b233dc670c", + "sha256:9f1cd16b1e86c2968f2519d7fb31dd9d669916f515612c269d14e9ed52b51650" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.28" + }, + "questionary": { + "hashes": [ + "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90", + "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90" + ], + "index": "pypi", + "version": "==1.10.0" + }, + "requests": { + "hashes": [ + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + ], + "index": "pypi", + "version": "==2.27.1" + }, + "urllib3": { + "hashes": [ + "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed", + "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", + "version": "==1.26.8" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + } + }, + "develop": { + "mypy": { + "hashes": [ + "sha256:0b52778a018559a256c819ee31b2e21e10b31ddca8705624317253d6d08dbc35", + "sha256:0fdc9191a49c77ab5fa0439915d405e80a1118b163ab03cd2a530f346b12566a", + "sha256:13677cb8b050f03b5bb2e8bf7b2668cd918b001d56c2435082bbfc9d5f730f42", + "sha256:1903c92ff8642d521b4627e51a67e49f5be5aedb1fb03465b3aae4c3338ec491", + "sha256:1f66f2309cdbb07e95e60e83fb4a8272095bd4ea6ee58bf9a70d5fb304ec3e3f", + "sha256:2dba92f58610d116f68ec1221fb2de2a346d081d17b24a784624389b17a4b3f9", + "sha256:2efd76893fb8327eca7e942e21b373e6f3c5c083ff860fb1e82ddd0462d662bd", + "sha256:3ac14949677ae9cb1adc498c423b194ad4d25b13322f6fe889fb72b664c79121", + "sha256:471af97c35a32061883b0f8a3305ac17947fd42ce962ca9e2b0639eb9141492f", + "sha256:51be997c1922e2b7be514a5215d1e1799a40832c0a0dee325ba8794f2c48818f", + "sha256:628f5513268ebbc563750af672ccba5eef7f92d2d90154233edd498dfb98ca4e", + "sha256:68038d514ae59d5b2f326be502a359160158d886bd153fc2489dbf7a03c44c96", + "sha256:6eab2bcc2b9489b7df87d7c20743b66d13254ad4d6430e1dfe1a655d51f0933d", + "sha256:712affcc456de637e774448c73e21c84dfa5a70bcda34e9b0be4fb898a9e8e07", + "sha256:71bec3d2782d0b1fecef7b1c436253544d81c1c0e9ca58190aed9befd8f081c5", + "sha256:83f66190e3c32603217105913fbfe0a3ef154ab6bbc7ef2c989f5b2957b55840", + "sha256:8aaf18d0f8bc3ffba56d32a85971dfbd371a5be5036da41ac16aefec440eff17", + "sha256:a0e5657ccaedeb5fdfda59918cc98fc6d8a8e83041bc0cec347a2ab6915f9998", + "sha256:a168da06eccf51875fdff5f305a47f021f23f300e2b89768abdac24538b1f8ec", + "sha256:b1a116c451b41e35afc09618f454b5c2704ba7a4e36f9ff65014fef26bb6075b", + "sha256:b2fa5f2d597478ccfe1f274f8da2f50ea1e63da5a7ae2342c5b3b2f3e57ec340", + "sha256:d9d7647505bf427bc7931e8baf6cacf9be97e78a397724511f20ddec2a850752", + "sha256:f8fe1bfab792e4300f80013edaf9949b34e4c056a7b2531b5ef3a0fb9d598ae2" + ], + "index": "pypi", + "version": "==0.940" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.1" + } + } +} 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() + } |