rowrap 1.0.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.
robloxapi/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """
2
+ robloxapi - A Python wrapper for the Roblox web APIs.
3
+
4
+ Quickstart:
5
+ >>> from robloxapi import RobloxClient
6
+ >>> client = RobloxClient() # unauthenticated
7
+ >>> client = RobloxClient(cookie="ROBLOSECURITY") # authenticated
8
+
9
+ Sub-APIs:
10
+ client.users - User lookups, search, username history
11
+ client.games - Game details, visits, servers, votes, game page
12
+ client.catalog - Item search, details, resale data, bundles
13
+ client.groups - Group info, members, wall, audit log
14
+ client.friends - Friends, followers, followings
15
+ client.thumbnails - Avatar, game, asset, group thumbnail URLs
16
+ client.badges - Badge details, user badges, award dates
17
+ """
18
+
19
+ from .client import RobloxClient
20
+
21
+ __all__ = ["RobloxClient"]
22
+ __version__ = "1.0.0"
robloxapi/badges.py ADDED
@@ -0,0 +1,83 @@
1
+ """
2
+ Roblox Badges API - https://badges.roblox.com
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+
8
+ class BadgesAPI:
9
+ BASE = "https://badges.roblox.com/v1"
10
+
11
+ def __init__(self, client):
12
+ self._c = client
13
+
14
+ def get_badge(self, badge_id: int) -> dict:
15
+ """
16
+ Get badge info by ID.
17
+
18
+ Returns:
19
+ dict: {id, name, description, displayName, displayDescription,
20
+ enabled, iconImageId, displayIconImageId, created,
21
+ updated, statistics, awardingUniverse}
22
+ """
23
+ return self._c._get(f"{self.BASE}/badges/{badge_id}")
24
+
25
+ def get_universe_badges(self, universe_id: int, limit: int = 10,
26
+ cursor: Optional[str] = None,
27
+ sort_order: str = "Asc") -> dict:
28
+ """
29
+ Get all badges for a game/universe.
30
+
31
+ Returns:
32
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
33
+ """
34
+ params = {"limit": limit, "sortOrder": sort_order}
35
+ if cursor:
36
+ params["cursor"] = cursor
37
+ return self._c._get(
38
+ f"{self.BASE}/universes/{universe_id}/badges",
39
+ params=params,
40
+ )
41
+
42
+ def get_user_badges(self, user_id: int, limit: int = 10,
43
+ cursor: Optional[str] = None,
44
+ sort_order: str = "Asc") -> dict:
45
+ """
46
+ Get badges awarded to a user.
47
+
48
+ Returns:
49
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
50
+ """
51
+ params = {"limit": limit, "sortOrder": sort_order}
52
+ if cursor:
53
+ params["cursor"] = cursor
54
+ return self._c._get(
55
+ f"{self.BASE}/users/{user_id}/badges",
56
+ params=params,
57
+ )
58
+
59
+ def get_user_badges_by_game(self, user_id: int,
60
+ universe_id: int) -> dict:
61
+ """
62
+ Get badges a user has earned in a specific game.
63
+
64
+ Returns:
65
+ dict: {data: [{badge, awardedDate}, ...]}
66
+ """
67
+ return self._c._get(
68
+ f"{self.BASE}/users/{user_id}/badges/awarded-dates",
69
+ params={"badgeIds": universe_id},
70
+ )
71
+
72
+ def get_badge_awarded_dates(self, user_id: int,
73
+ badge_ids: List[int]) -> dict:
74
+ """
75
+ Check which badges a user has and when they were awarded.
76
+
77
+ Returns:
78
+ dict: {data: [{badgeId, awardedDate}, ...]}
79
+ """
80
+ return self._c._get(
81
+ f"{self.BASE}/users/{user_id}/badges/awarded-dates",
82
+ params={"badgeIds": ",".join(str(i) for i in badge_ids)},
83
+ )
robloxapi/catalog.py ADDED
@@ -0,0 +1,219 @@
1
+ """
2
+ Roblox Catalog API - https://catalog.roblox.com
3
+ Covers item search, item details, bundles, and resale data.
4
+ """
5
+
6
+ from typing import List, Optional
7
+
8
+
9
+ class CatalogAPI:
10
+ BASE = "https://catalog.roblox.com/v1"
11
+ BASE2 = "https://catalog.roblox.com/v2"
12
+
13
+ def __init__(self, client):
14
+ self._c = client
15
+
16
+ # ------------------------------------------------------------------ #
17
+ # Search #
18
+ # ------------------------------------------------------------------ #
19
+
20
+ def search_catalog(
21
+ self,
22
+ keyword: str = "",
23
+ category: str = "All",
24
+ subcategory: str = "All",
25
+ sort_type: str = "Relevance",
26
+ sort_aggregation: int = 5,
27
+ price_min: Optional[int] = None,
28
+ price_max: Optional[int] = None,
29
+ limit: int = 30,
30
+ cursor: Optional[str] = None,
31
+ include_not_for_sale: bool = False,
32
+ ) -> dict:
33
+ """
34
+ Search the Roblox avatar shop / catalog.
35
+
36
+ Args:
37
+ category: "All", "Clothing", "Accessories", "AvatarAnimations",
38
+ "BodyParts", "Gear", "Models", "Plugins", "Decals",
39
+ "Audio", "Meshes", "Images", "MeshParts".
40
+ subcategory: Varies by category (e.g. "TShirts", "Hats").
41
+ sort_type: "Relevance", "Favorited", "Sales", "Updated",
42
+ "PriceAsc", "PriceDesc".
43
+ sort_aggregation: Time window — 5=All time, 1=Past day, 3=Past week.
44
+ limit: 10, 28, 30, or 120.
45
+
46
+ Returns:
47
+ dict: {keyword, previousPageCursor, nextPageCursor,
48
+ data: [{id, name, itemType, assetType, bundleType,
49
+ description, productId, genres, bundledItems,
50
+ itemStatus, itemRestrictions, creatorType,
51
+ creatorTargetId, creatorName, price,
52
+ premiumPricing, lowestPrice, purchaseCount,
53
+ favoriteCount, offSaleDeadline}, ...]}
54
+ """
55
+ params = {
56
+ "category": category,
57
+ "subcategory": subcategory,
58
+ "sortType": sort_type,
59
+ "sortAggregation": sort_aggregation,
60
+ "limit": limit,
61
+ "includeNotForSale": include_not_for_sale,
62
+ }
63
+ if keyword:
64
+ params["keyword"] = keyword
65
+ if price_min is not None:
66
+ params["minPrice"] = price_min
67
+ if price_max is not None:
68
+ params["maxPrice"] = price_max
69
+ if cursor:
70
+ params["cursor"] = cursor
71
+ return self._c._get(f"{self.BASE}/search/items", params=params)
72
+
73
+ # ------------------------------------------------------------------ #
74
+ # Item details #
75
+ # ------------------------------------------------------------------ #
76
+
77
+ def get_item_details(self, items: List[dict]) -> dict:
78
+ """
79
+ Get details for multiple catalog items.
80
+
81
+ Args:
82
+ items: List of {itemType: "Asset"|"Bundle", id: int}
83
+
84
+ Returns:
85
+ dict: {data: [{id, itemType, assetType, name, description,
86
+ productId, price, ...}, ...]}
87
+ """
88
+ return self._c._post(
89
+ f"{self.BASE}/catalog/items/details",
90
+ json={"items": items},
91
+ )
92
+
93
+ def get_asset_details(self, asset_id: int) -> dict:
94
+ """
95
+ Shortcut to get a single asset's details.
96
+ """
97
+ data = self.get_item_details([{"itemType": "Asset", "id": asset_id}])
98
+ results = data.get("data", [])
99
+ return results[0] if results else {}
100
+
101
+ def get_bundle_details(self, bundle_id: int) -> dict:
102
+ """
103
+ Get details of a specific bundle.
104
+
105
+ Returns:
106
+ dict: {id, name, description, bundleType, items, creator,
107
+ product, ...}
108
+ """
109
+ return self._c._get(f"{self.BASE}/bundles/{bundle_id}/details")
110
+
111
+ def get_bundles_by_ids(self, bundle_ids: List[int]) -> dict:
112
+ """
113
+ Get multiple bundles by ID.
114
+
115
+ Returns:
116
+ dict: {data: [...]}
117
+ """
118
+ return self._c._get(
119
+ f"{self.BASE}/bundles/details",
120
+ params={"bundleIds": ",".join(str(i) for i in bundle_ids)},
121
+ )
122
+
123
+ # ------------------------------------------------------------------ #
124
+ # User / group catalog #
125
+ # ------------------------------------------------------------------ #
126
+
127
+ def get_user_bundles(self, user_id: int, bundle_type: Optional[str] = None,
128
+ limit: int = 10, cursor: Optional[str] = None) -> dict:
129
+ """
130
+ Get bundles owned by a user.
131
+
132
+ Returns:
133
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
134
+ """
135
+ params = {"limit": limit}
136
+ if bundle_type:
137
+ params["bundleType"] = bundle_type
138
+ if cursor:
139
+ params["cursor"] = cursor
140
+ url = (f"{self.BASE}/users/{user_id}/bundles"
141
+ if not bundle_type
142
+ else f"{self.BASE}/users/{user_id}/bundles/{bundle_type}")
143
+ return self._c._get(url, params=params)
144
+
145
+ def get_group_items(self, group_id: int, category: str = "2",
146
+ limit: int = 30, cursor: Optional[str] = None) -> dict:
147
+ """
148
+ Get catalog items created by a group.
149
+
150
+ Args:
151
+ category: Asset category integer as string ("2" = clothing, etc.)
152
+
153
+ Returns:
154
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
155
+ """
156
+ params = {"category": category, "limit": limit}
157
+ if cursor:
158
+ params["cursor"] = cursor
159
+ return self._c._get(
160
+ f"{self.BASE2}/search/items",
161
+ params={**params, "creatorType": "Group",
162
+ "creatorTargetId": group_id},
163
+ )
164
+
165
+ # ------------------------------------------------------------------ #
166
+ # Resale / economy #
167
+ # ------------------------------------------------------------------ #
168
+
169
+ def get_asset_resale_data(self, asset_id: int) -> dict:
170
+ """
171
+ Get resale price history and seller data for a limited item.
172
+
173
+ Returns:
174
+ dict: {assetStock, sales, numberRemaining, recentAveragePrice,
175
+ originalPrice, priceDataPoints: [{value, date}],
176
+ volumeDataPoints: [{value, date}]}
177
+ """
178
+ return self._c._get(
179
+ f"https://economy.roblox.com/v1/assets/{asset_id}/resale-data"
180
+ )
181
+
182
+ def get_asset_resellers(self, asset_id: int, limit: int = 10,
183
+ cursor: Optional[str] = None) -> dict:
184
+ """
185
+ Get users currently reselling a limited item.
186
+
187
+ Returns:
188
+ dict: {previousPageCursor, nextPageCursor,
189
+ data: [{userAssetId, seller, price, serialNumber}, ...]}
190
+ """
191
+ params = {"limit": limit}
192
+ if cursor:
193
+ params["cursor"] = cursor
194
+ return self._c._get(
195
+ f"https://economy.roblox.com/v1/assets/{asset_id}/resellers",
196
+ params=params,
197
+ )
198
+
199
+ # ------------------------------------------------------------------ #
200
+ # Favorites #
201
+ # ------------------------------------------------------------------ #
202
+
203
+ def get_asset_favorite_count(self, asset_id: int) -> dict:
204
+ """
205
+ Get the number of users who have favorited an asset.
206
+
207
+ Returns:
208
+ int (the count)
209
+ """
210
+ resp = self._c._get(
211
+ f"{self.BASE}/favorites/assets/{asset_id}/count"
212
+ )
213
+ return resp
214
+
215
+ def get_bundle_favorite_count(self, bundle_id: int) -> dict:
216
+ """Get favorite count for a bundle."""
217
+ return self._c._get(
218
+ f"{self.BASE}/favorites/bundles/{bundle_id}/count"
219
+ )
robloxapi/client.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ Roblox API Client - Main entry point for all API interactions.
3
+ """
4
+
5
+ import requests
6
+ from typing import Optional
7
+
8
+ from .users import UsersAPI
9
+ from .games import GamesAPI
10
+ from .catalog import CatalogAPI
11
+ from .groups import GroupsAPI
12
+ from .friends import FriendsAPI
13
+ from .thumbnails import ThumbnailsAPI
14
+ from .badges import BadgesAPI
15
+
16
+
17
+ class RobloxClient:
18
+ """
19
+ Main Roblox API client. Handles session management and provides
20
+ access to all sub-API modules.
21
+
22
+ Args:
23
+ cookie (str, optional): .ROBLOSECURITY cookie for authenticated requests.
24
+
25
+ Example:
26
+ >>> client = RobloxClient(cookie="YOUR_ROBLOSECURITY_COOKIE")
27
+ >>> user = client.users.get_user(1)
28
+ >>> print(user["name"])
29
+ """
30
+
31
+ BASE_HEADERS = {
32
+ "Content-Type": "application/json",
33
+ "Accept": "application/json",
34
+ }
35
+
36
+ def __init__(self, cookie: Optional[str] = None):
37
+ self.session = requests.Session()
38
+ self.session.headers.update(self.BASE_HEADERS)
39
+ self._cookie = None
40
+ self._csrf_token = None
41
+
42
+ if cookie:
43
+ self.set_cookie(cookie)
44
+
45
+ # Initialize sub-APIs
46
+ self.users = UsersAPI(self)
47
+ self.games = GamesAPI(self)
48
+ self.catalog = CatalogAPI(self)
49
+ self.groups = GroupsAPI(self)
50
+ self.friends = FriendsAPI(self)
51
+ self.thumbnails = ThumbnailsAPI(self)
52
+ self.badges = BadgesAPI(self)
53
+
54
+ def set_cookie(self, cookie: str):
55
+ """Set the .ROBLOSECURITY authentication cookie."""
56
+ self._cookie = cookie
57
+ self.session.cookies.set(".ROBLOSECURITY", cookie, domain=".roblox.com")
58
+ self._refresh_csrf()
59
+
60
+ def _refresh_csrf(self):
61
+ """Fetch and store a fresh CSRF token."""
62
+ try:
63
+ resp = self.session.post("https://auth.roblox.com/v2/logout")
64
+ token = resp.headers.get("x-csrf-token")
65
+ if token:
66
+ self._csrf_token = token
67
+ self.session.headers.update({"x-csrf-token": token})
68
+ except Exception:
69
+ pass
70
+
71
+ def _get(self, url: str, **kwargs) -> dict:
72
+ """Perform a GET request and return JSON."""
73
+ resp = self.session.get(url, **kwargs)
74
+ resp.raise_for_status()
75
+ return resp.json()
76
+
77
+ def _post(self, url: str, **kwargs) -> dict:
78
+ """Perform a POST request, refreshing CSRF on 403."""
79
+ resp = self.session.post(url, **kwargs)
80
+ if resp.status_code == 403:
81
+ self._refresh_csrf()
82
+ resp = self.session.post(url, **kwargs)
83
+ resp.raise_for_status()
84
+ return resp.json()
85
+
86
+ @property
87
+ def is_authenticated(self) -> bool:
88
+ """Returns True if a cookie has been set."""
89
+ return self._cookie is not None
robloxapi/friends.py ADDED
@@ -0,0 +1,118 @@
1
+ """
2
+ Roblox Friends API - https://friends.roblox.com
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+
8
+ class FriendsAPI:
9
+ BASE = "https://friends.roblox.com/v1"
10
+
11
+ def __init__(self, client):
12
+ self._c = client
13
+
14
+ def get_friends(self, user_id: int) -> dict:
15
+ """
16
+ Get a user's full friends list.
17
+
18
+ Returns:
19
+ dict: {data: [{id, name, displayName, hasVerifiedBadge,
20
+ isOnline, presenceType, isDeleted,
21
+ friendFrequentScore, friendFrequentRank}, ...]}
22
+ """
23
+ return self._c._get(f"{self.BASE}/users/{user_id}/friends")
24
+
25
+ def get_friend_count(self, user_id: int) -> dict:
26
+ """
27
+ Get the number of friends a user has.
28
+
29
+ Returns:
30
+ dict: {count}
31
+ """
32
+ return self._c._get(f"{self.BASE}/users/{user_id}/friends/count")
33
+
34
+ def get_followers(self, user_id: int, limit: int = 10,
35
+ cursor: Optional[str] = None,
36
+ sort_order: str = "Asc") -> dict:
37
+ """
38
+ Get users who follow a given user.
39
+
40
+ Returns:
41
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
42
+ """
43
+ params = {"limit": limit, "sortOrder": sort_order}
44
+ if cursor:
45
+ params["cursor"] = cursor
46
+ return self._c._get(
47
+ f"{self.BASE}/users/{user_id}/followers",
48
+ params=params,
49
+ )
50
+
51
+ def get_follower_count(self, user_id: int) -> dict:
52
+ """
53
+ Get the follower count for a user.
54
+
55
+ Returns:
56
+ dict: {count}
57
+ """
58
+ return self._c._get(f"{self.BASE}/users/{user_id}/followers/count")
59
+
60
+ def get_followings(self, user_id: int, limit: int = 10,
61
+ cursor: Optional[str] = None,
62
+ sort_order: str = "Asc") -> dict:
63
+ """
64
+ Get users that a given user follows.
65
+
66
+ Returns:
67
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
68
+ """
69
+ params = {"limit": limit, "sortOrder": sort_order}
70
+ if cursor:
71
+ params["cursor"] = cursor
72
+ return self._c._get(
73
+ f"{self.BASE}/users/{user_id}/followings",
74
+ params=params,
75
+ )
76
+
77
+ def get_following_count(self, user_id: int) -> dict:
78
+ """
79
+ Get how many users a user is following.
80
+
81
+ Returns:
82
+ dict: {count}
83
+ """
84
+ return self._c._get(f"{self.BASE}/users/{user_id}/followings/count")
85
+
86
+ def get_friend_requests(self, limit: int = 10,
87
+ cursor: Optional[str] = None) -> dict:
88
+ """
89
+ Get pending friend requests for the authenticated user (requires auth).
90
+
91
+ Returns:
92
+ dict: {previousPageCursor, nextPageCursor, data: [...]}
93
+ """
94
+ params = {"limit": limit}
95
+ if cursor:
96
+ params["cursor"] = cursor
97
+ return self._c._get(
98
+ f"{self.BASE}/my/friends/requests",
99
+ params=params,
100
+ )
101
+
102
+ def get_friend_request_count(self) -> dict:
103
+ """
104
+ Get the number of pending friend requests (requires auth).
105
+
106
+ Returns:
107
+ dict: {count}
108
+ """
109
+ return self._c._get(f"{self.BASE}/user/friend-requests/count")
110
+
111
+ def are_friends(self, user_id: int) -> dict:
112
+ """
113
+ Check if the authenticated user is friends with user_id (requires auth).
114
+
115
+ Returns:
116
+ dict: {isFriend}
117
+ """
118
+ return self._c._get(f"{self.BASE}/users/{user_id}/friends/statuses")