Possible logic error with ban code

Hello,

I made a longer ban script for a module I’m making, which has a lot of features to it. Only issue is I’m having a bit of a random logic error in unbans (like 619)

--- DarkPixlz 2022-2023 ---

--- Made for PreloadService 3 ---

--- Is appealing configured? ---

local Strings = {
	["Disclaimer"] = "WARNING: Your username, date, and time is being logged. Do NOT abuse this system." 

-- not used yet
}

--- Variables ---
local DSS = game:GetService("DataStoreService")
local KEY = "PreloadService-AdvancedBanPro_Bans" --[[ DO NOT CHANGE THIS UNLESS YOU HAVE TO!!]]
-- Changing that WILL RESET THE BANNED PLAYERS and ALL BANNED PLAYERS WILL BE UNBANNED
local PluginAPI = require(game.ReplicatedStorage:WaitForChild("PreloadService").PluginsAPI)
local BannedPlayers = DSS:GetDataStore(KEY)
local Folder = PluginAPI.PluginEventsFolder("AdvancedBanPro")
local MS = game:GetService("MessagingService")
local AdminsScript = script.Parent.Parent.Parent.Admins
local AdminIDs, GroupIDs = require(AdminsScript).Admins, require(AdminsScript).Groups
---

--- Text ---

-- EDIT ME
local UserBanMessage = "You're banned for " -- Reason will be added on
local AccountBlacklistMessage = "[Error 2] \n Your account has been blacklisted. You are not welcome in our game."
local AccountWordBlacklist = "[Error 3] \n Sorry, but your username or display name contains a blacklisted word "
--blacklisted word
local AccountWordBlacklistEndString = ". Please change this before attempting to join the game again."

-- Check to make sure that you didn't change the DataStore Key
local Keys_Info = DSS:GetDataStore("PreloadService-AdvancedBanPro-Keys")
local Success, Result = pcall(function()
	Keys_Info:GetAsync(KEY)
end)
if not Success then
	warn("[PreloadService]: CRITICAL: COULD NOT FIND BAN INFO FOR THIS KEY!\nIf you changed the key, REVERT IT! ALL PLAYERS ARE UNBANNED.")
end





--- Functions ---
local function ShouldAppeal(Moderator)
	if Moderator == "Automod" then
		return false
	else return true end 
end

local function IsAdmin(Player, IsString)
	-- Uses the same logic as core PS
	if IsString == nil or not IsString then
		if table.find(AdminIDs, Player.UserId) then
			return true
		else
			for i, v in pairs(GroupIDs) do
				if Player:IsInGroup(v) then
					return true
				end
			end
		end
		return false
	end
	if table.find(AdminIDs, game.Players:GetUserIdFromNameAsync(Player)) then
		return true
	else
		for i, v in pairs(GroupIDs) do
			if false then
				return true
			end
		end
	end
	return false
end

local TodaysBans = DSS:GetDataStore("PS-"..os.date("%A").."_"..os.date("%B").."_"..os.date("%d").."_"..os.date("%Y"))

local function GetPlayerBansToday(p)
	return  TodaysBans:GetAsync(p.UserId) or 0
end

local function AddToPlayerBans(p)
	local Data = GetPlayerBansToday(p)
	Data += 1 
	TodaysBans:SetAsync(p.UserId, Data)
end

local function Heat(rgb)
	local hexadecimal = '0x'
	for key, value in pairs(rgb) do
		local hex = ''
		while(value > 0)do
			local index = math.fmod(value, 16) + 1
			value = math.floor(value / 16)
			hex = string.sub('0123456789ABCDEF', index, index) .. hex			
		end
		if(string.len(hex) == 0)then
			hex = '00'
		elseif(string.len(hex) == 1)then
			hex = '0' .. hex
		end
		hexadecimal = hexadecimal .. hex
	end
	return hexadecimal
end

--test
--print(Heat({255, 69, 72}))

local function LocalTime()
	local Date = os.date("*t")
	local DayTxt = os.date("%A")
	local Month = os.date("%B")
	local DayDate = os.date("%d")
	local Hour = string.format("%0.2i", ((Date.hour % 12 == 0) and 12) or (Date.hour % 12))
	local Minute = string.format("%0.2i", Date.min)
	local Second = string.format("%0.2i", Date.sec)
	local Meridiem = (Date.hour < 12 and "AM" or "PM")

	return tostring(DayTxt..", "..Month.." "..DayDate.." "..Hour ..":".. Minute ..":".. Second .." ".. Meridiem)
end

local function Time()
	local Date = os.date("*t")
	local Hour = string.format("%0.2i", ((Date.hour % 12 == 0) and 12) or (Date.hour % 12))
	local Minute = string.format("%0.2i", Date.min)
	local Second = string.format("%0.2i", Date.sec)
	local Meridiem = (Date.hour < 12 and "AM" or "PM")

	return tostring(Hour ..":".. Minute ..":".. Second .." ".. Meridiem)
end
--[[
	local DayTxt = os.date("%A")
	local Month = os.date("%B")
	local DayDate = os.date("%d")
	]]
local function Date()
	local DayTxt = os.date("%A")
	local Month = os.date("%B")
	local DayDate = os.date("%d")
	local Year = os.date("%Y")

	return tostring(DayTxt..", "..Month.." "..DayDate.." "..Year)
end

local function GetPlayerJoinDate(PlayerName)
	-- I know using proxies is bad but I have to do it here
	local Data = game:GetService("HttpService"):JSONDecode(game:GetService("HttpService"):GetAsync("https://legoproxy.veriblox.ml/users/v1/users/"..game.Players:GetUserIdFromNameAsync(PlayerName)))["created"]
	local day, time = string.match(Data, "(%d+%-%d+%-%d+)T(%d+:%d+:%d+%.%d+)Z")
	local date = day
	local function GetTimes()
		local times = {}
		local count = 0
		local ConstructedString = ""
		for i = 1,#date do
			local number = tonumber(string.sub(date,i,i))
			if number then
				ConstructedString = ConstructedString..number
			else
				table.insert(times,tonumber(ConstructedString))
				ConstructedString = ""
			end
		end
		if #ConstructedString > 0 then
			table.insert(times,tonumber(ConstructedString))
		end
		return times[1],times[2],times[3]
	end
	local year,month,day = GetTimes()
	--print(year,month,day)

	local universaltimeunix = math.floor(tonumber(tostring(DateTime.fromUniversalTime(year,month,day))/1000))
	local unixtime = os.time()-universaltimeunix
	local DaysAgo = math.floor(unixtime/60/60/24)
	print(DaysAgo)
	print(day)
	return day
end


local function SendWebhook(Type,Title,Body, FieldName,FieldValue, Red)
	local HttpService = game:GetService("HttpService")
	if Red ~= nil and Red >= 255 then
		Red = 255
	end
	local Color = Heat({Red,137,34})
	print(Color)
	if Type == "BanJoin" then
		local url = ""

		local data = 
			{
				["contents"] = "",
				--["username"] = GameName.." Logging",
				--["avatar_url"] = "https://cdn.discordapp.com/avatars/1032431346385178655/e6bde5fcfd3b994456fa578b8956ff0c.png?size=4096",
				["embeds"] = {{
					["title"]= Title,
					["description"] = Body,
					["type"]= "rich",
					["color"]= tonumber(0x006DFF),
					["fields"]={
						{
							["name"]=FieldName,
							["value"]=FieldValue,
							["inline"]=true
						},
						{
							["name"]="Report Data", 
							["value"]="Sent at "..LocalTime(),
							["inline"]=true
						},
					},
					["footer"] = {
						["text"] = "Advanced Ban Pro 1.0 | Powered By PreloadService",
						["icon_url"] = "https://preloadservice.darkpixlz.com/uploads/default/original/1X/5ea4d4cface5e897e9f1e70118c757209a75edc9.png"
					}
				}
				}
			}
		HttpService:PostAsync(url,HttpService:JSONEncode(data))
	elseif Type == "Ban" then
		local url = ""

		local data = 
			{
				["contents"] = "",
				--["username"] = GameName.." logging",
				--["avatar_url"] = "https://cdn.discordapp.com/avatars/1032431346385178655/e6bde5fcfd3b994456fa578b8956ff0c.png?size=4096",
				["embeds"] = {{
					["title"]= Title,
					["description"] = Body,
					["type"]= "rich",
					["color"]= tonumber(Color),
					["fields"]={
						{
							["name"]=FieldName,
							["value"]=FieldValue,
							["inline"]=true
						},
						{
							["name"]="Report Data", 
							["value"]="Sent at "..LocalTime(),
							["inline"]=true
						},
					},
					["footer"] = {
						["text"] = "Advanced Ban Pro 1.0 | Powered By PreloadService",
						["icon_url"] = "https://preloadservice.darkpixlz.com/uploads/default/original/1X/5ea4d4cface5e897e9f1e70118c757209a75edc9.png"
					}
				}
				}
			}
		HttpService:PostAsync(url,HttpService:JSONEncode(data))

	elseif Type == "AttemptedBan" then
		local url = ""

		local data = 
			{
				--["content"] = "@everyone IMPORTANT",
				--["username"] = GameName.." Logging",
				--["avatar_url"] = "https://cdn.discordapp.com/avatars/1032431346385178655/e6bde5fcfd3b994456fa578b8956ff0c.png?size=4096",
				["embeds"] = {{
					["title"]= Title,
					["description"] = Body,
					["type"]= "rich",
					["color"]= tonumber(0x006DFF),
					["fields"]={
						{
							["name"]="Report Data", 
							["value"]="Sent at "..LocalTime(),
							["inline"]=true
						},
					},
					["footer"] = {
						["text"] = "Advanced Ban Pro 1.0 | Protected By PreloadService",
						["icon_url"] = "https://preloadservice.darkpixlz.com/uploads/default/original/1X/5ea4d4cface5e897e9f1e70118c757209a75edc9.png"
					}
				}
				}
			}
		HttpService:PostAsync(url,HttpService:JSONEncode(data))
	else
		warn("Invalid Type value.")
	end
end

local function OnInvoke(p, Player,Intent,Table)
	--[[
		Info:
		Intent 1: Check Status/Pull Data
		Intent 2: Unban player
		Intent 3: Ban Player
		Table Format:
		{
			Reason,
			CanAppeal,
			Length,
		}
	]]
	if Intent == 1 then --Check status
		local IsAdmin = IsAdmin(p)
		if not IsAdmin then
			SendWebhook(
				"AttemptedBan",
				"Exploiter attempted to loopup ban!",
				p.Name.." Attempted to look up ban data for "..Player.."! Player has been permabanned.\nPreloadService has automatically banned the user, no further action required, unless stated.",
				"",
				""
			)
			require(script.Configuration).ExploitsDetected(p)
			local DataToWrite = {
				IsBanned = true,
				TimeToUnban = 0,
				Reason = "Ban exploits. Method: CHECK",
				Moderator = "Automod",
				Date = Date(),
				Time = Time(),
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
			p:Kick("Exploits Detected! Powered by PreloadService.")
			local succ, err = pcall(function()
				BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(p.Name).."_BanV1", DataToWrite)
			end)
			if not succ then
				SendWebhook("AttemptedBan", "DSS BAN FAILED, PLEASE BAN INSTANTLY", "Error: "..err,"","")
			end
			p:Destroy()
			return
		end
		local data = BannedPlayers:GetAsync(game.Players:GetUserIdFromNameAsync(Player).."_BanV1")
		if not data then
			warn("Data not found.")
			return  {
				IsBanned = false,
				TimeToUnban = 0,
				Reason = "",
				Moderator = "",
				Date = "",
				Time = "",
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
		end
		return data
	elseif Intent == 2 then -- Unban Player
		local IsAdmin = IsAdmin(p)
		if not IsAdmin then
			SendWebhook(
				"AttemptedBan",
				"EXPLOITER FIRED BAN EVENT",
				p.Name.." Attempted to unban "..Player.."! Player has been permabanned.\nPreloadService has automatically banned the user, no further action required, unless stated.",
				"",
				""
			)
			require(script.Configuration).ExploitsDetected(p)
			local DataToWrite = {
				IsBanned = true,
				TimeToUnban = 9999999999999999,
				Reason = "Ban Exploits.",
				Moderator = "Automod",
				Date = Date(),
				Time = Time(),
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
			p:Kick("Exploits Detected! Powered by PreloadService.")
			local succ, err = pcall(function()
				BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(p.Name).."_BanV1", DataToWrite)
			end)
			if not succ then
				SendWebhook("AttemptedBan", "DSS BAN FAILED, PLEASE BAN INSTANTLY", "Error: "..err,"","")
			end
			p:Destroy()
			return
		end

		--Completely overwrite data
		local DataToWrite = {
			IsBanned = false,
			TimeToUnban = 0,
			Reason = "",
			Moderator = "",
			Date = "",
			Time = "",
			DoesExist=true,
			Appeals = {
				CanAppeal = false,
				AppealSubmitted = false
			}
		}
		local Data = DataToWrite
		BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(Player).."_BanV1", DataToWrite)
		SendWebhook(
			"Ban",
			"Player has been unbanned!",
			"More info below.", 
			"Event details", 
			"Player name: "..Player.."\nPlayer ID: "..game.Players:GetUserIdFromNameAsync(Player).." \Original ban was on on "..Data["Date"].." at "..Data["Time"].."\nResponsible Moderator: @"..p.Name.."\n Server Time: "..LocalTime()
		)
	elseif Intent == 3 then --Ban player
		local Found = IsAdmin(Player, true)
		if Found then
			SendWebhook(
				"AttemptedBan",
				"Admin attempted to unban other admin!",
				p.Name.." Attempted to ban "..Player.." for "..Table[1] or "N/A".."! Attention required.",
				"",
				""
			)
			p:Kick("Attempt to ban an admin. We've logged this and your request did not go through.")
			return
		end
		local IsAdmin = IsAdmin(p)
		if not IsAdmin then
			SendWebhook(
				"AttemptedBan",
				"EXPLOITER HAS ACCESS TO BANS",
				p.Name.." Attempted to unban "..Player.." for "..Table[1] or "N/A".."! Player has been permabanned.\nATTENTION NEEDED ASAP",
				"",
				""
			)
			p:Kick("Ban was successful. Please rejoin to ban more players.")
			local DataToWrite = {
				IsBanned = true,
				TimeToUnban = 999999999999,
				Reason = "Exploits are not permitted!",
				Moderator = "Automoderator",
				Date = Date(),
				Time = Time(),
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
			local Data = DataToWrite
			local succ, err = pcall(function()
				BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(p.Name).."_BanV1", DataToWrite)
			end)
			if not succ then
				SendWebhook("AttemptedBan", "DSS BAN FAILED, PLEASE BAN INSTANTLY", "Error: "..err,"","")
			end
			return
		end
		--Everything for future use will not be included
		-- I'll add times and that stuff laterrr
		print(GetPlayerJoinDate(Player))
		if string.lower(Table[3]) == "perm" then
			Table[3] = 99999999999999999
		end
		local DataToWrite = {
			IsBanned = true,
			TimeToUnban = Table[3] + GetPlayerJoinDate(Player),
			Reason = Table[1],
			Moderator=tostring(p.Name),
			Date = Date(),
			Time = Time(),
			DoesExist=true,
			Appeals = {
				CanAppeal = Table[2],
				AppealSubmitted = false
			}
		}
		AddToPlayerBans(p)
		local Data = DataToWrite
		BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(Player).."_BanV1", DataToWrite)
		SendWebhook(
			"Ban",
			"Player has been banned!",
			"More info below.", 
			"Event details", 
			"Player name: "..Player.."\nPlayer ID: "..game.Players:GetUserIdFromNameAsync(Player).."\nBan reason: "..Data["Reason"].." \nBanned on "..Data["Date"].." at "..Data["Time"].."\nBanned by @"..Data["Moderator"]..".\nCan Appeal: "..tostring(Data["Appeals"]["CanAppeal"]).."\nBan Length: "..tostring(Data["TimeToUnban"] -GetPlayerJoinDate(Player).."\nAdmin's Bans Today: "..tostring(GetPlayerBansToday(p))),           
			GetPlayerBansToday(p)*17
		)
		MS:PublishAsync("OnPlayerBanned")
	end
end

---


local function SafeTeleport(Player)
	local AttemptIndex = 0
	local success, result -- define pcall results outside of loop so results can be reported later on
	repeat
		success, result = pcall(function()
			return game:GetService("TeleportService"):TeleportAsync(12207611270, {Player}) -- teleport the user in a protected call to prevent erroring
		end)
		AttemptIndex += 1
		if not success then
			task.wait(2)
		end
	until success or AttemptIndex == 25 -- stop trying to teleport if call was successful, or if retry limit has been reached


	if not success then
		warn(result) -- print the failure reason to output
	end
	return success, result
end



function RemoveNegativeSign(num)
	if num < 0 then
		num = -num
	end
	return num
end


local function PlrAdded(Player)
	-- Get old data
	local OldData
	local succ, err = pcall(function()
		OldData = DSS:GetDataStore("CUR_BANNED_PLRS_v1"):GetAsync(Player.UserId.."_BanV1")
	end)
	if OldData then
		warn("Found old data for "..Player.Name.."!")
		--Set new data
		BannedPlayers:SetAsync(
			Player.UserId.."_BanV1", {
				IsBanned = OldData["IsBanned"],
				TimeToUnban = 0,
				Reason = OldData["Reason"],
				Moderator = OldData["Moderator"],
				Date = OldData["Date"],
				Time = OldData["Time"],
				DoesExist=true,
				Appeals = {
					CanAppeal = ShouldAppeal(OldData["Moderator"]),
					AppealSubmitted = false
				}
			}
		)
		print("Moved over to new data!")
		DSS:GetDataStore("CUR_BANNED_PLRS_v1"):RemoveAsync(Player.UserId.."_BanV1")
	end
	print(Player.Name)
	local Retries = 0
	local keytest = BannedPlayers:GetAsync(Player.UserId.."_BanV1")
	--Ban check will happen in ./BanChecker
	if not BannedPlayers:GetAsync(Player.UserId.."_BanV1") then
		warn("Not found")
		--Player is new
		BannedPlayers:SetAsync(
			Player.UserId.."_BanV1", {
				IsBanned = false,
				TimeToUnban = 0,
				Reason = "",
				Moderator = "",
				Date = "",
				Time = "",
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
		)
	elseif keytest["IsBanned"] then
		local ui = script.ScreenBlocker:Clone()
		ui.Parent = Player.PlayerGui
		print("Is banned!")
		if keytest["Appeals"]["CanAppeal"] then
			print("Can Appeal")
			--The same system is used in Jailbreak, and a known exploit there can be used to avoid the ban.
			-- So, we try up to 5 times, then make the ban permanant	
				task.spawn(function()
					local Success, Error = SafeTeleport(Player)
				end)
			if not Success then
				SendWebhook(
					"AttemptedBan",
					"Exploiter detected!",
					Player.Name.." used exploits to try and avoid the appeal teleport! Player has been banned.",
					"",
					""
				)
				Player:Kick("Exploits detected, or teleport failed. Powered by PreloadService.")
				local DataToWrite = {
					IsBanned = true,
					TimeToUnban = 99999999999,
					Reason = "Exploits. Method: Avoid appeal",
					Moderator = "",
					Date = "",
					Time = "",
					DoesExist=true,
					Appeals = {
						CanAppeal = false,
						AppealSubmitted = false
					}
				}
				local Data = DataToWrite
				local succ, err = pcall(function()
					BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(Player.Name).."_BanV1", DataToWrite)
				end)
				if not succ then
					SendWebhook("AttemptedBan", "DSS BAN FAILED, PLEASE BAN INSTANTLY", "Error: "..err,"","")
				end
			end
			task.wait(15)
			Player:Kick()
			return
		end
		if Player.AccountAge >= keytest["TimeToUnban"] then
			print(Player.AccountAge.."-"..keytest["TimeToUnban"])
			warn("Unbanning as time has come!")
			local DataToWrite = {
				IsBanned = false,
				TimeToUnban = 0,
				Reason = "",
				Moderator = "",
				Date = "",
				Time = "",
				DoesExist=true,
				Appeals = {
					CanAppeal = false,
					AppealSubmitted = false
				}
			}
			local Data = DataToWrite
			local succ, err = pcall(function()
				BannedPlayers:SetAsync(game.Players:GetUserIdFromNameAsync(Player.Name).."_BanV1", DataToWrite)
			end)
			ui:Destroy()
			return
		else
			warn(tostring(Player.AccountAge.."-"..keytest["TimeToUnban"]).." left!")
		end
		print("Kicking!")
		Player:Kick(UserBanMessage..keytest["Reason"].." by "..keytest["Moderator"]..". You'll be unbanned in "..tostring(RemoveNegativeSign(Player.AccountAge - keytest["TimeToUnban"])).." days.")
		SendWebhook("BanJoin",
			"Banned user attempted to join.",
			"More info below.", 
			"Event details", 
			"Player name: "..Player.Name.."\nPlayer ID: "..Player.UserId.."\nBan reason: "..keytest["Reason"].." \nBanned on "..keytest["Date"].." at "..keytest["Time"].."\nBanned by @"..keytest["Moderator"].."\nCan Appeal: "..tostring(keytest["Appeals"]["CanAppeal"]).."\nBan Length: "..tostring(keytest["TimeToUnban"]).."\Has Appealed: "..tostring(keytest["Appeals"]["HasAppealed"]))
	end
end
game.Players.PlayerAdded:Connect(PlrAdded)
--catch the leftovers
for i,v in ipairs(game.Players:GetPlayers()) do
	PlrAdded(v)
end

-- Finish API calls
PluginAPI.NewRemoteFunction("BanPlr", "AdvancedBanPro", OnInvoke)
PluginAPI.NewButton("rbxassetid://12177660905", "Bans", script.Bans, "G")

print("========================================================================================")
print("------------------------- PreloadService Ban v1 -------------------------")
print("========================================================================================")
print("                   Plugin is running! Errors will be logged here.                   ")

The actual ban happens a 413 if that’s worth pointing out as well.
I’ve tried everything and I’m not sure. Thanks for any help.

1 Like

We’re going to need a lot more details. First, take out as much code as possible until the error can no longer be reproduced. Most of the code you’ve provided isn’t relevant. Then, explain exactly how your system is supposed to work, and what you’ve tried to fix the errors. Without reading your code, this is probably a DataStore problem or a check is failing somewhere.

Is the system not working, or is it erroring somewhere? If it’s the latter, include the error somewhere.

1 Like

Sorry, my screenshots must have not sent.

On the line highlighted in the post, it’s checking the account age vs when the player should be unbanned, which is determined when the ban is initiated.

If the join date is higher than the join date at the time of the ban + the length, then unban. In the console, for me it was outputting 537-99999, Unbanning! (the account age of my alt, 99999 which I set manually)

As stated in the post, it’s a logic thing. It works, but players are being unbanned too early.

That feels convoluted. I’d use os.time(). This simplifies the logic, gets rid of the API call, and allows for more granularity in ban lengths.

As for why it isn’t working, from the console it looks like it’s subtracting the ban length (537) from your account age (99999) which of course yields less than the ban length. To do the check how you want it, you’d want to subtract their account age from the saved account age, and compare that difference to the ban length. I.e., 99999-537.

1 Like

Sorry for my late response, I’ve been busy.

When the ban check is happening, it’s checking their account age versus the itme to unabn, which is set at the time the ban is decided.

			TimeToUnban = Table[3] + GetPlayerJoinDate(Player),

And although I alreayd said this, this is the check happening when the ban is being read over.

		if Player.AccountAge >= keytest["TimeToUnban"] then
			print(Player.AccountAge.."-"..keytest["TimeToUnban"])
			warn("Unbanning as time has come!")

When they’ve been deemed as banned and are being kicked, it’ll tell them which may have that error.

You'll be unbanned in "..tostring(RemoveNegativeSign(Player.AccountAge - keytest["TimeToUnban"])).." days.")