Skip to content

Commit 3b7871b

Browse files
committed
feat: add item thumbnail support
at least in v347, tool thumbnails are fetched through `/Game/Tools/ThumbnailAsset.ashx`. usually the asset ids are decals, but i chose to route them through roblox's main api out of ease
1 parent 304f49f commit 3b7871b

4 files changed

Lines changed: 63 additions & 1 deletion

File tree

Source/assets/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Internal or local application imports
1212
import util.const
13-
from . import material, queue, returns, serialisers, extractor
13+
from . import material, queue, returns, serialisers, extractor, thumbnail
1414

1515

1616
@dataclasses.dataclass
@@ -144,6 +144,8 @@ def _load_asset_num(self, asset_id: int) -> bytes | None:
144144
def _load_asset_str(self, asset_id: str) -> bytes | None:
145145
if material.check(asset_id):
146146
return material.load_asset(asset_id)
147+
if thumbnail.check(asset_id):
148+
return thumbnail.load_asset(asset_id)
147149
return None
148150

149151
def _load_redir_asset(self, asset_id: int | str, redirect: asset_redirect) -> returns.base_type:

Source/assets/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ID_PREFIX = 'rbxmtl-'
2+
THUMB_PREFIX = 'rbxthmb-'
23

34
MATERIAL_DICT_2014 = {
45
'aluminum-diffuse.dds': 153465459,

Source/assets/thumbnail.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from . import const, extractor
2+
import json
3+
4+
def transform_to_id_num(asset_id: str) -> int:
5+
asset_sub = asset_id[len(const.THUMB_PREFIX):-4] # strip .png
6+
7+
try:
8+
return int(asset_sub)
9+
except:
10+
return 0
11+
12+
def check(asset_id: str):
13+
return asset_id.startswith(const.THUMB_PREFIX)
14+
15+
def load_asset(asset_id: str) -> bytes | None:
16+
id = transform_to_id_num(asset_id)
17+
if id == 0:
18+
return None
19+
20+
# technically we could also download the decal and parse its contents
21+
# to extract the thumbnail, but that might be overkill
22+
raw = extractor.download_item(
23+
"https://thumbnails.roblox.com/v1/assets?assetids=%s&size=700x700&format=Png&isCircular=false" %
24+
(id,)
25+
)
26+
if raw is None:
27+
return None
28+
29+
parsed = json.loads(raw)
30+
31+
if "data" in parsed and len(parsed["data"]) != 1:
32+
return None
33+
34+
return extractor.download_item(parsed["data"][0]['imageUrl'])

Source/web_server/endpoints/assets.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from web_server._logic import web_server_handler, server_path
22
import assets.returns as returns
33
import util.const
4+
import assets.const
45

56

67
@server_path("/asset")
@@ -57,3 +58,27 @@ def _(self: web_server_handler) -> bool:
5758
'''
5859
self.send_json('true')
5960
return True
61+
62+
@server_path('/Game/Tools/ThumbnailAsset.ashx')
63+
def _(self: web_server_handler) -> bool:
64+
asset_cache = self.game_config.asset_cache
65+
asset_id = self.query["aid"]
66+
if asset_id is None:
67+
self.send_error(404)
68+
return True
69+
70+
asset = asset_cache.get_asset(
71+
f"{assets.const.THUMB_PREFIX}{asset_id}.png",
72+
bypass_blocklist=self.is_privileged,
73+
)
74+
75+
if isinstance(asset, returns.ret_data):
76+
self.send_data(asset.data)
77+
return True
78+
elif isinstance(asset, returns.ret_none):
79+
self.send_error(404)
80+
return True
81+
elif isinstance(asset, returns.ret_relocate):
82+
self.send_redirect(asset.url)
83+
return True
84+
return False

0 commit comments

Comments
 (0)