Initial commit

This commit is contained in:
2025-12-21 00:07:51 -05:00
commit 18eb2d611f
9 changed files with 1259 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
from fastapi import FastAPI
import models
from utils import roblox_service
app = FastAPI()
@app.get("/asset/{asset_id}")
def get_asset_info(asset_id: int):
asset = roblox_service.asset_from_id(asset_id)
final_dict = {asset}
if isinstance(asset, models.ClothingAsset):
print("clothing asset found")
image = roblox_service.fetch_clothing_image(asset)
with open(f"tests/{asset.asset_id}.png", "wb") as output:
output.write(image)
return final_dict
+73
View File
@@ -0,0 +1,73 @@
from enum import IntEnum
from typing import Literal
# Enums
class RbxAssetType(IntEnum):
IMAGE = 1
SHIRT = 11
PANTS = 12
# Types
ClothingAssetType = Literal[
RbxAssetType.SHIRT,
RbxAssetType.PANTS,
]
CreatorType = Literal["User", "Group"]
# Roblox Creator class (for creator info - mainly useful for returning data + TUI)
class RbxCreator:
def __init__(self, creator_id: int, username: str, creator_type: CreatorType):
self.creator_id = creator_id
self.username = username
self.creator_type = creator_type
# Asset base class
class RbxAsset:
def __init__(
self,
asset_id: int,
creator: RbxCreator,
name: str,
description: str,
asset_type: RbxAssetType,
) -> None:
self.asset_id = asset_id
self.name = name
self.description = description
self.creator = creator
self.asset_type = asset_type
# Clothing asset class
class ClothingAsset(RbxAsset):
def __init__(
self,
asset_id: int,
creator: RbxCreator,
name: str,
description: str,
asset_type: ClothingAssetType,
) -> None:
super().__init__(
asset_id=asset_id,
creator=creator,
name=name,
description=description,
asset_type=asset_type,
)
def get_image(self) -> bytes:
from utils import roblox_service
return roblox_service.fetch_clothing_image(self)
+113
View File
@@ -0,0 +1,113 @@
import json
import os
import xml.etree.ElementTree
import requests
from dotenv import load_dotenv
import models
load_dotenv()
# Constants
ROBLOSECURITY = os.getenv("ROBLOSECURITY_TOKEN")
if not ROBLOSECURITY:
raise EnvironmentError("ROBLOSECURITY_TOKEN is missing from environment.")
FETCH_HEADERS = {
"Cookie": f".ROBLOSECURITY={ROBLOSECURITY}",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
# Economy is an unauthed API; we should minimize cookie use where possible
ANONYMIZED_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}
ASSET_DELIVERY_BASE_URL = "https://assetdelivery.roblox.com/v1/asset/?id={asset_id}"
ECONOMY_BASE_URL = "https://economy.roblox.com/v2/assets/{asset_id}/details"
# Internal methods
def _economy_request(asset_id: int) -> requests.Response:
return requests.get(
ECONOMY_BASE_URL.format(asset_id=asset_id), headers=FETCH_HEADERS
)
def _asset_delivery_request(asset_id: int) -> requests.Response:
return requests.get(
ASSET_DELIVERY_BASE_URL.format(asset_id=asset_id), headers=FETCH_HEADERS
)
def _get_asset_xml(asset: models.RbxAsset) -> xml.etree.ElementTree.Element:
response = _asset_delivery_request(asset.asset_id)
content = response.content.decode("utf-8")
response.raise_for_status()
xml_root = xml.etree.ElementTree.fromstring(content)
return xml_root
def _get_shirt_template_id_from_xml(root: xml.etree.ElementTree.Element) -> int:
url_element = root.find(".//url")
if url_element is None:
raise ValueError("XML did not contain a <url> tag.")
url = url_element.text
if not url:
raise ValueError("<url> tag did not contain any text.")
template_id = url.split("id=")[1]
return int(template_id)
# External methods
def asset_from_id(id: int) -> models.RbxAsset:
response = _economy_request(id)
response.raise_for_status()
asset_info = json.loads(response.content)
creator_info = asset_info["Creator"]
asset_creator = models.RbxCreator(
creator_id=creator_info["Id"],
username=creator_info["Name"],
creator_type=creator_info["CreatorType"],
)
asset_type_id = asset_info["AssetTypeId"]
if (
asset_type_id == models.RbxAssetType.SHIRT
or asset_type_id == models.RbxAssetType.PANTS
):
return models.ClothingAsset(
asset_id=asset_info["AssetId"],
creator=asset_creator,
name=asset_info["Name"],
description=asset_info["Description"],
asset_type=asset_info["AssetTypeId"],
)
return models.RbxAsset(
asset_id=asset_info["AssetId"],
creator=asset_creator,
name=asset_info["Name"],
description=asset_info["Description"],
asset_type=asset_info["AssetTypeId"],
)
def fetch_clothing_image(asset: models.ClothingAsset) -> bytes:
try:
xml = _get_asset_xml(asset)
template_id = _get_shirt_template_id_from_xml(xml)
image = _asset_delivery_request(template_id)
image.raise_for_status()
return image.content
except Exception:
raise # TODO add logging
# def upload_clothing_image(image: bytes, target: models.RbxCreator) -> models.RbxAsset: