From 0b03918288fd4b0b0cfe1119f776e8cf337db698 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 21 Oct 2022 22:02:40 +1000 Subject: [PATCH 01/27] added creators.py - READ THE README.md FIRST! --- README.md | 67 ++++++++++++ rblxopencloud/__init__.py | 3 +- rblxopencloud/creator.py | 196 ++++++++++++++++++++++++++++++++++++ rblxopencloud/exceptions.py | 3 +- setup.py | 6 +- 5 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 rblxopencloud/creator.py diff --git a/README.md b/README.md index a33c065..31ee06d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,70 @@ +## **IMPORTANT** + +**The Assets API is an alpha API subject to breaking changes and it is not avaliable to everyone. DO NOT try to use it unless you have access. You might be looking for [main](https://github.com/TreeBen77/rblx-open-cloud/tree/main).** + +## If you understand the above: + +Installing: +```console +pip install git+https://github.com/TreeBen77/rblx-open-cloud/tree/assetsapi +``` +there's no documantation yet. but here's a cheat sheet +```py +import rblxopencloud + +# to upload to your own account, use Creator - insert your user ID and api key +creator = rblxopencloud.Creator(287113233, "api-key-here") +# to upload to your group, use GroupCreator - insert your group ID and api key +creator = rblxopencloud.GroupCreator(9697297, "api-key-here") + +# let's start with decals. open the file as read bytes. +with open("experiment.png", "rb") as file: + # call the create_decal object, name is the item name and description is the item description + asset = creator.create_decal(file, "name", "description") + # asset will be either Asset or PendingAsset. Asset contains it's ID and version number + # PendingAsset means roblox hasn't finnished processing it. This will have a fetch_status method + # and that's it. the fetch status method will check if it's ready yet, it will return None or Asset. + + # here's a method that will wait until the decal is ready + if isinstance(asset, rblxopencloud.Asset): + # if it's already been processed, then print the asset. + print(asset) + else: + # otherwise, we'll go into a loop that continuosly loops through that. + while True: + # this will try to check if the asset has been processed yet + status = asset.fetch_status() + if status: + # if it has been print it then stop the loop. + print(status) + break + +# now for audio. it's the same as above but with audio +# there's a bug with audio and groups preventing it from uploading right now. +with open("experiment.mp3", "rb") as file: + # call the create_decal object, name is the item name and description is the item description + asset = creator.create_audio(file, "name", "description") + # for simplicity, i won't include the asset fetch_status part above here but it also works. + + # please note that you can only upload 10 audios a month and if you're ID verified that's 100. + +# and finally for .fbx files. it's the same as above but with a model +with open("experiment.fbx", "rb") as file: + # call the create_decal object, name is the item name and description is the item description + asset = creator.create_fbx(file, "name", "description") + # for simplicity, i won't include the asset fetch_status part above here but it also works. + # you're post likely going to recieve a PendingAsset with models because roblox has to render them which takes time + +# you can also update uploaded fbx files: +with open("experiment.2.fbx", "rb") as file: + # the first number is the fbx asset you want to update + asset = creator.create_fbx(11326252443, file) + # for simplicity, i won't include the asset fetch_status part above here but it also works. + # you're post likely going to recieve a PendingAsset with models because roblox has to render them which takes time + +``` +**IMPORTANT:** This code is also subject to breaking changes. Don't use it in production code! + # rblx-open-cloud Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-cloud/index). diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index e8899ca..dbfcbd1 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -1,3 +1,4 @@ from .universe import * from .exceptions import * -from .datastore import * \ No newline at end of file +from .datastore import * +from .creator import * \ No newline at end of file diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py new file mode 100644 index 0000000..edee500 --- /dev/null +++ b/rblxopencloud/creator.py @@ -0,0 +1,196 @@ +from .exceptions import * +import requests, json, io +from typing import * +import urllib3, magic + +class Asset(): + def __init__(self, id, version) -> None: + self.id = id + self.version = version + + def __repr__(self) -> str: + return f"rblxopencloud.Asset({self.id}, version={self.version})" + +class PendingAsset(): + def __init__(self, operation, api_key) -> None: + self.__operation = operation + self.__api_key = api_key + + def __repr__(self) -> str: + return f"rblxopencloud.PendingAsset()" + + def fetch_status(self) -> Union[None, Asset]: + response = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{self.__operation}", + headers={"x-api-key": self.__api_key}) + + if response.ok: + info = response.json() + if info["status"] == "Pending": return None + return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + +class Creator(): + def __init__(self, userid: int, api_key: str) -> None: + self.id = userid + self.__api_key = api_key + self.__creator_type = "User" + + def __repr__(self) -> str: + return f"rblxopencloud.Creator({self.id})" + + def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: + read = file.read() + body, contentType = urllib3.encode_multipart_formdata({ + "request": json.dumps({ + "targetType": "Decal", + "creationContext": { + "assetName": name, + "assetDescription": description, + "creator": { + "creatorType": self.__creator_type, + "creatorId": self.id + } + } + }), + "fileContent": (magic.from_buffer(read, mime=True), read) + }) + response = requests.post(f"https://apis.roblox.com/assets/v1/create", + headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) + + if response.status_code == 400: raise InvalidAsset(f"The file is not a decal or is corrupted") + elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") + elif response.status_code == 429: raise RateLimited("You're being rate limited.") + elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") + elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") + + response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + headers={"x-api-key": self.__api_key}) + + if not response_status.ok: + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + info = response_status.json() + if info["status"] == "Pending": + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + + def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: + read = file.read() + body, contentType = urllib3.encode_multipart_formdata({ + "request": json.dumps({ + "targetType": "Audio", + "creationContext": { + "assetName": name, + "assetDescription": description, + "creator": { + "creatorType": self.__creator_type, + "creatorId": self.id + } + } + }), + "fileContent": (magic.from_buffer(read, mime=True), read) + }) + response = requests.post(f"https://apis.roblox.com/assets/v1/create", + headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) + + if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") + elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") + elif response.status_code == 429: raise RateLimited("You're being rate limited.") + elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") + elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") + + response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + headers={"x-api-key": self.__api_key}) + + if not response_status.ok: + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + info = response_status.json() + if info["status"] == "Pending": + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + + def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: + read = file.read() + body, contentType = urllib3.encode_multipart_formdata({ + "request": json.dumps({ + "targetType": "ModelFromFbx", + "creationContext": { + "assetName": name, + "assetDescription": description, + "creator": { + "creatorType": self.__creator_type, + "creatorId": self.id + }, + "assetId": "11326252443" + } + }), + "fileContent": (magic.from_buffer(read, mime=True), read) + }) + response = requests.post(f"https://apis.roblox.com/assets/v1/create", + headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) + + if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") + elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") + elif response.status_code == 429: raise RateLimited("You're being rate limited.") + elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") + elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") + + response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + headers={"x-api-key": self.__api_key}) + + if not response_status.ok: + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + info = response_status.json() + if info["status"] == "Pending": + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + + def update_fbx(self, id: int, file: io.BytesIO) -> Union[Asset, PendingAsset]: + read = file.read() + body, contentType = urllib3.encode_multipart_formdata({ + "request": json.dumps({ + "targetType": "ModelFromFbx", + "creationContext": { + "assetName": "null", + "assetDescription": "null", + "creator": { + "creatorType": self.__creator_type, + "creatorId": self.id + }, + "assetId": id + } + }), + "fileContent": (magic.from_buffer(read, mime=True), read) + }) + response = requests.post(f"https://apis.roblox.com/assets/v1/create", + headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) + + if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") + elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") + elif response.status_code == 429: raise RateLimited("You're being rate limited.") + elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") + elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") + + response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + headers={"x-api-key": self.__api_key}) + + if not response_status.ok: + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + info = response_status.json() + if info["status"] == "Pending": + return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + else: + return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + +class GroupCreator(Creator): + def __init__(self, groupid: int, api_key: str) -> None: + super().__init__(groupid, api_key) + self._Creator__creator_type = "Group" + + def __repr__(self) -> str: + return f"rblxopencloud.GroupCreator({self.id})" \ No newline at end of file diff --git a/rblxopencloud/exceptions.py b/rblxopencloud/exceptions.py index f9de56a..d8d8eeb 100644 --- a/rblxopencloud/exceptions.py +++ b/rblxopencloud/exceptions.py @@ -7,4 +7,5 @@ class PreconditionFailed(rblx_opencloudException): def __init__(self, value, info, *args: object) -> None: self.value = value self.info = info - super().__init__(*args) \ No newline at end of file + super().__init__(*args) +class InvalidAsset(rblx_opencloudException): pass \ No newline at end of file diff --git a/setup.py b/setup.py index 6610576..50e38a2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version='0.3.2', + version='0.4.0', long_description=long_description, long_description_content_type="text/markdown", license='MIT', @@ -17,6 +17,8 @@ url='https://github.com/TreeBen77/rblx-open-cloud', keywords='roblox, open-cloud, data-store, place-publishing, mesageing-service', install_requires=[ - 'requests' + 'requests', + 'python-magic', + 'python-magic-bin' ] ) From faf6c8b57358fc282601d2f39b7e39559c29ee7f Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:27:20 +1000 Subject: [PATCH 02/27] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31ee06d..1052e36 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ with open("experiment.fbx", "rb") as file: # you can also update uploaded fbx files: with open("experiment.2.fbx", "rb") as file: # the first number is the fbx asset you want to update - asset = creator.create_fbx(11326252443, file) + asset = creator.update_fbx(11326252443, file) # for simplicity, i won't include the asset fetch_status part above here but it also works. # you're post likely going to recieve a PendingAsset with models because roblox has to render them which takes time @@ -164,4 +164,4 @@ datastore.remove("key-name") # publish a message with the topic 'topic-name' universe.publish_message("topic-name", "Hello World!") -``` \ No newline at end of file +``` From 3e5994bcd1d7dc6bf831eef00335ec7a653b9d20 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Wed, 26 Oct 2022 19:45:27 +1000 Subject: [PATCH 03/27] made creatorId a string so it works with the API --- rblxopencloud/creator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index edee500..a355938 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -47,7 +47,7 @@ def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[A "assetDescription": description, "creator": { "creatorType": self.__creator_type, - "creatorId": self.id + "creatorId": str(self.id) } } }), @@ -84,7 +84,7 @@ def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[A "assetDescription": description, "creator": { "creatorType": self.__creator_type, - "creatorId": self.id + "creatorId": str(self.id) } } }), @@ -121,7 +121,7 @@ def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Ass "assetDescription": description, "creator": { "creatorType": self.__creator_type, - "creatorId": self.id + "creatorId": str(self.id) }, "assetId": "11326252443" } From 665c7f58b35e4bffca967c9a84ee4401b6be5d0f Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 27 Oct 2022 00:38:13 +1000 Subject: [PATCH 04/27] removed python-magicn and python-magic-bin dependencies --- rblxopencloud/creator.py | 14 +++++--------- setup.py | 4 +--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index a355938..36aeded 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -1,7 +1,7 @@ from .exceptions import * import requests, json, io from typing import * -import urllib3, magic +import urllib3, mimetypes class Asset(): def __init__(self, id, version) -> None: @@ -38,7 +38,6 @@ def __repr__(self) -> str: return f"rblxopencloud.Creator({self.id})" def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: - read = file.read() body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ "targetType": "Decal", @@ -51,7 +50,7 @@ def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[A } } }), - "fileContent": (magic.from_buffer(read, mime=True), read) + "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) }) response = requests.post(f"https://apis.roblox.com/assets/v1/create", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) @@ -75,7 +74,6 @@ def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[A return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: - read = file.read() body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ "targetType": "Audio", @@ -88,7 +86,7 @@ def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[A } } }), - "fileContent": (magic.from_buffer(read, mime=True), read) + "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) }) response = requests.post(f"https://apis.roblox.com/assets/v1/create", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) @@ -112,7 +110,6 @@ def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[A return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: - read = file.read() body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ "targetType": "ModelFromFbx", @@ -126,7 +123,7 @@ def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Ass "assetId": "11326252443" } }), - "fileContent": (magic.from_buffer(read, mime=True), read) + "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) }) response = requests.post(f"https://apis.roblox.com/assets/v1/create", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) @@ -150,7 +147,6 @@ def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Ass return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) def update_fbx(self, id: int, file: io.BytesIO) -> Union[Asset, PendingAsset]: - read = file.read() body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ "targetType": "ModelFromFbx", @@ -164,7 +160,7 @@ def update_fbx(self, id: int, file: io.BytesIO) -> Union[Asset, PendingAsset]: "assetId": id } }), - "fileContent": (magic.from_buffer(read, mime=True), read) + "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) }) response = requests.post(f"https://apis.roblox.com/assets/v1/create", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) diff --git a/setup.py b/setup.py index 50e38a2..16ba4fb 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,6 @@ url='https://github.com/TreeBen77/rblx-open-cloud', keywords='roblox, open-cloud, data-store, place-publishing, mesageing-service', install_requires=[ - 'requests', - 'python-magic', - 'python-magic-bin' + 'requests' ] ) From eee77634bacea14758e82608be4da22c23f928fe Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 29 Oct 2022 13:25:08 +1000 Subject: [PATCH 05/27] added version info - READ README.md FIRST --- rblxopencloud/__init__.py | 5 ++++- setup.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index dbfcbd1..55a17aa 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -1,4 +1,7 @@ from .universe import * from .exceptions import * from .datastore import * -from .creator import * \ No newline at end of file +from .creator import * + +VERSION = "0.4.1" +VERSION_INFO = "alpha" diff --git a/setup.py b/setup.py index 16ba4fb..98a30d3 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from setuptools import setup +import rblxopencloud with open("README.md", "r") as file: long_description = file.read() @@ -6,7 +7,7 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version='0.4.0', + version=rblxopencloud.VERSION, long_description=long_description, long_description_content_type="text/markdown", license='MIT', From f2131bcbe4362fa0c79ee6eafab9282db6a27a74 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 29 Oct 2022 15:49:16 +1000 Subject: [PATCH 06/27] updated setup.py to require python >=3.9 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 98a30d3..0e8724a 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ long_description=long_description, long_description_content_type="text/markdown", license='MIT', + python_requires='>=3.9', author="TreeBen77", packages=[ 'rblxopencloud' From b3fa25a5824652a71160a159becae91d79cb3472 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:33:28 +1000 Subject: [PATCH 07/27] removed unnesasry print in EnteryVersion.__init__ --- rblxopencloud/datastore.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rblxopencloud/datastore.py b/rblxopencloud/datastore.py index 617b720..f3a0ef9 100644 --- a/rblxopencloud/datastore.py +++ b/rblxopencloud/datastore.py @@ -27,7 +27,6 @@ def __init__(self, version, deleted, content_length, created, key_created, datas self.deleted = deleted self.content_length = content_length self.created = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) - print(key_created) self.key_created = datetime.datetime.fromisoformat((key_created.split("Z")[0]+"0"*6)[0:26]) self.__datastore = datastore self.__key = key From 0dc26ce55abf8e49f0d3e1088d4e687a898f541b Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:36:03 +1000 Subject: [PATCH 08/27] updated version 0.4.2 -> 0.4.3 --- rblxopencloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index 97babb6..ae5cb5d 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -3,5 +3,5 @@ from .exceptions import * # don't forget to update version here and in setup.py! -VERSION = "0.4.2" +VERSION = "0.4.3" VERSION_INFO = "final" \ No newline at end of file From dfae6d1434c891871f1bec7d41bb9ceaab6ec37e Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:42:38 +1000 Subject: [PATCH 09/27] fixed the workflow so it install requests --- .github/workflows/python-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index ec70354..c222bf5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -30,6 +30,7 @@ jobs: run: | python -m pip install --upgrade pip pip install build + pip install requests - name: Build package run: python -m build - name: Publish package From 444608f478a5656827e54d9837ae77e2eecdc55c Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 2 Dec 2022 18:10:18 +1000 Subject: [PATCH 10/27] removed get version from library in setup.py --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0e8724a..a62306c 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from setuptools import setup -import rblxopencloud with open("README.md", "r") as file: long_description = file.read() @@ -7,7 +6,7 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version=rblxopencloud.VERSION, + version="0.4.3", long_description=long_description, long_description_content_type="text/markdown", license='MIT', From 3a100783f9259b2a4968c766dc4aaddaa62115e5 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 3 Dec 2022 01:55:41 +1000 Subject: [PATCH 11/27] added Discord server invite to README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a33c065..c282c8c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-c **Documentation: https://rblx-open-cloud.readthedocs.io** +**Discord Server: https://discord.gg/gEBdHNAR46** + ## Quickstart ### Getting Started @@ -97,4 +99,4 @@ datastore.remove("key-name") # publish a message with the topic 'topic-name' universe.publish_message("topic-name", "Hello World!") -``` \ No newline at end of file +``` From 4a4ffa6fd11d480621d59b1a1a30d837e86065eb Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 14 Jan 2023 18:51:27 +1000 Subject: [PATCH 12/27] changed Universe to Experience & v1 (breaking changes) --- README.md | 24 +++++++++--------- docs/source/datastore.rst | 8 +++--- docs/source/{universe.rst => experience.rst} | 16 ++++++------ docs/source/index.rst | 26 ++++++++++---------- rblxopencloud/__init__.py | 4 +-- rblxopencloud/datastore.py | 18 +++++++------- rblxopencloud/{universe.py => experience.py} | 14 +++++------ setup.py | 2 +- 8 files changed, 56 insertions(+), 56 deletions(-) rename docs/source/{universe.rst => experience.rst} (89%) rename rblxopencloud/{universe.py => experience.py} (92%) diff --git a/README.md b/README.md index c282c8c..34e12ee 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-c 3. Add the following code to your project and replace `api-key-from-step-2` with the key you generated. ```py - # create a Universe object with your universe/experience ID and your api key - # TODO: replace '13058' with your universe ID - universe = rblxopencloud.Universe(13058, api_key="api-key-from-step-2") + # create an Experience object with your experience ID and your api key + # TODO: replace '13058' with your experience ID + experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") ``` - If you don't know how to get the universe or place ID read [Publishing Places with API Keys](https://create.roblox.com/docs/open-cloud/publishing-places-with-api-keys#:~:text=Find%20the%20experience,is%206985028626.) + If you don't know how to get the experience or place ID read [Publishing Places with API Keys](https://create.roblox.com/docs/open-cloud/publishing-places-with-api-keys#:~:text=Find%20the%20experience,is%206985028626.) 4. If you want to start by accessing your game's data stores go to [Data Stores](#accessing-data-stores) otherwise, you can go to [Messaging Service](#publishing-to-message-service) if you want to publish messages to live game servers, or [Place Publishing](#publish-or-save-a-rbxl-file) if you'd like to upload `.rbxl` files to Roblox.** @@ -31,7 +31,7 @@ Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-c **NOTE: Roblox doesn't support access to ordered data stores via open cloud at the moment.** ```py # get the data store, using the data store name and scope (defaults to global) -datastore = universe.get_data_store("data-store-name", scope="global") +datastore = experience.get_data_store("data-store-name", scope="global") # sets the key 'key-name' to 68 and provides users and metadata # DataStore.set does not return the value or an EntryInfo object, instead it returns a EntryVersion object. @@ -57,7 +57,7 @@ datastore.remove("key-name") **NOTE: Messages published with Open Cloud only arrive in live game servers and not in Studio, so you'll have to publish the place to test this.** ```py # publish a message with the topic 'topic-name' -universe.publish_message("topic-name", "Hello World!") +experience.publish_message("topic-name", "Hello World!") ``` ### Publish or Save a `.rbxl` File @@ -67,16 +67,16 @@ universe.publish_message("topic-name", "Hello World!") with open("path-to/place-file.rbxl", "rb") as file: # the first number is the place ID to update, and publish denotes wether to publish or save the place. # TODO: replace '1818' with your place ID - universe.upload_place(1818, file, publish=False) + experience.upload_place(1818, file, publish=False) ``` ## Final Result (a.k.a copy and paste section) ```py -# create a Universe object with your universe/experience ID and your api key -# TODO: replace '13058' with your universe ID -universe = rblxopencloud.Universe(13058, api_key="api-key-from-step-2") +# create an Experience object with your experience ID and your api key +# TODO: replace '13058' with your experience ID +experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") # get the data store, using the data store name and scope (defaults to global) -datastore = universe.get_data_store("data-store-name", scope="global") +datastore = experience.get_data_store("data-store-name", scope="global") # sets the key 'key-name' to 68 and provides users and metadata # DataStore.set does not return the value or an EntryInfo object, instead it returns a EntryVersion object. @@ -98,5 +98,5 @@ print(value, info) datastore.remove("key-name") # publish a message with the topic 'topic-name' -universe.publish_message("topic-name", "Hello World!") +experience.publish_message("topic-name", "Hello World!") ``` diff --git a/docs/source/datastore.rst b/docs/source/datastore.rst index 5ade6ba..bc8b787 100644 --- a/docs/source/datastore.rst +++ b/docs/source/datastore.rst @@ -9,7 +9,7 @@ Data Store .. warning:: - This class is not designed to be created by users. It is returned by :meth:`Universe.get_data_store` and :meth:`Universe.list_data_stores`. + This class is not designed to be created by users. It is returned by :meth:`Experience.get_data_store` and :meth:`Experience.list_data_stores`. .. attribute:: name @@ -19,13 +19,13 @@ Data Store :type: Union[str, None] - .. attribute:: universe + .. attribute:: experience - :type: rblx-open-cloud.Universe + :type: rblx-open-cloud.Experience .. attribute:: created - The data store's creation timestamp, could be ``None`` if not retrieved by :meth:`Universe.list_data_stores`. + The data store's creation timestamp, could be ``None`` if not retrieved by :meth:`Experience.list_data_stores`. :type: Union[datetime.datetime, None] diff --git a/docs/source/universe.rst b/docs/source/experience.rst similarity index 89% rename from docs/source/universe.rst rename to docs/source/experience.rst index cafd3c7..1ff255b 100644 --- a/docs/source/universe.rst +++ b/docs/source/experience.rst @@ -1,18 +1,18 @@ -Universe +Experience ============================= .. currentmodule:: rblx-open-cloud -.. class:: Universe(id, api_key) +.. class:: Experience(id, api_key) - Class for interacting with the API for a specific universe. + Class for interacting with the API for a specific experience. - :param int id: A Universe ID. Read How to find yours here: `Publishing Places with API Keys `__ + :param int id: A Experience ID. Read How to find yours here: `Publishing Places with API Keys `__ :param str api_key: An API key created from `Creator Dashboard `__. *this should be kept safe, as anyone with the key can use it!* .. attribute:: id - The universe's ID + The Experience's ID :type: int @@ -33,7 +33,7 @@ Universe .. method:: list_data_stores(prefix="", scope="global") - Returns an Iterable of all :class:`rblx-open-cloud.DataStore` in the Universe which includes :attr:`rblx-open-cloud.DataStore.created`, optionally matching a prefix. + Returns an Iterable of all :class:`rblx-open-cloud.DataStore` in the Experience which includes :attr:`rblx-open-cloud.DataStore.created`, optionally matching a prefix. Lua equivalent: `DataStoreService:ListDataStoresAsync() `__ @@ -41,14 +41,14 @@ Universe .. code:: py - for datastore in universe.list_data_stores(): + for datastore in experience.list_data_stores(): print(datastore.name) You can simply convert it to a list by putting it in the list function: .. code:: py - list(universe.list_data_stores()) + list(experience.list_data_stores()) :param str prefix: Only Iterates datastores with that start with this string :param Union[None, int] limit: Will not return more datastores than this number. Set to ``None`` for no limit. diff --git a/docs/source/index.rst b/docs/source/index.rst index 68e93f8..8daa200 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ Table of Contents .. toctree:: - universe + experience datastore exceptions @@ -29,16 +29,16 @@ Getting Started 3. Add the following code to your project and replace ``api-key-from-step-2`` with the key you generated. - If you don't know how to get the universe or place ID read + If you don't know how to get the experience or place ID read `Publishing Places with API Keys `__ .. code:: py import rblxopencloud - # create a Universe object with your universe/experience ID and your api key - # TODO: replace '13058' with your universe ID - universe = rblxopencloud.Universe(13058, api_key="api-key-from-step-2")` + # create an Experience object with your experience ID and your api key + # TODO: replace '13058' with your experience ID + experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2")` 4. If you want to start by accessing your game's data stores go to `Data Stores <#accessing-data-stores>`__ otherwise, you can go to @@ -56,7 +56,7 @@ cloud at the moment.** .. code:: py # get the data store, using the data store name and scope (defaults to global) - datastore = universe.get_data_store("data-store-name", scope="global") + datastore = experience.get_data_store("data-store-name", scope="global") # sets the key 'key-name' to 68 and provides users and metadata # DataStore.set does not return the value or an EntryInfo object, instead it returns a EntryVersion object. @@ -87,7 +87,7 @@ this.** .. code:: py # publish a message with the topic 'topic-name' - universe.publish_message("topic-name", "Hello World!") + experience.publish_message("topic-name", "Hello World!") Publish or Save a ``.rbxl`` File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -101,19 +101,19 @@ included in this example due to it requiring an ``.rbxl`` file.** with open("path-to/place-file.rbxl", "rb") as file: # the first number is the place ID to update, and publish denotes wether to publish or save the place. # TODO: replace '1818' with your place ID - universe.upload_place(1818, file, publish=False) + experience.upload_place(1818, file, publish=False) Final Result (a.k.a copy and paste section) ------------------------------------------- .. code:: py - # create a Universe object with your universe/experience ID and your api key - # TODO: replace '13058' with your universe ID - universe = rblxopencloud.Universe(13058, api_key="api-key-from-step-2") + # create a Experience object with your experience ID and your api key + # TODO: replace '13058' with your experience ID + experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") # get the data store, using the data store name and scope (defaults to global) - datastore = universe.get_data_store("data-store-name", scope="global") + datastore = experience.get_data_store("data-store-name", scope="global") # sets the key 'key-name' to 68 and provides users and metadata # DataStore.set does not return the value or an EntryInfo object, instead it returns a EntryVersion object. @@ -135,4 +135,4 @@ Final Result (a.k.a copy and paste section) datastore.remove("key-name") # publish a message with the topic 'topic-name' - universe.publish_message("topic-name", "Hello World!") \ No newline at end of file + experience.publish_message("topic-name", "Hello World!") \ No newline at end of file diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index ae5cb5d..3875100 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -1,7 +1,7 @@ -from .universe import * +from .experience import * from .datastore import * from .exceptions import * # don't forget to update version here and in setup.py! -VERSION = "0.4.3" +VERSION = "1.0.0" VERSION_INFO = "final" \ No newline at end of file diff --git a/rblxopencloud/datastore.py b/rblxopencloud/datastore.py index f3a0ef9..bd09fee 100644 --- a/rblxopencloud/datastore.py +++ b/rblxopencloud/datastore.py @@ -61,11 +61,11 @@ def __repr__(self) -> str: return f"rblxopencloud.ListedEntry(\"{self.key}\", scope=\"{self.scope}\")" class DataStore(): - def __init__(self, name, universe, api_key, created, scope): + def __init__(self, name, experience, api_key, created, scope): self.name = name self.__api_key = api_key self.scope = scope - self.universe = universe + self.experience = experience if created: self.created = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) else: self.created = None @@ -91,7 +91,7 @@ def list_keys(self, prefix: str="", limit: Union[None, int]=None) -> Iterable[Li nextcursor = "" yields = 0 while limit == None or yields < limit: - response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries", + response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, "scope": self.scope, @@ -119,7 +119,7 @@ def get(self, key: str) -> tuple[Union[str, dict, list, int, float], EntryInfo]: if not self.scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") - response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry", + response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, "scope": self.scope if self.scope else scope, @@ -151,7 +151,7 @@ def set(self, key: str, value: Union[str, dict, list, int, float], users:list=No if users == None: users = [] data = json.dumps(value) - response = requests.post(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry", + response = requests.post(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry", headers={"x-api-key": self.__api_key, "roblox-entry-userids": json.dumps(users), "roblox-entry-attributes": json.dumps(metadata), "content-md5": base64.b64encode(hashlib.md5(data.encode()).digest())}, data=data, params={ "datastoreName": self.name, @@ -192,7 +192,7 @@ def increment(self, key: str, increment: Union[int, float], users:list=None, met raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") if users == None: users = [] - response = requests.post(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry/increment", + response = requests.post(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/increment", headers={"x-api-key": self.__api_key, "roblox-entry-userids": json.dumps(users), "roblox-entry-attributes": json.dumps(metadata)}, params={ "datastoreName": self.name, "scope": self.scope if self.scope else scope, @@ -219,7 +219,7 @@ def remove(self, key: str) -> None: if not self.scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") - response = requests.delete(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry", + response = requests.delete(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, "scope": self.scope if self.scope else scope, @@ -253,7 +253,7 @@ def list_versions(self, key: str, after: datetime.datetime=None, before: datetim nextcursor = "" yields = 0 while limit == None or yields < limit: - response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry/versions", + response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/versions", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, "scope": self.scope if self.scope else scope, @@ -284,7 +284,7 @@ def get_version(self, key: str, version: str) -> tuple[Union[str, dict, list, in if not self.scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") - response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.universe.id}/standard-datastores/datastore/entries/entry/versions/version", + response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/versions/version", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, "scope": self.scope if self.scope else scope, diff --git a/rblxopencloud/universe.py b/rblxopencloud/experience.py similarity index 92% rename from rblxopencloud/universe.py rename to rblxopencloud/experience.py index 207d171..cd8349e 100644 --- a/rblxopencloud/universe.py +++ b/rblxopencloud/experience.py @@ -4,33 +4,33 @@ from .datastore import DataStore __all__ = ( - "Universe", + "Experience", ) -class Universe(): +class Experience(): def __init__(self, id: int, api_key: str): self.id = id self.__api_key = api_key def __repr__(self) -> str: - return f"rblxopencloud.Universe({self.id})" + return f"rblxopencloud.Experience({self.id})" def get_data_store(self, name: str, scope: Union[str, None]="global") -> DataStore: """Creates a `rblx-open-cloud.DataStore` without `DataStore.created` with the provided name and scope. If `scope` is `None` then keys require to be formatted like `scope/key` and `DataStore.list_keys` will return keys from all scopes.""" return DataStore(name, self, self.__api_key, None, scope) def list_data_stores(self, prefix: str="", limit: Union[None, int]=None, scope: str="global") -> list[DataStore]: - """Returns an `Iterable` of all `rblx-open-cloud.DataStore` in the Universe which includes `DataStore.created`, optionally matching a prefix. The example below would list all versions, along with their value. + """Returns an `Iterable` of all `rblx-open-cloud.DataStore` in the Experience which includes `DataStore.created`, optionally matching a prefix. The example below would list all versions, along with their value. ```py - for universe in universe.list_data_stores(): - print(universe) + for datastore in experience.list_data_stores(): + print(datastore) ``` You can simply convert it to a list by putting it in the list function: ```py - list(universe.list_data_stores()) + list(experience.list_data_stores()) ```""" nextcursor = "" yields = 0 diff --git a/setup.py b/setup.py index a62306c..05da6cb 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version="0.4.3", + version="1.0.0", long_description=long_description, long_description_content_type="text/markdown", license='MIT', From d24b6993e782ea2083943a1c17a7d8426d35aefa Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 14 Jan 2023 18:53:24 +1000 Subject: [PATCH 13/27] changed Universe to Experience for examples --- examples/datastores-basic.py | 2 +- examples/datastores-listing.py | 2 +- examples/datastores-preconditions.py | 2 +- examples/datastores-versions.py | 2 +- examples/messaging.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/datastores-basic.py b/examples/datastores-basic.py index 69b58ad..bb5c553 100644 --- a/examples/datastores-basic.py +++ b/examples/datastores-basic.py @@ -1,7 +1,7 @@ import rblxopencloud # create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Universe(3499447036, "api-key-here") +universe = rblxopencloud.Experience(3499447036, "api-key-here") # create a datastore with the name 'exampleStore' and the scope of 'open-cloud' datastore = universe.get_data_store("exampleStore", scope="open-cloud") diff --git a/examples/datastores-listing.py b/examples/datastores-listing.py index a539011..3b68fdd 100644 --- a/examples/datastores-listing.py +++ b/examples/datastores-listing.py @@ -1,7 +1,7 @@ import rblxopencloud # create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Universe(3499447036, "api-key-here") +universe = rblxopencloud.Experience(3499447036, "api-key-here") # create a datastore with the name 'exampleStore' and the scope of 'open-cloud' datastore = universe.get_data_store("exampleStore", scope="open-cloud") diff --git a/examples/datastores-preconditions.py b/examples/datastores-preconditions.py index 3c869d3..850a347 100644 --- a/examples/datastores-preconditions.py +++ b/examples/datastores-preconditions.py @@ -1,7 +1,7 @@ import rblxopencloud # create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Universe(3499447036, "api-key-here") +universe = rblxopencloud.Experience(3499447036, "api-key-here") # create a datastore with the name 'exampleStore' and the scope of 'open-cloud' datastore = universe.get_data_store("exampleStore", scope="open-cloud") diff --git a/examples/datastores-versions.py b/examples/datastores-versions.py index 14335f0..9bd4106 100644 --- a/examples/datastores-versions.py +++ b/examples/datastores-versions.py @@ -4,7 +4,7 @@ from datetime import datetime # create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Universe(3499447036, "api-key-here") +universe = rblxopencloud.Experience(3499447036, "api-key-here") # create a datastore with the name 'exampleStore' and the scope of 'open-cloud' datastore = universe.get_data_store("exampleStore", scope="open-cloud") diff --git a/examples/messaging.py b/examples/messaging.py index 11ae05e..b51e539 100644 --- a/examples/messaging.py +++ b/examples/messaging.py @@ -1,7 +1,7 @@ import rblxopencloud # create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Universe(3499447036, "api-key-here") +universe = rblxopencloud.Experience(3499447036, "api-key-here") # this will publish a message to that topic in every server in that experience. it's this simple. universe.publish_message("topic-name", "Hello World!") From cedb31340f834557a1ddf45a2e082d36d0cfc73c Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Mon, 13 Feb 2023 03:11:36 +1000 Subject: [PATCH 14/27] fixed 2 errors in experience docs --- docs/source/experience.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/experience.rst b/docs/source/experience.rst index 1ff255b..8ee575f 100644 --- a/docs/source/experience.rst +++ b/docs/source/experience.rst @@ -29,7 +29,7 @@ Experience :returns: :class:`rblx-open-cloud.DataStore` .. note:: - Roblox does not support accessing OrderedDataStores with Open Cloud. + Ordered DataStores are still in alpha, to use them you must `sign up for the beta `__ and then `install the beta library __` .. method:: list_data_stores(prefix="", scope="global") @@ -85,7 +85,7 @@ Experience :param int place_id: The place ID to update :param io.BytesIO file: The file to send. should be opened as bytes. - :param publish bool: Wether to publish the place or just save it. + :param bool publish: Wether to publish the place or just save it. :returns: :class:`int` :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to upload places. :raises rblx-open-cloud.NotFound: The place ID is invalid From 134f18a40ecf6b5911bd770eb7b3465a0ce0d3c1 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:09:00 +1000 Subject: [PATCH 15/27] datastore.list_keys no longer skips some keys --- rblxopencloud/datastore.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rblxopencloud/datastore.py b/rblxopencloud/datastore.py index bd09fee..50f2b30 100644 --- a/rblxopencloud/datastore.py +++ b/rblxopencloud/datastore.py @@ -109,7 +109,7 @@ def list_keys(self, prefix: str="", limit: Union[None, int]=None) -> Iterable[Li for key in data["keys"]: yields += 1 yield ListedEntry(key["key"], key["scope"]) - if limit == None or yields >= limit: break + if limit != None and yields >= limit: break nextcursor = data.get("nextPageCursor") if not nextcursor: break diff --git a/setup.py b/setup.py index 05da6cb..1a93089 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version="1.0.0", + version="1.0.1", long_description=long_description, long_description_content_type="text/markdown", license='MIT', From 9c526987a4a1c00eeba10c2db0543d18d9e2f271 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 10 Mar 2023 21:16:39 +1000 Subject: [PATCH 16/27] added typing to attributes --- rblxopencloud/datastore.py | 45 ++++++++++++++++++++----------------- rblxopencloud/exceptions.py | 18 +++++++++++++-- rblxopencloud/experience.py | 12 +++++----- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/rblxopencloud/datastore.py b/rblxopencloud/datastore.py index 50f2b30..37aef46 100644 --- a/rblxopencloud/datastore.py +++ b/rblxopencloud/datastore.py @@ -1,8 +1,11 @@ -from .exceptions import * +from .exceptions import rblx_opencloudException, InvalidKey, NotFound, RateLimited, ServiceUnavailable, PreconditionFailed import requests, json, datetime -from typing import Union, Iterable +from typing import Union, Optional, Iterable, TYPE_CHECKING import base64, hashlib +if TYPE_CHECKING: + from .experience import Experience + __all__ = ( "EntryInfo", "EntryVersion", @@ -12,22 +15,22 @@ class EntryInfo(): def __init__(self, version, created, updated, users, metadata) -> None: - self.version = version - self.created = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) - self.updated = datetime.datetime.fromisoformat((updated.split("Z")[0]+"0"*6)[0:26]) - self.users = users - self.metadata = metadata + self.version: str = version + self.created: datetime.date = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) + self.updated: datetime.date = datetime.datetime.fromisoformat((updated.split("Z")[0]+"0"*6)[0:26]) + self.users: list[int] = users + self.metadata: dict = metadata def __repr__(self) -> str: return f"rblxopencloud.EntryInfo(\"{self.version}\", users={self.users}, metadata={self.metadata})" class EntryVersion(): def __init__(self, version, deleted, content_length, created, key_created, datastore, key, scope) -> None: - self.version = version - self.deleted = deleted - self.content_length = content_length - self.created = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) - self.key_created = datetime.datetime.fromisoformat((key_created.split("Z")[0]+"0"*6)[0:26]) + self.version: str = version + self.deleted: bool = deleted + self.content_length: int = content_length + self.created: datetime.datetime = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) + self.key_created: datetime.datetime = datetime.datetime.fromisoformat((key_created.split("Z")[0]+"0"*6)[0:26]) self.__datastore = datastore self.__key = key self.__scope = scope @@ -49,8 +52,8 @@ def __repr__(self) -> str: class ListedEntry(): def __init__(self, key, scope) -> None: - self.key = key - self.scope = scope + self.key: str = key + self.scope: str = scope def __eq__(self, object) -> bool: if not isinstance(object, ListedEntry): @@ -62,10 +65,10 @@ def __repr__(self) -> str: class DataStore(): def __init__(self, name, experience, api_key, created, scope): - self.name = name - self.__api_key = api_key - self.scope = scope - self.experience = experience + self.name: str = name + self.__api_key: str = api_key + self.scope: str = scope + self.experience: Experience = experience if created: self.created = datetime.datetime.fromisoformat((created.split("Z")[0]+"0"*6)[0:26]) else: self.created = None @@ -75,7 +78,7 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.name - def list_keys(self, prefix: str="", limit: Union[None, int]=None) -> Iterable[ListedEntry]: + def list_keys(self, prefix: str="", limit: Optional[int]=None) -> Iterable[ListedEntry]: """Returns an `Iterable` of keys in the database and scope, optionally matching a prefix. Will return keys from all scopes if `DataStore.scope` is `None`. The example below would list all versions, along with their value. ```py @@ -141,7 +144,7 @@ def get(self, key: str) -> tuple[Union[str, dict, list, int, float], EntryInfo]: elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") else: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - def set(self, key: str, value: Union[str, dict, list, int, float], users:list=None, metadata:dict={}, exclusive_create:bool=False, previous_version:Union[None, str]=None) -> EntryVersion: + def set(self, key: str, value: Union[str, dict, list, int, float], users:list=None, metadata:dict={}, exclusive_create:bool=False, previous_version:Optional[str]=None) -> EntryVersion: """Sets the value of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" if previous_version and exclusive_create: raise ValueError("previous_version and exclusive_create can not both be set") try: @@ -233,7 +236,7 @@ def remove(self, key: str) -> None: elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") else: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - def list_versions(self, key: str, after: datetime.datetime=None, before: datetime.datetime=None, limit: Union[None, int]=None, descending: bool=True) -> Iterable[EntryVersion]: + def list_versions(self, key: str, after: datetime.datetime=None, before: datetime.datetime=None, limit: Optional[int]=None, descending: bool=True) -> Iterable[EntryVersion]: """Returns an Iterable of previous versions of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`. The example below would list all versions, along with their value. ```py diff --git a/rblxopencloud/exceptions.py b/rblxopencloud/exceptions.py index f9de56a..237517f 100644 --- a/rblxopencloud/exceptions.py +++ b/rblxopencloud/exceptions.py @@ -1,3 +1,17 @@ +from typing import Union, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from .datastore import EntryInfo + +__all__ = ( + "rblx_opencloudException", + "NotFound", + "InvalidKey", + "RateLimited", + "ServiceUnavailable", + "PreconditionFailed", +) + class rblx_opencloudException(Exception): pass class NotFound(rblx_opencloudException): pass class InvalidKey(rblx_opencloudException): pass @@ -5,6 +19,6 @@ class RateLimited(rblx_opencloudException): pass class ServiceUnavailable(rblx_opencloudException): pass class PreconditionFailed(rblx_opencloudException): def __init__(self, value, info, *args: object) -> None: - self.value = value - self.info = info + self.value: Optional[Union[str, dict, list, int, float]] = value + self.info: Optional[EntryInfo] = info super().__init__(*args) \ No newline at end of file diff --git a/rblxopencloud/experience.py b/rblxopencloud/experience.py index cd8349e..f639ade 100644 --- a/rblxopencloud/experience.py +++ b/rblxopencloud/experience.py @@ -1,6 +1,6 @@ -from .exceptions import * +from .exceptions import rblx_opencloudException, InvalidKey, NotFound, RateLimited, ServiceUnavailable import requests, io -from typing import Union +from typing import Optional from .datastore import DataStore __all__ = ( @@ -9,17 +9,17 @@ class Experience(): def __init__(self, id: int, api_key: str): - self.id = id - self.__api_key = api_key + self.id: int = id + self.__api_key: str = api_key def __repr__(self) -> str: return f"rblxopencloud.Experience({self.id})" - def get_data_store(self, name: str, scope: Union[str, None]="global") -> DataStore: + def get_data_store(self, name: str, scope: Optional[str]="global") -> DataStore: """Creates a `rblx-open-cloud.DataStore` without `DataStore.created` with the provided name and scope. If `scope` is `None` then keys require to be formatted like `scope/key` and `DataStore.list_keys` will return keys from all scopes.""" return DataStore(name, self, self.__api_key, None, scope) - def list_data_stores(self, prefix: str="", limit: Union[None, int]=None, scope: str="global") -> list[DataStore]: + def list_data_stores(self, prefix: str="", limit: Optional[int]=None, scope: str="global") -> list[DataStore]: """Returns an `Iterable` of all `rblx-open-cloud.DataStore` in the Experience which includes `DataStore.created`, optionally matching a prefix. The example below would list all versions, along with their value. ```py From a83218ca3f664b19ad906a6ba760f36078535b47 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 10 Mar 2023 21:51:28 +1000 Subject: [PATCH 17/27] bumped version to v1.1.0 and updated version info --- rblxopencloud/__init__.py | 9 ++++++--- setup.py | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index 3875100..1cb4a96 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -2,6 +2,9 @@ from .datastore import * from .exceptions import * -# don't forget to update version here and in setup.py! -VERSION = "1.0.0" -VERSION_INFO = "final" \ No newline at end of file +from typing import Literal + +VERSION: str = "1.1.0" +VERSION_INFO: Literal['alpha', 'beta', 'final'] = "beta" + +del Literal \ No newline at end of file diff --git a/setup.py b/setup.py index 1a93089..d98ac0a 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,8 @@ from setuptools import setup +import re + +with open("rblxopencloud/__init__.py", "r") as file: + version = re.search(r"VERSION: str = \"(\d+\.\d+\.\d+)\"", file.read()).group(1) with open("README.md", "r") as file: long_description = file.read() @@ -6,11 +10,11 @@ setup( name='rblx-open-cloud', description='API wrapper for Roblox Open Cloud', - version="1.0.1", + version=version, long_description=long_description, long_description_content_type="text/markdown", license='MIT', - python_requires='>=3.9', + python_requires='>=3.9.0', author="TreeBen77", packages=[ 'rblxopencloud' From d2d72729074ae01177b1b6ddc79b80633c944ea0 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:09:13 +1000 Subject: [PATCH 18/27] fixed and improved typing for functions. --- rblxopencloud/datastore.py | 40 ++++++++++++++++++++----------------- rblxopencloud/experience.py | 4 ++-- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/rblxopencloud/datastore.py b/rblxopencloud/datastore.py index 37aef46..eb6b3c9 100644 --- a/rblxopencloud/datastore.py +++ b/rblxopencloud/datastore.py @@ -73,7 +73,7 @@ def __init__(self, name, experience, api_key, created, scope): else: self.created = None def __repr__(self) -> str: - return f"rblxopencloud.DataStore(\"{self.name}\", scope=\"{self.scope}\", universe={repr(self.universe)})" + return f"rblxopencloud.DataStore(\"{self.name}\", scope=\"{self.scope}\", universe={repr(self.experience)})" def __str__(self) -> str: return self.name @@ -119,13 +119,14 @@ def list_keys(self, prefix: str="", limit: Optional[int]=None) -> Iterable[Liste def get(self, key: str) -> tuple[Union[str, dict, list, int, float], EntryInfo]: """Gets the value of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key }) @@ -137,18 +138,18 @@ def get(self, key: str) -> tuple[Union[str, dict, list, int, float], EntryInfo]: return json.loads(response.text), EntryInfo(response.headers["roblox-entry-version"], response.headers["roblox-entry-created-time"], response.headers["roblox-entry-version-created-time"], userids, metadata) - elif response.status_code == 204: return None elif response.status_code == 401: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") elif response.status_code == 404: raise NotFound(f"The key {key} does not exist.") elif response.status_code == 429: raise RateLimited("You're being rate limited.") elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") else: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - def set(self, key: str, value: Union[str, dict, list, int, float], users:list=None, metadata:dict={}, exclusive_create:bool=False, previous_version:Optional[str]=None) -> EntryVersion: + def set(self, key: str, value: Union[str, dict, list, int, float], users:Optional[list[int]]=None, metadata:dict={}, exclusive_create:bool=False, previous_version:Optional[str]=None) -> EntryVersion: """Sets the value of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" if previous_version and exclusive_create: raise ValueError("previous_version and exclusive_create can not both be set") try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") if users == None: users = [] @@ -158,7 +159,7 @@ def set(self, key: str, value: Union[str, dict, list, int, float], users:list=No headers={"x-api-key": self.__api_key, "roblox-entry-userids": json.dumps(users), "roblox-entry-attributes": json.dumps(metadata), "content-md5": base64.b64encode(hashlib.md5(data.encode()).digest())}, data=data, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key, "exclusiveCreate": exclusive_create, "matchVersion": previous_version @@ -187,10 +188,11 @@ def set(self, key: str, value: Union[str, dict, list, int, float], users:list=No response.headers["roblox-entry-version-created-time"], userids, metadata), error) else: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - def increment(self, key: str, increment: Union[int, float], users:list=None, metadata:dict={}) -> tuple[Union[str, dict, list, int, float], EntryInfo]: + def increment(self, key: str, increment: Union[int, float], users:Optional[list[int]]=None, metadata:dict={}) -> tuple[Union[str, dict, list, int, float], EntryInfo]: """Increments the value of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") if users == None: users = [] @@ -198,7 +200,7 @@ def increment(self, key: str, increment: Union[int, float], users:list=None, met response = requests.post(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/increment", headers={"x-api-key": self.__api_key, "roblox-entry-userids": json.dumps(users), "roblox-entry-attributes": json.dumps(metadata)}, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key, "incrementBy": increment }) @@ -219,13 +221,14 @@ def increment(self, key: str, increment: Union[int, float], users:list=None, met def remove(self, key: str) -> None: """Removes a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") response = requests.delete(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key }) @@ -236,7 +239,7 @@ def remove(self, key: str) -> None: elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") else: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - def list_versions(self, key: str, after: datetime.datetime=None, before: datetime.datetime=None, limit: Optional[int]=None, descending: bool=True) -> Iterable[EntryVersion]: + def list_versions(self, key: str, after: Optional[datetime.datetime]=None, before: Optional[datetime.datetime]=None, limit: Optional[int]=None, descending: bool=True) -> Iterable[EntryVersion]: """Returns an Iterable of previous versions of a key. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`. The example below would list all versions, along with their value. ```py @@ -250,7 +253,8 @@ def list_versions(self, key: str, after: datetime.datetime=None, before: datetim list(datastore.list_versions("key-name")) ```""" try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") nextcursor = "" @@ -259,7 +263,7 @@ def list_versions(self, key: str, after: datetime.datetime=None, before: datetim response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/versions", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key, "sortOrder": "Descending" if descending else "Ascending", "cursor": nextcursor if nextcursor else None, @@ -284,13 +288,14 @@ def list_versions(self, key: str, after: datetime.datetime=None, before: datetim def get_version(self, key: str, version: str) -> tuple[Union[str, dict, list, int, float], EntryInfo]: """Gets the value of a key version. If `DataStore.scope` is `None` then `key` must be formatted like `scope/key`.""" try: - if not self.scope: scope, key = key.split("/", maxsplit=1) + scope = self.scope + if not scope: scope, key = key.split("/", maxsplit=1) except(ValueError): raise ValueError("a scope and key seperated by a forward slash is required for DataStore without a scope.") response = requests.get(f"https://apis.roblox.com/datastores/v1/universes/{self.experience.id}/standard-datastores/datastore/entries/entry/versions/version", headers={"x-api-key": self.__api_key}, params={ "datastoreName": self.name, - "scope": self.scope if self.scope else scope, + "scope": scope, "entryKey": key, "versionId": version }) @@ -303,7 +308,6 @@ def get_version(self, key: str, version: str) -> tuple[Union[str, dict, list, in return json.loads(response.text), EntryInfo(response.headers["roblox-entry-version"], response.headers["roblox-entry-created-time"], response.headers["roblox-entry-version-created-time"], userids, metadata) - elif response.status_code == 204: return None elif response.status_code == 401: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") elif response.status_code == 404: raise NotFound(f"The key {key} does not exist.") elif response.status_code == 429: raise RateLimited("You're being rate limited.") diff --git a/rblxopencloud/experience.py b/rblxopencloud/experience.py index f639ade..b5f2d42 100644 --- a/rblxopencloud/experience.py +++ b/rblxopencloud/experience.py @@ -1,6 +1,6 @@ from .exceptions import rblx_opencloudException, InvalidKey, NotFound, RateLimited, ServiceUnavailable import requests, io -from typing import Optional +from typing import Optional, Iterable from .datastore import DataStore __all__ = ( @@ -19,7 +19,7 @@ def get_data_store(self, name: str, scope: Optional[str]="global") -> DataStore: """Creates a `rblx-open-cloud.DataStore` without `DataStore.created` with the provided name and scope. If `scope` is `None` then keys require to be formatted like `scope/key` and `DataStore.list_keys` will return keys from all scopes.""" return DataStore(name, self, self.__api_key, None, scope) - def list_data_stores(self, prefix: str="", limit: Optional[int]=None, scope: str="global") -> list[DataStore]: + def list_data_stores(self, prefix: str="", limit: Optional[int]=None, scope: str="global") -> Iterable[DataStore]: """Returns an `Iterable` of all `rblx-open-cloud.DataStore` in the Experience which includes `DataStore.created`, optionally matching a prefix. The example below would list all versions, along with their value. ```py From 53e6d344d9e3304b10861a18e0afdfd69e654ac6 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Sat, 18 Mar 2023 09:54:22 +1000 Subject: [PATCH 19/27] moved table of contents to bottom of index page --- docs/source/index.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 8daa200..0f39f24 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,15 +1,6 @@ Welcome to ``rblx-open-cloud``! =============== -Table of Contents ----------- - -.. toctree:: - - experience - datastore - exceptions - Quickstart ---------- @@ -135,4 +126,13 @@ Final Result (a.k.a copy and paste section) datastore.remove("key-name") # publish a message with the topic 'topic-name' - experience.publish_message("topic-name", "Hello World!") \ No newline at end of file + experience.publish_message("topic-name", "Hello World!") + +Table of Contents +---------- + +.. toctree:: + + experience + datastore + exceptions From 39d8e7e15858d5951ddbac32b5e745b33b542eb5 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:13:31 +1000 Subject: [PATCH 20/27] redone the assets api --- rblxopencloud/__init__.py | 4 +- rblxopencloud/creator.py | 209 ++++++++++++++------------------------ rblxopencloud/group.py | 10 ++ rblxopencloud/user.py | 10 ++ 4 files changed, 99 insertions(+), 134 deletions(-) create mode 100644 rblxopencloud/group.py create mode 100644 rblxopencloud/user.py diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index 55a17aa..07bc511 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -1,7 +1,9 @@ from .universe import * from .exceptions import * from .datastore import * +from .user import * +from .group import * from .creator import * -VERSION = "0.4.1" +VERSION = "1.1.1" VERSION_INFO = "alpha" diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index 36aeded..d2c458a 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -1,58 +1,90 @@ from .exceptions import * import requests, json, io from typing import * -import urllib3, mimetypes +import urllib3 +from enum import Enum +from datetime import datetime + +class AssetType(Enum): + Unknown = 0 + Decal = 1 + Audio = 2 + Model = 3 class Asset(): - def __init__(self, id, version) -> None: - self.id = id - self.version = version + def __init__(self, assetObject, creator=None) -> None: + self.id = assetObject.get("assetId") + self.name = assetObject.get("displayName") + self.description = assetObject.get("description") + + self.creator = creator if creator else None + + self.revision_id = assetObject.get("revisionId") + self.revision_time = datetime.fromisoformat(assetObject["revisionCreateTime"][0:26]) if assetObject.get("revisionCreateTime") else None + + if assetObject.get("assetType") in ["Decal", "ASSET_TYPE_DECAL"]: self.type = AssetType.Decal + elif assetObject.get("assetType") in ["Audio", "ASSET_TYPE_AUDIO"]: self.type = AssetType.Audio + elif assetObject.get("assetType") in ["Model", "ASSET_TYPE_MODEL"]: self.type = AssetType.Model + else: self.type = AssetType.Unknown def __repr__(self) -> str: - return f"rblxopencloud.Asset({self.id}, version={self.version})" + return f"rblxopencloud.Asset({self.id}, name=\"{self.name}\", type={self.type})" class PendingAsset(): - def __init__(self, operation, api_key) -> None: - self.__operation = operation + def __init__(self, path, api_key, creator=None) -> None: + self.__path = path self.__api_key = api_key + self.__creator = creator def __repr__(self) -> str: return f"rblxopencloud.PendingAsset()" - def fetch_status(self) -> Union[None, Asset]: - response = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{self.__operation}", + def fetch_operation(self) -> Union[None, Asset]: + response = requests.get(f"https://apis.roblox.com/assets/v1/{self.__path}", headers={"x-api-key": self.__api_key}) if response.ok: info = response.json() - if info["status"] == "Pending": return None - return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) + if not info.get("done"): return None + return Asset(info["response"], self.__creator) + +mimetypes = { + "mp3": "audio/mpeg", + "ogg": "audio/ogg", + "png": "image/png", + "jpeg": "image/jpeg", + "bmp": "image/bmp", + "tga": "image/tga", + "fbx": "model/fbx" +} class Creator(): - def __init__(self, userid: int, api_key: str) -> None: + def __init__(self, userid, api_key, type) -> None: self.id = userid self.__api_key = api_key - self.__creator_type = "User" + self.__creator_type = type def __repr__(self) -> str: return f"rblxopencloud.Creator({self.id})" - def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: + def upload_asset(self, file: io.BytesIO, asset_type: Union[AssetType, str], name: str, description: str, expected_robux_price: int = 0) -> Union[Asset, PendingAsset]: body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ - "targetType": "Decal", + "assetType": asset_type.name if type(asset_type) == AssetType else asset_type, "creationContext": { - "assetName": name, - "assetDescription": description, "creator": { - "creatorType": self.__creator_type, - "creatorId": str(self.id) - } - } + "userId": str(self.id), + } if self.__creator_type == "User" else { + "groupId": str(self.id) + }, + "expectedPrice": expected_robux_price + }, + "displayName": name, + "description": description }), - "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) + "fileContent": (file.name, file.read(), mimetypes.get(file.name.split(".")[-1])) }) - response = requests.post(f"https://apis.roblox.com/assets/v1/create", + response = requests.post(f"https://apis.roblox.com/assets/v1/assets", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) if response.status_code == 400: raise InvalidAsset(f"The file is not a decal or is corrupted") @@ -61,132 +93,43 @@ def create_decal(self, file: io.BytesIO, name: str, description: str) -> Union[A elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + response_op = requests.get(f"https://apis.roblox.com/assets/v1/{response.json()['path']}", headers={"x-api-key": self.__api_key}) - - if not response_status.ok: - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) - else: - info = response_status.json() - if info["status"] == "Pending": - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) - else: - return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) - - def create_audio(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: - body, contentType = urllib3.encode_multipart_formdata({ - "request": json.dumps({ - "targetType": "Audio", - "creationContext": { - "assetName": name, - "assetDescription": description, - "creator": { - "creatorType": self.__creator_type, - "creatorId": str(self.id) - } - } - }), - "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) - }) - response = requests.post(f"https://apis.roblox.com/assets/v1/create", - headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) - if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") - elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") - elif response.status_code == 429: raise RateLimited("You're being rate limited.") - elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") - elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - - response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", - headers={"x-api-key": self.__api_key}) - - if not response_status.ok: - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + if not response_op.ok: + return PendingAsset(response_op.json()['path'], self.__api_key, self) else: - info = response_status.json() - if info["status"] == "Pending": - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + info = response_op.json() + if not info.get("done"): + return PendingAsset(response_op.json()['path'], self.__api_key, self) else: - return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) - - def create_fbx(self, file: io.BytesIO, name: str, description: str) -> Union[Asset, PendingAsset]: - body, contentType = urllib3.encode_multipart_formdata({ - "request": json.dumps({ - "targetType": "ModelFromFbx", - "creationContext": { - "assetName": name, - "assetDescription": description, - "creator": { - "creatorType": self.__creator_type, - "creatorId": str(self.id) - }, - "assetId": "11326252443" - } - }), - "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) - }) - response = requests.post(f"https://apis.roblox.com/assets/v1/create", - headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) - - if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") - elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") - elif response.status_code == 429: raise RateLimited("You're being rate limited.") - elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") - elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - - response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", - headers={"x-api-key": self.__api_key}) + return Asset(info["response"], self) - if not response_status.ok: - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) - else: - info = response_status.json() - if info["status"] == "Pending": - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) - else: - return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) - - def update_fbx(self, id: int, file: io.BytesIO) -> Union[Asset, PendingAsset]: + def update_asset(self, asset_id: int, file: io.BytesIO) -> Union[Asset, PendingAsset]: body, contentType = urllib3.encode_multipart_formdata({ "request": json.dumps({ - "targetType": "ModelFromFbx", - "creationContext": { - "assetName": "null", - "assetDescription": "null", - "creator": { - "creatorType": self.__creator_type, - "creatorId": self.id - }, - "assetId": id - } + "assetId": asset_id }), - "fileContent": (mimetypes.guess_type(file.name)[0], file.read()) + "fileContent": (file.name, file.read(), mimetypes.get(file.name.split(".")[-1])) }) - response = requests.post(f"https://apis.roblox.com/assets/v1/create", + response = requests.patch(f"https://apis.roblox.com/assets/v1/assets/{asset_id}", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) - if response.status_code == 400: raise InvalidAsset(f"The file is not an audio or is corrupted") + if response.status_code == 400: raise InvalidAsset(f"The file is not a decal or is corrupted") elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") elif response.status_code == 429: raise RateLimited("You're being rate limited.") elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") - response_status = requests.get(f"https://apis.roblox.com/assets/v1/create/status/{response.json()['statusUrl'].split('/')[-1]}", + response_op = requests.get(f"https://apis.roblox.com/assets/v1/{response.json()['path']}", headers={"x-api-key": self.__api_key}) - - if not response_status.ok: - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + + if not response_op.ok: + return PendingAsset(response_op.json()['path'], self.__api_key, self) else: - info = response_status.json() - if info["status"] == "Pending": - return PendingAsset(response.json()['statusUrl'].split('/')[-1], self.__api_key) + info = response_op.json() + if not info.get("done"): + return PendingAsset(response_op.json()['path'], self.__api_key, self) else: - return Asset(info["result"]["assetInfo"]["assetId"], info["result"]["assetInfo"]["assetVersionNumber"]) - -class GroupCreator(Creator): - def __init__(self, groupid: int, api_key: str) -> None: - super().__init__(groupid, api_key) - self._Creator__creator_type = "Group" - - def __repr__(self) -> str: - return f"rblxopencloud.GroupCreator({self.id})" \ No newline at end of file + return Asset(info["response"], self) + \ No newline at end of file diff --git a/rblxopencloud/group.py b/rblxopencloud/group.py new file mode 100644 index 0000000..0e18404 --- /dev/null +++ b/rblxopencloud/group.py @@ -0,0 +1,10 @@ +from .creator import Creator + +class Group(Creator): + def __init__(self, id: int, api_key: str) -> None: + self.id = id + self.__api_key = api_key + super().__init__(id, api_key, "Group") + + def __repr__(self) -> str: + return f"rblxopencloud.Group({self.id})" \ No newline at end of file diff --git a/rblxopencloud/user.py b/rblxopencloud/user.py new file mode 100644 index 0000000..a6d5bba --- /dev/null +++ b/rblxopencloud/user.py @@ -0,0 +1,10 @@ +from .creator import Creator + +class User(Creator): + def __init__(self, id: int, api_key: str) -> None: + self.id = id + self.__api_key = api_key + super().__init__(id, api_key, "User") + + def __repr__(self) -> str: + return f"rblxopencloud.User({self.id})" \ No newline at end of file From 17033e1920dd6eb97e2b038f9a1da4d03f3a8809 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:28:24 +1000 Subject: [PATCH 21/27] added __all__, typing and fixed exceptions --- rblxopencloud/creator.py | 35 +++++++++++++++++++++++------------ rblxopencloud/exceptions.py | 6 ++---- rblxopencloud/group.py | 6 +++++- rblxopencloud/user.py | 6 +++++- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index d2c458a..21785b8 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -1,10 +1,21 @@ from .exceptions import * import requests, json, io -from typing import * +from typing import Union, Optional, TYPE_CHECKING import urllib3 from enum import Enum from datetime import datetime +if TYPE_CHECKING: + from .user import User + from .group import Group + +__all__ = ( + "AssetType", + "Asset", + "PendingAsset", + "Creator" +) + class AssetType(Enum): Unknown = 0 Decal = 1 @@ -13,19 +24,19 @@ class AssetType(Enum): class Asset(): def __init__(self, assetObject, creator=None) -> None: - self.id = assetObject.get("assetId") - self.name = assetObject.get("displayName") - self.description = assetObject.get("description") + self.id: int = assetObject.get("assetId") + self.name: str = assetObject.get("displayName") + self.description: str = assetObject.get("description") - self.creator = creator if creator else None + self.creator: Union[User, Group] = creator if creator else None - self.revision_id = assetObject.get("revisionId") - self.revision_time = datetime.fromisoformat(assetObject["revisionCreateTime"][0:26]) if assetObject.get("revisionCreateTime") else None + self.revision_id: Optional[int] = assetObject.get("revisionId") + self.revision_time: Optional[datetime] = datetime.fromisoformat(assetObject["revisionCreateTime"][0:26]) if assetObject.get("revisionCreateTime") else None - if assetObject.get("assetType") in ["Decal", "ASSET_TYPE_DECAL"]: self.type = AssetType.Decal - elif assetObject.get("assetType") in ["Audio", "ASSET_TYPE_AUDIO"]: self.type = AssetType.Audio - elif assetObject.get("assetType") in ["Model", "ASSET_TYPE_MODEL"]: self.type = AssetType.Model - else: self.type = AssetType.Unknown + if assetObject.get("assetType") in ["Decal", "ASSET_TYPE_DECAL"]: self.type: AssetType = AssetType.Decal + elif assetObject.get("assetType") in ["Audio", "ASSET_TYPE_AUDIO"]: self.type: AssetType = AssetType.Audio + elif assetObject.get("assetType") in ["Model", "ASSET_TYPE_MODEL"]: self.type: AssetType = AssetType.Model + else: self.type: AssetType = AssetType.Unknown def __repr__(self) -> str: return f"rblxopencloud.Asset({self.id}, name=\"{self.name}\", type={self.type})" @@ -60,7 +71,7 @@ def fetch_operation(self) -> Union[None, Asset]: class Creator(): def __init__(self, userid, api_key, type) -> None: - self.id = userid + self.id: int = userid self.__api_key = api_key self.__creator_type = type diff --git a/rblxopencloud/exceptions.py b/rblxopencloud/exceptions.py index d48392c..5320c8b 100644 --- a/rblxopencloud/exceptions.py +++ b/rblxopencloud/exceptions.py @@ -10,6 +10,7 @@ "RateLimited", "ServiceUnavailable", "PreconditionFailed", + "InvalidAsset", ) class rblx_opencloudException(Exception): pass @@ -19,10 +20,7 @@ class RateLimited(rblx_opencloudException): pass class ServiceUnavailable(rblx_opencloudException): pass class PreconditionFailed(rblx_opencloudException): def __init__(self, value, info, *args: object) -> None: - self.value = value - self.info = info - super().__init__(*args) -class InvalidAsset(rblx_opencloudException): pass self.value: Optional[Union[str, dict, list, int, float]] = value self.info: Optional[EntryInfo] = info super().__init__(*args) +class InvalidAsset(rblx_opencloudException): pass diff --git a/rblxopencloud/group.py b/rblxopencloud/group.py index 0e18404..e028765 100644 --- a/rblxopencloud/group.py +++ b/rblxopencloud/group.py @@ -1,8 +1,12 @@ from .creator import Creator +__all__ = ( + "Group", +) + class Group(Creator): def __init__(self, id: int, api_key: str) -> None: - self.id = id + self.id: int = id self.__api_key = api_key super().__init__(id, api_key, "Group") diff --git a/rblxopencloud/user.py b/rblxopencloud/user.py index a6d5bba..a16b1b6 100644 --- a/rblxopencloud/user.py +++ b/rblxopencloud/user.py @@ -1,8 +1,12 @@ from .creator import Creator +__all__ = ( + "User", +) + class User(Creator): def __init__(self, id: int, api_key: str) -> None: - self.id = id + self.id: int = id self.__api_key = api_key super().__init__(id, api_key, "User") From febbab6e06a98400e56eb8b67203f28fe93ea921 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:45:31 +1000 Subject: [PATCH 22/27] added exceptions for PendingAsset.fetch_operation --- rblxopencloud/creator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index 21785b8..6e41820 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -58,6 +58,11 @@ def fetch_operation(self) -> Union[None, Asset]: info = response.json() if not info.get("done"): return None return Asset(info["response"], self.__creator) + else: + if response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") + elif response.status_code == 429: raise RateLimited("You're being rate limited.") + elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") + elif not response.ok: raise rblx_opencloudException(f"Unexpected HTTP {response.status_code}") mimetypes = { "mp3": "audio/mpeg", From 5983b0d07d37a475819c6bc0431afc495c2c993e Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:47:06 +1000 Subject: [PATCH 23/27] added exceptions for PendingAsset.fetch_operation --- rblxopencloud/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rblxopencloud/creator.py b/rblxopencloud/creator.py index 6e41820..b4bdf22 100644 --- a/rblxopencloud/creator.py +++ b/rblxopencloud/creator.py @@ -103,7 +103,7 @@ def upload_asset(self, file: io.BytesIO, asset_type: Union[AssetType, str], name response = requests.post(f"https://apis.roblox.com/assets/v1/assets", headers={"x-api-key": self.__api_key, "content-type": contentType}, data=body) - if response.status_code == 400: raise InvalidAsset(f"The file is not a decal or is corrupted") + if response.status_code == 400: raise InvalidAsset(f"The file is not a supported type, or is corrupted") elif response.status_code == 401 or response.status_code == 403: raise InvalidKey("Your key may have expired, or may not have permission to access this resource.") elif response.status_code == 429: raise RateLimited("You're being rate limited.") elif response.status_code >= 500: raise ServiceUnavailable("The service is unavailable or has encountered an error.") From 97ea7ef31bc33cb965d5d3077209130b297579ed Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:22:25 +1000 Subject: [PATCH 24/27] added place publishing and assets example and modifed others --- README.md | 158 ++++++++++------------------------- examples/assets.py | 50 +++++++++++ examples/datastores-basic.py | 6 +- examples/messaging.py | 6 +- examples/place-publishing.py | 10 +++ 5 files changed, 108 insertions(+), 122 deletions(-) create mode 100644 examples/assets.py create mode 100644 examples/place-publishing.py diff --git a/README.md b/README.md index e953f73..743f4d8 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,3 @@ -## **IMPORTANT** - -**The Assets API is an alpha API subject to breaking changes and it is not avaliable to everyone. DO NOT try to use it unless you have access. You might be looking for [main](https://github.com/TreeBen77/rblx-open-cloud/tree/main).** - -## If you understand the above: - -Installing: -```console -pip install git+https://github.com/TreeBen77/rblx-open-cloud/tree/assetsapi -``` -there's no documantation yet. but here's a cheat sheet -```py -import rblxopencloud - -# to upload to your own account, use Creator - insert your user ID and api key -creator = rblxopencloud.Creator(287113233, "api-key-here") -# to upload to your group, use GroupCreator - insert your group ID and api key -creator = rblxopencloud.GroupCreator(9697297, "api-key-here") - -# let's start with decals. open the file as read bytes. -with open("experiment.png", "rb") as file: - # call the create_decal object, name is the item name and description is the item description - asset = creator.create_decal(file, "name", "description") - # asset will be either Asset or PendingAsset. Asset contains it's ID and version number - # PendingAsset means roblox hasn't finnished processing it. This will have a fetch_status method - # and that's it. the fetch status method will check if it's ready yet, it will return None or Asset. - - # here's a method that will wait until the decal is ready - if isinstance(asset, rblxopencloud.Asset): - # if it's already been processed, then print the asset. - print(asset) - else: - # otherwise, we'll go into a loop that continuosly loops through that. - while True: - # this will try to check if the asset has been processed yet - status = asset.fetch_status() - if status: - # if it has been print it then stop the loop. - print(status) - break - -# now for audio. it's the same as above but with audio -# there's a bug with audio and groups preventing it from uploading right now. -with open("experiment.mp3", "rb") as file: - # call the create_decal object, name is the item name and description is the item description - asset = creator.create_audio(file, "name", "description") - # for simplicity, i won't include the asset fetch_status part above here but it also works. - - # please note that you can only upload 10 audios a month and if you're ID verified that's 100. - -# and finally for .fbx files. it's the same as above but with a model -with open("experiment.fbx", "rb") as file: - # call the create_decal object, name is the item name and description is the item description - asset = creator.create_fbx(file, "name", "description") - # for simplicity, i won't include the asset fetch_status part above here but it also works. - # you're post likely going to recieve a PendingAsset with models because roblox has to render them which takes time - -# you can also update uploaded fbx files: -with open("experiment.2.fbx", "rb") as file: - # the first number is the fbx asset you want to update - asset = creator.update_fbx(11326252443, file) - # for simplicity, i won't include the asset fetch_status part above here but it also works. - # you're post likely going to recieve a PendingAsset with models because roblox has to render them which takes time - -``` -**IMPORTANT:** This code is also subject to breaking changes. Don't use it in production code! - # rblx-open-cloud Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-cloud/index). @@ -82,21 +15,19 @@ Python API wrapper for [Roblox Open Cloud](https://create.roblox.com/docs/open-c pip install rblx-open-cloud ``` -2. Create an API key from the [Creator Dashboard](https://create.roblox.com/credentials). You can read [Managing API Keys](https://create.roblox.com/docs/open-cloud/managing-api-keys) if you get stuck. +2. Create an API key from the [Creator Dashboard](https://create.roblox.com/credentials). You can read [Managing API Keys](https://create.roblox.com/docs/open-cloud/managing-api-keys) for help. -3. Add the following code to your project and replace `api-key-from-step-2` with the key you generated. - ```py - # create an Experience object with your experience ID and your api key - # TODO: replace '13058' with your experience ID - experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") - ``` - If you don't know how to get the experience or place ID read [Publishing Places with API Keys](https://create.roblox.com/docs/open-cloud/publishing-places-with-api-keys#:~:text=Find%20the%20experience,is%206985028626.) - -4. If you want to start by accessing your game's data stores go to [Data Stores](#accessing-data-stores) otherwise, you can go to [Messaging Service](#publishing-to-message-service) if you want to publish messages to live game servers, or [Place Publishing](#publish-or-save-a-rbxl-file) if you'd like to upload `.rbxl` files to Roblox.** +You've got the basics down, below are examples for some of the APIs. ### Accessing Data Stores -**NOTE: Roblox doesn't support access to ordered data stores via open cloud at the moment.** +**NOTE: Roblox doesn't support access to ordered data stores via open cloud yet.** ```py +import rblxopencloud + +# create an Experience object with your experience ID and your api key +# TODO: replace '13058' with your experience ID +experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") + # get the data store, using the data store name and scope (defaults to global) datastore = experience.get_data_store("data-store-name", scope="global") @@ -121,50 +52,45 @@ datastore.remove("key-name") ``` ### Publishing To Message Service -**NOTE: Messages published with Open Cloud only arrive in live game servers and not in Studio, so you'll have to publish the place to test this.** +**NOTE: Messages published with Open Cloud only arrive in live game servers and not in Studio.** ```py -# publish a message with the topic 'topic-name' -experience.publish_message("topic-name", "Hello World!") -``` +import rblxopencloud -### Publish or Save a `.rbxl` File -**NOTE: [Place Publishing](#publish-or-save-a-rbxl-file) isn't included in this example due to it requiring an `.rbxl` file.** -```py -#open the .rbxl file as read bytes -with open("path-to/place-file.rbxl", "rb") as file: - # the first number is the place ID to update, and publish denotes wether to publish or save the place. - # TODO: replace '1818' with your place ID - experience.upload_place(1818, file, publish=False) -``` -## Final Result (a.k.a copy and paste section) -```py # create an Experience object with your experience ID and your api key # TODO: replace '13058' with your experience ID experience = rblxopencloud.Experience(13058, api_key="api-key-from-step-2") -# get the data store, using the data store name and scope (defaults to global) -datastore = experience.get_data_store("data-store-name", scope="global") - -# sets the key 'key-name' to 68 and provides users and metadata -# DataStore.set does not return the value or an EntryInfo object, instead it returns a EntryVersion object. -datastore.set("key-name", 68, users=[287113233], metadata={"key": "value"}) - -# get the value with the key 'number' -# info is a EntryInfo object which contains data like the version code, metadata, userids and timestamps. -value, info = datastore.get("key-name") - -print(value, info) - -# increments the key 'key-name' by 1 and ensures to keep the old users and metadata -# DataStore.increment retuens a value and info pair, just like DataStore.get and unlike DataStore.set -value, info = datastore.increment("key-name", 1, users=info.users, metadata=info.metadata) - -print(value, info) - -# deletes the key -datastore.remove("key-name") - # publish a message with the topic 'topic-name' - experience.publish_message("topic-name", "Hello World!") ``` + +### Uploading Assets +**NOTE: Only `Decal`, `Audio`, and `Model` (fbx) are supported right now.** +```py +import rblxopencloud + +# create an User object with your user ID and your api key +# TODO: replace '13058' with your user ID +user = rblxopencloud.User(13058, api_key="api-key-from-step-2") +# or, create a Group object: +group = rblxopencloud.Group(13058, api_key="api-key-from-step-2") + +# this example is for uploading a decal: +with open("path-to/file-object.png", "rb") as file: + user.upload_asset(file, rblxopencloud.AssetType.Decal, "name", "description") + +# this will wait until Roblox has processed the upload +if isinstance(asset, rblxopencloud.Asset): + # if it's already been processed, then print the asset. + print(asset) +else: + # otherwise, we'll go into a loop that continuosly checks if it's done. + while True: + # this will try to check if the asset has been processed yet + operation = asset.fetch_operation() + if operation: + # if it has been print it then stop the loop. + print(operation) + break +``` +Examples for more APIs are avalible in the [examples](https://github.com/TreeBen77/rblx-open-cloud/tree/main/examples) directory. \ No newline at end of file diff --git a/examples/assets.py b/examples/assets.py new file mode 100644 index 0000000..3cb6b35 --- /dev/null +++ b/examples/assets.py @@ -0,0 +1,50 @@ +import rblxopencloud + +# to upload assets to your account, choose User +creator = rblxopencloud.User(13058, api_key="api-key-from-step-2") +# if you want to upload assets to a group, choose Group +creator = rblxopencloud.Group(13058, api_key="api-key-from-step-2") + +# let's start with decals. open the file as read bytes. +with open("example.png", "rb") as file: + # call the upload_asset function, name is the item name and description is the item description, you should use an item type of Decal. + asset = creator.upload_asset(file, rblxopencloud.AssetType.Decal, "name", "description") + # asset will be either Asset or PendingAsset. Asset contains information about the asset. + # PendingAsset means Roblox hasn't finnished processing it. This will have a fetch_operation method + # the fetch operation method will check if it's ready yet, it will return either None or Asset. + + # here's some code that will wait until the decal is ready + if isinstance(asset, rblxopencloud.Asset): + # if it's already been processed, then print the asset. + print(asset) + else: + # otherwise, we'll go into a loop that continuosly loops through that. + while True: + # this will try to check if the asset has been processed yet + status = asset.fetch_status() + if status: + # if it has been print it then stop the loop. + print(status) + break + +# now for audio. it's the same as above but with audio +with open("example.mp3", "rb") as file: + # call the upload_asset object, name is the item name and description is the item description, you should use an item type of Audio. + asset = creator.upload_asset(file, rblxopencloud.AssetType.Audio, "name", "description") + # for simplicity, I won't include the asset fetch_operation part above here but it also works. + + # please note that you can only upload 10 audios a month and if you're ID verified that's 100. + +# and finally for .fbx files. it's the same as above but with a model +with open("example.fbx", "rb") as file: + # call the upload_asset object, name is the item name and description is the item description, you should use an item type of Audio. + asset = creator.upload_asset(file, rblxopencloud.AssetType.Model, "name", "description") + # for simplicity, I won't include the asset fetch_operation part above here but it also works. + # you're most likely going to recieve a PendingAsset with models because roblox has to render them which takes time + +# you can also update uploaded fbx files: +with open("example.fbx", "rb") as file: + # call the update_asset object, 1234 is the asset's ID + asset = creator.update_asset(1234, file) + # for simplicity, I won't include the asset fetch_operation part above here but it also works. + # you're most likely going to recieve a PendingAsset with models because roblox has to render them which takes time \ No newline at end of file diff --git a/examples/datastores-basic.py b/examples/datastores-basic.py index bb5c553..c1c01f8 100644 --- a/examples/datastores-basic.py +++ b/examples/datastores-basic.py @@ -1,10 +1,10 @@ import rblxopencloud -# create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Experience(3499447036, "api-key-here") +# create a experience object, with the first value being the experience ID and the second being your api key +experience = rblxopencloud.Experience(3499447036, "api-key-here") # create a datastore with the name 'exampleStore' and the scope of 'open-cloud' -datastore = universe.get_data_store("exampleStore", scope="open-cloud") +datastore = experience.get_data_store("exampleStore", scope="open-cloud") # this will get the key for '287113233' value, info = datastore.get("287113233") diff --git a/examples/messaging.py b/examples/messaging.py index b51e539..25a9053 100644 --- a/examples/messaging.py +++ b/examples/messaging.py @@ -1,10 +1,10 @@ import rblxopencloud -# create a universe object, with the first value being the universe ID and the second being your api key -universe = rblxopencloud.Experience(3499447036, "api-key-here") +# create a experience object, with the first value being the experience ID and the second being your api key +experience = rblxopencloud.Experience(3499447036, "api-key-here") # this will publish a message to that topic in every server in that experience. it's this simple. -universe.publish_message("topic-name", "Hello World!") +experience.publish_message("topic-name", "Hello World!") # unfortently, there is currently no way to recieve messages. # roblox also doesnt support publishing anything other than strings with open cloud. \ No newline at end of file diff --git a/examples/place-publishing.py b/examples/place-publishing.py new file mode 100644 index 0000000..c1bd320 --- /dev/null +++ b/examples/place-publishing.py @@ -0,0 +1,10 @@ +import rblxopencloud + +# create a universe object, with the first value being the experience ID and the second being your api key +experience = rblxopencloud.Experience(3499447036, "api-key-here") + +# open the .rbxl file as read bytes +with open("path-to/place-file.rbxl", "rb") as file: + # the first number is the place ID to update, and publish denotes wether to publish or save the place. + # TODO: replace '1818' with your place ID + experience.upload_place(1818, file, publish=False) \ No newline at end of file From 22c4d06d006e1d609153e4eaef933e82dcaba1f4 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:23:45 +1000 Subject: [PATCH 25/27] changed version to final --- rblxopencloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rblxopencloud/__init__.py b/rblxopencloud/__init__.py index 04cfdd0..0616a50 100644 --- a/rblxopencloud/__init__.py +++ b/rblxopencloud/__init__.py @@ -8,6 +8,6 @@ from typing import Literal VERSION: str = "1.1.0" -VERSION_INFO: Literal['alpha', 'beta', 'final'] = "beta" +VERSION_INFO: Literal['alpha', 'beta', 'final'] = "final" del Literal From 0414dc2d1824db5d6d76ed6422d010f2fea7340a Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 19:05:52 +1000 Subject: [PATCH 26/27] added docs for assets api v1 --- docs/source/creator.rst | 160 ++++++++++++++++++++++++++++++++++++++++ docs/source/group.rst | 65 ++++++++++++++++ docs/source/index.rst | 3 + docs/source/user.rst | 65 ++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 docs/source/creator.rst create mode 100644 docs/source/group.rst create mode 100644 docs/source/user.rst diff --git a/docs/source/creator.rst b/docs/source/creator.rst new file mode 100644 index 0000000..fae38e2 --- /dev/null +++ b/docs/source/creator.rst @@ -0,0 +1,160 @@ +Creator +============================= + +.. currentmodule:: rblx-open-cloud + +.. class:: Creator() + + Class for interacting with the API to upload assets to a user or group. + + .. versionadded:: 1.1 + + .. warning:: + + This class is not designed to be created by users. It a inherited by :class:`rblx-open-cloud.User` and :class:`rblx-open-cloud.Group`. + + .. attribute:: id + + The Creator's ID + + :type: int + + .. method:: upload_asset(file, asset_type, name, description, expected_robux_price=0) + + Uploads an asset onto Roblox. + + :param io.BytesIO file: The file opened in bytes to be uploaded. + :param rblx-open-cloud.AssetType: The type of asset you're uploading. + :param str: The name of your asset. + :param str: The description of your asset. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + + .. method:: update_asset(asset_id, file) + + Updates an existing asset on Roblox. + + :param int asset_id: The ID of the asset to update. + :param io.BytesIO file: The file opened in bytes to be uploaded. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Model` (as `fbx`) can be updated right now. + +.. class:: Asset() + + Contains data about an asset, such as it's id, name, and type. + + .. versionadded:: 1.1 + + .. warning:: + + This class is not designed to be created by users. It is returned by :meth:`Creator.upload_asset` and :meth:`Creator.update_asset`. + + .. attribute:: id + + The asset's ID + + :type: int + + .. attribute:: name + + The asset's name + + :type: str + + .. attribute:: description + + The asset's description + + :type: str + + .. attribute:: creator + + The asset's creator + + :type: Union[:class:`rblx-open-cloud.Creator`, :class:`rblx-open-cloud.User`, :class:`rblx-open-cloud.Group`] + + .. attribute:: revision_id + + The ID of the asset's current version/revision + + :type: Optional[int] + + .. attribute:: revision_time + + The time the asset was last update + + :type: Optional[datetime.datetime] + + .. attribute:: type + + The asset's type + + :type: :class:`rblx-open-cloud.AssetType` + +.. class:: PendingAsset() + + Class for assets which aren't processed yet. + + .. versionadded:: 1.1 + + .. warning:: + + This class is not designed to be created by users. It is returned by :meth:`Creator.upload_asset` and :meth:`Creator.update_asset`. + + .. method:: fetch_operation() + + Fetches the asset info, if completed processing. + + :returns: Optional[:class:`rblx-open-cloud.Asset`] + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + +.. class:: AssetType() + + Enum to denote what type an asset is. + + .. versionadded:: 1.1 + + .. attribute:: Unkown + + An unkown asset type (e.g. an unimplemented type) + + .. attribute:: Decal + + An decal asset type + + .. attribute:: Audio + + An audio asset type + + .. attribute:: Model + + An model asset type (fbx) \ No newline at end of file diff --git a/docs/source/group.rst b/docs/source/group.rst new file mode 100644 index 0000000..0dd3206 --- /dev/null +++ b/docs/source/group.rst @@ -0,0 +1,65 @@ +Group +============================= + +.. currentmodule:: rblx-open-cloud + +.. class:: User() + + Class for interacting with the API for a specific group. + + .. versionadded:: 1.1 + + :param int id: A group ID. It appears in the group URL + :param str api_key: An API key created from `Creator Dashboard `__. *this should be kept safe, as anyone with the key can use it!* + + .. attribute:: id + + The group's ID + + :type: int + + .. method:: upload_asset(file, asset_type, name, description, expected_robux_price=0) + + Uploads an asset onto Roblox. + + :param io.BytesIO file: The file opened in bytes to be uploaded. + :param rblx-open-cloud.AssetType: The type of asset you're uploading. + :param str: The name of your asset. + :param str: The description of your asset. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + + .. method:: update_asset(asset_id, file) + + Updates an existing asset on Roblox. + + :param int asset_id: The ID of the asset to update. + :param io.BytesIO file: The file opened in bytes to be uploaded. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Model` (as `fbx`) can be updated right now. diff --git a/docs/source/index.rst b/docs/source/index.rst index 0f39f24..1c8fca5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -134,5 +134,8 @@ Table of Contents .. toctree:: experience + user + group datastore + creator exceptions diff --git a/docs/source/user.rst b/docs/source/user.rst new file mode 100644 index 0000000..791bbce --- /dev/null +++ b/docs/source/user.rst @@ -0,0 +1,65 @@ +User +============================= + +.. currentmodule:: rblx-open-cloud + +.. class:: User() + + Class for interacting with the API for a specific user. + + .. versionadded:: 1.1 + + :param int id: Your user ID. It appears in your profile URL + :param str api_key: An API key created from `Creator Dashboard `__. *this should be kept safe, as anyone with the key can use it!* + + .. attribute:: id + + The user's ID + + :type: int + + .. method:: upload_asset(file, asset_type, name, description, expected_robux_price=0) + + Uploads an asset onto Roblox. + + :param io.BytesIO file: The file opened in bytes to be uploaded. + :param rblx-open-cloud.AssetType: The type of asset you're uploading. + :param str: The name of your asset. + :param str: The description of your asset. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + + .. method:: update_asset(asset_id, file) + + Updates an existing asset on Roblox. + + :param int asset_id: The ID of the asset to update. + :param io.BytesIO file: The file opened in bytes to be uploaded. + + :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] + :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted + :raises rblx-open-cloud.InvalidToken: The token is invalid or doesn't have sufficent permissions to read and write assets. + :raises rblx-open-cloud.RateLimited: You're being rate limited by Roblox. Try again in a minute. + :raises rblx-open-cloud.ServiceUnavailable: Roblox's servers are currently experiencing downtime. + :raises rblx-open-cloud.rblx_opencloudException: Roblox's response was unexpected. + + .. danger:: + + Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + + .. note:: + + Only `Model` (as `fbx`) can be updated right now. From dcc43a77ace3b247894250dba287a6a91e32d9a0 Mon Sep 17 00:00:00 2001 From: TreeBen77 <77905642+TreeBen77@users.noreply.github.com> Date: Thu, 30 Mar 2023 19:16:03 +1000 Subject: [PATCH 27/27] fixed formatting and reworded asset moderation alert docs --- docs/source/creator.rst | 21 +++++++++++---------- docs/source/exceptions.rst | 6 +++++- docs/source/group.rst | 17 +++++++++-------- docs/source/user.rst | 17 +++++++++-------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/docs/source/creator.rst b/docs/source/creator.rst index fae38e2..ef97a84 100644 --- a/docs/source/creator.rst +++ b/docs/source/creator.rst @@ -24,9 +24,10 @@ Creator Uploads an asset onto Roblox. :param io.BytesIO file: The file opened in bytes to be uploaded. - :param rblx-open-cloud.AssetType: The type of asset you're uploading. - :param str: The name of your asset. - :param str: The description of your asset. + :param rblx-open-cloud.AssetType asset_type: The type of asset you're uploading. + :param str name: The name of your asset. + :param str description: The description of your asset. + :param str expected_robux_price: The amount of robux expected to upload. Fails if lower than actual price. :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted @@ -37,11 +38,11 @@ Creator .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. - + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. + .. note:: - Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + Only ``Decal``, ``Audio``, and ``Model`` (as ``fbx``) are supported right now. .. method:: update_asset(asset_id, file) @@ -59,11 +60,11 @@ Creator .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. .. note:: - Only `Model` (as `fbx`) can be updated right now. + Only ``Model`` (as ``fbx``) can be updated right now. .. class:: Asset() @@ -143,9 +144,9 @@ Creator .. versionadded:: 1.1 - .. attribute:: Unkown + .. attribute:: Unknown - An unkown asset type (e.g. an unimplemented type) + An unknown asset type (e.g. an unimplemented type) .. attribute:: Decal diff --git a/docs/source/exceptions.rst b/docs/source/exceptions.rst index 39753b4..9414560 100644 --- a/docs/source/exceptions.rst +++ b/docs/source/exceptions.rst @@ -37,4 +37,8 @@ Exceptions The key metadata. - :type: :class:`rblx-open-cloud.EntryInfo` \ No newline at end of file + :type: :class:`rblx-open-cloud.EntryInfo` + +.. exception:: InvalidAsset() + + The asset you upload is the wrong type, or is corrupted. \ No newline at end of file diff --git a/docs/source/group.rst b/docs/source/group.rst index 0dd3206..6563295 100644 --- a/docs/source/group.rst +++ b/docs/source/group.rst @@ -23,9 +23,10 @@ Group Uploads an asset onto Roblox. :param io.BytesIO file: The file opened in bytes to be uploaded. - :param rblx-open-cloud.AssetType: The type of asset you're uploading. - :param str: The name of your asset. - :param str: The description of your asset. + :param rblx-open-cloud.AssetType asset_type: The type of asset you're uploading. + :param str name: The name of your asset. + :param str description: The description of your asset. + :param str expected_robux_price: The amount of robux expected to upload. Fails if lower than actual price. :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted @@ -36,11 +37,11 @@ Group .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. - + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. + .. note:: - Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + Only ``Decal``, ``Audio``, and ``Model`` (as ``fbx``) are supported right now. .. method:: update_asset(asset_id, file) @@ -58,8 +59,8 @@ Group .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. .. note:: - Only `Model` (as `fbx`) can be updated right now. + Only ``Model`` (as ``fbx``) can be updated right now. diff --git a/docs/source/user.rst b/docs/source/user.rst index 791bbce..7334c72 100644 --- a/docs/source/user.rst +++ b/docs/source/user.rst @@ -23,9 +23,10 @@ User Uploads an asset onto Roblox. :param io.BytesIO file: The file opened in bytes to be uploaded. - :param rblx-open-cloud.AssetType: The type of asset you're uploading. - :param str: The name of your asset. - :param str: The description of your asset. + :param rblx-open-cloud.AssetType asset_type: The type of asset you're uploading. + :param str name: The name of your asset. + :param str description: The description of your asset. + :param str expected_robux_price: The amount of robux expected to upload. Fails if lower than actual price. :returns: Union[:class:`rblx-open-cloud.Asset`, :class:`rblx-open-cloud.PendingAsset`] :raises rblx-open-cloud.InvalidAsset: The file is not a supported, or is corrupted @@ -36,11 +37,11 @@ User .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. - + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. + .. note:: - Only `Decal`, `Audio`, and `Model` (as `fbx`) are supported right now. + Only ``Decal``, ``Audio``, and ``Model`` (as ``fbx``) are supported right now. .. method:: update_asset(asset_id, file) @@ -58,8 +59,8 @@ User .. danger:: - Assets are uploaded under your name, and can get your account banned! Be very careful what assets you choose to upload. + Assets uploaded with Open Cloud can still get your account banned if they're inappropriate. .. note:: - Only `Model` (as `fbx`) can be updated right now. + Only ``Model`` (as ``fbx``) can be updated right now.