Easy To Use Moderation System

Here’s a simple, easy-to-use moderation system I built and want to share with the community. It has 4 main functions.

  • Ban - Kicks the player every time they join
  • Temporary Ban - Same thing as the ban but after a set time period they may rejoin again
  • Unban - Allows the player to rejoin
  • Kicking - Kicks the player
Script
-- Made by @bonejon

local DSS = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")

local banStorage = DSS:GetDataStore("Bans")

local banEvent = RS.Moderation:FindFirstChild("banEvent")
local unbanEvent = RS.Moderation:FindFirstChild("unbanEvent")

local kickEvent = RS.Moderation:FindFirstChild("kickEvent")

--Controls
local groupID = 0 -- Replace with groups ID
local adminRank = 255 -- 0 is not in group, 255 is owner
local adminIds = { -- Replace with any user's ID if you want them to have access
	12345678,
	12345678
}

-- Takes formatted duration and returns it in seconds
local function durationToUnixSeconds(duration)
	local years = duration.years or 0
	local months = duration.months or 0
	local weeks = duration.weeks or 0
	local days = duration.days or 0
	local hours = duration.hours or 0
	local minutes = duration.minutes or 0
	local seconds = duration.seconds or 0

	--1 month = 30 days and 1 year = 365 days
	seconds = seconds + years * 365 * 24 * 60 * 60
	seconds = seconds + months * 30 * 24 * 60 * 60
	seconds = seconds + weeks * 7 * 24 * 60 * 60
	seconds = seconds + days * 24 * 60 * 60
	seconds = seconds + hours * 60 * 60
	seconds = seconds + minutes * 60

	return seconds
end

-- Banning logic
banEvent.OnServerEvent:Connect(function(player, oPlayerName, reason, banTime)
	-- Get offender instance
	local oPlayer = PS:FindFirstChild(oPlayerName)
	-- Get players rank in set group
	local groupRank = player:GetRankInGroup(groupID)
	-- Check if player is on the admin list
	local adminStatus = table.find(adminIds, player.UserId)
	print(adminStatus)
	
	-- If player is an admin then
	if adminStatus or groupRank >= adminRank then
		-- If we passed a time then its a temporary ban, otherwise its permanent
		if banTime then
			-- Add current unix time (seconds) and add it formatted ban duration in seconds to get unix time of a persons unban
			local unbanTime = os.time() + durationToUnixSeconds(banTime)
			-- Adds moderator name, the reason for the ban, and the unix time of the unban to the datastore under the UID of offender
			banStorage:SetAsync(oPlayer.UserId, HttpService:JSONEncode({player.Name, reason, unbanTime}))
			-- Kicks the offender and displays, reason, moderator banned by, formatted date of the unban
			oPlayer:Kick("You have been banned for " .. reason .. ". You were banned by " .. player.Name .. ". You will be unbanned on " .. os.date("%Y/%m/%d %H:%M", unbanTime) .. ".")
		else
			-- Adds the moderator name, the reason for the ban, no time because its permanent, to the datastore under the UID of the offender
			banStorage:SetAsync(oPlayer.UserId, HttpService:JSONEncode({player.Name, reason}))
			-- Kicks the offender and displays, reason, and moderator banned by
			oPlayer:Kick("You have been banned permanently for " .. reason .. ". You were banned by " .. player.Name .. ".")
		end		
	end
end)

-- Unbanning logic
unbanEvent.OnServerEvent:Connect(function(player, oPlayerUID)
	-- Get players rank in set group
	local groupRank = player:GetRankInGroup(groupID)
	-- Check if player is on the admin list
	local adminStatus = table.find(adminIds, player.UserId)
	print(adminStatus)

	-- If player is an admin then
	if adminStatus or groupRank >= adminRank then
		-- Removes the datastore entry with the offenders User ID
		banStorage:RemoveAsync(oPlayerUID)
	end
end)

-- Kicking logic
kickEvent.OnServerEvent:Connect(function(player, oPlayerName, reason)
	-- Get offender instance
	local oPlayer = PS:FindFirstChild(oPlayerName)
	-- Get players rank in set group
	local groupRank = player:GetRankInGroup(groupID)
	-- Check if player is on the admin list
	local adminStatus = table.find(adminIds, player.UserId)
	print(adminStatus)

	-- If player is an admin then
	if adminStatus or groupRank >= adminRank then
		-- Kicks the offender and displays reason and moderator kicked by
		oPlayer:Kick("You have been kicked for " .. reason .. ". You have been kicked by " .. player.Name .. ".")
	end
end)

-- Kick banned players logic
PS.PlayerAdded:Connect(function(oPlayer)
	-- Get all data from datastore about player
	local success, rawData = pcall(function()
		return banStorage:GetAsync(oPlayer.UserId)
	end)
	
	-- If data was found then decode and kick
	if success and rawData then
		local data = HttpService:JSONDecode(rawData)
		
		-- Seperate data into easy use variables
		local modName = data[1]
		local reason = data[2]
		local unbanTime = data[3]

		-- If there is unbanTime then its temporary
		if unbanTime then
			-- If unix time (seconds) is greater than unban time (seconds) then unban, otherwise kick
			if os.time() > unbanTime then
				-- Remove offender from datastore
				banStorage:RemoveAsync(oPlayer.UserId)
			else
				-- Kick em because there is still time left on their ban
				oPlayer:Kick("You have been banned for " .. reason .. ". You were banned by " .. modName .. ". You will be unbanned on " .. os.date("%Y/%m/%d %H:%M", unbanTime) .. ".")
			end
		else
			-- Kick em because it is a permanent ban
			oPlayer:Kick("You have been banned permanently for " .. reason .. ". You were banned by " .. modName .. ".")
		end
	end
end)
Set Up

This all gets called with 3 remoteEvent’s in a folder in ReplicatedStorage and the script sits in ServerScriptStorage.

Inside the script there is a two settings you need to change by the top of the script

--Controls
local groupID = 0 -- Replace with groups ID
local adminRank = 255 -- 0 is not in group, 255 is owner
local adminIds = { -- Replace with any user's ID if you want them to have access
	12345678,
	12345678
}

These are who can call these functions, if you have a group with admins then you can add them. If you do not have a group, or have people who you want to admin your game but are not in the group then you can add them.

GroupID is found by heading to your group and copying the group id from the url and pasting into groupID (See Picture.)


To find the rank of the admin you need to go to, the three dots and selecting Configure Community (See Picture.)


Then click on roles (See Picture.)

Then click on the lowest role you want to have admin access (See Picture.)

Then copy rank and paste it into adminRank (See Picture.)

To set up individual users as admins, first go to the users profile (See Picture.)


Then copy and paste their user id into the adminIds array, the more the merrier (See Picture.)

Any words wrapped in is a variable. A double dash – is an explanation. You also can only :FireServer() in a localscript, this was designed for UI use but all the code will work without the remoteEvent with some tinkering.

Calling Ban:
local banEvent = game.ReplicatedStorage.Moderation:WaitForChild("banEvent")
banEvent:FireServer( [OffendingPlayersName] , [Reason] )
Calling Temporary Ban:
local banEvent = game.ReplicatedStorage.Moderation:WaitForChild("banEvent")
local time = {
 years = 0,
 months = 0,
 weeks = 0,
 days = 0,
 hours = 0,
 minutes = 0,
 seconds = 0
}
banEvent:FireServer( [OffendingPlayersName] , [Reason] , time) --You may replace time with an array like {minutes = 1, seconds = 30}
Calling Unban:
local unbanEvent = game.ReplicatedStorage.Moderation:WaitForChild("unbanEvent")
unbanEvent:FireServer( [OffendingPlayersUserId] )
Calling A Kick:
local kickEvent = game.ReplicatedStorage.Moderation:WaitForChild("kickEvent")
kickEvent:FireServer( [OffendingPlayersName], [Reason] )

Video of system working.

If you find any issues please comment and I will try and fix it.

5 Likes

Pretty cool tbh one of the better admin systems rn, cuz of how simple it is

1 Like

roblox has a ban feature already, not sure why would you make your own.

From what Ive researched, the roblox Ban API only works for 50 users at a time. Please correct me if im wrong.

It means you can only ban 50 at a single time, meaning a table with more then 50 ban calls will be rejected, it’s not saying you can’t ban more then 50 people overall.

3 Likes

Using a single RemoteEvent and passing the action type as an argument is a cleaner, and a much better solution overall. Way easier to maintain, especially when you start scaling your projects to larger scales.

But just a lesson for you; separate remotes for similar functions is redundant and harder to maintain.

1 Like