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

-- If your game allows you to reload your avatar (for example your game has an Avatar Editor), export the Scan function
-- by making this a ModuleScript or put it into shared
-- and run it after reloading

-- Note that the Scan function will reset your HumanoidDescription if you have CheckAssetInfo enabled
-- This means, custom bundles / hats / accessories applied by the game will be gone
-- to fix this, pass your custom HumanoidDescription as second argument in Scan or as third argument in RemoveAccessoriesWithBlacklistedAssetInfo

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

local DebugPrints = true

local Settings = {
	RemoveLayeredClothing = false,
	
	RemoveGlitchedBundles = { -- Remove bundles that fling you or are very small
		Enabled = true,
		Action = "Kick", -- "Kick" | "Fix" # Fix will make an attempt to remove the malicious parts
		KickMessage = "You have a glitched bundle equipped. Take it off to be able to play this game"
	}, 
	
	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
			"Freaky", -- inappropriate slang
		} :: {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, CustomHumanoidDescription: HumanoidDescription?)
	local HumanoidDescription = CustomHumanoidDescription or 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 CheckVectors(Descendant: BasePart | Model, Size: Vector3): boolean
	local TargetSize: Vector3; do
		if Descendant:IsA("Model") then
			TargetSize = Descendant:GetExtentsSize()
		else
			TargetSize = Descendant.Size
		end
	end
	
	for _, Sizes in {
		{ TargetSize.X, Size.X }, 
		{ TargetSize.Y, Size.Y },
		{ TargetSize.Z, Size.Z },
	} do
		if Sizes[1] <= Sizes[2] then
			return true
		end
	end
	
	return false
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, CustomHumanoidDescription: HumanoidDescription?)
	local Player = Players:GetPlayerFromCharacter(Character)
	
	local Humanoid: Humanoid | nil = Character:WaitForChild("Humanoid") :: Humanoid
	if not Humanoid then
		return
	end
	
	if Settings.CheckAssetInfo.Enabled then
		RemoveAccessoriesWithBlacklistedAssetInfo(Player, Humanoid, CustomHumanoidDescription)
	end
	
	WaitForAppearance(Player)
	
	if Settings.RemoveGlitchedBundles.Enabled then
		local Action = Settings.RemoveGlitchedBundles.Action
		
		if CheckVectors(Character, Vector3.new(2, 4, 2)) and Action == "Kick" then
			Player:Kick(Settings.RemoveGlitchedBundles.KickMessage)
		end
		
		for _, Descendant in ipairs(Character:GetDescendants()) do
			if Descendant:IsA("MeshPart") then
				if CheckVectors(Descendant, Vector3.new(0.1, 0.1, 0.1)) then
					if Action == "Fix" then
						Descendant:Destroy() -- Destroying the parts fixes the mass issue
					elseif Action == "Kick" then
						Player:Kick(Settings.RemoveGlitchedBundles.KickMessage) 
						break
					end
				end
				
				if Descendant.Mass > 1e3 and Action == "Kick" then
					Player:Kick(Settings.RemoveGlitchedBundles.KickMessage)
				end
			end
		end
	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
21 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!

3 Likes

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

2 Likes

Update

  • Product info is now cached
  • Attempted to detect glitched bundles (read comments)
  • Added “Freaky” to word blacklist
1 Like

hey,you should really make this into a customizeable module so you can easily set up more blacklisted groups,words,etc, this would be really helpfull(sorry for bumping or reviving this topic)

Theres settings in the script‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎