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
- Fork the repository
- Create a feature branch (
git checkout -b feat/new-feature) - Follow naming conventions and code style
- Write tests for new functionality
- Commit with conventional commits (
feat:,fix:, etc.) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
License
MIT License - see LICENSE file for details.
Metadata
Release files for shadowlib 3.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| shadowlib-3.4.0.tar.gz | 179.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| shadowlib-3.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 388.8 kB
Release files / shadowlib-3.4.0.tar.gz
| Download URL | shadowlib-3.4.0.tar.gz |
|---|---|
| Size | 179.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
47f2831facb55b0a7d7218b3c1c5bfe0770d4c5954c349b4b5a4f798f12b56fd
|
|
BLAKE2b-256 checksum How to use checksums |
4246f682852abd34ec93ec2a550fbab4e62f092ccc75b88b585ce8dd05d41fcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|
Release files / shadowlib-3.4.0-py3-none-any.whl
| Download URL | shadowlib-3.4.0-py3-none-any.whl |
|---|---|
| Size | 209.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
910395ba33ca94777ce472c1cea920ac0261394d78c6b214ecef898dc28e92cc
|
|
BLAKE2b-256 checksum How to use checksums |
1d6c48380f41c992ea860adc672adfbbfe419dc810065178b2e998e860de5eca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|