diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..002e766 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,30 @@ +name: Deploy docs +on: + push: + branches: + - dev + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Setup Python + uses: actions/setup-python@v2.2.2 + - name: Build pages + run: | + pip install pdoc3 requests + pdoc --html runescapeapi + - name: Publish pages + working-directory: ./html/runescapeapi + run: | + git init . + git checkout -B gh-pages + git config user.name "actions" + git config user.email "actions@github.com" + git add . + git commit -am "Deployed docs for commit ${{github.sha}}" + git remote add origin https://${{github.repository_owner}}:${{secrets.GITHUB_TOKEN}}@github.com/${{github.repository}} + git push origin gh-pages --force \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecd555c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vscode +build +dist +venv +runescapeapi.egg-info +*.pyc +html \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..da7141a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Raatty + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 86604e4..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include *.txt \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..52502ed --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# RuneScape API + +``` +pip install runescapeapi +``` \ No newline at end of file diff --git a/makefile b/makefile deleted file mode 100644 index 6bbe663..0000000 --- a/makefile +++ /dev/null @@ -1,11 +0,0 @@ -SHELL = bash - -install: venv - source venv/bin/activate; \ - python3 setup.py install; \ - -clean: - rm -rf venv build dist runescapeapi.egg-info - -venv: - python3 -m venv venv diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5a3c46 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/readme.txt b/readme.txt deleted file mode 100644 index 9f6fd83..0000000 --- a/readme.txt +++ /dev/null @@ -1,443 +0,0 @@ -Help on module runescapeapi: - -NAME - runescapeapi - -DESCRIPTION - A slightly memes wrapper for the runescape api, hopefully helps people - best way to contact me with any problems is on discord raatty#3522 - read the rest of the docs for module usage step one is - import runescapeapi as runescape - the code in here might not be done in the best way but everything you - need should be there - -CLASSES - builtins.object - Beasts - Clan - GrandExchange - Highscores - Player - _Wikia(builtins.object) - Rs3Wikia - osrsWikia - - class Beasts(builtins.object) - | a bunch of methods for getting stats and seaching for monsters - | - | Static methods defined here: - | - | area_names() - | returns a list of area names - | usage: - | runescape.Beasts.area_names() - | it returns the same list every time so you should only need to call this - | once in your code - | - | by_area(area:str) - | returns a list of beasts in a specific area - | see runescape.Beasts.area_names() for the list - | usage: - | runescape.Beasts.by_area('Rat pits') - | gives you a list of all the beasts in the rat pits - | - | by_category(cat) - | returns a list of monsters of a given slayer category - | input a category id as an int or a str of the category name - | searching by string will take longer tho - | see runescape.Beasts.category_names() for categorys - | - | by_id(id:int) - | returns stats and info about a beast by id - | usage: - | runescape.Beasts.by_id(47) - | returns information about level 1 rats - | to get the ids use runescape.Beasts.search(name) - | - | by_letter(letter:str) - | list of beasts starting with a letter - | usage: - | runescape.Beasts.by_letter('r') - | will give a list of all the beasts starting with 'r' - | - | by_level(lower_:int, upper_:int) - | returns a list of beasts within a given level range, takes - | two arguments both ints first argument is the lowest level - | you want to see and the second is the highest level - | you can use 1 for the first and a high number like 10000 - | for the second to get a list of all the beasts - | - | by_weakness(weakness) - | returns a list of beasts with a given weakness, input a weakness - | id or a weakness name searching by name will take longer tho - | see runescape.Beasts.weakness_names() for weaknesses - | - | category_names() - | returns a list of slayer categorys - | - | search(query:str) - | searches the beast database for a name and returns a list seperate with + - | usage: - | runescape.Beasts.search('rat+cat') - | will give you a list of all the beasts that have rat or cat in their name - | also that gives you the id - | - | weakness_names() - | returns a list of weaknesses - | - | ---------------------------------------------------------------------- - | Data descriptors defined here: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - - class Clan(builtins.object) - | Gets a list of members in a clan - | usage: - | list(runescape.Clan('empire of elitez')) - | or - | for member in runescape.Clan('empire of elitez') - | #do stuff - | - | Methods defined here: - | - | __init__(self, clan:str) - | Initialize self. See help(type(self)) for accurate signature. - | - | __iter__(self) - | - | __len__(self) - | - | adminish(self) - | will give a list of the adminish ranks in clan - | (the ones that kind of look like little badges like admin rank) - | usage: - | list(runescape.Clan('empire of elitez').adminish()) - | or - | for member in runescape.Clan('empire of elitez').adminish(): - | #do stuff - | - | bannanas(self) - | will give a list of the banana ranks in clan - | usage: - | list(runescape.Clan('empire of elitez').bananas()) - | or - | for member in runescape.Clan('empire of elitez').bananas(): - | #do stuff - | - | keys(self) - | will give a list of the key ranks in clan - | usage: - | list(runescape.Clan('empire of elitez').keys()) - | or - | for member in runescape.Clan('empire of elitez').keys(): - | #do stuff - | - | rank(self, rank:str) - | will give a list of the members with a specific rank in a clan - | usage: - | list(runescape.Clan('empire of elitez').rank('Owner')) - | or - | for member in runescape.Clan('empire of elitez').rank('Owner'): - | #do stuff - | - | stars(self) - | will give a list of the star ranks in clan - | usage: - | list(runescape.Clan('empire of elitez').stars()) - | or - | for member in runescape.Clan('empire of elitez').stars(): - | #do stuff - | - | ---------------------------------------------------------------------- - | Data descriptors defined here: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - | - | ---------------------------------------------------------------------- - | Data and other attributes defined here: - | - | CLAN_MEM_URL = 'http://services.runescape.com/m=clan-hiscores/members_... - | - | CLAN_MOTIF_URL = 'http://services.runescape.com/m=avatar-rs/{}/clanmot... - - class GrandExchange(builtins.object) - | provides methods for fetching information about items in the grandexchange - | - | Static methods defined here: - | - | cat_count(id:int) - | tells you how many items there are for each letter in a category - | give the id from 0 to 37 check runescape.GrandExchange.CATEGORYS for ids - | usage: - | runescape.cat_count(12) - | will display the letter counts of the food and drink category - | yes thats where you can find cheese! - | - | graph(id:int) - | gets info on the prices over time - | usage: - | runescape.GrandExchange.graph(1985) - | that ^ will give you data plot points to create a graph - | of cheese over time - | - | item(id:int) - | gets information on a single item usage - | usage: - | runescape.GrandExchange.item(1985) - | that ^ will give you information about Cheese - | - | iter_category(category:int, page_sleep:int=0) - | iterates through an entire category in put an int from 0 to 37 - | if you start getting errors use the second argument that is how many seconds - | to sleep between pages default is 0 whitch will be fine for short bursts - | anouther way to use this is: - | ge = [c for i in range(0,38) for c in runescape.GrandExchange.iter_category(i, 5)] - | to get the full grand exchange in one list, will take some time tho - | so its best to only be getting one category at a time - | regurlar usage: - | for i in runescape.GrandExchange.iter_category(12, 1): - | #do stuff - | - | iter_letter(letter:str, category:int, page_sleep:int=0) - | iterates through a letter (a-z or #(for number)) in a category - | see runescape.GrandExchange.CATEGORYS for categorys - | usage: - | for item in runescape.GrandExchange.iter_leter('c', 12, 1): - | #do stuff (find cheese maybe?) - | that loop will go through all the items in the food and drink - | category that start with the letter 'c' will also sleep for a second - | each grand exchange page to be nice to the runescape servers and to reduce - | errors - | - | ---------------------------------------------------------------------- - | Data descriptors defined here: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - | - | ---------------------------------------------------------------------- - | Data and other attributes defined here: - | - | CATEGORYS = {'ammo': 1, 'arrows': 2, 'bolts': 3, 'construction materia... - | - | LETTER_URL = 'http://services.runescape.com/m=itemdb_rs/api/catalogue/... - - class Highscores(builtins.object) - | fetches highscores and gives em back as a list of dicts - | 6 different types hiscore, hiscore_ironman, hiscore_hardcore_ironman - | 'hiscore_oldschool', 'hiscore_oldschool_ironman', - | 'hiscore_oldschool_ultimate - | this class takes two arguments rsn and type 'hiscore' is the default - | usage: - | person = runescape.Highscores('raatty', 'hiscore') - | or person = runescape.Highscores('raatty') would give same effect - | person.skills will give a list of skills in game order - | person.total will give you the total - | person.rsn in case you forget who you looked up - | - | Methods defined here: - | - | __init__(self, rsn:str, type_:str=None) - | Initialize self. See help(type(self)) for accurate signature. - | - | skill(self, name) - | - | ---------------------------------------------------------------------- - | Data descriptors defined here: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - | - | ---------------------------------------------------------------------- - | Data and other attributes defined here: - | - | HIGHSCORES_URL = 'http://services.runescape.com/m={}/index_lite.ws?pla... - | - | SKILL_NAMES = {'Agility': 5, 'Attack': 1, 'Constitution': 2, 'Construc... - - class Player(builtins.object) - | will look up lots about a player - | the required argument is the rsn as a string but - | there is also a second argument auto_fetch which - | is of type bool, delfault is False but if it is - | True this class will auto maticly visit all the different - | links to get information but if its left blank or set - | to False the data will just be fetched as needed - | LookupError will be raised if the rsn does not exist - | usage: - | me = runescape.Player('raatty', True) - | - | Methods defined here: - | - | __init__(self, rsn:str, auto_fetch:bool=False) - | Initialize self. See help(type(self)) for accurate signature. - | - | alog(self) - | returns a list of all the activitys on a persons alog or - | an empty list for private people - | usage: - | me.alog() - | - | clan(self) - | returns the clan name if they are in one - | usage: - | me.clan() - | - | combat(self) - | returns the combat level either strait from - | runemetics or calculated from highscores for - | private people - | usage: - | me.combat() - | - | forum_pic(self) - | returns a url of the forum picture - | usage: - | me.forum_pic() - | - | overall_total(self) - | returns total level, total xp and rank - | usage: - | me.overall_total() - | - | quest(self, name) - | searches throught the quest list then returns the quest - | that matches that name - | usage: - | me.quest('rat catchers') - | - | quest_list(self) - | returns a list of all the quests and the lookuped players - | progree in them - | usage: - | me.quest_list() - | - | quest_summary(self) - | returns the counts of the started finished and not started quests - | or None for thease three values if they are private - | usage: - | me.quest_summary() - | - | rsn(self) - | will return the rsn of the looked up player - | correctly capitalised if their runemetics wasnt private - | usage: - | me.rsn() - | - | stats(self) - | returns a list of a persons levels - | usage: - | me.stats() - | - | title(self) - | returns the title and weather its a suffix - | usage: - | me.title() - | - | ---------------------------------------------------------------------- - | Data descriptors defined here: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - | - | ---------------------------------------------------------------------- - | Data and other attributes defined here: - | - | CLAN_AND_TITLE_URL = 'http://services.runescape.com/m=website-data/pla... - | - | FORUM_PIC_URL = 'http://services.runescape.com/m=avatar-rs/{}/chat.png... - | - | RUNEMETRICS_BASE_URL = 'https://apps.runescape.com/runemetrics/' - | - | RUNE_METRICS_QUESTS_URL = 'https://apps.runescape.com/runemetrics/ques... - | - | RUNE_METRICS_URL = 'https://apps.runescape.com/runemetrics/profile/pro... - - class Rs3Wikia(_Wikia) - | methords for exploring the rs3 wikia - | - | Method resolution order: - | Rs3Wikia - | _Wikia - | builtins.object - | - | Methods defined here: - | - | __init__(self) - | Initialize self. See help(type(self)) for accurate signature. - | - | ---------------------------------------------------------------------- - | Methods inherited from _Wikia: - | - | isearch(self, searchTerm:str) - | returns a generator that iterates though the search results pages - | - | page(self, pageName:str) - | returns an instance of a wikia page - | - | search(self, searchTerm:str) - | just returns a list of search results from the wikia - | - | ---------------------------------------------------------------------- - | Data descriptors inherited from _Wikia: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - - class osrsWikia(_Wikia) - | methods for exploring the osrs wikia - | - | Method resolution order: - | osrsWikia - | _Wikia - | builtins.object - | - | Methods defined here: - | - | __init__(self) - | Initialize self. See help(type(self)) for accurate signature. - | - | ---------------------------------------------------------------------- - | Methods inherited from _Wikia: - | - | isearch(self, searchTerm:str) - | returns a generator that iterates though the search results pages - | - | page(self, pageName:str) - | returns an instance of a wikia page - | - | search(self, searchTerm:str) - | just returns a list of search results from the wikia - | - | ---------------------------------------------------------------------- - | Data descriptors inherited from _Wikia: - | - | __dict__ - | dictionary for instance variables (if defined) - | - | __weakref__ - | list of weak references to the object (if defined) - -DATA - BASE_URL = 'http://services.runescape.com/' - diff --git a/runescapeapi/__init__.py b/runescapeapi/__init__.py index 433563a..e776397 100644 --- a/runescapeapi/__init__.py +++ b/runescapeapi/__init__.py @@ -1 +1 @@ -from .runescapeapi import * +BASE_URL: str = "https://services.runescape.com/" diff --git a/runescapeapi/beasts.py b/runescapeapi/beasts.py new file mode 100644 index 0000000..d08ca1c --- /dev/null +++ b/runescapeapi/beasts.py @@ -0,0 +1,91 @@ +import json +from dataclasses import dataclass +from typing import Dict, List + +import requests + +from runescapeapi import BASE_URL + +AREA_NAMES_URL: str = f"{BASE_URL}m=itemdb_rs/bestiary/areaNames.json" +""" +returns data like `List[str]` of area names compatable with `AreaBeasts` +""" +SLAYER_CAT_NAMES: str = f"{BASE_URL}m=itemdb_rs/bestiary/slayerCatNames.json" +""" +`Dict[str, int]` +""" +WEAKNESS_NAMES: str = f"{BASE_URL}m=itemdb_rs/bestiary/weaknessNames.json" +""" +`Dict[str, int]` +""" + + +@dataclass +class BeastData: + name: str + beast_id: int + members: bool + weakness: str + level: int + life_points: int + defence: int + attack: int + magic: int + ranged: int + xp: float + slayer_level: int + slayer_cat: str + size: int + attackable: bool + aggressive: bool + poisonous: bool + description: str + areas: List[str] + animations: Dict[str, int] + + @staticmethod + def url(beast_id: int) -> str: + return ( + f"{BASE_URL}m=itemdb_rs/bestiary/beastData.json?beastid={beast_id}" + ) + + @staticmethod + def from_response(raw_responce: str) -> "BeastData": + print(raw_responce) + j: dict[str:str] = json.loads(raw_responce) + j.setdefault("slayerlevel", None) + j.setdefault("slayercat", None) + return BeastData( + name=j["name"], + beast_id=j["id"], + members=j["members"], + weakness=j["weakness"], + level=j["level"], + life_points=j["lifepoints"], + defence=j["defence"], + attack=j["attack"], + magic=j["magic"], + ranged=j["ranged"], + xp=float(j["xp"]), + slayer_level=j["slayerlevel"], + slayer_cat=j["slayercat"], + size=j["size"], + attackable=j["attackable"], + aggressive=j["aggressive"], + poisonous=j["poisonous"], + description=j["description"], + areas=j["areas"], + animations=j["animations"], + ) + + @classmethod + def try_fetch(cls, beast_id: int) -> "BeastData": + url = cls.url(beast_id) + print(url) + r = requests.get(url).text + print(r) + return cls.from_response(r) + + +print() +print(BeastData.try_fetch(89)) # UNICORN? diff --git a/runescapeapi/clan.py b/runescapeapi/clan.py new file mode 100644 index 0000000..7840516 --- /dev/null +++ b/runescapeapi/clan.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass +from typing import List + +import requests + +from . import BASE_URL + + +@dataclass +class ClanMember: + rsn: str + rank: str + clan_xp: int + kills: int + + @staticmethod + def url(clan_name: str) -> str: + url = f"{BASE_URL}m=clan-hiscores/members_lite.ws?clanName={clan_name}" + return url.replace(" ", "%20") + + @staticmethod + def from_response(raw_responce: str) -> List["ClanMember"]: + members = [] + for member in raw_responce.replace("\xa0", " ").splitlines()[1:]: + print(member) + rsn, rank, clan_xp, kills = member.split(",") + members.append( + ClanMember( + rsn=rsn, rank=rank, clan_xp=int(clan_xp), kills=int(kills) + ) + ) + return members + + @classmethod + def try_fetch(cls, clan_name: str) -> List["ClanMember"]: + r = requests.get(cls.url(clan_name)).text + return cls.from_response(r) diff --git a/runescapeapi/runemetrics/__init__.py b/runescapeapi/runemetrics/__init__.py new file mode 100644 index 0000000..2018f57 --- /dev/null +++ b/runescapeapi/runemetrics/__init__.py @@ -0,0 +1,39 @@ +import json +from dataclasses import dataclass +from datetime import datetime +from typing import List + +import requests + + +RUNEMETRICS_BASE = "https://apps.runescape.com/runemetrics/" +SKILL_NAMES = [ + "Attack", + "Defence", + "Strength", + "Constitution", + "Ranged", + "Prayer", + "Magic", + "Cooking", + "Woodcutting", + "Fletching", + "Fishing", + "Firemaking", + "Crafting", + "Smithing", + "Mining", + "Herblore", + "Agility", + "Thieving", + "Slayer", + "Farming", + "Runecrafting", + "Hunter", + "Construction", + "Summoning", + "Dungeoneering", + "Divination", + "Invention", + "Archaeology", +] diff --git a/runescapeapi/runemetrics/monthly_xp.py b/runescapeapi/runemetrics/monthly_xp.py new file mode 100644 index 0000000..62add6d --- /dev/null +++ b/runescapeapi/runemetrics/monthly_xp.py @@ -0,0 +1,55 @@ +import json +from dataclasses import dataclass +from datetime import datetime +from typing import List + +import requests +from runescapeapi.runemetrics import RUNEMETRICS_BASE, SKILL_NAMES + + +@dataclass +class MonthData: + xp_gain: int + timestamp: datetime + rank: int + + +@dataclass +class MonthlyXpGain: + skill_id: int + total_xp: int + average_xp_gain: int + total_gain: int + month_data: List[MonthData] + + @staticmethod + def url(user_name: str, skill: str) -> str: + return ( + f"{RUNEMETRICS_BASE}xp-monthly?searchName={user_name}" + f"&skillid={SKILL_NAMES.index(skill)}" + ) + + @staticmethod + def from_responce(raw_response: str) -> "MonthlyXpGain": + j = json.loads(raw_response)["monthlyXpGain"][0] + return MonthlyXpGain( + skill_id=j["skillId"], + total_xp=j["totalXp"], + average_xp_gain=j["averageXpGain"], + total_gain=j["totalGain"], + month_data=[ + MonthData( + xp_gain=month["xpGain"], + timestamp=datetime.fromtimestamp( + month["timestamp"] // 1000 + ), + rank=month["rank"], + ) + for month in j["monthData"] + ], + ) + + @classmethod + def try_fetch(cls, user_name: str, skill: str) -> "MonthlyXpGain": + r = requests.get(cls.url(user_name, skill)).text + return cls.from_responce(r) diff --git a/runescapeapi/runemetrics/profile.py b/runescapeapi/runemetrics/profile.py new file mode 100644 index 0000000..1bb1cd8 --- /dev/null +++ b/runescapeapi/runemetrics/profile.py @@ -0,0 +1,95 @@ +import json +from dataclasses import dataclass +from datetime import datetime +from typing import List + +import requests +from runescapeapi.runemetrics import RUNEMETRICS_BASE, SKILL_NAMES + + +@dataclass +class Activity: + date: datetime + details: str + text: str + + +@dataclass +class Skill: + name: str + level: int + xp: int + rank: int + id: int + + +@dataclass +class QuestsSummary: + started: int + complete: int + not_started: int + + +@dataclass +class Profile: + name: str + rank: int + total_skill: int + total_xp: int + combat_level: int + magic: int + melee: int + ranged: int + quests_summary: QuestsSummary + activities: List[Activity] + skill_values: List[Skill] + logged_in: bool + + @staticmethod + def url(user_name: str) -> str: + return ( + f"{RUNEMETRICS_BASE}profile/profile?user={user_name}&activities=20" + ) + + @staticmethod + def from_responce(raw_response: str) -> "Profile": + j = json.loads(raw_response) + return Profile( + name=j["name"], + rank=int(j["rank"].replace(",", "")), + total_skill=j["totalskill"], + total_xp=j["totalxp"], + combat_level=j["combatlevel"], + magic=j["magic"], + melee=j["melee"], + ranged=j["ranged"], + quests_summary=QuestsSummary( + started=j["questsstarted"], + complete=j["questscomplete"], + not_started=j["questsnotstarted"], + ), + activities=[ + Activity( + date=datetime.strptime(act["date"], "%d-%b-%Y %H:%M"), + details=act["details"], + text=act["text"], + ) + for act in j["activities"] + ], + skill_values=[ + Skill( + name=SKILL_NAMES[skill["id"]], + level=skill["level"], + xp=skill["xp"] / 10, + rank=skill["rank"], + id=skill["id"], + ) + for skill in j["skillvalues"] + ], + logged_in=j["loggedIn"], + ) + + @classmethod + def try_fetch(cls, user_name: str) -> "Profile": + r = requests.get(cls.url(user_name)).text + return cls.from_responce(r) diff --git a/runescapeapi/runemetrics/quest_list.py b/runescapeapi/runemetrics/quest_list.py new file mode 100644 index 0000000..6027d1d --- /dev/null +++ b/runescapeapi/runemetrics/quest_list.py @@ -0,0 +1,40 @@ +import json +from dataclasses import dataclass +from typing import List + +import requests +from runescapeapi.runemetrics import RUNEMETRICS_BASE + + +@dataclass +class Quest: + title: str + status: str + difficulty: int + members: bool + quest_points: int + user_elegible: bool + + @staticmethod + def url(user_name: str) -> str: + return f"{RUNEMETRICS_BASE}quests?user={user_name}" + + @staticmethod + def from_response(raw_response: str) -> List["Quest"]: + j = json.loads(raw_response) + return [ + Quest( + title=q["title"], + status=q["status"], + difficulty=q["difficulty"], + members=q["members"], + quest_points=q["questPoints"], + user_elegible=q["userEligible"], + ) + for q in j["quests"] + ] + + @classmethod + def try_fetch(cls, user_name: str) -> List["Quest"]: + r = requests.get(cls.url(user_name)).text + return cls.from_response(r) diff --git a/runescapeapi/runescapeapi.py b/runescapeapi/runescapeapi.py deleted file mode 100644 index 08f6520..0000000 --- a/runescapeapi/runescapeapi.py +++ /dev/null @@ -1,742 +0,0 @@ -""" -A slightly memes wrapper for the runescape api, hopefully helps people -best way to contact me with any problems is on discord raatty#3522 -read the rest of the docs for module usage step one is - import runescapeapi as runescape -the code in here might not be done in the best way but everything you -need should be there -""" -import requests -import math -import json -import time - -BASE_URL = "http://services.runescape.com/" - - -class Highscores: - """ - fetches highscores and gives em back as a list of dicts - 6 different types hiscore, hiscore_ironman, hiscore_hardcore_ironman - 'hiscore_oldschool', 'hiscore_oldschool_ironman', - 'hiscore_oldschool_ultimate - this class takes two arguments rsn and type 'hiscore' is the default - usage: - person = runescape.Highscores('raatty', 'hiscore') - or person = runescape.Highscores('raatty') would give same effect - person.skills will give a list of skills in game order - person.total will give you the total - person.rsn in case you forget who you looked up - """ - - HIGHSCORES_URL = BASE_URL + "m={}/index_lite.ws?player={}" - SKILL_NAMES = { - "Total": 0, - "Attack": 1, - "Defence": 7, - "Strength": 4, - "Constitution": 2, - "Ranged": 10, - "Prayer": 13, - "Magic": 16, - "Cooking": 12, - "Woodcutting": 18, - "Fletching": 17, - "Fishing": 9, - "Firemaking": 15, - "Crafting": 14, - "Smithing": 6, - "Mining": 3, - "Herblore": 8, - "Agility": 5, - "Thieving": 11, - "Slayer": 20, - "Farming": 21, - "Runecrafting": 19, - "Hunter": 23, - "Construction": 22, - "Summoning": 24, - "Dungeoneering": 25, - "Divination": 26, - "Invention": 27, - "Archaeology": 28, - } - - def _fetch(self, rsn, type, skill_count: int): - url = self.HIGHSCORES_URL.format(type, rsn.replace(" ", "+")) - fetched_scores = requests.get(url).text - levels = [] - for row in fetched_scores.split("\n"): - for col in row.split(" "): - if len(levels) <= skill_count: - levels.append(col) - for index, lvl in enumerate(levels): - lvl_split = lvl.split(",") - yield { - "level": int(lvl_split[1]), - "xp": int(lvl_split[2]), - "rank": int(lvl_split[0]), - "id": index, - "name": list(self.SKILL_NAMES.keys())[index], - } - - def _calculate_combat(self): - if self.skill_count == 27: - melee = self.skill("Attack") + self.skill("Strength") - magic = 2 * self.skill("Magic") - range = 2 * self.skill("Ranged") - m = max(melee, magic, range) - one = 1.3 * m - two = self.skill("Defence") + self.skill("Constitution") - three = self.skill("Prayer") // 2 - four = self.skill("Summoning") // 2 - return int(sum([one, two, three, four]) // 4) - elif self.skill_count == 23: - first = 0.325 * max( - self.skill("Attack") + self.skill("Strength"), - 1.5 * self.skill("Magic"), - 1.5 * self.skill("Ranged"), - ) - second = self.skill("Defence") / 4 - third = self.skill("Constitution") / 4 - fourth = (self.skill("Prayer") // 2) / 4 - return int(sum([first, second, third, fourth])) - - def skill(self, name): - for s in self.skills: - if name == s["name"]: - return s["level"] - - def __init__(self, rsn: str, type_: str = None): - self.rsn = rsn - if type_ is None: - self.type = "hiscore" - self.skill_count = 27 - else: - if type_ in ["hiscore", "hiscore_ironman", "hiscore_hardcore_ironman"]: - self.type = type_ - self.skill_count = 27 - elif type_ in [ - "hiscore_oldschool", - "hiscore_oldschool_ironman", - "hiscore_oldschool_ultimate", - ]: - self.type = type_ - self.skill_count = 23 - else: - raise AttributeError - self.skills = list(self._fetch(self.rsn, self.type, self.skill_count)) - self.total = self.skills[0] - self.skills = sorted(self.skills[1:], key=lambda x: self.SKILL_NAMES[x["name"]]) - self.combat_level = self._calculate_combat() - - -class Player: - """ - will look up lots about a player - the required argument is the rsn as a string but - there is also a second argument auto_fetch which - is of type bool, delfault is False but if it is - True this class will auto maticly visit all the different - links to get information but if its left blank or set - to False the data will just be fetched as needed - LookupError will be raised if the rsn does not exist - usage: - me = runescape.Player('raatty', True) - """ - - RUNEMETRICS_BASE_URL = "https://apps.runescape.com/runemetrics/" - RUNE_METRICS_URL = RUNEMETRICS_BASE_URL + "profile/profile?user={}&activities=20" - RUNE_METRICS_QUESTS_URL = RUNEMETRICS_BASE_URL + "quests?user={}" - FORUM_PIC_URL = BASE_URL + "m=avatar-rs/{}/chat.png" - CLAN_AND_TITLE_URL = ( - BASE_URL - + "m=website-data/playerDetails.ws?names=%5B%22{}%22%5D&callback=jQuery000000000000000_0000000000&_=0" - ) - - def _fetch_runemetrics(self): - metrics = requests.get(self.RUNE_METRICS_URL.format(self._rsn)).json() - try: - error = metrics["error"] - if error == "NO_PROFILE": - raise LookupError - elif error == "PROFILE_PRIVATE": - self.profile["rsn"] = self._rsn.replace("%20", " ") - stats = Highscores(self.profile["rsn"]) - self.profile["overall_total"] = stats.total - self.profile["combat"] = {"combatlevel": stats.combat_level} - self.profile["quest_summary"] = { - "started": None, - "complete": None, - "notstarted": None, - } - self.profile["alog"] = [] - self.profile["stats"] = stats.skills - except KeyError: - self.profile["rsn"] = metrics["name"] - self.profile["overall_total"] = { - "xp": metrics["totalxp"], - "level": metrics["totalskill"], - "rank": metrics["rank"], - } - self.profile["combat"] = {"combatlevel": metrics["combatlevel"]} - self.profile["quest_summary"] = { - "started": metrics["questsstarted"], - "complete": metrics["questscomplete"], - "notstarted": metrics["questsnotstarted"], - } - self.profile["alog"] = metrics["activities"] - self.profile["stats"] = sorted( - [ - dict( - i, **{"name": list(Highscores.SKILL_NAMES.keys())[i["id"] + 1]} - ) - for i in metrics["skillvalues"] - ], - key=lambda x: Highscores.SKILL_NAMES[x["name"]], - ) - - def _fetch_quests(self): - self.profile["quest_list"] = requests.get( - self.RUNE_METRICS_QUESTS_URL.format(self._rsn) - ).json()["quests"] - - def _fetch_clan_and_title(self): - c_and_t = requests.get(self.CLAN_AND_TITLE_URL.format(self._rsn)).content - c_and_t = json.loads(c_and_t[34:-4]) - try: - self.profile["clan"] = c_and_t["clan"] - except KeyError: - self.profile["clan"] = "" - try: - self.profile["title"] = { - "isSuffix": c_and_t["isSuffix"], - "title": c_and_t["title"], - } - except KeyError: - self.profile["title"] = {"isSuffix": False, "title": ""} - - def __init__(self, rsn: str, auto_fetch: bool = False): - self.profile = { - "rsn": None, - "overall_total": None, - "combat": None, - "quest_summary": None, - "alog": None, - "stats": None, - "quest_list": None, - "clan": None, - "title": None, - } - self._rsn = rsn.replace(" ", "%20") - if auto_fetch: - self._fetch_runemetrics() - self._fetch_quests() - self._fetch_clan_and_title() - - def rsn(self): - """ - will return the rsn of the looked up player - correctly capitalised if their runemetics wasnt private - usage: - me.rsn() - """ - if self.profile["rsn"] is None: - self._fetch_runemetrics() - return self.profile["rsn"] - - def overall_total(self): - """ - returns total level, total xp and rank - usage: - me.overall_total() - """ - if self.profile["overall_total"] is None: - self._fetch_runemetrics() - return self.profile["overall_total"] - - def combat(self): - """ - returns the combat level either strait from - runemetics or calculated from highscores for - private people - usage: - me.combat() - """ - if self.profile["combat"] is None: - self._fetch_runemetrics() - return self.profile["combat"] - - def quest_summary(self): - """ - returns the counts of the started finished and not started quests - or None for thease three values if they are private - usage: - me.quest_summary() - """ - if self.profile["quest_summary"] is None: - self._fetch_runemetrics() - return self.profile["quest_summary"] - - def alog(self): - """ - returns a list of all the activitys on a persons alog or - an empty list for private people - usage: - me.alog() - """ - if self.profile["alog"] is None: - self._fetch_runemetrics() - return self.profile["alog"] - - def stats(self): - """ - returns a list of a persons levels - usage: - me.stats() - """ - if self.profile["stats"] is None: - self._fetch_runemetrics() - return self.profile["stats"] - - def quest_list(self): - """ - returns a list of all the quests and the lookuped players - progree in them - usage: - me.quest_list() - """ - if self.profile["quest_list"] is None: - self._fetch_quests() - return self.profile["quest_list"] - - def quest(self, name): - """ - searches throught the quest list then returns the quest - that matches that name - usage: - me.quest('rat catchers') - """ - for q in self.quest_list(): - if q["title"].lower() == name.lower(): - return q - - def clan(self): - """ - returns the clan name if they are in one - usage: - me.clan() - """ - if self.profile["clan"] is None: - self._fetch_clan_and_title() - return self.profile["clan"] - - def title(self): - """ - returns the title and weather its a suffix - usage: - me.title() - """ - if self.profile["title"] is None: - self._fetch_clan_and_title() - return self.profile["title"] - - def forum_pic(self): - """ - returns a url of the forum picture - usage: - me.forum_pic() - """ - return self.FORUM_PIC_URL.format(self._rsn) - - -class Clan: - """ - Gets a list of members in a clan - usage: - list(runescape.Clan('empire of elitez')) - or - for member in runescape.Clan('empire of elitez') - #do stuff - """ - - CLAN_MEM_URL = BASE_URL + "m=clan-hiscores/members_lite.ws?clanName={}" - CLAN_MOTIF_URL = BASE_URL + "m=avatar-rs/{}/clanmotif.png" - - def __init__(self, clan: str): - self.name = clan - self.members = ( - requests.get(self.CLAN_MEM_URL.format(clan.replace(" ", "+"))) - .content.replace(b"\xa0", b" ") - .decode("ascii") - .split("\n")[1:-1] - ) - self.motif = self.CLAN_MOTIF_URL.format(clan.replace(" ", "%20")) - - def __iter__(self): - for member in self.members: - member = member.split(",") - yield { - "rsn": member[0], - "rank": member[1], - "clanxp": member[2], - "kills": member[3], - } - - def __len__(self): - return len(self.members) - - def keys(self): - """ - will give a list of the key ranks in clan - usage: - list(runescape.Clan('empire of elitez').keys()) - or - for member in runescape.Clan('empire of elitez').keys(): - #do stuff - """ - for i in self.members: - keyRanks = ["Owner", "Deputy Owner", "Overseer"] - i = i.split(",") - if i[1] in keyRanks: - yield {"rsn": i[0], "rank": i[1], "clanxp": i[2], "kills": i[3]} - - def adminish(self): - """ - will give a list of the adminish ranks in clan - (the ones that kind of look like little badges like admin rank) - usage: - list(runescape.Clan('empire of elitez').adminish()) - or - for member in runescape.Clan('empire of elitez').adminish(): - #do stuff - """ - for i in self.members: - adminishRanks = ["Coordinator", "Organiser", "Admin"] - i = i.split(",") - if i[1] in adminishRanks: - yield {"rsn": i[0], "rank": i[1], "clanxp": i[2], "kills": i[3]} - - def stars(self): - """ - will give a list of the star ranks in clan - usage: - list(runescape.Clan('empire of elitez').stars()) - or - for member in runescape.Clan('empire of elitez').stars(): - #do stuff - """ - for i in self.members: - starRanks = ["General", "Captain", "Lieutenant"] - i = i.split(",") - if i[1] in starRanks: - yield {"rsn": i[0], "rank": i[1], "clanxp": i[2], "kills": i[3]} - - def bannanas(self): - """ - will give a list of the banana ranks in clan - usage: - list(runescape.Clan('empire of elitez').bananas()) - or - for member in runescape.Clan('empire of elitez').bananas(): - #do stuff - """ - for i in self.members: - bananaRanks = ["Sergeant", "Corporal", "Recruit"] - i = i.split(",") - if i[1] in bananaRanks: - yield {"rsn": i[0], "rank": i[1], "clanxp": i[2], "kills": i[3]} - - def rank(self, rank: str): - """ - will give a list of the members with a specific rank in a clan - usage: - list(runescape.Clan('empire of elitez').rank('Owner')) - or - for member in runescape.Clan('empire of elitez').rank('Owner'): - #do stuff - """ - for i in self.members: - i = i.split(",") - if i[1] == rank: - yield {"rsn": i[0], "rank": i[1], "clanxp": i[2], "kills": i[3]} - - -class Beasts: - """ - a bunch of methods for getting stats and seaching for monsters - """ - - @staticmethod - def by_id(id: int): - """ - returns stats and info about a beast by id - usage: - runescape.Beasts.by_id(47) - returns information about level 1 rats - to get the ids use runescape.Beasts.search(name) - """ - BEAST_ID_URL = BASE_URL + "m=itemdb_rs/bestiary/beastData.json?beastid={}" - return requests.get(BEAST_ID_URL.format(id)).json() - - @staticmethod - def search(query: str): - """ - searches the beast database for a name and returns a list seperate with + - usage: - runescape.Beasts.search('rat+cat') - will give you a list of all the beasts that have rat or cat in their name - also that gives you the id - """ - BEAST_SEARCH_URL = BASE_URL + "m=itemdb_rs/bestiary/beastSearch.json?term={}" - return requests.get(BEAST_SEARCH_URL.format(query)).json() - - @staticmethod - def by_letter(letter: str): - """ - list of beasts starting with a letter - usage: - runescape.Beasts.by_letter('r') - will give a list of all the beasts starting with 'r' - """ - BEAST_LETTER_URL = ( - BASE_URL + "m=itemdb_rs/bestiary/bestiaryNames.json?letter={}" - ) - return requests.get(BEAST_LETTER_URL.format(letter.upper())).json() - - @staticmethod - def area_names(): - """ - returns a list of area names - usage: - runescape.Beasts.area_names() - it returns the same list every time so you should only need to call this - once in your code - """ - AREA_URL = BASE_URL + "m=itemdb_rs/bestiary/areaNames.json" - return requests.get(AREA_URL).json() - - @staticmethod - def by_area(area: str): - """ - returns a list of beasts in a specific area - see runescape.Beasts.area_names() for the list - usage: - runescape.Beasts.by_area('Rat pits') - gives you a list of all the beasts in the rat pits - """ - BY_AREA_URL = BASE_URL + "m=itemdb_rs/bestiary/areaBeasts.json?identifier={}" - return requests.get(BY_AREA_URL.format(area.replace(" ", "+"))).json() - - @staticmethod - def category_names(): - """ - returns a list of slayer categorys - """ - CATEGORY_URL = BASE_URL + "m=itemdb_rs/bestiary/slayerCatNames.json" - return requests.get(CATEGORY_URL).json() - - @staticmethod - def by_category(cat): - """ - returns a list of monsters of a given slayer category - input a category id as an int or a str of the category name - searching by string will take longer tho - see runescape.Beasts.category_names() for categorys - """ - BY_CATEGORY_URL = ( - BASE_URL + "m=itemdb_rs/bestiary/slayerBeasts.json?identifier={}" - ) - if type(cat) == int: - return requests.get(BY_CATEGORY_URL.format(cat)).json() - elif type(cat) == str: - cat_list = Beasts.category_names() - cat = cat_list[cat] - return requests.get(BY_CATEGORY_URL.format(cat)).json() - - @staticmethod - def weakness_names(): - """ - returns a list of weaknesses - """ - WEAKNESS_URL = BASE_URL + "m=itemdb_rs/bestiary/weaknessNames.json" - return requests.get(WEAKNESS_URL).json() - - @staticmethod - def by_weakness(weakness): - """ - returns a list of beasts with a given weakness, input a weakness - id or a weakness name searching by name will take longer tho - see runescape.Beasts.weakness_names() for weaknesses - """ - BY_WEAKNESS_URL = ( - BASE_URL + "m=itemdb_rs/bestiary/weaknessBeasts.json?identifier={}" - ) - if type(weakness) == int: - return requests.get(BY_WEAKNESS_URL.format(weakness)).json() - elif type(weakness) == str: - weakness_list = Beasts.weakness_names() - weakness = weakness_list[weakness] - return requests.get(BY_WEAKNESS_URL.format(weakness)).json() - - @staticmethod - def by_level(lower_: int, upper_: int): - """ - returns a list of beasts within a given level range, takes - two arguments both ints first argument is the lowest level - you want to see and the second is the highest level - you can use 1 for the first and a high number like 10000 - for the second to get a list of all the beasts - """ - BY_LEVEL_URL = ( - BASE_URL + "m=itemdb_rs/bestiary/levelGroup.json?identifier={}-{}" - ) - return requests.get(BY_LEVEL_URL.format(lower_, upper_)).json() - - -class GrandExchange: - """ - provides methods for fetching information about items in the grandexchange - """ - MODE = "itemdb_rs" - LETTER_URL = ( - BASE_URL + "m={}/api/catalogue/items.json?category={}&alpha={}&page={}" - ) - CATEGORYS = { - "miscellaneous": 0, - "ammo": 1, - "arrows": 2, - "bolts": 3, - "construction materials": 4, - "construction projects": 5, - "cooking ingredients": 6, - "costumes": 7, - "crafting materials": 8, - "familiars": 9, - "farming produce": 10, - "fletching materials": 11, - "food and drink": 12, - "herblore materials": 13, - "hunting equipment": 14, - "hunting produce": 15, - "jewellery": 16, - "mage armour": 17, - "mage weapons": 18, - "melee armour - low level": 19, - "melee armour mid level": 20, - "melee armour high level": 21, - "melee weapons - low level": 22, - "melee weapons - mid level": 23, - "melee weapons - high level": 24, - "mining and smithing": 25, - "potions": 26, - "prayer armour": 27, - "prayer materials": 28, - "range armour": 29, - "range weapons": 30, - "runecrafting": 31, - "runes spells and teleports": 32, - "seeds": 33, - "summoning scrolls": 34, - "tools and containers": 35, - "woodcutting product": 36, - "pocket items": 37, - } - - @classmethod - def item(cls, id: int): - """ - gets information on a single item usage - usage: - runescape.GrandExchange.item(1985) - that ^ will give you information about Cheese - """ - ITEM_URL = BASE_URL + "m={}/api/catalogue/detail.json?item={}" - return requests.get(ITEM_URL.format(cls.MODE, id)).json() - - @classmethod - def graph(cls, id: int): - """ - gets info on the prices over time - usage: - runescape.GrandExchange.graph(1985) - that ^ will give you data plot points to create a graph - of cheese over time - """ - GRAPH_URL = BASE_URL + "m={}/api/graph/{}.json" - return requests.get(GRAPH_URL.format(cls.MODE, id)).json() - - @classmethod - def cat_count(cls, id: int): - """ - tells you how many items there are for each letter in a category - give the id from 0 to 37 check runescape.GrandExchange.CATEGORYS for ids - usage: - runescape.GrandExchange.cat_count(12) - will display the letter counts of the food and drink category - yes thats where you can find cheese! - """ - CAT_COUNT_URL = BASE_URL + "m={}/api/catalogue/category.json?category={}" - return requests.get(CAT_COUNT_URL.format(cls.MODE, id)).json()["alpha"] - - @classmethod - def iter_letter(cls, letter: str, category: int, page_sleep: int = 0): - """ - iterates through a letter (a-z or #(for number)) in a category - see runescape.GrandExchange.CATEGORYS for categorys - usage: - for item in runescape.GrandExchange.iter_leter('c', 12, 1): - #do stuff (find cheese maybe?) - that loop will go through all the items in the food and drink - category that start with the letter 'c' will also sleep for a second - each grand exchange page to be nice to the runescape servers and to reduce - errors - """ - for l in GrandExchange.cat_count(category): - if letter == l["letter"]: - letter_count = l["items"] - break - page_count = math.ceil(letter_count / 12) - for page in range(1, page_count + 1): - time.sleep(page_sleep) - p = requests.get( - GrandExchange.LETTER_URL.format( - cls.MODE, category, letter.replace("#", "%23"), page - ) - ).json() - for item in p["items"]: - yield item - - @classmethod - def iter_category(cls, category: int, page_sleep: int = 0): - """ - iterates through an entire category in put an int from 0 to 37 - if you start getting errors use the second argument that is how many seconds - to sleep between pages default is 0 whitch will be fine for short bursts - anouther way to use this is: - ge = [c for i in range(0,38) for c in runescape.GrandExchange.iter_category(i, 5)] - to get the full grand exchange in one list, will take some time tho - so its best to only be getting one category at a time - regurlar usage: - for i in runescape.GrandExchange.iter_category(12, 1): - #do stuff - """ - cat_counts = GrandExchange.cat_count(category) - for letter in cat_counts: - letter_count = letter["items"] - for page in range(1, math.ceil(letter_count / 12) + 1): - time.sleep(page_sleep) - p = requests.get( - GrandExchange.LETTER_URL.format( - cls.MODE, category, letter["letter"].replace("#", "%23"), page - ) - ) - for item in p.json()["items"]: - yield item - - -class OSRSGrandExchange(GrandExchange): - """ - provides methods for fetching information about items in the old school grandexchange - """ - MODE = "itemdb_oldschool" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1a3341a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,21 @@ +[metadata] +name = runescapeapi +version = 3 +author = Raatty +author_email = me@raatty.club +license = MIT +description = grandexchange, highscores, beasts, runemetrics +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/Raatty/runescapeapi +project_urls = + Bug Tracker = https://github.com/Raatty/runescapeapi/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent +requires = + requests + +[options] +packages = find: diff --git a/setup.py b/setup.py deleted file mode 100644 index 6e32b34..0000000 --- a/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup, find_packages - -with open('readme.txt') as f: - ld = f.read() -setup( - name='runescapeapi', - version='2', - description='grandexchange, highscores, beasts, runemetrics', - long_description=ld, - long_description_content_type="text/plain", - author='raatty', - py_modules=["runesapeapi"], - install_requires=['requests'], - python_requires='>=3', - license='MIT', - packages=find_packages() -)