diff --git a/examples/place_search.py b/examples/place_search.py new file mode 100644 index 00000000..fd902c5c --- /dev/null +++ b/examples/place_search.py @@ -0,0 +1,42 @@ +""" +Grabs multiple places' information. with just the a keyword +A cookie is not required to grab places' information. +Please note that its roblox that gives builder info as "" +""" + +import asyncio +from roblox import Client +client = Client() + + +async def main(): + # ==== for 1 place ==== + + # place = await client.search_place('Phantom forces') + # print("ID:", place.id) + # print("\tName:", place.name) + # print(f"\tDescription: {place.description!r}") + # print("\tPlayable:", place.is_playable) + # if not place.is_playable: + # print("\tReason:", place.reason_prohibited) + # if place.price > 0: + # print("\tPrice:", place.price) + # print("\tCreator:", place.builder) + + # ==== for multiple places ==== + + places = await client.search_places('Phantom forces') + for place in places: + print("ID:", place.id) + print("\tName:", place.name) + print(f"\tDescription: {place.description!r}") + print("\tPlayable:", place.is_playable) + if not place.is_playable: + print("\tReason:", place.reason_prohibited) + if place.price > 0: + print("\tPrice:", place.price) + + print("\tCreator:", place.builder) + + +asyncio.get_event_loop().run_until_complete(main()) diff --git a/roblox/client.py b/roblox/client.py index fbea3a43..ed89f139 100644 --- a/roblox/client.py +++ b/roblox/client.py @@ -32,6 +32,7 @@ from .utilities.iterators import PageIterator from .utilities.requests import Requests from .utilities.url import URLGenerator +import time class Client: @@ -179,7 +180,8 @@ async def get_users_by_usernames( """ users_response = await self._requests.post( url=self._url_generator.get_url("users", f"v1/usernames/users"), - json={"usernames": usernames, "excludeBannedUsers": exclude_banned_users}, + json={"usernames": usernames, + "excludeBannedUsers": exclude_banned_users}, ) users_data = users_response.json()["data"] @@ -250,7 +252,8 @@ def user_search(self, keyword: str, page_size: int = 10, page_size=page_size, max_items=max_items, extra_parameters={"keyword": keyword}, - handler=lambda client, data: PreviousUsernamesPartialUser(client=client, data=data), + handler=lambda client, data: PreviousUsernamesPartialUser( + client=client, data=data), ) # Groups @@ -266,7 +269,8 @@ async def get_group(self, group_id: int) -> Group: """ try: group_response = await self._requests.get( - url=self._url_generator.get_url("groups", f"v1/groups/{group_id}") + url=self._url_generator.get_url( + "groups", f"v1/groups/{group_id}") ) except BadRequest as exception: raise GroupNotFound( @@ -383,6 +387,61 @@ async def get_place(self, place_id: int) -> Place: except IndexError: raise PlaceNotFound("Invalid place.") from None + async def search_places(self, keyword: str, max_items: int = 25) -> List[Place]: + """ + Grabs a list of places corresponding to each ID in the list. + + Arguments: + keyword: game(s) name + max_items: amount of games to be searched with similar keyword + + Returns: + A list of Places. + """ + + # max_items=25 cuz at one point roblox somehow gives goofy ahh games that are some how related to the keyword. At 1000 it will start to give slightly related games + # also please note, this api has like no ratelimit while i was testing. its the same endpoint when u scroll down searching for a game in roblox UI + # so far i have gotten like 5k games withen a few mins, and no ratelimit at all. + + itemsFound = {} + + nextPagetoken = None + # roblox will use this to prevent games that have already been showen + sessionID = round(time.time()) + while len(itemsFound.keys()) <= max_items: + places_response = await self._requests.get( + url=self._url_generator.get_url( + "apis", f"search-api/omni-search" + ), + params={"searchQuery": keyword, + "pageToken": nextPagetoken, 'sessionId': sessionID, 'pageType': 'all'}, + ) + places_data = places_response.json() + for gameJSON in places_data['searchResults']: + # the json is weird for this endpoint so i had to modify places.py + game = Place(client=self, data=gameJSON['contents'][0]) + itemsFound[game.id] = game + nextPagetoken = places_data['nextPageToken'] + + # without [:max_items] it will give like 40+ its not exact + return [value for value in itemsFound.values()][:max_items] + + async def search_place(self, keyword: str) -> Place: + ''' + Could just use the other api for this, but already made search_places + search_places is already there so i just use [0] + [0] is always the best game (roblox already sorts it out) + + Arguments: + keyword: Game name you want to search + + Returns: + First result of game search + + ''' + game = await self.search_places(keyword, 1) + return game[0] + def get_base_place(self, place_id: int) -> BasePlace: """ Gets a base place. diff --git a/roblox/places.py b/roblox/places.py index dea1e63a..8948369c 100644 --- a/roblox/places.py +++ b/roblox/places.py @@ -37,26 +37,36 @@ def __init__(self, client: Client, data: dict): client: The Client object, which is passed to all objects this Client generates. data: data to make the magic happen. """ - super().__init__(client=client, place_id=data["placeId"]) + super().__init__(client=client, place_id=data.get( + 'placeId') or data.get('rootPlaceId')) + + # sorry for goofy ahh changes, but it will work with other functions. This is just support to place search endpoint self._client: Client = client - self.id: int = data["placeId"] + self.id: int = data.get('placeId') or data.get('rootPlaceId') self.name: str = data["name"] self.description: str = data["description"] - self.url: str = data["url"] - - self.builder: str = data["builder"] - self.builder_id: int = data["builderId"] + self.url: str = data.get( + 'url') or f'https://www.roblox.com/games/{self.id}' # roblox would redirect automaticly if given just ID - self.is_playable: bool = data["isPlayable"] - self.reason_prohibited: str = data["reasonProhibited"] - self.universe: BaseUniverse = BaseUniverse(client=self._client, universe_id=data["universeId"]) - self.universe_root_place: BasePlace = BasePlace(client=self._client, place_id=data["universeRootPlaceId"]) + # the builder in place search is messed up (roblox side), for some reason only the top game and some games only return an actuall name. but rest just returns "" + self.builder: str = data.get('builder') or data.get('creatorName') + self.builder_id: int = data.get("builderId") or data.get('creatorId') - self.price: int = data["price"] - self.image_token: str = data["imageToken"] - self.has_verified_badge: bool = data["hasVerifiedBadge"] + self.is_playable: bool = data.get("isPlayable") or True + self.reason_prohibited: str = data.get("reasonProhibited") + self.universe: BaseUniverse = BaseUniverse( + client=self._client, universe_id=data["universeId"]) + if data.get('universeRootPlaceId'): # not given in search endpoint + self.universe_root_place: BasePlace = BasePlace( + client=self._client, place_id=data["universeRootPlaceId"]) + else: + self.universe_root_place = None + self.price: int = data.get("price") or 0 + self.image_token: str = data.get("imageToken") + self.has_verified_badge: bool = data.get( + "hasVerifiedBadge") or data.get('creatorHasVerifiedBadge') def __repr__(self): return f"<{self.__class__.__name__} id={self.id} name={self.name!r}>" diff --git a/roblox/utilities/requests.py b/roblox/utilities/requests.py index bd1937d0..ab1b9b5d 100644 --- a/roblox/utilities/requests.py +++ b/roblox/utilities/requests.py @@ -107,7 +107,6 @@ async def request(self, method: str, *args, **kwargs) -> Response: except JSONDecodeError: pass errors = data and data.get("errors") - exception = get_exception_from_status_code(response.status_code)( response=response, errors=errors