Skip to content

Commit 34076a4

Browse files
authored
Merge pull request #6 from RecNetBot-Development/official-api
Merge official API implementation to main
2 parents 8f0befe + dce855b commit 34076a4

File tree

19 files changed

+192
-126
lines changed

19 files changed

+192
-126
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "recnetpy"
7-
version = "0.1.51"
7+
version = "0.2.75"
88
authors = [
99
{ name="RecNetBot Development"}
1010
]

src/recnetpy/dataclasses/account.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from .base import BaseDataClass
44
from .progression import Progression
55
from ..misc import date_to_unix, bitmask_decode
6+
from ..rest.exceptions import RateLimited
67

78
if TYPE_CHECKING:
89
from . import Event, Image, Room
910
from ..misc.api_responses import AccountResponse, ProgressionResponse, BioResponse
1011
from ..rest import Response
1112

1213

13-
PLATFORM_LIST: List[str] = ['Steam', 'Meta', 'PlayStation', 'Xbox', 'HeadlessBot', 'iOS', 'Android', 'Standalone', 'Pico']
14+
PLATFORM_LIST: List[str] = ['Steam', 'Meta', 'PlayStation', 'Xbox', 'RecNet', 'iOS', 'Android', 'Standalone', 'Pico', 'Nintendo']
1415
PERSONAL_PRONOUNS_LIST: List[str] = ['She / her', 'He / him', 'They / them', 'Ze / hir', 'Ze / zir', 'Xe / xem']
1516
IDENTITY_FLAGS_LIST: List[str] = ['LGBTQIA', 'Transgender', 'Bisexual', 'Lesbian', 'Pansexual', 'Asexual', 'Intersex', 'Genderqueer', 'Nonbinary', 'Aromantic']
1617

@@ -25,10 +26,12 @@ class Account(BaseDataClass['AccountResponse']):
2526
username: str
2627
#: This is what appears in bold above the username on an account's page on RecNet. The display name is not unique unlike the username.
2728
display_name: str
29+
#: This is the RR+ exclusive display emoji.
30+
display_emoji: Optional[str] = None
2831
#: This is the file name of an account's profile picture.
2932
profile_image: str
3033
#: This is true if the account is a junior account, false if the account is a non-junior account.
31-
is_junior: bool
34+
# is_junior: bool DEPRECATED
3235
#: This is a list of platforms a user plays on. It has these possible values ``['Steam', 'Meta', 'PlayStation', 'Xbox', 'RecNet', 'iOS', 'Android', 'Standalone']``.
3336
platforms: List[str]
3437
#: This is the list of pronouns a user goes by. It has these possible values ``['She / her', 'He / him', 'They / them', 'Ze / hir', 'Ze / zir', 'Xe / xem']``.
@@ -73,11 +76,12 @@ def patch_data(self, data: 'AccountResponse') -> None:
7376
self.display_name = data['displayName']
7477
self.profile_image = data['profileImage']
7578
self.banner_image = data.get("bannerImage", None)
76-
self.is_junior = bool(data['isJunior'])
79+
self.display_emoji = data.get("displayEmoji", None)
80+
#self.is_junior = bool(data['isJunior'])
7781
self.platforms = bitmask_decode(data['platforms'], PLATFORM_LIST)
7882
self.personal_pronouns = bitmask_decode(data['personalPronouns'], PERSONAL_PRONOUNS_LIST)
7983
self.identity_flags = bitmask_decode(data['identityFlags'], IDENTITY_FLAGS_LIST)
80-
self.created_at = date_to_unix(data['createdAt'])
84+
self.created_at = date_to_unix(data['createdAt'], new=False)
8185

8286
async def get_events(self, take: int = 16, skip: int = 0, force: bool = False) -> List['Event']:
8387
"""
@@ -167,8 +171,11 @@ async def get_bio(self, force: bool = False) -> str:
167171
:return: The player's bio.
168172
"""
169173
if self.bio is None or force:
170-
data: 'Response[BioResponse]' = await self.rec_net.accounts(self.id).bio.make_request('get')
171-
self.bio = data.data['bio']
174+
try:
175+
data: 'Response[BioResponse]' = await self.rec_net.accounts(self.id).bio.make_request('get')
176+
self.bio = data.data['bio']
177+
except RateLimited:
178+
self.bio = "Unable to fetch bio!"
172179
return self.bio
173180

174181
async def get_level(self, force: bool = False) -> 'Progression':
@@ -180,7 +187,7 @@ async def get_level(self, force: bool = False) -> 'Progression':
180187
:return: This player's level.
181188
"""
182189
if self.level is None or force:
183-
data: 'Response[List[ProgressionResponse]]' = await self.rec_net.api.players.v2.progression.bulk.make_request('post', body = {'id': [self.id]})
190+
data: 'Response[List[ProgressionResponse]]' = await self.rec_net.apim.players.progression.bulk.make_request('post', body = {'id': [self.id]})
184191
self.level = Progression(data.data[0])
185192
return self.level
186193

@@ -193,8 +200,12 @@ async def get_subscriber_count(self, force: bool = False) -> int:
193200
:return: This player's subscriber count.
194201
"""
195202
if self.subscriber_count is None or force:
203+
#try:
196204
data: 'Response[int]' = await self.rec_net.clubs.subscription.subscribercount(self.id).make_request('get')
197205
self.subscriber_count = data.data
206+
#except:
207+
# self.subscriber_count = -1
208+
198209
return self.subscriber_count
199210

200211
async def get_is_influencer(self, force: bool = False) -> bool:
@@ -206,6 +217,6 @@ async def get_is_influencer(self, force: bool = False) -> bool:
206217
:return: This player's subscriber count.
207218
"""
208219
if self.is_influencer is None or force:
209-
data: 'Response[bool]' = await self.rec_net.api.influencerpartnerprogram.isinfluencer.make_request('get', params = {'accountId': self.id})
220+
data: 'Response[bool]' = await self.rec_net.apim.influencerpartnerprogram.isinfluencer.make_request('get', params = {'accountId': self.id})
210221
self.is_influencer = data.data
211222
return self.is_influencer

src/recnetpy/dataclasses/event.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .base import BaseDataClass
44
from .event_response import EventInteraction
5-
from ..misc import date_to_unix
5+
from ..misc import date_to_unix, list_chunks
66
from ..misc.constants import ACCESSIBILITY_DICT
77

88
if TYPE_CHECKING:
@@ -74,18 +74,18 @@ def patch_data(self, data: 'EventResponse') -> None:
7474
self.creator_player_id = data['CreatorPlayerId']
7575
self.image_name = data['ImageName']
7676
self.room_id = data['RoomId']
77-
self.subroom_id = data['SubRoomId']
77+
#self.subroom_id = data['SubRoomId']
7878
self.club = data['ClubId']
7979
self.name = data['Name']
8080
self.description = data['Description']
81-
self.start_time = date_to_unix(data['StartTime'])
82-
self.end_time = date_to_unix(data['EndTime'])
81+
self.start_time = date_to_unix(data['StartTime'], new=False)
82+
self.end_time = date_to_unix(data['EndTime'], new=False)
8383
self.attendee_count = data['AttendeeCount']
84-
self.accessibility = ACCESSIBILITY_DICT.get(data['Accessibility'], "Unknown")
85-
self.is_multi_instance = data['IsMultiInstance']
86-
self.support_multi_instance_room_chat = data['SupportMultiInstanceRoomChat']
87-
self.default_broadcast_permissions = BROADCAST_PERMISSION_DICT.get(data['DefaultBroadcastPermissions'], "Unknown")
88-
self.can_request_broadcast_permissions = BROADCAST_PERMISSION_DICT.get(data['CanRequestBroadcastPermissions'], "Unkown")
84+
#self.accessibility = ACCESSIBILITY_DICT.get(data['Accessibility'], "Unknown")
85+
#self.is_multi_instance = data['IsMultiInstance']
86+
#self.support_multi_instance_room_chat = data['SupportMultiInstanceRoomChat']
87+
#self.default_broadcast_permissions = BROADCAST_PERMISSION_DICT.get(data['DefaultBroadcastPermissions'], "Unknown")
88+
#self.can_request_broadcast_permissions = BROADCAST_PERMISSION_DICT.get(data['CanRequestBroadcastPermissions'], "Unkown")
8989

9090
async def get_images(self, take: int = 16, skip: int = 0, force: bool = False) -> List['Image']:
9191
"""
@@ -149,7 +149,7 @@ async def get_responses(self, force: bool = False) -> List['EventInteraction']:
149149
:return: A list of event interaction objects.
150150
"""
151151
if self.responses is None or force:
152-
data: Response[List['EventResponseResponse']] = await self.rec_net.api.playerevents.v1(self.id).responses.make_request('get')
152+
data: Response[List['EventResponseResponse']] = await self.rec_net.events(self.id).responses.make_request('get')
153153
self.responses = EventInteraction.create_from_list(data.data)
154154
return self.responses
155155

@@ -170,6 +170,21 @@ async def resolve_responders(self, force: bool = False) -> List['EventInteractio
170170
player = self.client.accounts.create_dataclass(response.player_id)
171171
response.player = player
172172
players[response.player_id] = player
173-
data: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.account.bulk.make_request('post', body = {"id": players.keys()})
174-
for data_response in data.data: players.get(data_response['accountId']).patch_data(data_response)
173+
174+
player_ids = list(players.keys())
175+
data: List[AccountResponse] = []
176+
177+
# 750 == roughly 9750 bytes of payload
178+
# limit of 10240 bytes in API
179+
if len(player_ids) > 750:
180+
player_chunks = list_chunks(player_ids, 750)
181+
for i in player_chunks:
182+
response: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.bulk.make_request('post', body = {"id": i})
183+
data += response.data
184+
else:
185+
# Fits in a single payload
186+
response: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.bulk.make_request('post', body = {"id": player_ids})
187+
data = response.data
188+
189+
for data_response in data: players.get(data_response['accountId']).patch_data(data_response)
175190
return self.responses

src/recnetpy/dataclasses/image.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ class Image(BaseDataClass['ImageResponse']):
2929
#: This is an image's unique identifier.
3030
id: int
3131
#: This is the type of image which has the possible value of ``[None, 'Share Camera', 'Outfit Thumbnail', 'Room Thumbnail', 'Profile Thumbnail', 'Invention Thumbnail', 'Player Event Thumbnail', 'Room Load Screen']``.
32-
type: str
32+
#type: str
3333
#: This is the visibilty of the image which has the possible value of ``['Private', 'Public', 'Unlisted']``.
34-
accessibility: str
34+
#accessibility: str
3535
#: This is true if the accessiblity of the image is fixed, false if its able to able to be changed.
36-
accessibility_locked: bool
36+
#accessibility_locked: bool
3737
#: This is the file name of the image itself.
3838
image_name: str
3939
#: This is the description of the image.
@@ -75,16 +75,17 @@ def patch_data(self, data: 'ImageResponse') -> None:
7575
"""
7676
self.data = data
7777
self.id = data['Id']
78-
self.type = IMAGE_TYPE.get(data['Type'], "Unknown")
79-
self.accessibility = ACCESSIBILITY_DICT.get(data['Accessibility'], "Unknown")
80-
self.accessibility_locked = data['AccessibilityLocked']
78+
#self.type = IMAGE_TYPE.get(data['Type'], "Unknown")
79+
#self.accessibility = ACCESSIBILITY_DICT.get(data['Accessibility'], "Unknown")
80+
#self.accessibility_locked = data['AccessibilityLocked']
8181
self.image_name = data['ImageName']
8282
self.description = data['Description']
8383
self.player_id = data['PlayerId']
84-
self.tagged_player_ids = data['TaggedPlayerIds']
84+
#self.tagged_player_ids = data['TaggedPlayerIds']
85+
self.tagged_player_ids = []
8586
self.room_id = data['RoomId']
8687
self.event_id = data['PlayerEventId']
87-
self.created_at = date_to_unix(data['CreatedAt'])
88+
self.created_at = date_to_unix(data['CreatedAt'], new=False)
8889
self.cheer_count = data['CheerCount']
8990
self.comment_count = data['CommentCount']
9091

@@ -162,7 +163,7 @@ async def get_cheers(self, force: bool = False) -> List[int]:
162163
"""
163164
if self.cheer_count == 0: return []
164165
if self.cheer_player_ids is None or force:
165-
data: 'Response[List[int]]' = await self.rec_net.api.images.v1(self.id).cheers.make_request('get')
166+
data: 'Response[List[int]]' = await self.rec_net.images(self.id).cheers.make_request('get')
166167
self.cheer_player_ids = data.data
167168
return self.cheer_player_ids
168169

@@ -176,7 +177,7 @@ async def get_comments(self, force: bool = False) -> List['Comment']:
176177
"""
177178
if self.comment_count == 0: return []
178179
if self.comments is None or force:
179-
data: 'Response[List[CommentResponse]]' = await self.rec_net.api.images.v1(self.id).comments.make_request('get')
180+
data: 'Response[List[CommentResponse]]' = await self.rec_net.images(self.id).comments.make_request('get')
180181
self.comments = Comment.create_from_list(data.data)
181182
return self.comments
182183

@@ -211,6 +212,6 @@ async def resolve_commenters(self, force: bool = False) -> List['Comment']:
211212
player = self.client.accounts.create_dataclass(comment.player_id)
212213
comment.player = player
213214
players[comment.player_id] = player
214-
data: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.account.bulk.make_request('post', body = {id: players.keys})
215+
data: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.bulk.make_request('post', body = {id: players.keys})
215216
for data_response in data.data: players.get(data_response['accountId']).patch_data(data_response)
216217
return self.comments

src/recnetpy/dataclasses/role.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
ROLE_DICT: Dict[int, str] = {
1010
0: None,
1111
1: "Banned",
12-
10: "Member",
12+
10: "Host",
1313
20: "Moderator",
14+
25: "Contributor",
1415
30: "Co-Owner",
1516
31: "Temporary Co-Owner",
1617
255: "Owner"
@@ -20,11 +21,11 @@ class Role(VariableClass['RoleResponse']):
2021
"""
2122
This class represents a room's player roles.
2223
"""
23-
#: This is the id of the role which has the possible values of ``{0: 'None', 10: 'Member', 20: 'Moderator', 30: 'Co-Owner', 31: 'Temporary Co-Owner', 255: 'Owner'}``.
24+
#: This is the id of the role which has the possible values of ``{0: 'None', 10: 'Host', 20: 'Moderator', 30: 'Co-Owner', 31: 'Temporary Co-Owner', 255: 'Owner'}``.
2425
id: int
2526
#: This is the id of the player who owns this role.
2627
account_id: int
27-
#: This is the name of the role the player owns which has the possible values of ``['None', 'Member', 'Moderator', 'Co-Owner', 'Temporary Co-Owner', Owner']``
28+
#: This is the name of the role the player owns which has the possible values of ``['None', 'Host', 'Moderator', 'Co-Owner', 'Temporary Co-Owner', Owner']``
2829
name: str
2930
#: This is the id of the account who updated the player's role.
3031
last_changed_by_account_id: Optional[int]

src/recnetpy/dataclasses/room.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class Room(BaseDataClass['RoomResponse']):
102102
visitor_count: int
103103
#: This in the number of times players have joined the room.
104104
visit_count: int
105+
#: This is the amount of boosts the room has.
106+
boost_count: int
105107
#: This is an account object which represents the player who created the room.
106108
creator_account: Optional['Account'] = None
107109
#: This a list of subroom objects which represents the room's subrooms.
@@ -133,22 +135,22 @@ def patch_data(self, data: 'RoomResponse') -> None:
133135
self.data = data
134136
self.id = data["RoomId"]
135137
self.is_dorm = data["IsDorm"]
136-
self.max_player_calculation_mode = MAX_PLAYER_CALCULATION_MODE.get(data["MaxPlayerCalculationMode"], "Unknown")
138+
#self.max_player_calculation_mode = MAX_PLAYER_CALCULATION_MODE.get(data["MaxPlayerCalculationMode"], "Unknown")
137139
self.max_players = data["MaxPlayers"]
138-
self.cloning_allowed = data["CloningAllowed"]
139-
self.disable_mic_auto_mute = data["DisableMicAutoMute"]
140-
self.disable_room_comments = data["DisableRoomComments"]
141-
self.encrypted_voice_chat = data["EncryptVoiceChat"]
142-
self.voice_moderated = data["ToxmodEnabled"]
143-
self.load_screen_locked = data["LoadScreenLocked"]
140+
#self.cloning_allowed = data["CloningAllowed"]
141+
#self.disable_mic_auto_mute = data["DisableMicAutoMute"]
142+
#self.disable_room_comments = data["DisableRoomComments"]
143+
#self.encrypted_voice_chat = data["EncryptVoiceChat"]
144+
#self.voice_moderated = data["ToxmodEnabled"]
145+
#self.load_screen_locked = data["LoadScreenLocked"]
144146
self.name = data["Name"]
145147
self.description = data["Description"]
146148
self.image_name = data["ImageName"]
147149
self.warnings = bitmask_decode(data["WarningMask"], WARNING_MASK_LIST)
148150
self.custom_warning = data["CustomWarning"]
149151
self.creator_account_id = data["CreatorAccountId"]
150-
self.state = ROOM_MODERATION_STATE.get(data["State"], "Unknown")
151-
self.accessibility = ACCESSIBILITY_DICT.get(data["Accessibility"], "Unknown")
152+
#self.state = ROOM_MODERATION_STATE.get(data["State"], "Unknown")
153+
#self.accessibility = ACCESSIBILITY_DICT.get(data["Accessibility"], "Unknown")
152154
self.supports_level_voting = data["SupportsLevelVoting"]
153155
self.is_rro = data["IsRRO"]
154156
self.supports_screens = data["SupportsScreens"]
@@ -164,6 +166,7 @@ def patch_data(self, data: 'RoomResponse') -> None:
164166
self.favorite_count = data["Stats"]["FavoriteCount"]
165167
self.visitor_count = data["Stats"]["VisitorCount"]
166168
self.visit_count = data["Stats"]["VisitCount"]
169+
self.boost_count = data["BoostCount"]
167170
self.subrooms = SubRoom.create_from_list(data.get("SubRooms"))
168171
self.roles = Role.create_from_list(data.get("Roles"))
169172
self.tags = Tag.create_from_list(data.get("Tags"))
@@ -221,14 +224,15 @@ async def resolve_role_owners(self) -> Optional[List['Role']]:
221224
222225
:return: A list of role objects, or None if roles is None
223226
"""
227+
224228
if self.roles is None: return None
225229
roles = self.roles
226230
accounts: Dict[int, Account] = {}
227231
for role in roles:
228232
account = self.client.accounts.create_dataclass(role.account_id)
229233
role.account = account
230234
accounts[role.account_id] = account
231-
data: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.account.bulk.make_request('post', body = {"id": accounts.keys()})
235+
data: 'Response[List[AccountResponse]]' = await self.rec_net.accounts.bulk.make_request('post', body = {"id": accounts.keys()})
232236
for data_response in data.data: accounts.get(data_response['accountId']).patch_data(data_response)
233237

234238
# Search for deleted accounts

src/recnetpy/dataclasses/subroom.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,19 @@ class SubRoom(VariableClass['SubRoomResponse']):
4040
description: Optional[str]
4141

4242
def __init__(self, data: 'SubRoomResponse'):
43-
if data.get("CurrentSave"):
44-
description = data["CurrentSave"].get("Description")
45-
saved_at = date_to_unix(data["CurrentSave"]["CreatedAt"])
46-
saved_by = data["CurrentSave"].get("SavedByAccountId", 1)
47-
else:
48-
saved_at, saved_by, description = None, 1, None
49-
5043
self.supports_join_in_progress = data['SupportsJoinInProgress']
5144
self.use_level_based_matchmaking = data['UseLevelBasedMatchmaking']
5245
self.use_age_based_matchmaking = data['UseAgeBasedMatchmaking']
5346
self.use_rec_royale_matchmaking = data['UseRecRoyaleMatchmaking']
5447
self.subroom_id = data['SubRoomId']
5548
self.room_id = data['RoomId']
56-
self.unity_scene_id = data['UnitySceneId']
49+
#self.unity_scene_id = data['UnitySceneId']
50+
self.last_moderated_save_moderation_state = data["LastModeratedSaveModerationState"]
5751
self.name = data['Name']
58-
self.data_blob = data.get("DataBlob", None)
59-
self.data_saved_at = saved_at
60-
self.data_saved_by = saved_by
61-
self.description = description
52+
#self.data_blob = data.get("DataBlob", None)
53+
#self.data_saved_at = saved_at
54+
#self.data_saved_by = saved_by
55+
#self.description = description
6256
self.is_sandbox = data['IsSandbox']
6357
self.max_players = data['MaxPlayers']
6458
self.accessibility = ACCESSIBILITY_DICT.get(data['Accessibility'])

0 commit comments

Comments
 (0)