Skip to main content

OSRS Bot Development SDK with game-native structure

Project description

ShadowLib

A Python SDK for Old School RuneScape (OSRS) bot development that communicates with RuneLite via a high-performance bridge. The architecture mirrors the game's interface, making it intuitive for OSRS developers.

Requirements

  • Python 3.12+
  • Linux with inotify support (required for event system)
  • RuneLite with ShadowBot plugin running

Features

  • Game-Native Structure: Directory layout mirrors OSRS client interface (tabs, interfaces, world)
  • Event-Driven Architecture: Zero-CPU inotify-based event system for real-time game state
  • Singleton Pattern: All modules are singletons with lazy initialization
  • Type-Safe: Full type hints with IDE autocomplete support
  • Auto-Generated Constants: ItemID, NpcID, ObjectID, InterfaceID, and more
  • 3D Projection: Convert local/world coordinates to screen coordinates

Installation

# Clone the repository
git clone https://github.com/shadowbot/shadowlib.git
cd shadowlib

# Install with development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
pre-commit install

Quick Start

from shadowlib.client import client

# Client auto-connects on import and starts event consumer
# All modules are singletons - no instantiation needed

# Access inventory
items = client.tabs.inventory.getItems()
for item in items:
    print(f"{item.name}: {item.quantity}")

# Check player state
pos = client.player.position
print(f"Player at: {pos}")

# Use bank interface
if client.interfaces.bank.isOpen():
    client.interfaces.bank.depositAll()

# Direct module access (alternative pattern)
from shadowlib.tabs.inventory import inventory
from shadowlib.input.mouse import mouse

inventory.clickItem(995)  # Click coins
mouse.leftClick(100, 200)

Architecture

Module Structure

shadowlib/
├── client.py           # Main singleton - auto-connects to RuneLite bridge
├── globals.py          # Global accessors (getClient, getApi, getEventCache)
│
├── tabs/               # Side panel tabs (14 tabs)
│   ├── inventory.py    # Inventory management
│   ├── equipment.py    # Worn equipment
│   ├── skills.py       # Skill levels and XP
│   ├── prayer.py       # Prayer activation
│   ├── magic.py        # Spellbook
│   ├── combat.py       # Combat options
│   └── ...             # friends, settings, logout, etc.
│
├── interfaces/         # Overlay windows
│   ├── bank.py         # Bank interface
│   └── ...             # GE, shop, dialogue (planned)
│
├── world/              # 3D world entities
│   ├── ground_items.py # Items on the ground
│   └── projection.py   # Coordinate projection (local → screen)
│
├── input/              # OS-level input
│   ├── mouse.py        # Mouse control
│   ├── keyboard.py     # Keyboard input
│   └── runelite.py     # RuneLite window management
│
├── interactions/       # Game interactions
│   └── menu.py         # Right-click menu handling
│
├── player/             # Player state
│   └── player.py       # Position, stats, status
│
├── navigation/         # Movement systems
│   └── pathfinder.py   # Pathfinding (planned)
│
├── types/              # Type definitions
│   ├── item.py         # Item dataclass
│   ├── widget.py       # Widget mask builder
│   ├── packed_position.py  # Coordinate packing
│   └── ...
│
├── utilities/          # Helper functions
│   ├── timing.py       # sleep, waitUntil
│   └── text.py         # Text utilities
│
└── _internal/          # Internal implementation
    ├── api.py          # RuneLite bridge API
    ├── query_builder.py # Fluent query interface
    ├── cache/          # Event cache and state builder
    ├── events/         # Inotify event consumer
    └── resources/      # Varps, objects database

Singleton Pattern

All modules use the singleton pattern with __new__ + _init():

# Two equivalent access patterns:

# Via client namespace
from shadowlib.client import client
client.tabs.inventory.getItems()

# Direct import
from shadowlib.tabs.inventory import inventory
inventory.getItems()

Event System

The SDK uses an inotify-based event system for zero-CPU-usage when idle:

from shadowlib.client import client

# Access cached game state (updated automatically)
tick = client.cache.tick
energy = client.cache.energy
position = client.cache.position

# Get recent events
chats = client.cache.getRecentEvents('chat_message', n=10)
stats = client.cache.getRecentEvents('stat_changed', n=5)

# Access varps/varbits
quest_points = client.cache.getVarp(101)

# Check data freshness
if client.cache.isFresh(max_age=1.0):
    print(f"Data age: {client.cache.getAge():.2f}s")

Event Channels:

Channel Type Channels Description
Ring Buffer varbit_changed, chat_message, item_container_changed, stat_changed, animation_changed Guaranteed delivery, keeps history
Latest State gametick, camera_changed, world_view_loaded, world_entity, menu_open, ground_items Current state only

Projection System

Convert local/world coordinates to screen coordinates:

from shadowlib.world.projection import projection

# Auto-configured from events - just use it
screen_pos = projection.localToCanvasSingle(localX, localY, plane)
if screen_pos:
    screenX, screenY = screen_pos
    print(f"Screen position: ({screenX}, {screenY})")

# Batch projection
import numpy as np
xs = np.array([1000, 2000, 3000])
ys = np.array([1000, 2000, 3000])
screenX, screenY, valid = projection.localToCanvas(xs, ys, plane=0)

Query Builder

Direct RuneLite API access with fluent interface:

from shadowlib.client import client

# Build and execute queries
q = client.query()
result = q.execute({
    "inventory": q.client.getItemContainer(93),
    "position": q.localPlayer.getWorldLocation(),
    "health": q.localPlayer.getHealthRatio()
})

Code Style

Naming Conventions

Element Convention Example
Functions/Methods camelCase getItems(), isInventoryFull()
Classes PascalCase Inventory, BankInterface
Constants UPPER_CASE MAX_INVENTORY_SIZE
Private _camelCase _internalHelper()

Dependencies

# Use dependency injection with global fallback
class Inventory:
    def __init__(self, client=None):
        self.client = client or getClient()

Development

Running Tests

pytest                           # All tests
pytest --cov=shadowlib           # With coverage
pytest tests/test_inventory.py   # Specific file
pytest -k "test_bank"            # Pattern match

Linting and Formatting

ruff check .           # Lint
ruff check --fix .     # Auto-fix
ruff format .          # Format
python check_naming.py # Verify naming conventions

Pre-commit Hooks

pre-commit install       # Install hooks
pre-commit run --all-files  # Run manually

Generated Files

On first import, ShadowLib downloads and generates:

~/.cache/shadowlib/
├── generated/           # Proxy classes, constants
│   ├── constants/       # ItemID, NpcID, ObjectID, etc.
│   └── proxies/         # API proxy classes
└── data/                # Game data
    ├── api_data.json    # API metadata
    ├── varps.json       # Varp definitions
    └── objects.json     # Object database

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/new-feature)
  3. Follow naming conventions and code style
  4. Write tests for new functionality
  5. Commit with conventional commits (feat:, fix:, etc.)
  6. Open a Pull Request

See CONTRIBUTING.md for detailed guidelines.

License

MIT License - see LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shadowlib-3.4.0.tar.gz (179.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shadowlib-3.4.0-py3-none-any.whl (209.7 kB view details)

Uploaded Python 3

File details

Details for the file shadowlib-3.4.0.tar.gz.

File metadata

  • Download URL: shadowlib-3.4.0.tar.gz
  • Upload date:
  • Size: 179.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for shadowlib-3.4.0.tar.gz
Algorithm Hash digest
SHA256 47f2831facb55b0a7d7218b3c1c5bfe0770d4c5954c349b4b5a4f798f12b56fd
MD5 f6fc3922be76a4066b3e4d8584c76c8c
BLAKE2b-256 4246f682852abd34ec93ec2a550fbab4e62f092ccc75b88b585ce8dd05d41fcd

See more details on using hashes here.

File details

Details for the file shadowlib-3.4.0-py3-none-any.whl.

File metadata

  • Download URL: shadowlib-3.4.0-py3-none-any.whl
  • Upload date:
  • Size: 209.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for shadowlib-3.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 910395ba33ca94777ce472c1cea920ac0261394d78c6b214ecef898dc28e92cc
MD5 6afd4a75dabdde65863fd0455774be21
BLAKE2b-256 1d6c48380f41c992ea860adc672adfbbfe419dc810065178b2e998e860de5eca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page