I made a relatively easy to use system to catch alternate accounts. It’s easy to expand on and you can change values based on your needs.
It uses a table with a couple of functions, each of which returns true
or false
. If it returns true, the heat associated with that function (stored in the key) is added to totalHeat, which is then checked after all functions run.
Some of these functions have negative heat values, which means that they remove from the total heat if they return true. This is especially important because it removes the amount of false cases that occur by just checking account age.
In my opinion, it’s safer and better than using just the player’s account age to determine if they are an alt. This poses a couple problems:
- It isn’t friendly with people with new accounts that aren’t alternate accounts (new roblox players)
- Most people who are serious about using alts have alts that are way older than a month, which is the time that people typically use to determine if a player is an alt.
I want to expand this so it also saves people flagged, and then checks mutual friend groups between all people it flags. I can also add other things like checking their currency (I’m pretty sure we don’t have permissions to use this API, but I’m not sure.),
seeing if any of these people were banned for exploiting, and seeing if any group of these flagged accounts joined at the same time as one another.
Before I do this, though, I want to see if a system like this is practical, or if I should worry this much about alternate accounts at all, which is why I’m posting this here.
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local DataStore = game:GetService("DataStoreService"):GetDataStore("Test")
local checks = { -- key: heat, value: function which takes player as an argument
[-5] = function(player)
local url = ("https://devforum.rprxy.xyz/u/%s.json/"):format(player.Name)
local success, result = pcall(function()
return HttpService:JSONDecode(HttpService:GetAsync(url)).user.trust_level
end)
return success and tonumber(result) >= 1 or false
end,
[-2] = function(player)
return player.MembershipType == Enum.MembershipType.Premium
end,
[2] = function(player)
local url = ("https://friends.rprxy.xyz/v1/users/%s/followers/count")
:format(tostring(player.UserId))
local success, result = pcall(function()
return HttpService:JSONDecode(HttpService:GetAsync(url)).count
end)
return success and result <= 1 or false
end,
[3] = function(player)
local url = ("https://badges.rprxy.xyz/v1/users/%s/badges")
:format(tostring(player.UserId))
local success, result = pcall(function()
return #HttpService:JSONDecode(HttpService:GetAsync(url))
end)
-- broken atm
return false
end,
[4] = function(player)
return player.AccountAge < 32
end,
[5] = function(player)
local url = ("https://friends.rprxy.xyz/v1/users/%s/friends/count")
:format(tostring(player.UserId))
local success, result = pcall(function()
return HttpService:JSONDecode(HttpService:GetAsync(url)).count
end)
return success and result <= 1 or false
end
}
local function checkPlayer(player)
local success, result = xpcall(function()
local data = DataStore:GetAsync(player.UserId)
-- not checked if no entry
return data.previouslyChecked or false
end, function(err)
warn("Unable to get data \n", err)
end)
-- use true to skip it since datastore is down
return result or true
end
Players.PlayerAdded:Connect(function(player)
-- has been checked before
if checkPlayer(player) then return end
local totalHeat = 0
for heat, func in pairs(checks) do
-- if it returns true, add the heat
totalHeat = func(player) and totalHeat + heat or totalHeat
end
if totalHeat >= maxHeat then
-- player is probably an alt, maybe save them in a datastore
-- or report them to a discord channel using a webhook?
end
end)
--// thanks for stopping by
Note: all these values are temporary.