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
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47f2831facb55b0a7d7218b3c1c5bfe0770d4c5954c349b4b5a4f798f12b56fd
|
|
| MD5 |
f6fc3922be76a4066b3e4d8584c76c8c
|
|
| BLAKE2b-256 |
4246f682852abd34ec93ec2a550fbab4e62f092ccc75b88b585ce8dd05d41fcd
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
910395ba33ca94777ce472c1cea920ac0261394d78c6b214ecef898dc28e92cc
|
|
| MD5 |
6afd4a75dabdde65863fd0455774be21
|
|
| BLAKE2b-256 |
1d6c48380f41c992ea860adc672adfbbfe419dc810065178b2e998e860de5eca
|