Metadata-Version: 2.4
Name: roboat
Version: 1.1.0
Summary: The best Python wrapper for the Roblox API — OAuth, async, typed models, datastores, events, marketplace tools
Home-page: https://github.com/Addi9000/roboat
Author: roboat contributors
Author-email: bwesttwink@gmail.com
License: MIT
Project-URL: Bug Tracker, https://github.com/Addi9000/roboat/issues
Project-URL: Documentation, https://www.roboat.pro/docs
Project-URL: Source, https://github.com/Addi9000/roboat
Keywords: roblox,api,wrapper,roblox-api,roboat,games,catalog,trading,opencloud,datastore,automation,bot,economy
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Games/Entertainment
Classifier: Environment :: Console
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.8.0; extra == "async"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Provides-Extra: all
Requires-Dist: aiohttp>=3.8.0; extra == "all"
Requires-Dist: pytest>=7.0; extra == "all"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

<div align="center">

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:6EE7F7,100:A78BFA&height=200&section=header&text=roboat&fontSize=80&fontColor=ffffff&animation=fadeIn&fontAlignY=38&desc=The%20best%20Python%20wrapper%20for%20the%20Roblox%20API&descAlignY=60&descAlign=50" width="100%"/>

<br/>

[![Python](https://img.shields.io/badge/Python-3.8%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://python.org)
[![License](https://img.shields.io/badge/License-MIT-A78BFA?style=for-the-badge)](LICENSE)
[![Version](https://img.shields.io/badge/Version-2.1.0-6EE7F7?style=for-the-badge)](https://github.com/valeoncehadadream/roboat)
[![Status](https://img.shields.io/badge/Status-Production%20Ready-22c55e?style=for-the-badge)](https://github.com/valeoncehadadream/roboat)

<br/>

<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&size=22&pause=1000&color=6EE7F7&center=true&vCenter=true&width=600&lines=OAuth+2.0+Authentication;Typed+Models+for+Every+Response;Async+%2B+Sync+Clients;Built-in+SQLite+Database;Open+Cloud+%2B+DataStores;Real-time+Event+System;Marketplace+%26+Economy+Tools;Interactive+Terminal+REPL" alt="Typing SVG" />

<br/><br/>

</div>

---

## Install

```bash
pip install .
pip install ".[async]"   # with aiohttp for async support
pip install ".[all]"     # everything including tests
```

---

## Quick Start

```python
from roboat import RoboatClient

client = RoboatClient()

user = client.users.get_user(156)
print(user)  # Builderman (@builderman) [ID: 156]

game = client.games.get_game(2753915549)
print(f"{game.name} — {game.visits:,} visits")
```

---

## Interactive Terminal

```bash
roboat
# or
python -m roboat
```

```
  ____       _     _            _    ____ ___
 |  _ \ ___ | |__ | | _____  __/ \  |  _ \_ _|
 | |_) / _ \| '_ \| |/ _ \ \/ / _ \ | |_) | |
 |  _ < (_) | |_) | | (_) >  < ___ \|  __/| |
 |_| \_\___/|_.__/|_|\___/_/\_/_/  \_|_|  |___|

  roboat v2.1.0  —  type 'help' to begin

» start 156
» auth
» game 2753915549
» inventory 156
```

Every command requires `start <userid>` first. The terminal will remind you if you forget.

---

## OAuth Authentication

```python
from roboat import OAuthManager, RoboatClient

manager = OAuthManager(
    on_success=lambda token: print("Authenticated!"),
    on_failure=lambda err:   print(f"Failed: {err}"),
    timeout=120,
)

token = manager.authenticate()  # opens browser, waits 120s

if token:
    client = RoboatClient(oauth_token=token)
```

In the terminal, type `auth`. Your browser opens automatically and you have **120 seconds** to complete login with a live countdown shown.

---

## All Modules

| Module | What It Does |
|:---|:---|
| `client.py` | Main sync client with caching and rate limiting |
| `async_client.py` | Full async mirror using aiohttp |
| `oauth.py` | OAuth 2.0 with 120s timeout |
| `session.py` | Interactive terminal REPL |
| `database.py` | Local SQLite persistence |
| `models.py` | 20+ typed dataclass response models |
| `exceptions.py` | Typed exceptions per HTTP status |
| `events.py` | Background polling event system |
| `analytics.py` | Parallel report aggregation |
| `marketplace.py` | Limited items, RAP tracking, profit estimator |
| `social.py` | Friend graph, mutual analysis, presence tools |
| `notifications.py` | Experience push notifications |
| `publish.py` | Asset upload (images, audio, models) |
| `moderation.py` | Reports, blocks, chat filter |
| `opencloud.py` | Open Cloud API key client |
| `develop.py` | Universe, DataStores, bans, Team Create |
| `users.py` | User lookup, search, bulk fetch |
| `games.py` | Games, visits, servers, votes |
| `catalog.py` | Avatar shop search and item details |
| `groups.py` | Full group management and payouts |
| `friends.py` | Friends, followers, followings |
| `thumbnails.py` | All thumbnail types as {id: url} |
| `badges.py` | Badges and award dates |
| `economy.py` | Transactions and resale data |
| `presence.py` | Online and in-game status |
| `avatar.py` | Avatar assets and outfits |
| `trades.py` | Trade list, send, accept, decline |
| `messages.py` | Private messages and chat |
| `inventory.py` | Ownership, RAP, collectibles |
| `utils/cache.py` | Thread-safe TTL LRU cache |
| `utils/ratelimit.py` | Token bucket and @retry decorator |
| `utils/paginator.py` | Lazy auto-pagination iterator |

---

## Client Setup

```python
from roboat import RoboatClient, ClientBuilder

# Simple
client = RoboatClient()

# Builder — full control
client = (
    ClientBuilder()
    .set_oauth_token("YOUR_TOKEN")
    .set_timeout(15)
    .set_cache_ttl(60)
    .set_cache_size(512)
    .set_rate_limit(10)
    .set_proxy("http://proxy:8080")
    .build()
)
```

## Async Client

```python
import asyncio
from roboat import AsyncRoboatClient

async def main():
    async with AsyncRoboatClient() as client:
        game, votes, icon = await asyncio.gather(
            client.games.get_game(2753915549),
            client.games.get_votes([2753915549]),
            client.thumbnails.get_game_icons([2753915549]),
        )
        users = await client.users.get_users_by_ids(list(range(1, 501)))

asyncio.run(main())
```

---

## Users

```python
user  = client.users.get_user(156)
users = client.users.get_users_by_ids([1, 156, 261])
users = client.users.get_users_by_usernames(["Roblox", "builderman"])
page  = client.users.search_users("builderman", limit=10)
page  = client.users.get_username_history(156)
```

## Games

```python
game   = client.games.get_game(2753915549)
game   = client.games.get_game_from_place(6872265039)
visits = client.games.get_visits([2753915549, 286090429])
votes  = client.games.get_votes([2753915549])
page   = client.games.get_servers(6872265039, limit=10)
page   = client.games.get_user_games(156)
page   = client.games.get_group_games(2868472)
page   = client.games.search_games("obby", limit=20)
```

## Groups

```python
group   = client.groups.get_group(7)
roles   = client.groups.get_roles(7)
members = client.groups.get_members(7, limit=100)

# Management (auth required)
client.groups.set_member_role(7, user_id=1234, role_id=role.id)
client.groups.kick_member(7, user_id=1234)
client.groups.post_shout(7, "Message here")
client.groups.post_to_wall(7, "Wall post")
client.groups.accept_all_join_requests(7)
client.groups.pay_out(7, user_id=1234, amount=500)
```

## Catalog

```python
page  = client.catalog.search(keyword="fedora", category="Accessories", sort_type="Sales")
item  = client.catalog.get_asset(1028606)
item  = client.catalog.get_bundle(192)
resale = client.catalog.get_resale_data(1028606)
```

---

## Marketplace Tools

```python
from roboat.marketplace import MarketplaceAPI

market = MarketplaceAPI(client)

data   = market.get_limited_data(1365767)
print(data.recent_average_price, data.price_trend)

profit = market.estimate_resale_profit(1365767, purchase_price=12000)
deals  = market.find_underpriced_limiteds([1365767, 1028606])

tracker = market.create_rap_tracker([1365767, 1028606])
tracker.snapshot()
tracker.snapshot()
print(tracker.summary())
```

## Social Graph

```python
from roboat.social import SocialGraph

sg = SocialGraph(client)

mutuals     = sg.mutual_friends(156, 261)
is_following = sg.does_follow(156, 261)
snap        = sg.presence_snapshot([156, 261, 1234])
online_ids  = sg.who_is_online([156, 261, 1234])
nodes       = sg.most_followed_in_group([156, 261, 1234])
suggestions = sg.follow_suggestions(156, limit=10)
```

## Notifications

```python
from roboat.notifications import NotificationsAPI

notif = NotificationsAPI(api_key="roblox-KEY-xxxx")

result = notif.send(
    universe_id=123456789,
    user_id=1234,
    message_id="daily-reward",
    attributes={"coins": "500"},
    join_experience={"launchData": "reward"},
)

results = notif.send_bulk(123456789, [1234, 5678], "daily-reward")
quota   = notif.get_quota(123456789)
```

## Asset Publishing

```python
from roboat.publish import PublishAPI

pub = PublishAPI(api_key="roblox-KEY-xxxx", creator_id=156, creator_type="User")

asset = pub.upload_image("thumbnail.png", name="Game Icon")
asset = pub.upload_audio("bgm.ogg",       name="Background Music")
asset = pub.upload_model("char.fbx",      name="Character Model")
asset = pub.upload_auto("file.png",       name="Auto Detected")

final = pub.wait_for_asset(asset.operation_id, max_wait=30)
```

## Moderation

```python
from roboat.moderation import ModerationAPI

mod = ModerationAPI(client)

standing = mod.get_account_standing(user_id=1234)
mod.report_user(1234, reason="Spam")
mod.report_asset(11111, reason="Inappropriate")
mod.block_user(1234)
result = mod.filter_text("Hello!", user_id=1234)
```

---

## Open Cloud — Developer Tools

```python
API_KEY  = "roblox-KEY-xxxxx"
UNIVERSE = 123456789

# DataStores
client.develop.set_datastore_entry(UNIVERSE, "PlayerData", "player_1234", {"coins": 500}, API_KEY)
client.develop.get_datastore_entry(UNIVERSE, "PlayerData", "player_1234", API_KEY)
client.develop.increment_datastore_entry(UNIVERSE, "Stats", "deaths", 1, API_KEY)
client.develop.delete_datastore_entry(UNIVERSE, "Sessions", "key", API_KEY)
client.develop.list_datastore_keys(UNIVERSE, "PlayerData", API_KEY)

# Ordered DataStores (leaderboards)
client.develop.list_ordered_datastore(UNIVERSE, "Leaderboard", API_KEY, max_page_size=10)
client.develop.set_ordered_datastore_entry(UNIVERSE, "Leaderboard", "player_1234", 9500, API_KEY)
client.develop.increment_ordered_datastore(UNIVERSE, "Leaderboard", "player_1234", 100, API_KEY)

# MessagingService — broadcast to all live servers
client.develop.publish_message(UNIVERSE, "Announcements", "Event in 5 minutes!", API_KEY)
client.develop.announce(UNIVERSE, API_KEY, "Server restart soon!")
client.develop.broadcast_shutdown(UNIVERSE, API_KEY)

# Bans
client.develop.ban_user(UNIVERSE, 1234, API_KEY, duration_seconds=86400, display_reason="Banned.")
client.develop.ban_user(UNIVERSE, 5678, API_KEY, duration_seconds=None)  # permanent
client.develop.unban_user(UNIVERSE, 1234, API_KEY)
client.develop.list_bans(UNIVERSE, API_KEY)

# Team Create
client.develop.update_team_create(UNIVERSE, is_enabled=True)
client.develop.add_team_create_member(UNIVERSE, user_id=1234)
client.develop.get_team_create_members(UNIVERSE)

# Game Stats
stats = client.develop.get_game_stats(UNIVERSE, stat_type="Visits", granularity="Daily")
```

---

## Database Layer

```python
from roboat import SessionDatabase

db = SessionDatabase.load_or_create("myproject")

db.save_user(client.users.get_user(156))
db.save_game(client.games.get_game(2753915549))

user_data = db.get_user(156)
all_users = db.get_all_users()
all_games = db.get_all_games()

db.set("tracked_ids", [156, 261, 1234])
val = db.get("tracked_ids")

print(db.stats())
# {'users': 10, 'games': 5, 'session_keys': 3, 'log_entries': 42}

db.close()
```

---

## Events

```python
from roboat import EventPoller

poller = EventPoller(client, interval=30)

@poller.on_friend_online
def on_online(user):
    print(f"{user.display_name} came online!")

@poller.on_new_friend
def on_friend(user):
    print(f"New friend: {user.display_name}")

poller.track_game(2753915549, milestone_step=1_000_000)

@poller.on("visit_milestone")
def on_milestone(game, count):
    print(f"{game.name} hit {count:,} visits!")

poller.start(interval=30)
```

---

## Pagination

```python
from roboat.utils import Paginator

for follower in Paginator(
    lambda cursor: client.friends.get_followers(156, limit=100, cursor=cursor)
):
    print(follower)

top_500 = Paginator(
    lambda c: client.friends.get_followers(156, limit=100, cursor=c),
    max_items=500,
).collect()
```

---

## Error Handling

```python
from roboat import UserNotFoundError, RateLimitedError, RoboatAPIError
from roboat.utils import retry

@retry(max_attempts=3, backoff=2.0)
def safe_get(user_id):
    return client.users.get_user(user_id)

try:
    user = safe_get(99999999999)
except UserNotFoundError:
    print("Not found")
except RateLimitedError:
    print("Rate limited")
except RoboatAPIError as e:
    print(f"Error: {e}")
```

---

## Analytics

```python
from roboat.analytics import Analytics

an = Analytics(client)

print(an.user_report(156))
print(an.compare_games([2753915549, 286090429]))
print(an.group_report(7))
print(an.rich_leaderboard_str([2753915549, 286090429], by="visits"))
```

---

## Terminal Commands

| Command | Description |
|:---|:---|
| `start <userid>` | Begin session (required first) |
| `auth` | OAuth login via browser |
| `whoami` | Current session info |
| `newdb / loaddb / listdb` | Database management |
| `user <id>` | User profile |
| `game <id>` | Game stats |
| `friends / followers <id>` | Social info |
| `likes <id>` | Vote stats |
| `search user/game <kw>` | Search |
| `presence / avatar <id>` | Status and avatar |
| `servers <placeid>` | Active servers |
| `badges <id>` | Game badges |
| `catalog <keyword>` | Shop search |
| `trades` | Trade list |
| `inventory / rap <id>` | Limiteds and RAP |
| `messages` | Private messages |
| `owns <uid> <assetid>` | Ownership check |
| `universe <id>` | Dev universe info |
| `save user/game <id>` | Save to DB |
| `cache [clear]` | Cache stats |
| `watch <id>` | Visit milestone alerts |
| `history / clear / exit` | Utility |

---

## License

MIT

---

<div align="center">

<img src="https://capsule-render.vercel.app/api?type=waving&color=0:A78BFA,100:6EE7F7&height=120&section=footer" width="100%"/>

**Built for the Roblox developer community**

[![GitHub](https://img.shields.io/badge/GitHub-valeoncehadadream%2Froboat-181717?style=for-the-badge&logo=github)](https://github.com/valeoncehadadream/roboat)

</div>
