roterminal 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
roterminal/__init__.py ADDED
@@ -0,0 +1,268 @@
1
+ """RoTerminal: live data on every Roblox game.
2
+
3
+ Docs: https://roterminal.co/docs/sdks · Every endpoint: https://roterminal.co/docs/api
4
+
5
+ from roterminal import RoTerminal
6
+
7
+ rt = RoTerminal() # reads ROTERMINAL_API_KEY
8
+ game = rt.games.retrieve("grow a garden")["data"]
9
+ print(game["name"], game["playing_now"])
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import time
17
+ import urllib.error
18
+ import urllib.parse
19
+ import urllib.request
20
+ from typing import Any, Optional, Union
21
+
22
+ __version__ = "0.1.0"
23
+ __all__ = ["RoTerminal", "RoTerminalError"]
24
+
25
+ BASE_URL = "https://roterminal.co/api/v1"
26
+
27
+
28
+ class RoTerminalError(Exception):
29
+ """An error from the API, with its code (https://roterminal.co/docs/errors)."""
30
+
31
+ def __init__(self, message: str, *, status: int, code: str, request_id: Optional[str] = None, doc_url: Optional[str] = None, param: Optional[str] = None, retry_after: Optional[int] = None) -> None:
32
+ super().__init__(message)
33
+ self.message = message
34
+ self.status = status
35
+ self.code = code
36
+ self.request_id = request_id
37
+ self.doc_url = doc_url
38
+ self.param = param
39
+ self.retry_after = retry_after
40
+
41
+ def __repr__(self) -> str:
42
+ return f"RoTerminalError(code={self.code!r}, status={self.status}, message={self.message!r})"
43
+
44
+
45
+ class _Games:
46
+ def __init__(self, client: "RoTerminal") -> None:
47
+ self._c = client
48
+
49
+ def retrieve(self, game: Union[str, int]) -> dict:
50
+ """Retrieve a game. Everything on one Roblox game right now: players and its rank, the change over an hour and a day, highs, momentum, its genre, its developer, recent updates and how players moved after each, its closest rivals, its places on Roblox’s charts and the last 7 days of players.
51
+
52
+ https://roterminal.co/docs/api/games-retrieve
53
+ """
54
+ return self._c._get("/games/{game}", {"game": game})
55
+
56
+ def days(self, game: Union[str, int], *, days: Optional[int] = None) -> dict:
57
+ """List a game’s days. A game’s players day by day, newest first: the average, peak and low, the visits that day, and what happened (updates and events). Days run midnight to midnight, New York time.
58
+
59
+ https://roterminal.co/docs/api/games-days
60
+ """
61
+ return self._c._get("/games/{game}/days", {"game": game}, {"days": days})
62
+
63
+ def live(self, game: Union[str, int]) -> dict:
64
+ """Get live players. A game’s players this second, its rank on Roblox and its 24-hour high. Light enough to ask every 15 seconds, and it doesn’t count toward your daily calls.
65
+
66
+ https://roterminal.co/docs/api/games-live
67
+ """
68
+ return self._c._get("/games/{game}/live", {"game": game})
69
+
70
+ def developer(self, game: Union[str, int]) -> dict:
71
+ """Retrieve a game’s developer. Who makes a game, a Roblox group or one person: its owner, members, whether it’s verified, its rank among developers, everyone playing its games now, and each of its games.
72
+
73
+ https://roterminal.co/docs/api/games-developer
74
+ """
75
+ return self._c._get("/games/{game}/developer", {"game": game})
76
+
77
+ def search(self, q: str) -> dict:
78
+ """Search games. Find Roblox games by name: up to 8 matches with their universe ID, developer, genre and players now, the most played first.
79
+
80
+ https://roterminal.co/docs/api/games-search
81
+ """
82
+ return self._c._get("/games/search", {}, {"q": q})
83
+
84
+ def compare(self, games: list) -> dict:
85
+ """Compare games. Two to five games side by side: players now, the change over a day, momentum, rank in their genre, likes, the latest update and how it did, and the last 7 days of players for each.
86
+
87
+ https://roterminal.co/docs/api/games-compare
88
+ """
89
+ return self._c._get("/games/compare", {}, {"games": ",".join(str(g) for g in games)})
90
+
91
+ def top(self, *, list: Optional[str] = None, genre: Optional[str] = None, limit: Optional[int] = None) -> dict:
92
+ """List the most played games. Roblox games ranked by players right now, or only the games that came out in the last 30 days. Optionally one genre.
93
+
94
+ https://roterminal.co/docs/api/games-top
95
+ """
96
+ return self._c._get("/games/top", {}, {"list": list, "genre": genre, "limit": limit})
97
+
98
+ def rising(self, *, range: Optional[str] = None, genre: Optional[str] = None, limit: Optional[int] = None) -> dict:
99
+ """List rising games. The games gaining players fastest against a day ago or a week ago, each with its players now and then, the change, its genre and when it came out.
100
+
101
+ https://roterminal.co/docs/api/games-rising
102
+ """
103
+ return self._c._get("/games/rising", {}, {"range": range, "genre": genre, "limit": limit})
104
+
105
+ def screen(self, *, genre: Optional[str] = None, theme: Optional[str] = None, min_playing: Optional[int] = None, max_playing: Optional[int] = None, min_change_24h: Optional[float] = None, min_change_7d: Optional[float] = None, max_age_days: Optional[int] = None, min_like_pct: Optional[float] = None, sort: Optional[str] = None, limit: Optional[int] = None) -> dict:
106
+ """Screen games. Filter the 600 most played games by their numbers: genre, a theme in their names and descriptions, players, growth, age and likes, sorted the way you pick.
107
+
108
+ https://roterminal.co/docs/api/games-screen
109
+ """
110
+ return self._c._get("/games/screen", {}, {"genre": genre, "theme": theme, "min_playing": min_playing, "max_playing": max_playing, "min_change_24h": min_change_24h, "min_change_7d": min_change_7d, "max_age_days": max_age_days, "min_like_pct": min_like_pct, "sort": sort, "limit": limit})
111
+
112
+
113
+ class _Trends:
114
+ def __init__(self, client: "RoTerminal") -> None:
115
+ self._c = client
116
+
117
+ def list(self, *, limit: Optional[int] = None) -> dict:
118
+ """List trends. What developers are making games about: the words in new games’ names, ranked by how many new games used them in the last 30 days against the 30 before, each with its biggest game.
119
+
120
+ https://roterminal.co/docs/api/trends-list
121
+ """
122
+ return self._c._get("/trends", {}, {"limit": limit})
123
+
124
+ def retrieve(self, word: Union[str, int]) -> dict:
125
+ """Retrieve a trend. One word, like horror or brainrot: how many new games used it in the last 30 days and the 30 before, how many games use it in all, everyone playing them now, and its biggest game. Use it to check whether an idea has been done.
126
+
127
+ https://roterminal.co/docs/api/trends-retrieve
128
+ """
129
+ return self._c._get("/trends/{word}", {"word": word})
130
+
131
+
132
+ class _Genres:
133
+ def __init__(self, client: "RoTerminal") -> None:
134
+ self._c = client
135
+
136
+ def list(self) -> dict:
137
+ """List genres. Every genre on Roblox now: players, its share of everyone playing, the change on a day ago, how many games, its subgenres and its biggest games.
138
+
139
+ https://roterminal.co/docs/api/genres-list
140
+ """
141
+ return self._c._get("/genres", {})
142
+
143
+
144
+ class _Charts:
145
+ def __init__(self, client: "RoTerminal") -> None:
146
+ self._c = client
147
+
148
+ def list(self) -> dict:
149
+ """List Roblox’s charts. Roblox’s own charts (Top Earning, Top Trending, Up-and-Coming and the rest), as RoTerminal keeps them day by day.
150
+
151
+ https://roterminal.co/docs/api/charts-list
152
+ """
153
+ return self._c._get("/charts", {})
154
+
155
+ def retrieve(self, chart: Union[str, int], *, limit: Optional[int] = None) -> dict:
156
+ """Retrieve a chart. One of Roblox’s charts now: its games in order, each with its players and its best place on the chart.
157
+
158
+ https://roterminal.co/docs/api/charts-retrieve
159
+ """
160
+ return self._c._get("/charts/{chart}", {"chart": chart}, {"limit": limit})
161
+
162
+
163
+ class _Developers:
164
+ def __init__(self, client: "RoTerminal") -> None:
165
+ self._c = client
166
+
167
+ def retrieve(self, developer: Union[str, int]) -> dict:
168
+ """Retrieve a developer. A developer by name, a Roblox group or one person: its owner, members, whether it’s verified, its rank among developers, everyone playing its games now, and each of its games.
169
+
170
+ https://roterminal.co/docs/api/developers-retrieve
171
+ """
172
+ return self._c._get("/developers/{developer}", {"developer": developer})
173
+
174
+
175
+ class _Roblox:
176
+ def __init__(self, client: "RoTerminal") -> None:
177
+ self._c = client
178
+
179
+ def retrieve(self) -> dict:
180
+ """Get players on Roblox. Everyone playing Roblox right now, the day’s high and low, the same time a week ago, and the last days’ averages and peaks.
181
+
182
+ https://roterminal.co/docs/api/roblox-retrieve
183
+ """
184
+ return self._c._get("/roblox", {})
185
+
186
+
187
+ class _Me:
188
+ def __init__(self, client: "RoTerminal") -> None:
189
+ self._c = client
190
+
191
+ def retrieve(self) -> dict:
192
+ """Retrieve your account. Who the key belongs to, its plan, and the day’s calls: how many you’ve made, how many you have, and when they reset. It doesn’t count toward your daily calls.
193
+
194
+ https://roterminal.co/docs/api/me-retrieve
195
+ """
196
+ return self._c._get("/me", {})
197
+
198
+ def games(self) -> dict:
199
+ """List your games. Your own games, the ones My Games shows you: players now against usual for this time, the latest update and how players moved after it, and today’s alerts. Needs an API key.
200
+
201
+ https://roterminal.co/docs/api/me-games
202
+ """
203
+ return self._c._get("/me/games", {})
204
+
205
+
206
+
207
+ class RoTerminal:
208
+ """The RoTerminal API.
209
+
210
+ api_key: your key (rta_...). Defaults to the ROTERMINAL_API_KEY environment
211
+ variable; without one, calls are made without a key (50 a day).
212
+ timeout: seconds before a request gives up.
213
+ max_retries: how many times to retry a 429 (per minute) or a 5xx.
214
+ """
215
+
216
+ def __init__(self, api_key: Optional[str] = None, *, base_url: str = BASE_URL, timeout: float = 20.0, max_retries: int = 2) -> None:
217
+ self.api_key = api_key if api_key is not None else os.environ.get("ROTERMINAL_API_KEY")
218
+ self.base_url = base_url.rstrip("/")
219
+ self.timeout = timeout
220
+ self.max_retries = max_retries
221
+ self.games = _Games(self)
222
+ self.trends = _Trends(self)
223
+ self.genres = _Genres(self)
224
+ self.charts = _Charts(self)
225
+ self.developers = _Developers(self)
226
+ self.roblox = _Roblox(self)
227
+ self.me = _Me(self)
228
+
229
+ def _get(self, path: str, at: dict, query: Optional[dict] = None) -> dict:
230
+ for key, value in at.items():
231
+ path = path.replace("{" + key + "}", urllib.parse.quote(str(value), safe=""))
232
+ params = {k: (",".join(str(x) for x in v) if isinstance(v, (list, tuple)) else v) for k, v in (query or {}).items() if v is not None and v != ""}
233
+ url = self.base_url + path + ("?" + urllib.parse.urlencode(params) if params else "")
234
+ headers = {"Accept": "application/json", "User-Agent": f"roterminal-python/{__version__}", "X-RoTerminal-Agent": "sdk/python"}
235
+ if self.api_key:
236
+ headers["Authorization"] = f"Bearer {self.api_key}"
237
+ attempt = 0
238
+ while True:
239
+ request = urllib.request.Request(url, headers=headers)
240
+ try:
241
+ with urllib.request.urlopen(request, timeout=self.timeout) as response:
242
+ return json.loads(response.read().decode("utf-8"))
243
+ except urllib.error.HTTPError as e:
244
+ try:
245
+ body: Any = json.loads(e.read().decode("utf-8"))
246
+ except Exception:
247
+ body = {}
248
+ err = body.get("error", {}) if isinstance(body, dict) else {}
249
+ retry_after = _int(e.headers.get("Retry-After")) or err.get("retry_after")
250
+ again = (e.code == 429 and err.get("code") == "rate_limited") or e.code >= 500
251
+ if again and attempt < self.max_retries and (retry_after or 1) <= 60:
252
+ time.sleep(retry_after or 2 ** attempt)
253
+ attempt += 1
254
+ continue
255
+ raise RoTerminalError(err.get("message") or f"RoTerminal answered {e.code}.", status=e.code, code=err.get("code", "internal_error"), request_id=err.get("request_id") or e.headers.get("X-Request-Id"), doc_url=err.get("doc_url"), param=err.get("param"), retry_after=retry_after) from None
256
+ except (urllib.error.URLError, TimeoutError) as e:
257
+ if attempt < self.max_retries:
258
+ time.sleep(0.5 * 2 ** attempt)
259
+ attempt += 1
260
+ continue
261
+ raise RoTerminalError(f"Couldn't reach RoTerminal: {e}", status=0, code="network_error") from None
262
+
263
+
264
+ def _int(value: Optional[str]) -> Optional[int]:
265
+ try:
266
+ return int(value) if value else None
267
+ except ValueError:
268
+ return None
roterminal/py.typed ADDED
File without changes
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: roterminal
3
+ Version: 0.1.0
4
+ Summary: Live data on every Roblox game: players now, history, rising games, trends and developers. The RoTerminal API for Python.
5
+ Author: RoTerminal
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://roterminal.co/docs/sdks
8
+ Project-URL: Documentation, https://roterminal.co/docs/api
9
+ Project-URL: Changelog, https://roterminal.co/docs/changelog
10
+ Keywords: roblox,roblox-api,analytics,sdk,roterminal
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # roterminal
20
+
21
+ Live data on every Roblox game, for Python: players right now, each game's history since Sep 27, 2026, rising games, trends, genres, Roblox's charts and the developers behind each game.
22
+
23
+ ```
24
+ pip install roterminal
25
+ ```
26
+
27
+ ```python
28
+ from roterminal import RoTerminal
29
+
30
+ rt = RoTerminal() # reads ROTERMINAL_API_KEY
31
+
32
+ game = rt.games.retrieve("grow a garden")["data"]
33
+ print(game["name"], game["playing_now"])
34
+
35
+ for g in rt.games.rising(range="7d", limit=5)["data"]["games"]:
36
+ print(g["name"], g["change"])
37
+ ```
38
+
39
+ The key is optional: without one you get 50 calls a day for each address. Create a free key at https://roterminal.co/account/developer.
40
+
41
+ Errors raise `roterminal.RoTerminalError` with `code`, `status` and `request_id`.
42
+
43
+ Every endpoint: https://roterminal.co/docs/api
44
+
45
+ Version 0.1.0. MIT licence.
@@ -0,0 +1,7 @@
1
+ roterminal/__init__.py,sha256=UQXDkg87L5j-9Sa1i4RCX-YpOE9qpW8N3rQJNdl7Ru4,12788
2
+ roterminal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ roterminal-0.1.0.dist-info/licenses/LICENSE,sha256=9p4hcueQ1_lQGelwMuBvld0UBIePT7rHkXsuGwyb04E,1082
4
+ roterminal-0.1.0.dist-info/METADATA,sha256=s4lTOsRB_1jm_xe2cXjMYNLdhbcrt4hD0z3Y2X6yIh8,1503
5
+ roterminal-0.1.0.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
6
+ roterminal-0.1.0.dist-info/top_level.txt,sha256=7pN8y1pO3MMvrkPB6vbMVddxNeTx71lr1M7aqsBrD8c,11
7
+ roterminal-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pixel Board Services Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ roterminal