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 +22 -0
- robloxapi/badges.py +83 -0
- robloxapi/catalog.py +219 -0
- robloxapi/client.py +89 -0
- robloxapi/friends.py +118 -0
- robloxapi/games.py +292 -0
- robloxapi/groups.py +124 -0
- robloxapi/thumbnails.py +168 -0
- robloxapi/users.py +95 -0
- rowrap-1.0.0.dist-info/METADATA +319 -0
- rowrap-1.0.0.dist-info/RECORD +14 -0
- rowrap-1.0.0.dist-info/WHEEL +5 -0
- rowrap-1.0.0.dist-info/licenses/LICENSE +21 -0
- rowrap-1.0.0.dist-info/top_level.txt +1 -0
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")
|