Heyo!
Here’s the code
-- Please credit me @mastawba
local escapecharacters = {lt = "<",gt = ">",qout = '"',apot = "'",amp = "&"}
local urlIdExtractor = {
"http://www.roblox.com/asset/%?id=(%d+)",
"https://www.roblox.com/asset/%?id=(%d+)",
"rbxassetid://(%d+)",
"rbxthumb://type=Asset&id=(%d+).*"
}
local function freecharacters(chars : string)
local new = chars:gsub("&.-;",function(a)
return escapecharacters[a:sub(2, -2)] or a
end)
return new
end
local function parseTag(line)
local tag, attributes_line = line:match("<([%w/:]+)%s(.-)>")
if not tag then -- Tag with no attribute
tag = line:match("<([%w/:]+)>")
end
local attributes = {}
if attributes_line ~= nil then
for key, value in attributes_line:gmatch("([%w:]+)=(\".-\")") do
attributes[key] = freecharacters(value:sub(2, -2)) -- Remove quotes
end
end
return tag, attributes
end
type XMLTag = {children : {XMLTag | string}, tag : string, tag_attributes : {}}
function ParseXML(XML : string) : {XMLTag | string}
local current = {children = {}}
local stack = {current}
local XMLBuffer = buffer.fromstring(XML)
local lastlt, lastt = nil, 0
local function extractText(pointer)
if lastt == nil then return end
local textData = buffer.readstring(XMLBuffer,lastt,pointer - lastt)
if textData:match("%S") then table.insert(current.children,freecharacters(textData)) end
lastt = nil
end
for pointer = 0,buffer.len(XMLBuffer) - 1 do
local byte = buffer.readu8(XMLBuffer,pointer)
if byte == 60 then -- <
extractText(pointer)
lastlt = pointer
elseif byte == 62 then -- >
lastt = pointer + 1
local tag = buffer.readstring(XMLBuffer,lastlt,pointer - lastlt + 1)
if tag:sub(1,2) == "<?" and tag:sub(#tag-1,#tag) == "?>" then -- xml declaration
elseif tag:sub(1,4) == "<!--" and tag:sub(#tag-2,#tag) == "-->" then -- comment
elseif tag:sub(1,9) == "<!DOCTYPE" then -- doctype
else
if tag:sub(2,2) == "/" then -- closing tag
table.remove(stack)
current = stack[#stack]
else
local tag_name, tag_attributes = parseTag(tag)
local newChild = {
tag = tag_name,
tag_attributes = tag_attributes,
children = {}
}
table.insert(current.children,newChild)
table.insert(stack,newChild)
current = newChild
end
end
lastlt = nil
end
end
extractText(buffer.len(XMLBuffer))
return current.children
end
local function GetTag(start : {XMLTag | string},name,attributesToLook : {string:any}?) : XMLTag?
attributesToLook = attributesToLook or {}
local cc = 0; for _ in attributesToLook do cc += 1 end
for _,child in start do
if type(child) == "table" and child.tag == name then
local c1 = 0; for name,value in child.tag_attributes do if attributesToLook[name] == value then c1 += 1 end end
local c2 = 0; for name,value in attributesToLook do if child.tag_attributes[name] == value then c2 += 1 end end
if c1 == cc and c2 == cc then return child end
end
end
end
function GetImageIdFromDecalId(AssetId : string) : number
if type(AssetId) ~= "number" then
error("Invalid argument #1 (number expected, got "..typeof(AssetId)..")")
end
local Work, ParsedResponse = pcall(function()
return ParseXML(game:GetService("HttpService"):GetAsync("https://assetdelivery.roproxy.com/v1/asset/?id="..AssetId))
end)
if not Work then
error("Failed to get parsed XML response from Roblox",22)
end
local roblox = GetTag(ParsedResponse,"roblox")
local Item = roblox and GetTag(roblox.children,"Item",{class = "Decal"})
local DecalProperties = Item and GetTag(Item.children,"Properties")
local content = DecalProperties and GetTag(DecalProperties.children,"Content",{name = "Texture"})
local urltag = content and GetTag(content.children,"url")
if urltag then
local url = urltag.children[1]
if type(url) == "string" then
for _,extractor in urlIdExtractor do
local id = tonumber(url:match(extractor))
if id then
return id
end
end
end
else
error("Not a decal Id",2)
end
end
return {
GetImageIdFromDecalId = GetImageIdFromDecalId
}
DECAL ID TO IMAGE ID
Lemme get this straight, I can totally relate to some developers that it’s so frustrating to get the image id from decal id ESPECIALLY IF YOU HAVE A SYSTEM WITH DECAL INPUT TEXTBOX FOR USERS. Even worse when Roblox has changed on how their asset ids works to be semantic which makes it even more difficult.
hehehe i made a very simple and easy way to actually get the image id from decal id. Just by putting the code into ModuleScript
for example and then this. Best part is it works in live production servers. (I tested it guaranteed)
local GetImageIdFromDecalId = require(script.GetImageIdFromDecalId)
print(GetImageIdFromDecalId.GetImageIdFromDecalId(128747854507573)) --> 97504313148702
Just to be sure, please wrap it in pcall
local GetImageIdFromDecalId = require(script.GetImageIdFromDecalId)
local work, decalid = pcall(function()
return GetImageIdFromDecalId.GetImageIdFromDecalId(128747854507573)
end)
print(work, decalid) --> true, 97504313148702
I COOKED:fire:SO HOW DID I DO IT?
This silly url https://assetdelivery.roblox.com/v1/asset/?id=id
returns some very useful data about the asset Id.
Now ofc you can’t use this url because Roblox doesn’t let you use their resource or whatever they say.
Now ofc I fixed it by using roproxy https://assetdelivery.roproxy.com/v1/asset/?id=id
For example when I GET this url https://assetdelivery.roblox.com/v1/asset/?id=128747854507573
it returns this
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
<External>null</External>
<External>nil</External>
<Item class="Decal" referent="RBX0">
<Properties>
<token name="Face">5</token>
<string name="Name">Decal</string>
<float name="Shiny">20</float>
<float name="Specular">0</float>
<Content name="Texture">
<url>http://www.roblox.com/asset/?id=97504313148702</url>
</Content>
<bool name="archivable">true</bool>
</Properties>
</Item>
</roblox>
Now this particular XML is a representation of a Decal instance written in XML for some reason. We can see its properties such as Face, Name, Shiny, Specular, archiveable, and TEXTURE!!
TEXTURE that’s what we are looking for because it contains a url linked to the image id!!
So then I wrote a short comprehensive code that parses XML to lua table.
Then with that parsed XML thing, the code will try to get the Texture URL then format it to a number id thingy.
Happy “custom decal game hangout” 'ing!