From 22d4e50ee3abd855682367e5578a96236b36ac55 Mon Sep 17 00:00:00 2001
From: secret-rare
Date: Sat, 7 Mar 2026 19:07:30 +0000
Subject: [PATCH] deploy: 94e457f2407a84734f76ab9d88431ff73bf98d20
---
docs/rbx_upload.html | 232 ++++++++++++++++++++++---------------------
1 file changed, 117 insertions(+), 115 deletions(-)
diff --git a/docs/rbx_upload.html b/docs/rbx_upload.html
index ecae2be..2bb9ca9 100644
--- a/docs/rbx_upload.html
+++ b/docs/rbx_upload.html
@@ -543,68 +543,69 @@ rbx_upload
304 data = response.json()
305 collectible_item_id = data.get("collectibleItemId")
306 if not collectible_item_id:
-307 raise UploadError(f"publish_collectible did not return a collectibleItemId: {data}")
-308 return collectible_item_id
-309
-310 async def onsale_asset(
-311 self,
-312 collectible_item_id: str,
-313 price: int = 5,
-314 ) -> dict:
-315 """Put an asset on sale."""
-316 csrf = await self._get_csrf_token()
-317 response = await self._http.patch(
-318 f"https://itemconfiguration.roblox.com/v1/collectibles/{collectible_item_id}",
-319 json={
-320 "saleLocationConfiguration": {"saleLocationType": 1, "places": []},
-321 "saleStatus": 0,
-322 "quantityLimitPerUser": 0,
-323 "resaleRestriction": 2,
-324 "priceInRobux": price,
-325 "priceOffset": 0,
-326 "optOutFromRegionalPricing": False,
-327 "isFree": False,
-328 },
-329 headers={
-330 "X-CSRF-TOKEN": csrf,
-331 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
-332 "Referer": "https://create.roblox.com/",
-333 "Origin": "https://create.roblox.com",
-334 },
-335 cookies=self._csrf_cookies,
-336 )
-337
-338 if response.status_code == 429:
-339 raise RateLimitError("Rate limit hit during onsale.")
-340 if response.status_code in (401, 403):
-341 raise AuthError("Not authorized to put this asset on sale.")
-342
-343 response.raise_for_status()
-344 return response.json() if response.text else {}
-345
-346 async def get_collectible_item_id(self, asset_id: int, max_attempts: int = 10, poll_interval: float = 3.0) -> str:
-347 """Look up the collectible item ID (UUID) for a given asset ID, retrying until available."""
-348 for _ in range(max_attempts):
-349 response = await self._http.get(
-350 f"https://itemconfiguration.roblox.com/v1/collectibles/0/{asset_id}",
-351 cookies=self._csrf_cookies,
-352 )
-353 response.raise_for_status()
-354 collectible_item_id = response.json().get("collectibleItemId")
-355 if collectible_item_id:
-356 return collectible_item_id
-357 await asyncio.sleep(poll_interval)
-358 raise UploadError(f"collectibleItemId not available for asset {asset_id} after {max_attempts} attempts.")
-359
-360 async def close(self):
-361 """Close the underlying HTTP client."""
-362 await self._http.aclose()
-363
-364 async def __aenter__(self):
-365 return self
-366
-367 async def __aexit__(self, *args):
-368 await self.close()
+307 # status=0 means already published — look up the ID
+308 collectible_item_id = await self.get_collectible_item_id(asset_id)
+309 return collectible_item_id
+310
+311 async def onsale_asset(
+312 self,
+313 collectible_item_id: str,
+314 price: int = 5,
+315 ) -> dict:
+316 """Put an asset on sale."""
+317 csrf = await self._get_csrf_token()
+318 response = await self._http.patch(
+319 f"https://itemconfiguration.roblox.com/v1/collectibles/{collectible_item_id}",
+320 json={
+321 "saleLocationConfiguration": {"saleLocationType": 1, "places": []},
+322 "saleStatus": 0,
+323 "quantityLimitPerUser": 0,
+324 "resaleRestriction": 2,
+325 "priceInRobux": price,
+326 "priceOffset": 0,
+327 "optOutFromRegionalPricing": False,
+328 "isFree": False,
+329 },
+330 headers={
+331 "X-CSRF-TOKEN": csrf,
+332 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
+333 "Referer": "https://create.roblox.com/",
+334 "Origin": "https://create.roblox.com",
+335 },
+336 cookies=self._csrf_cookies,
+337 )
+338
+339 if response.status_code == 429:
+340 raise RateLimitError("Rate limit hit during onsale.")
+341 if response.status_code in (401, 403):
+342 raise AuthError("Not authorized to put this asset on sale.")
+343
+344 response.raise_for_status()
+345 return response.json() if response.text else {}
+346
+347 async def get_collectible_item_id(self, asset_id: int, max_attempts: int = 10, poll_interval: float = 3.0) -> str:
+348 """Look up the collectible item ID (UUID) for a given asset ID, retrying until available."""
+349 for _ in range(max_attempts):
+350 response = await self._http.get(
+351 f"https://itemconfiguration.roblox.com/v1/collectibles/0/{asset_id}",
+352 cookies=self._csrf_cookies,
+353 )
+354 response.raise_for_status()
+355 collectible_item_id = response.json().get("collectibleItemId")
+356 if collectible_item_id:
+357 return collectible_item_id
+358 await asyncio.sleep(poll_interval)
+359 raise UploadError(f"collectibleItemId not available for asset {asset_id} after {max_attempts} attempts.")
+360
+361 async def close(self):
+362 """Close the underlying HTTP client."""
+363 await self._http.aclose()
+364
+365 async def __aenter__(self):
+366 return self
+367
+368 async def __aexit__(self, *args):
+369 await self.close()
@@ -961,8 +962,9 @@ failures in the returned BatchResult.
304 data = response.json()
305 collectible_item_id = data.get("collectibleItemId")
306 if not collectible_item_id:
-307 raise UploadError(f"publish_collectible did not return a collectibleItemId: {data}")
-308 return collectible_item_id
+307 # status=0 means already published — look up the ID
+308 collectible_item_id = await self.get_collectible_item_id(asset_id)
+309 return collectible_item_id
@@ -982,41 +984,41 @@ failures in the returned BatchResult.
- 310 async def onsale_asset(
-311 self,
-312 collectible_item_id: str,
-313 price: int = 5,
-314 ) -> dict:
-315 """Put an asset on sale."""
-316 csrf = await self._get_csrf_token()
-317 response = await self._http.patch(
-318 f"https://itemconfiguration.roblox.com/v1/collectibles/{collectible_item_id}",
-319 json={
-320 "saleLocationConfiguration": {"saleLocationType": 1, "places": []},
-321 "saleStatus": 0,
-322 "quantityLimitPerUser": 0,
-323 "resaleRestriction": 2,
-324 "priceInRobux": price,
-325 "priceOffset": 0,
-326 "optOutFromRegionalPricing": False,
-327 "isFree": False,
-328 },
-329 headers={
-330 "X-CSRF-TOKEN": csrf,
-331 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
-332 "Referer": "https://create.roblox.com/",
-333 "Origin": "https://create.roblox.com",
-334 },
-335 cookies=self._csrf_cookies,
-336 )
-337
-338 if response.status_code == 429:
-339 raise RateLimitError("Rate limit hit during onsale.")
-340 if response.status_code in (401, 403):
-341 raise AuthError("Not authorized to put this asset on sale.")
-342
-343 response.raise_for_status()
-344 return response.json() if response.text else {}
+ 311 async def onsale_asset(
+312 self,
+313 collectible_item_id: str,
+314 price: int = 5,
+315 ) -> dict:
+316 """Put an asset on sale."""
+317 csrf = await self._get_csrf_token()
+318 response = await self._http.patch(
+319 f"https://itemconfiguration.roblox.com/v1/collectibles/{collectible_item_id}",
+320 json={
+321 "saleLocationConfiguration": {"saleLocationType": 1, "places": []},
+322 "saleStatus": 0,
+323 "quantityLimitPerUser": 0,
+324 "resaleRestriction": 2,
+325 "priceInRobux": price,
+326 "priceOffset": 0,
+327 "optOutFromRegionalPricing": False,
+328 "isFree": False,
+329 },
+330 headers={
+331 "X-CSRF-TOKEN": csrf,
+332 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
+333 "Referer": "https://create.roblox.com/",
+334 "Origin": "https://create.roblox.com",
+335 },
+336 cookies=self._csrf_cookies,
+337 )
+338
+339 if response.status_code == 429:
+340 raise RateLimitError("Rate limit hit during onsale.")
+341 if response.status_code in (401, 403):
+342 raise AuthError("Not authorized to put this asset on sale.")
+343
+344 response.raise_for_status()
+345 return response.json() if response.text else {}
@@ -1036,19 +1038,19 @@ failures in the returned BatchResult.
- 346 async def get_collectible_item_id(self, asset_id: int, max_attempts: int = 10, poll_interval: float = 3.0) -> str:
-347 """Look up the collectible item ID (UUID) for a given asset ID, retrying until available."""
-348 for _ in range(max_attempts):
-349 response = await self._http.get(
-350 f"https://itemconfiguration.roblox.com/v1/collectibles/0/{asset_id}",
-351 cookies=self._csrf_cookies,
-352 )
-353 response.raise_for_status()
-354 collectible_item_id = response.json().get("collectibleItemId")
-355 if collectible_item_id:
-356 return collectible_item_id
-357 await asyncio.sleep(poll_interval)
-358 raise UploadError(f"collectibleItemId not available for asset {asset_id} after {max_attempts} attempts.")
+ 347 async def get_collectible_item_id(self, asset_id: int, max_attempts: int = 10, poll_interval: float = 3.0) -> str:
+348 """Look up the collectible item ID (UUID) for a given asset ID, retrying until available."""
+349 for _ in range(max_attempts):
+350 response = await self._http.get(
+351 f"https://itemconfiguration.roblox.com/v1/collectibles/0/{asset_id}",
+352 cookies=self._csrf_cookies,
+353 )
+354 response.raise_for_status()
+355 collectible_item_id = response.json().get("collectibleItemId")
+356 if collectible_item_id:
+357 return collectible_item_id
+358 await asyncio.sleep(poll_interval)
+359 raise UploadError(f"collectibleItemId not available for asset {asset_id} after {max_attempts} attempts.")
@@ -1068,9 +1070,9 @@ failures in the returned BatchResult.
- 360 async def close(self):
-361 """Close the underlying HTTP client."""
-362 await self._http.aclose()
+ 361 async def close(self):
+362 """Close the underlying HTTP client."""
+363 await self._http.aclose()