UGC Accessory Cleaner Script

This Roblox hat remover script was made to automatically delete the “biggest” and “flashiest” hats from players in a game. It targets large and annoying accessories that might cause annoyance or be too attention-grabbing. It works by checking different properties of the accessories, including their size, mesh ID, and texture. It can also filter out hats based on specific blacklisted words, descriptions, and creators.

The script is pretty configurable and you can adjust settings like removing layered clothing, checking asset descriptions, and only allowing hats from verified creators. The default settings and comments within the script pretty much explain everything needed to customise these options.

--!strict
--!optimize 1
-- optimization flag should get rid of debug prints if DebugPrints is set to false

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")

local DebugPrints = true

local Settings = {
	RemoveLayeredClothing = false,
	
	CheckMeshIDs = true, -- See BlacklistedMeshes below
	CheckTextures = true, -- See BlacklistedTextures below
	CheckAccessorySize = true, -- Accessories that are bigger than 5 studs are deleted
	
	CheckAssetInfo = {
		-- Will check ProductInfo for any matches, if found then the accessory is deleted
		Enabled = true,
		CheckDescription = true, -- Check description for mentions of blacklisted words? See DescriptionBlackist below
		
		OnlyVerifiedHats = false, -- Will only keep hats made by players with the verified badge
		RemoveOffSaleHats = false, -- Will only remove hats that were manually made off-sale, limiteds will still be wearable
		
		WordBlacklist = {
			-- Capitalization doesn't matter, it also doesn't have to be the full name
			
			"Flashiest", -- epilepsy inducing accesories
			"Biggest", -- large accessories
			"/ugcs", -- ugc group where bypassed large hats are uploaded
		} :: {string},
		
		DescriptionBlackist = {
			-- Capitalization doesn't matter, it also doesn't have to be the full name
		} :: {string},
		
		CreatorBlacklist = {
			-- Can be group ids or userids
			
			-- GROUP
			34474186, -- makes "skybox" / large hats
			16640808, -- makes "Biggest ___" hats
			11130075, -- makes epilepsy inducing accessories
			
			-- USER
			485466993, -- makes epilepsy inducing accessories
		} :: {number}
	},
	
	BlacklistedMeshes = {
		"rbxassetid://18100307103",
		"rbxassetid://17345242336",
		"rbxassetid://14282144118",
		"rbxassetid://15423734896",
		"rbxassetid://17323421898",
		"rbxassetid://11502882458",
		"rbxassetid://17285248611",
		"rbxassetid://17214242215",
		"rbxassetid://15423734737",
		"rbxassetid://18373548780",
		"rbxassetid://18100088851",
	},

	BlacklistedTextures = {
		-- Lot of the "flashiest" and "biggest" hats share the same
		-- TextureID, this is a simple way to blacklist 3-4 hats with just one entry
		"rbxassetid://17375589753",
		"rbxassetid://11499419610",
		"rbxassetid://17285296554",
		"rbxassetid://15423775085",
		"rbxassetid://18352649038",
		"rbxassetid://18100098116",
	}
}

local ProductInfoCache = {}

local function GetAccessoryMesh(Accessory: Accessory): SpecialMesh | MeshPart | nil
	local Handle: BasePart | MeshPart | nil = Accessory:FindFirstChild("Handle") :: any
	if not Handle then
		return
	end

	if Handle:IsA("MeshPart") then
		return Handle
	end

	local Mesh = Handle:FindFirstChildOfClass("SpecialMesh")
	if Mesh then
		return Mesh
	end

	return nil
end

local function GetTextureID(Mesh: SpecialMesh | MeshPart)
	return if Mesh:IsA("SpecialMesh") then Mesh.TextureId else Mesh.TextureID
end

local function WaitForAppearance(Player: Player)
	if not Player:HasAppearanceLoaded() then
		Player.CharacterAppearanceLoaded:Wait()
	end
end

local function RemoveBlacklistedAccessory(Player: Player, Accessory: Accessory)
	if DebugPrints then
		warn(`{Player.Name} had a blacklisted accessory equipped called {Accessory.Name}`)
	end

	if not (pcall(Accessory.Destroy, Accessory)) then
		WaitForAppearance(Player)
		pcall(Player.LoadCharacter, Player)
	end
end

local function GetHatInfo(HatID: number)
	local ProductInfo
	
	if ProductInfoCache[HatID] then
		ProductInfo = ProductInfoCache[HatID]
	else
		local Success, Response = pcall(MarketplaceService.GetProductInfo, MarketplaceService, HatID, Enum.InfoType.Asset)
		
		if not Success then
			warn(`MarketplaceService:GetProductInfo({HatID}, Enum.InfoType.Asset) errored: {Response}`)
			return { Creator = 0, ByVerifiedPlayer = false, OffSale = false, Name = "", Description = "" }
		end
		
		ProductInfo = Response
		ProductInfoCache[HatID] = Response
	end
	
	return {
		Creator = ProductInfo.Creator.CreatorTargetId,
		ByVerifiedPlayer = ProductInfo.Creator.HasVerifiedBadge,
		
		OffSale = not ProductInfo.IsForSale and not ProductInfo.IsLimited and not ProductInfo.IsLimitedUnique,
		
		Name = ProductInfo.Name,
		Description = ProductInfo.Description
	}
end

local function RemoveAccessoriesWithBlacklistedAssetInfo(Player: Player, Humanoid: Humanoid)
	local HumanoidDescription = Players:GetHumanoidDescriptionFromUserId(Player.UserId)
	local AccessoryList = HumanoidDescription:GetAccessories(true)
	local NewAccessoryList = {}
	
	type AccessoryEntry = {
		AccessoryType: Enum.AccessoryType,
		AssetId: number,
		IsLayered: boolean,
	}
	
	type HatInfo = {
		Creator: number,
		ByVerifiedPlayer: boolean,
		OffSale: boolean,
		Name: string,
		Description: string
	}
	
	for _, AccessoryEntry: AccessoryEntry in AccessoryList do
		if AccessoryEntry.IsLayered and Settings.RemoveLayeredClothing then
			continue
		end
		
		local Info: HatInfo = GetHatInfo(AccessoryEntry.AssetId)
		
		if Settings.CheckAssetInfo.RemoveOffSaleHats and Info.OffSale then
			if DebugPrints then
				warn(`{Info.Name} is off-sale`)
			end
			
			continue
		elseif Settings.CheckAssetInfo.OnlyVerifiedHats and not Info.ByVerifiedPlayer then
			if DebugPrints then
				warn(`{Info.Name} is not made by a verified player`)
			end
			
			continue
		end
		
		local DontIncludeHat = false; do
			for _, BlacklistedWord in Settings.CheckAssetInfo.WordBlacklist do
				if string.find(Info.Name:lower(), BlacklistedWord:lower(), nil, true) then
					if DebugPrints then
						warn(`{Info.Name} contains a blacklisted word in the name: {BlacklistedWord}`)
					end
					
					DontIncludeHat = true
					break
				end
			end
			
			if not DontIncludeHat and Settings.CheckAssetInfo.CheckDescription then
				for _, BlacklistedWord in Settings.CheckAssetInfo.DescriptionBlackist do
					if string.find(Info.Description:lower(), BlacklistedWord:lower(), nil, true) then
						if DebugPrints then
							warn(`{Info.Name} contains a blacklisted word in the description: {BlacklistedWord}`)
						end

						DontIncludeHat = true
						break
					end
				end
			end
			
			if not DontIncludeHat and table.find(Settings.CheckAssetInfo.CreatorBlacklist, Info.Creator) then
				if DebugPrints then
					warn(`{Info.Name} is made by a blacklisted user/group: {Info.Creator}`)
				end
				
				DontIncludeHat = true
			end
		end
		
		if DontIncludeHat then
			continue
		end
		
		table.insert(NewAccessoryList, AccessoryEntry)
	end
	
	HumanoidDescription:SetAccessories(NewAccessoryList, true)
	Humanoid:ApplyDescription(HumanoidDescription)
end

local function CheckAccessoryObjects(Player: Player, Humanoid: Humanoid)
	local Accessories = Humanoid:GetAccessories()

	if #Accessories > 25 then
		return Player:Kick(">25 Hats")
	end

	for _, Accessory in Accessories do
		local Handle = Accessory:FindFirstChild("Handle") :: BasePart
		if not Handle then
			continue
		end
		
		if Settings.CheckAccessorySize then
			for _, AxisSize in { Handle.Size.X, Handle.Size.Y, Handle.Size.Z } do
				if AxisSize > 5 then
					RemoveBlacklistedAccessory(Player, Accessory)
					return
				end
			end
		end
		
		local Mesh = GetAccessoryMesh(Accessory); do
			if Mesh then
				if Settings.CheckMeshIDs and table.find(Settings.BlacklistedMeshes, Mesh.MeshId) then
					RemoveBlacklistedAccessory(Player, Accessory)
					continue
				elseif Settings.CheckTextures and table.find(Settings.BlacklistedTextures, GetTextureID(Mesh)) then
					RemoveBlacklistedAccessory(Player, Accessory)
					continue
				end
			end
		end
	end
end

local function Scan(Character: Model)
	local Player = Players:GetPlayerFromCharacter(Character)
	
	local Humanoid: Humanoid | nil = Character:WaitForChild("Humanoid") :: Humanoid
	if not Humanoid then
		return
	end
	
	WaitForAppearance(Player)
	
	if Settings.CheckAssetInfo.Enabled then
		RemoveAccessoriesWithBlacklistedAssetInfo(Player, Humanoid)
	end
	
	CheckAccessoryObjects(Player, Humanoid)
end

local function PlayerAdded(Player: Player)
	Player.CharacterAdded:Connect(Scan)
	
	if Player.Character then
		Scan(Player.Character)
	end
end

Players.PlayerAdded:Connect(PlayerAdded)
for _, Player in Players:GetPlayers() do
	task.spawn(PlayerAdded, Player)
end
8 Likes

My only suggestion is turning this into a ModuleScript for versatility. Other than that it looks great, and Ill be using this for my game, thank you!

True, you can easily turn this into a module or put the settings in a dedicated module, I was more going for a drag and drop solution

1 Like