--[[
───────────────────────────────────────────────────────────────
📦 Basic Admin Essentials Plugin — Demote Command
───────────────────────────────────────────────────────────────
Developed and maintained by: Panora Connect LLC
Website: https://panora.cc
This module integrates Panora Connect’s API with Basic Admin Essentials,
allowing workspace administrators to demote users directly in-game using
the official Panora ranking system.
───────────────────────────────────────────────────────────────
⚙️ Configuration Guide
───────────────────────────────────────────────────────────────
The configuration is stored at the top of this script.
You can set the following variables:
• API_KEY → Your Panora API key (from your workspace)
• GROUP_ID → Your Roblox group ID
• COOLDOWN_TIME → Optional; adds cooldown between demotions
• WEBHOOK_URL → Optional; External webhook for demotion logs
• MIN_USERNAME_LENGTH → Optional; minimum username length to demote
• EveryoneAntiAbuse → Optional; prevents non-Everyone rank demotion
Once configured, simply place this plugin inside your Basic Admin
“Plugins” folder. No other dependencies need to be modified.
───────────────────────────────────────────────────────────────
💬 Support & Contact
───────────────────────────────────────────────────────────────
For assistance, please reach out through one of the following:
📧 Email: [email protected]🌐 Live Chat: https://app.panora.cc
💬 Community Discord: https://discord.gg/panora
───────────────────────────────────────────────────────────────
(c) 2025 Panora Connect LLC. All rights reserved.
Unauthorized redistribution or sale of this module is prohibited.
───────────────────────────────────────────────────────────────
]]
local HttpService = game:GetService("HttpService")
local GroupServiceModule = require(95677908346714)
local PanoraAPI = require(111358642560007)
local Plugin = function(...)
local Data = {...}
local remoteEvent = Data[1][1]
local remoteFunction = Data[1][2]
local returnPermissions = Data[1][3]
local Commands = Data[1][4]
local Prefix = Data[1][5]
local actionPrefix = Data[1][6]
local returnPlayers = Data[1][7]
local cleanData = Data[1][8]
local pluginName = script.Name
local pluginPrefix = Prefix
local pluginLevel = 2
local pluginUsage = "<username>"
local pluginDescription = "Demote a user | Panora"
local BASE_URL = "https://api.panora.cc/v1/ranker"
local API_KEY = "PANORA-*********************-API-********************"
local GROUP_ID = 000000
local COOLDOWN_TIME = 0
local WEBHOOK_URL = ""
local MIN_USERNAME_LENGTH = 2
local EveryoneAntiAbuse = true
--─────────────────────────────────────────────────────────────────────────────────────────────────────────
-- ⚠️ DO NOT EDIT PAST THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING. WE DO NOT ASSIST WITH MODIFIED CODES!!
--─────────────────────────────────────────────────────────────────────────────────────────────────────────
local recentDemotions = {}
local function log(tag, ...)
print(string.format("[%s]", tag), ...)
end
local function isPlayer(obj)
return typeof(obj) == "Instance" and obj:IsA("Player")
end
local function safeName(obj)
if isPlayer(obj) then return obj.Name end
return tostring(obj)
end
local function getRolesSafe()
local ok, roles = pcall(function()
return GroupServiceModule.getGroupRoles(GROUP_ID)
end)
if not ok or type(roles) ~= "table" then
return nil
end
return roles
end
local function findPreviousRank(currentRank)
local roles = getRolesSafe()
if not roles then return nil end
local sortable = {}
for _, r in pairs(roles) do
if type(r) == "table" and type(r.Rank) == "number" then
table.insert(sortable, r)
end
end
table.sort(sortable, function(a, b)
return a.Rank < b.Rank
end)
for i = #sortable, 1, -1 do
if sortable[i].Rank < currentRank then
return sortable[i]
end
end
return nil
end
local function processDemotion(player, target)
if not isPlayer(target) then
log("DEBUG", "Invalid target passed to processDemotion:", safeName(target))
return string.format("%s - Skipped (Invalid target)", safeName(target))
end
log("DEMOTE", "Processing", target.Name)
if isPlayer(player) and player.UserId == target.UserId then
log("DEBUG", "Skipped self-demotion for", target.Name)
return string.format("%s - Skipped (Cannot demote self)", target.Name)
end
local last = recentDemotions[target.UserId]
if last and os.time() - last < COOLDOWN_TIME then
local remaining = COOLDOWN_TIME - (os.time() - last)
log("DEBUG", string.format("Cooldown active for %s (%ds left)", target.Name, remaining))
return string.format("%s - Cooldown active (%ds left)", target.Name, remaining)
end
local okRank, currentRank = pcall(function()
return GroupServiceModule.getUserGroupRank(target.UserId, GROUP_ID)
end)
if not okRank then
return string.format("%s - Failed (Could not fetch current rank)", target.Name)
end
local currentRoleName = "Unknown"
local roles = getRolesSafe()
if roles then
for _, role in pairs(roles) do
if role and role.Rank == currentRank then
currentRoleName = role.Name or currentRoleName
break
end
end
end
local prevRank = findPreviousRank(currentRank)
if not prevRank then
log("DEBUG", string.format("%s is already at lowest rank (or roles missing)", target.Name))
return string.format("%s - Already at lowest rank", target.Name)
end
local rankData = {
rankerName = isPlayer(player) and player.Name or "Unknown",
rankerId = isPlayer(player) and player.UserId or 0,
rankeeName = target.Name,
rankeeId = target.UserId,
newRankId = prevRank.Rank,
oldRankName = currentRoleName,
command = "demote | BAE Integration",
prefix = Prefix,
webhookUrl = WEBHOOK_URL,
}
local okApi, success, message = pcall(function()
return PanoraAPI.rankUser(BASE_URL, API_KEY, rankData)
end)
if not okApi then
log("PANORA", "Demotion API call errored for", target.Name)
return string.format("%s - Failed (API error)", target.Name)
end
if success then
recentDemotions[target.UserId] = os.time()
log("PANORA", string.format("Demotion succeeded for %s (%s)", target.Name, tostring(message)))
return string.format("%s - Success", target.Name)
else
log("PANORA", string.format("Demotion failed for %s (%s)", target.Name, tostring(message)))
return string.format("%s - Failed (%s)", target.Name, message or "Unknown error")
end
end
local function processMultiple(player, targets)
if type(targets) ~= "table" then
return "No valid targets."
end
local results = {}
for _, t in pairs(targets) do
if isPlayer(t) then
table.insert(results, processDemotion(player, t))
else
table.insert(results, string.format("%s - Skipped (Invalid target)", safeName(t)))
end
end
local summary = table.concat(results, "\n")
remoteEvent:FireClient(player, summary)
return summary
end
local function pluginFunction(Args)
local player = Args[1]
local rawTarget = Args[3]
if rawTarget == nil then
return "Please provide a username or team prefix."
end
local targetText = tostring(rawTarget)
local lowered = string.lower(targetText)
if EveryoneAntiAbuse and (lowered == "everyone" or lowered == "all" or lowered == "others") then
return "Anti-Abuse setting is enabled. Mass demotions are not allowed."
end
if #targetText < MIN_USERNAME_LENGTH then
return string.format("Username must be at least %d characters long.", MIN_USERNAME_LENGTH)
end
if targetText:sub(1, 1) == "%" then
local teamName = targetText:sub(2)
local team = game.Teams:FindFirstChild(teamName)
local victims = {}
if not team then
for _, t in pairs(game.Teams:GetChildren()) do
if tostring(t.Name):sub(1, 1):lower() == tostring(teamName):sub(1, 1):lower() then
team = t
break
end
end
end
if team then
for _, plr in pairs(game.Players:GetPlayers()) do
if plr.Team == team then
table.insert(victims, plr)
end
end
end
if #victims == 0 then
return "No players found in the specified team."
end
log("DEMOTE", string.format("Team demotion started (%s) - %d users", team.Name, #victims))
return processMultiple(player, victims)
end
local victims
local okResolve, resolveResult = pcall(function()
return returnPlayers(player, targetText)
end)
if okResolve then victims = resolveResult end
if not victims or type(victims) ~= "table" or #victims == 0 then
return "Couldn't find player."
end
return processMultiple(player, victims)
end
return pluginName, pluginFunction, pluginLevel, pluginPrefix, { pluginName, pluginUsage, pluginDescription }
end
return Plugin