Skip to content

Commit

Permalink
added itemdb endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
extreme4all committed Oct 27, 2024
1 parent 84ffc0c commit b9b1fdc
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 11 deletions.
37 changes: 30 additions & 7 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

# This workflow will install Python dependencies, run tests with pytest, and publish package on successful builds.
name: Python package

on:
Expand All @@ -9,25 +7,50 @@ on:
- main

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7','3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-test.txt
- name: Run tests
run: |
pytest --maxfail=5 --disable-warnings
deploy:
needs: test
runs-on: ubuntu-latest
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
# Specifying a GitHub environment is optional, but strongly encouraged
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@release/v1
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,33 @@ async def main():

loop = asyncio.get_running_loop()
await loop.create_task(main())
```
```
```py
import asyncio
from aiohttp import ClientSession
from osrs.catalogue import Catalogue, Mode # Assuming this is the file/module name for your code

async def main():
# Initialize the Catalogue with optional proxy and rate limiter
catalogue = Catalogue(proxy="")

async with ClientSession() as session:
# Example 1: Fetching items by alphabetical filter
alpha = "A" # Items starting with "A"
page = 1 # First page of results
category = 1 # Category identifier, for OSRS there is only 1 category
items = await catalogue.get_items(session, alpha=alpha, page=page, mode=Mode.OLDSCHOOL, category=category)
print("Fetched Items:", items)

# Example 2: Fetching detailed information for a specific item
item_id = 4151 # Example item ID (Abyssal whip in OSRS)
item_detail = await catalogue.get_detail(session, item_id=item_id, mode=Mode.OLDSCHOOL)
print("Item Detail:", item_detail)

# Example 3: Fetching historical trade data (price graph) for a specific item
trade_history = await catalogue.get_graph(session, item_id=item_id, mode=Mode.OLDSCHOOL)
print("Trade History:", trade_history)

# Run the asynchronous main function
asyncio.run(main())
```
185 changes: 185 additions & 0 deletions osrs/async_api/osrs/itemdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import json
import logging
from datetime import datetime
from enum import Enum
from typing import Union

from aiohttp import ClientSession
from pydantic import BaseModel, HttpUrl

from osrs.utils import RateLimiter

logger = logging.getLogger(__name__)


class Mode(str, Enum):
OLDSCHOOL: str = "itemdb_oldschool"
RS3: str = "itemdb_rs"


class CurrentPrice(BaseModel):
trend: str
price: int | str # Price can be an int or a formatted string (e.g., "15.5k")


class TodayPrice(BaseModel):
trend: str
price: int | str # Price can also be an int or string (e.g., "+2")


class Item(BaseModel):
icon: HttpUrl
icon_large: HttpUrl
id: int
type: str
typeIcon: HttpUrl
name: str
description: str
current: CurrentPrice
today: TodayPrice
members: str | bool


class Items(BaseModel):
total: int
items: list[Item]


class PriceDetail(BaseModel):
trend: str
price: Union[int, str] # Price can be int or formatted string (e.g., "+9")


class ChangeDetail(BaseModel):
trend: str
change: str # Change is a percentage string (e.g., "+8.0%")


class ItemDetail(BaseModel):
icon: HttpUrl
icon_large: HttpUrl
id: int
type: str
typeIcon: HttpUrl
name: str
description: str
current: PriceDetail
today: PriceDetail
members: str # Boolean-like strings ("true"/"false")
day30: ChangeDetail
day90: ChangeDetail
day180: ChangeDetail


class Detail(BaseModel):
item: ItemDetail


class TradeHistory(BaseModel):
daily: dict[datetime, int]
average: dict[datetime, int]


class Catalogue:
BASE_URL = "https://secure.runescape.com"

def __init__(
self, proxy: str = "", rate_limiter: RateLimiter = RateLimiter()
) -> None:
"""Initialize the Catalogue with an optional proxy and rate limiter.
Args:
proxy (str): Proxy URL to use for API requests. Defaults to "".
rate_limiter (RateLimiter): Rate limiter to manage request throttling.
Defaults to a new RateLimiter instance.
"""
self.proxy = proxy
self.rate_limiter = rate_limiter

async def get_items(
self,
session: ClientSession,
alpha: str,
page: int | None = 1,
mode: Mode = Mode.OLDSCHOOL,
category: int = 1,
) -> Items:
"""Fetch items from the RuneScape item catalog based on alphabetical filter.
Args:
session (ClientSession): An active aiohttp session for making requests.
alpha (str): Alphabetical character to filter item names.
page (int, optional): Page number to retrieve. Defaults to 1.
mode (Mode, optional): Game mode (RS3 or OLDSCHOOL). Defaults to Mode.OLDSCHOOL.
category (int, optional): Category identifier for items. Defaults to 1.
Returns:
Items: List of items matching the filter.
"""
await self.rate_limiter.check()

url = f"{self.BASE_URL}/m={mode.value}/api/catalogue/items.json"
params = {"category": category, "alpha": alpha, "page": page}
params = {k: v for k, v in params.items()}

logger.info(f"[GET]: {url=}, {params=}")

async with session.get(url, proxy=self.proxy, params=params) as response:
response.raise_for_status()
data = await response.text()
return Items(**json.loads(data))

async def get_detail(
self,
session: ClientSession,
item_id: int,
mode: Mode = Mode.OLDSCHOOL,
) -> Detail:
"""Fetch detailed information about a specific item.
Args:
session (ClientSession): An active aiohttp session for making requests.
item_id (int): Unique identifier for the item.
mode (Mode, optional): Game mode (RS3 or OLDSCHOOL). Defaults to Mode.OLDSCHOOL.
Returns:
Detail: Detailed information about the item.
"""
await self.rate_limiter.check()

url = f"{self.BASE_URL}/m={mode.value}/api/catalogue/detail.json"
params = {"item": item_id}

logger.info(f"[GET]: {url=}, {params=}")

async with session.get(url, proxy=self.proxy, params=params) as response:
response.raise_for_status()
data = await response.text()
return Detail(**json.loads(data))

async def get_graph(
self,
session: ClientSession,
item_id: int,
mode: Mode = Mode.OLDSCHOOL,
) -> TradeHistory:
"""Fetch trade history graph data for a specific item.
Args:
session (ClientSession): An active aiohttp session for making requests.
item_id (int): Unique identifier for the item.
mode (Mode, optional): Game mode (RS3 or OLDSCHOOL). Defaults to Mode.OLDSCHOOL.
Returns:
TradeHistory: Historical trading data for the item.
"""
await self.rate_limiter.check()

url = f"{self.BASE_URL}/m={mode.value}/api/graph/{item_id}.json"

logger.info(f"[GET]: {url=}")

async with session.get(url, proxy=self.proxy) as response:
response.raise_for_status()
data = await response.text()
return TradeHistory(**json.loads(data))
6 changes: 3 additions & 3 deletions tests/test_async_hiscore.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import pytest
from aiohttp import ClientSession

from osrs.async_api.osrs.hiscores import Mode, PlayerStats, hiscore
from osrs.async_api.osrs.hiscores import Hiscore, Mode, PlayerStats
from osrs.exceptions import PlayerDoesNotExist


@pytest.mark.asyncio
async def test_get_valid():
hiscore_instance = hiscore()
hiscore_instance = Hiscore()
async with ClientSession() as session:
player_stats = await hiscore_instance.get(
mode=Mode.OLDSCHOOL,
Expand All @@ -25,7 +25,7 @@ async def test_get_valid():

@pytest.mark.asyncio
async def test_get_invalid():
hiscore_instance = hiscore()
hiscore_instance = Hiscore()
async with ClientSession() as session:
with pytest.raises(PlayerDoesNotExist):
_ = await hiscore_instance.get(
Expand Down
Loading

0 comments on commit b9b1fdc

Please sign in to comment.