From 2264546d153bd459cf39f01007fca4f8f2ecd4e0 Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 12:48:03 -0500 Subject: [PATCH 01/13] add multiple prefix support --- Loader/Config/Settings.luau | 2 +- MainModule/Server/Core/Admin.luau | 39 ++++++++++++++----- MainModule/Server/Core/Commands.luau | 10 ++++- .../Server/Dependencies/DefaultSettings.luau | 20 +++++----- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/Loader/Config/Settings.luau b/Loader/Config/Settings.luau index 51cc435e0f..f0f56a2f93 100644 --- a/Loader/Config/Settings.luau +++ b/Loader/Config/Settings.luau @@ -238,7 +238,7 @@ settings.SaveAdmins = true -- If true anyone you :admin or :headadmin in-game settings.LoadAdminsFromDS = true -- If false, any admins saved in your DataStores will not load settings.WhitelistEnabled = false -- If true enables the whitelist/server lock; Only lets admins & whitelisted users join -settings.Prefix = ":" -- The : in :kill me +settings.Prefix = {";", ":"} -- A list of prefixes for commands, the ; in ;kill me settings.PlayerPrefix = "!" -- The ! in !donate; Mainly used for commands that any player can run; Do not make it the same as settings.Prefix settings.SpecialPrefix = "" -- Used for things like "all", "me" and "others" (If changed to ! you would do :kill !me) settings.SplitKey = " " -- The space in :kill me (eg if you change it to / :kill me would be :kill/me) diff --git a/MainModule/Server/Core/Admin.luau b/MainModule/Server/Core/Admin.luau index 0ad9ff97ae..551cdbaf8f 100644 --- a/MainModule/Server/Core/Admin.luau +++ b/MainModule/Server/Core/Admin.luau @@ -1193,9 +1193,16 @@ return function(Vargs, GetEnv) for ind, data in Commands do if type(data) == "table" then for i,cmd in data.Commands do - if data.Prefix == "" then Admin.BlankPrefix = true end - tempPrefix[data.Prefix] = true - tempTable[string.lower(data.Prefix..cmd)] = ind + if type(data.Prefix) ~= "table" and data.Prefix == "" then Admin.BlankPrefix = true end + if type(data.Prefix) == "table" then + for _,p in data.Prefix do + tempPrefix[p] = true + tempTable[string.lower(p..cmd)] = ind + end + else + tempPrefix[data.Prefix] = true + tempTable[string.lower(data.Prefix..cmd)] = ind + end end end end @@ -1232,13 +1239,27 @@ return function(Vargs, GetEnv) end for _, v in data.Commands do - if not blacklistedCommands["/"..data.Prefix..v] then - if not command1 then - command1 = "/"..data.Prefix..v - else - command2 = "/"..data.Prefix..v + if type(data.Prefix) == "table" then + for _, p in data.Prefix do + if not blacklistedCommands["/"..p..v] then + if not command1 then + command1 = "/"..p..v + else + command2 = "/"..p..v + end + end end + else + if not blacklistedCommands["/"..data.Prefix..v] then + if not command1 then + command1 = "/"..data.Prefix..v + else + command2 = "/"..data.Prefix..v + end + end + end + end if command1 then @@ -1336,7 +1357,7 @@ return function(Vargs, GetEnv) FormatCommand = function(command, cmdn) return table.concat({ - (command.Prefix or ""), + (if type(command.Prefix) == "table" then command.Prefix[1] else command.Prefix or ""), tostring(command.Commands[cmdn or 1]), #command.Args > 0 and Settings.SplitKey or "", #command.Args > 0 and Admin.FormatCommandArguments(command) or "" diff --git a/MainModule/Server/Core/Commands.luau b/MainModule/Server/Core/Commands.luau index c8edb07110..e9b9ad82b8 100644 --- a/MainModule/Server/Core/Commands.luau +++ b/MainModule/Server/Core/Commands.luau @@ -34,7 +34,7 @@ return function(Vargs, GetEnv) t = server.Typechecker; local ValidateCommandDefinition = t.interface({ - Prefix = t.string, + Prefix = t.union(t.string, t.array(t.string)), Commands = t.array(t.string), Description = t.string, AdminLevel = t.union(t.string, t.number, t.nan, t.array(t.union(t.string, t.number, t.nan))), @@ -99,7 +99,13 @@ return function(Vargs, GetEnv) Admin.PrefixCache[cmd.Prefix] = true for _, v in cmd.Commands do - Admin.CommandCache[string.lower(cmd.Prefix..v)] = ind + if type(cmd.Prefix) == "table" then + for _,p in cmd.Prefix do + Admin.CommandCache[string.lower(p..v)] = ind + end + else + Admin.CommandCache[string.lower(cmd.Prefix..v)] = ind + end end cmd.Args = cmd.Args or cmd.Arguments or {} diff --git a/MainModule/Server/Dependencies/DefaultSettings.luau b/MainModule/Server/Dependencies/DefaultSettings.luau index cfc7bb1e61..f0f56a2f93 100644 --- a/MainModule/Server/Dependencies/DefaultSettings.luau +++ b/MainModule/Server/Dependencies/DefaultSettings.luau @@ -140,14 +140,13 @@ settings.HideScript = true -- When the game starts the Adonis_Loader model settings.DataStore = "Adonis_1" -- DataStore the script will use for saving data; Changing this will lose any saved data settings.DataStoreKey = "CHANGE_THIS" -- CHANGE THIS TO ANYTHING RANDOM! Key used to encrypt all datastore entries; Changing this will lose any saved data settings.DataStoreEnabled = true -- Disable if you don't want to load settings and admins from the datastore; PlayerData will still save -settings.LocalDatastore = false -- If this is turned on, a mock DataStore will forcibly be used instead and shall never save across servers +settings.LocalDatastore = false -- If this is turned on, a mock DataStore will forcibly be used instead and shall never save across servers -settings.Storage = game:GetService("ServerStorage") -- Where things like tools are stored -settings.RecursiveTools = false -- Whether tools that are included in sub-containers within settings.Storage will be available via the :give command (useful if your tools are organized into multiple folders) +settings.Storage = game:GetService("ServerStorage") -- Where things like tools are stored +settings.RecursiveTools = false -- Whether tools that are included in sub-containers within settings.Storage will be available via the :give command (useful if your tools are organized into multiple folders) settings.Theme = "Default" -- UI theme; settings.MobileTheme = "Mobilius" -- Theme to use on mobile devices; Some UI elements are disabled -settings.DefaultTheme = "Default" -- Theme to be used as a replacement for "Default". The new replacement theme can still use "Default" as its Base_Theme however any other theme that references "Default" as its redirects to this theme. --[[ **HOW TO ADD ADMINISTRATORS:** @@ -158,7 +157,7 @@ settings.DefaultTheme = "Default" -- Theme to be used as a replacement for "Defa settings.Ranks = { ["Moderators"] = { Level = 100; - Users = {"Username"; "Username:UserId"; UserId; "Group:GroupId:GroupRank"; "Group:GroupId"; "Item:ItemID"; "GamePass:GamePassID";} + Users = {"Username"; "Username:UserId"; UserId; "Group:GroupId:GroupRank"; "Group:GroupId"; "Item:ItemID"; "GamePass:GamePassID"; "Subscription:SubscriptionId";} } } @@ -211,7 +210,6 @@ settings.Aliases = { [":examplealias "] = ":ff | :fling | :fire " --// Order arguments appear in alias string determines their required order in the command message when ran later }; ---// Use the below table to define pre-set cameras settings.Cameras = { --[[ "Camera Name" would be the name of your camera @@ -240,7 +238,7 @@ settings.SaveAdmins = true -- If true anyone you :admin or :headadmin in-game settings.LoadAdminsFromDS = true -- If false, any admins saved in your DataStores will not load settings.WhitelistEnabled = false -- If true enables the whitelist/server lock; Only lets admins & whitelisted users join -settings.Prefix = ":" -- The : in :kill me +settings.Prefix = {";", ":"} -- A list of prefixes for commands, the ; in ;kill me settings.PlayerPrefix = "!" -- The ! in !donate; Mainly used for commands that any player can run; Do not make it the same as settings.Prefix settings.SpecialPrefix = "" -- Used for things like "all", "me" and "others" (If changed to ! you would do :kill !me) settings.SplitKey = " " -- The space in :kill me (eg if you change it to / :kill me would be :kill/me) @@ -295,7 +293,7 @@ settings.ChatCommands = true -- If false you will not be able to run commands settings.CreatorPowers = true -- Gives me creator-level admin; This is strictly used for debugging; I can't debug without full access to the script settings.CodeExecution = true -- Enables the use of code execution in Adonis; Scripting related (such as :s) and a few other commands require this settings.SilentCommandDenials = false -- If true, there will be no differences between the error messages shown when a user enters an invalid command and when they have insufficient permissions for the command -settings.OverrideChatCallbacks = true -- If the TextChatService ShouldDeliverCallbacks of all channels are overridden by Adonis on load. Required for slowmode. Mutes use a CanSend method to mute when this is set to false. +settings.OverrideChatCallbacks = true -- If the TextChatService ShouldDeliverCallbacks of all channels are overridden by Adonis on load. Required for slowmode. Mutes use a CanSend method to mute when this is set to false. settings.BanMessage = "Banned" -- Message shown to banned users upon kick settings.LockMessage = "Not Whitelisted" -- Message shown to people when they are kicked while the game is :slocked @@ -306,6 +304,7 @@ settings.SaveCommandLogs = true -- If command logs are saved to the d settings.Notification = true -- Whether or not to show the "You're an admin" and "Updated" notifications settings.SongHint = true -- Display a hint with the current song name and ID when a song is played via :music settings.TopBarShift = false -- By default hints and notifications will appear from the top edge of the window. Set this to true if you don't want hints/notifications to appear in that region. +settings.DefaultTheme = "Default" -- Theme to be used as a replacement for "Default". The new replacement theme can still use "Default" as its Base_Theme however any other theme that references "Default" as its redirects to this theme. settings.HiddenThemes = {} -- Hide themes from the theme selector tab inside the userpanel. Each theme name must be the specific name such as "Mobilius" settings.Messages = {} -- A list of notifications shown on join. Messages can either be strings or tables. Messages are shown to HeadAdmins+ by default but tables can define a different minimum level via .Level settings.AutoClean = false -- Will auto clean workspace of things like hats and tools @@ -408,7 +407,6 @@ descs.RecursiveTools = [[ Whether tools that are included in sub-containers with descs.Theme = [[ UI theme; ]] descs.MobileTheme = [[ Theme to use on mobile devices; Mobile themes are optimized for smaller screens; Some GUIs are disabled ]] -descs.DefaultTheme = [[ Theme to be used as a replacement for "Default". The new replacement theme can still use "Default" as its Base_Theme however any other theme that references "Default" as its redirects to this theme. ]] descs.Ranks = [[ All admin permission level ranks; ]]; descs.Moderators = [[ Mods; Format: {"Username"; "Username:UserId"; UserId; "Group:GroupId:GroupRank"; "Group:GroupId"; "Item:ItemID";} ]] @@ -481,6 +479,7 @@ descs.Notification = [[ Whether or not to show the "You're an admin" and "Update descs.CodeExecution = [[ Enables the use of code execution in Adonis; Scripting related and a few other commands require this ]] descs.SongHint = [[ Display a hint with the current song name and ID when a song is played via :music ]] descs.TopBarShift = [[ By default hints and notifs will appear from the top edge of the window. Set this to true if you don't want hints/notifications to appear in that region. ]] +descs.DefaultTheme = [[ Theme to be used as a replacement for "Default". The new replacement theme can still use "Default" as its Base_Theme however any other theme that references "Default" as its redirects to this theme. ]] descs.ReJail = [[ If true then when a player rejoins they'll go back into jail. Or if the moderator leaves everybody gets unjailed ]] descs.Messages = [[ A list of notifications shown on join. Messages can either be strings or tables. Messages are shown to HeadAdmins+ by default but tables can define a different minimum level via .Level ]] @@ -530,7 +529,6 @@ order = { " "; "Theme"; "MobileTheme"; - "DefaultTheme"; " "; "Ranks"; " "; @@ -598,6 +596,7 @@ order = { "Notification"; "SongHint"; "TopBarShift"; + "DefaultTheme"; "ReJail"; ""; "AutoClean"; @@ -605,6 +604,7 @@ order = { "AutoBackup"; " "; "PlayerList"; + " "; "Console"; "Console_AdminsOnly"; " "; From 8bb2d5ff7c610a413e66d07abe07f0ae5c12b8fa Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 13:51:11 -0500 Subject: [PATCH 02/13] fox ;cmdinfo --- MainModule/Server/Commands/Players.luau | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MainModule/Server/Commands/Players.luau b/MainModule/Server/Commands/Players.luau index 233703bd1b..d0ee68dece 100644 --- a/MainModule/Server/Commands/Players.luau +++ b/MainModule/Server/Commands/Players.luau @@ -31,11 +31,11 @@ return function(Vargs, env) for _,alias in cmd.Commands do if #cmdAliases >= 4 and (#cmd.Commands - #cmdAliases) ~= 0 then - table.insert(cmdAliases, `and {#cmd.Commands - #cmdAliases} more...`) + table.insert(cmdAliases, `and {#cmd.Commands - #cmdAliases} more...`) break end end - + table.remove(cmdAliases, 1) local permissionDesc = Admin.FormatCommandAdminLevel(cmd) @@ -83,7 +83,7 @@ return function(Vargs, env) local cmd, ind for i, v in Admin.SearchCommands(plr, "all") do for _, p in v.Commands do - if (v.Prefix or "")..string.lower(p) == string.lower(args[1]) then + if (if type(v.Prefix) == "table" then v.Prefix[1] else v.Prefix or "")..string.lower(p) == string.lower(args[1]) then cmd, ind = v, i break end @@ -107,7 +107,7 @@ return function(Vargs, env) Title = "Command Info"; Icon = server.MatIcons.Info; Table = { - {Text = `Prefix: {cmd.Prefix}`, Desc = "Prefix used to run the command"}, + {Text = `Prefix: {if type(cmd.Prefix) =="table" then table.concat(cmd.Prefix, ", ") else cmd.Prefix}`, Desc = "Prefix used to run the command"}, {Text = `Commands: {SanitizeXML(table.concat(cmd.Commands, ", "))}`, Desc = "Valid default aliases for the command"}, {Text = `Arguments: {if cmdArgs == "" then "-" else SanitizeXML(cmdArgs)}`, Desc = "Parameters taken by the command"}, {Text = `Admin Level: {Admin.FormatCommandAdminLevel(cmd)}`, Desc = "Rank required to run the command"}, From cef4bb8dac35b383b31a18d7f7b5d5bfb67ddce4 Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:06:33 -0500 Subject: [PATCH 03/13] Fix player command reference --- MainModule/Server/Commands/Players.luau | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/MainModule/Server/Commands/Players.luau b/MainModule/Server/Commands/Players.luau index d0ee68dece..3ef8f1fb8a 100644 --- a/MainModule/Server/Commands/Players.luau +++ b/MainModule/Server/Commands/Players.luau @@ -150,7 +150,7 @@ return function(Vargs, env) Description = "Shows you the command prefix using the :cmds command"; AdminLevel = "Players"; Function = function(plr: Player, args: {string}) - Functions.Hint(`{Settings.Prefix}cmds`, {plr}) + Functions.Hint(`{if type(Settings.Prefix) == "table" then Settings.Prefix[1] else Settings.Prefix}cmds`, {plr}) end }; @@ -567,16 +567,17 @@ return function(Vargs, env) Description = "Shows you how to use some syntax related things"; AdminLevel = "Players"; Function = function(plr: Player, args: {string}) + local Prefix = if type(Settings.Prefix) == "table" then Settings.Prefix[1] else Settings.Prefix local usage = { ""; "Mouse over things in lists to expand them"; "You can also resize windows by dragging the edges"; ""; - `Put /e in front to silence commands in chat (/e {Settings.Prefix}kill scel) or enable chat command hiding in client settings`; + `Put /e in front to silence commands in chat (/e {Prefix}kill scel) or enable chat command hiding in client settings`; `Player commands can be used by anyone, these commands have {Settings.PlayerPrefix} infront, such as {Settings.PlayerPrefix}info and {Settings.PlayerPrefix}rejoin`; ""; `――――― Player Selectors ―――――`; - `Usage example: {Settings.Prefix}kill {Settings.SpecialPrefix}all (where {Settings.SpecialPrefix}all is the selector)`; + `Usage example: {Prefix}kill {Settings.SpecialPrefix}all (where {Settings.SpecialPrefix}all is the selector)`; `{Settings.SpecialPrefix}me - Yourself`; `{Settings.SpecialPrefix}all - Everyone in the server`; `{Settings.SpecialPrefix}admins - All admins in the server`; @@ -584,29 +585,29 @@ return function(Vargs, env) `{Settings.SpecialPrefix}others - Everyone except yourself`; `{Settings.SpecialPrefix}random - A random person in the server excluding those removed with -SELECTION`; `@USERNAME - Targets a specific player with that exact username Ex: {Settings.Prefix}god @Sceleratis would give a player with the username 'Sceleratis' god powers`; - `#NUM - NUM random players in the server {Settings.Prefix}ff #5 will ff 5 random players excluding those removed with -SELECTION.`; + `#NUM - NUM random players in the server {Prefix}ff #5 will ff 5 random players excluding those removed with -SELECTION.`; `{Settings.SpecialPrefix}friends - Your friends who are in the server`; - `%TEAMNAME - Members of the team TEAMNAME Ex: {Settings.Prefix}kill %raiders`; + `%TEAMNAME - Members of the team TEAMNAME Ex: {Prefix}kill %raiders`; `$GROUPID - Members of the group with ID GROUPID (number in the Roblox group webpage URL)`; - `-SELECTION - Inverts the selection, ie. will remove SELECTION from list of players to run command on. {Settings.Prefix}kill all,-%TEAM will kill everyone except players on TEAM`; - `+SELECTION - Readds the selection, ie. will readd SELECTION from list of players to run command on. {Settings.Prefix}kill all,-%TEAM,+Lethalitics will kill everyone except players on TEAM but also Lethalitics`; - `radius-NUM -- Anyone within a NUM-stud radius of you. {Settings.Prefix}ff radius-5 will ff anyone within a 5-stud radius of you.`; + `-SELECTION - Inverts the selection, ie. will remove SELECTION from list of players to run command on. {Prefix}kill all,-%TEAM will kill everyone except players on TEAM`; + `+SELECTION - Readds the selection, ie. will readd SELECTION from list of players to run command on. {Prefix}kill all,-%TEAM,+Lethalitics will kill everyone except players on TEAM but also Lethalitics`; + `radius-NUM -- Anyone within a NUM-stud radius of you. {Prefix}ff radius-5 will ff anyone within a 5-stud radius of you.`; ""; `――――― Repetition ―――――`; - `Multiple player selections - {Settings.Prefix}kill me,noob1,noob2,{Settings.SpecialPrefix}random,%raiders,$123456,{Settings.SpecialPrefix}nonadmins,-scel`; - `Multiple Commands at a time - {Settings.Prefix}ff me {Settings.BatchKey} {Settings.Prefix}sparkles me {Settings.BatchKey} {Settings.Prefix}rocket jim`; - `You can add a delay if you want; {Settings.Prefix}ff me {Settings.BatchKey} !wait 10 {Settings.BatchKey} {Settings.Prefix}m hi we waited 10 seconds`; + `Multiple player selections - {Prefix}kill me,noob1,noob2,{Settings.SpecialPrefix}random,%raiders,$123456,{Settings.SpecialPrefix}nonadmins,-scel`; + `Multiple Commands at a time - {Prefix}ff me {Settings.BatchKey} {Settings.Prefix}sparkles me {Settings.BatchKey} {Prefix}rocket jim`; + `You can add a delay if you want; {Prefix}ff me {Settings.BatchKey} !wait 10 {Settings.BatchKey} {Prefix}m hi we waited 10 seconds`; `{Settings.Prefix}repeat 10(how many times to run the cmd) 1(how long in between runs) {Settings.Prefix}respawn jim`; ""; `――――― Reference Info ―――――`; - `{Settings.Prefix}cmds for a list of available commands`; - `{Settings.Prefix}cmdinfo <command> for detailed info about a specific command`; + `{Prefix}cmds for a list of available commands`; + `{Prefix}cmdinfo <command> for detailed info about a specific command`; `{Settings.PlayerPrefix}brickcolors for a list of BrickColors`; `{Settings.PlayerPrefix}materials for a list of materials`; ""; - `{Settings.Prefix}capes for a list of preset admin capes`; - `{Settings.Prefix}musiclist for a list of preset audios`; - `{Settings.Prefix}insertlist for a list of insertable assets using {Settings.Prefix}insert`; + `{Prefix}capes for a list of preset admin capes`; + `{Prefix}musiclist for a list of preset audios`; + `{Prefix}insertlist for a list of insertable assets using {Prefix}insert`; } Remote.MakeGui(plr, "List", { Title = "Usage"; @@ -937,7 +938,7 @@ return function(Vargs, env) IsDonor = Admin.CheckDonor(v); GameData = gameData; IsServerOwner = v.UserId == game.PrivateServerOwnerId; - CmdPrefix = Settings.Prefix; + CmdPrefix = Settings.Prefix[1]; CmdSplitKey = Settings.SplitKey; OnlineFriends = Remote.Get(v, "Function", "GetFriendsOnline"); }) From d0a3e668e2bcbdf3fb75891856488585238c176b Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:12:26 -0500 Subject: [PATCH 04/13] fix all players command + new func --- MainModule/Server/Commands/Players.luau | 10 +++++----- MainModule/Server/Core/Functions.luau | 8 +++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MainModule/Server/Commands/Players.luau b/MainModule/Server/Commands/Players.luau index 3ef8f1fb8a..ee7ac02dbd 100644 --- a/MainModule/Server/Commands/Players.luau +++ b/MainModule/Server/Commands/Players.luau @@ -584,7 +584,7 @@ return function(Vargs, env) `{Settings.SpecialPrefix}nonadmins - Non-admins (normal players) in the server`; `{Settings.SpecialPrefix}others - Everyone except yourself`; `{Settings.SpecialPrefix}random - A random person in the server excluding those removed with -SELECTION`; - `@USERNAME - Targets a specific player with that exact username Ex: {Settings.Prefix}god @Sceleratis would give a player with the username 'Sceleratis' god powers`; + `@USERNAME - Targets a specific player with that exact username Ex: {Prefix}god @Sceleratis would give a player with the username 'Sceleratis' god powers`; `#NUM - NUM random players in the server {Prefix}ff #5 will ff 5 random players excluding those removed with -SELECTION.`; `{Settings.SpecialPrefix}friends - Your friends who are in the server`; `%TEAMNAME - Members of the team TEAMNAME Ex: {Prefix}kill %raiders`; @@ -595,9 +595,9 @@ return function(Vargs, env) ""; `――――― Repetition ―――――`; `Multiple player selections - {Prefix}kill me,noob1,noob2,{Settings.SpecialPrefix}random,%raiders,$123456,{Settings.SpecialPrefix}nonadmins,-scel`; - `Multiple Commands at a time - {Prefix}ff me {Settings.BatchKey} {Settings.Prefix}sparkles me {Settings.BatchKey} {Prefix}rocket jim`; + `Multiple Commands at a time - {Prefix}ff me {Settings.BatchKey} {Prefix}sparkles me {Settings.BatchKey} {Prefix}rocket jim`; `You can add a delay if you want; {Prefix}ff me {Settings.BatchKey} !wait 10 {Settings.BatchKey} {Prefix}m hi we waited 10 seconds`; - `{Settings.Prefix}repeat 10(how many times to run the cmd) 1(how long in between runs) {Settings.Prefix}respawn jim`; + `{Prefix}repeat 10(how many times to run the cmd) 1(how long in between runs) {Prefix}respawn jim`; ""; `――――― Reference Info ―――――`; `{Prefix}cmds for a list of available commands`; @@ -938,7 +938,7 @@ return function(Vargs, env) IsDonor = Admin.CheckDonor(v); GameData = gameData; IsServerOwner = v.UserId == game.PrivateServerOwnerId; - CmdPrefix = Settings.Prefix[1]; + CmdPrefix = Functions.GetMainPrefix(); CmdSplitKey = Settings.SplitKey; OnlineFriends = Remote.Get(v, "Function", "GetFriendsOnline"); }) @@ -1019,7 +1019,7 @@ return function(Vargs, env) ServerAge = service.FormatTime(os.time() - server.ServerStartTime); ServerInternetInfo = serverInfo; Refreshables = Logs.ListUpdaters.ServerDetails(plr); - CmdPrefix = Settings.Prefix; + CmdPrefix = if type(Settings.Prefix) == "table" then Settings.Prefix[1] else Settings.Prefix, CmdPlayerPrefix = Settings.PlayerPrefix; SplitKey = Settings.SplitKey; }) diff --git a/MainModule/Server/Core/Functions.luau b/MainModule/Server/Core/Functions.luau index e81407b0e3..f137444a85 100644 --- a/MainModule/Server/Core/Functions.luau +++ b/MainModule/Server/Core/Functions.luau @@ -1664,5 +1664,11 @@ return function(Vargs, GetEnv) end return if allowNil then nil else BrickColor.random() end; - }; + + GetMainPrefix = function() + if type(Settings.Prefix) == "table" then return Settings.Prefix[1] else return Settings.Prefix end + end; + } + + end From 51a7b292417acf20eabeabc961305fc43605f77a Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:20:03 -0500 Subject: [PATCH 05/13] hacky metatable patch --- MainModule/Server/Server.luau | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MainModule/Server/Server.luau b/MainModule/Server/Server.luau index 486f3ca2ae..8fa9d74b23 100644 --- a/MainModule/Server/Server.luau +++ b/MainModule/Server/Server.luau @@ -597,6 +597,17 @@ return service.NewProxy({ end end + --// Attempts to patch Settings.Prefix to fix issues + if type(server.Settings.Prefix == "table") then + setmetatable(server.Settings.Prefix, { + __concat = function(self, value) + return self[1] + end, + }) + end + + + --// Bind cleanup service.DataModel:BindToClose(function(...) server.CleanUp(...) From f51eb93052a359501fa4d44fd97bda67b90a5604 Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:23:13 -0500 Subject: [PATCH 06/13] metatables more like magictables --- MainModule/Server/Server.luau | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MainModule/Server/Server.luau b/MainModule/Server/Server.luau index 8fa9d74b23..fe3e58a58e 100644 --- a/MainModule/Server/Server.luau +++ b/MainModule/Server/Server.luau @@ -603,11 +603,16 @@ return service.NewProxy({ __concat = function(self, value) return self[1] end, + __tostring = function(self) + return self[1] + end, + }) end + --// Bind cleanup service.DataModel:BindToClose(function(...) server.CleanUp(...) From 324e012321ae7e62b8a10f903fb11a9c0ba8a382 Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:24:25 -0500 Subject: [PATCH 07/13] oops --- MainModule/Server/Server.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MainModule/Server/Server.luau b/MainModule/Server/Server.luau index fe3e58a58e..393b3ea1aa 100644 --- a/MainModule/Server/Server.luau +++ b/MainModule/Server/Server.luau @@ -598,7 +598,7 @@ return service.NewProxy({ end --// Attempts to patch Settings.Prefix to fix issues - if type(server.Settings.Prefix == "table") then + if type(server.Settings.Prefix) == "table" then setmetatable(server.Settings.Prefix, { __concat = function(self, value) return self[1] From 640cbc6b1ab96c8440d7c615fc3600428472557a Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:27:15 -0500 Subject: [PATCH 08/13] fix some moderator commands --- MainModule/Server/Commands/Moderators.luau | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MainModule/Server/Commands/Moderators.luau b/MainModule/Server/Commands/Moderators.luau index 95b0420985..41690e9f5b 100644 --- a/MainModule/Server/Commands/Moderators.luau +++ b/MainModule/Server/Commands/Moderators.luau @@ -292,7 +292,7 @@ return function(Vargs, env) Commands = {"cn", "customsmallmessage", "cnmessage"}; Args = {"title", "message"}; Filter = true; - Description = `Same as {Settings.Prefix}n but says whatever you want the title to be instead of your name.`; + Description = `Same as {Functions.GetMainPrefix()}n but says whatever you want the title to be instead of your name.`; AdminLevel = "Moderators"; Function = function(plr: Player, args: {string}) Functions.Notify(service.BroadcastFilter(assert(args[1], "Missing title"), plr), service.BroadcastFilter(assert(args[2], "Missing message") , plr), service.GetPlayers()) @@ -377,7 +377,7 @@ return function(Vargs, env) Remote.RemoveGui(v, "Notify") Functions.Notify(`Warning from {service.FormatPlayer(plr)}`, reason, {v}) - Functions.Notification("Notification", `Warned {service.FormatPlayer(v)}`, {plr}, 5, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}warnings {v.Name}')`)) + Functions.Notification("Notification", `Warned {service.FormatPlayer(v)}`, {plr}, 5, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Functions.GetMainPrefix()}warnings {v.Name}')`)) end end end @@ -416,7 +416,7 @@ return function(Vargs, env) Core.CrossServer("RemovePlayer", v.Name, `Warning from {service.FormatPlayer(plr)}`, reason) end - Functions.Notification("Notification", `Kick-warned {service.FormatPlayer(v)}`, {plr}, 5, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}warnings {v.Name}')`)) + Functions.Notification("Notification", `Kick-warned {service.FormatPlayer(v)}`, {plr}, 5, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Functions.GetMainPrefix()}warnings {v.Name}')`)) end end end @@ -725,7 +725,7 @@ return function(Vargs, env) for k, t in v.Backpack:GetChildren() do t.Parent = tools end - Admin.RunCommand(`{Settings.Prefix}name`, v.Name, `-AFK-_{service.FormatPlayer(v)}_-AFK-`) + Admin.RunCommand(`{Functions.GetMainPrefix()}name`, v.Name, `-AFK-_{service.FormatPlayer(v)}_-AFK-`) local torso = v.Character.HumanoidRootPart local pos = torso.CFrame local running=true @@ -738,7 +738,7 @@ return function(Vargs, env) for k, t in tools:GetChildren() do t.Parent = v.Backpack end - Admin.RunCommand(`{Settings.Prefix}unname`, v.Name) + Admin.RunCommand(`{Functions.GetMainPrefix()}unname`, v.Name) event:Disconnect() end) repeat torso.CFrame = pos wait() until not v or not v.Character or not torso or not running or not torso.Parent @@ -811,7 +811,7 @@ return function(Vargs, env) Prefix = Settings.Prefix; Commands = {"fullgod", "totalgod"}; Args = {"player"}; - Description = `Same as {server.Settings.Prefix}god, but also provides blast protection`; + Description = `Same as {Functions.GetMainPrefix()}god, but also provides blast protection`; AdminLevel = "Moderators"; Function = function(plr: Player, args: {string}) for _, v in service.GetPlayers(plr, args[1]) do @@ -1243,7 +1243,7 @@ return function(Vargs, env) TitleButtons = { { Text = ""; - OnClick = Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}tools')`); + OnClick = Core.Bytecode(`client.Remote.Send('ProcessCommand','{Functions.GetMainPrefix()}tools')`); Children = { { Class = "ImageLabel"; @@ -1594,7 +1594,7 @@ return function(Vargs, env) local command = args[3] local name = string.lower(plr.Name) assert(command, "Missing command name to repeat") - if string.lower(string.sub(command, 1, #Settings.Prefix+string.len("repeat"))) == string.lower(`{Settings.Prefix}repeat`) or string.sub(command, 1, #Settings.Prefix+string.len("loop")) == string.lower(`{Settings.Prefix}loop`) or string.find(command, `^{Settings.Prefix}loop`) or string.find(command, `^{Settings.Prefix}repeat`) then + if string.lower(string.sub(command, 1, #Functions.GetMainPrefix()+string.len("repeat"))) == string.lower(`{Settings.Prefix}repeat`) or string.sub(command, 1, #Functions.GetMainPrefix()+string.len("loop")) == string.lower(`{Settings.Prefix}loop`) or string.find(command, `^{Settings.Prefix}loop`) or string.find(command, `^{Settings.Prefix}repeat`) then error("Cannot repeat the loop command in a loop command") return end From c14a9f3035cc20a40c4336d9a5edcb7c9d2f202d Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:30:36 -0500 Subject: [PATCH 09/13] fix all moderator commands --- MainModule/Server/Commands/Moderators.luau | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MainModule/Server/Commands/Moderators.luau b/MainModule/Server/Commands/Moderators.luau index 41690e9f5b..1dff5e8411 100644 --- a/MainModule/Server/Commands/Moderators.luau +++ b/MainModule/Server/Commands/Moderators.luau @@ -2116,7 +2116,7 @@ return function(Vargs, env) local data = { Tools = {}; SavedTools = {}; - Prefix = Settings.Prefix; + Prefix = Functions.GetMainPrefix(); SplitKey = Settings.SplitKey; SpecialPrefix = Settings.SpecialPrefix; } @@ -4468,7 +4468,7 @@ return function(Vargs, env) AdminLevel = "Moderators"; Function = function(plr: Player, args: {[number]:string}) Remote.MakeGui(plr, "Teams", { - CmdPrefix = Settings.Prefix; CmdPlayerPrefix = Settings.PlayerPrefix; CmdSpecialPrefix = Settings.SpecialPrefix; CmdSplitKey = Settings.SplitKey; + CmdPrefix = Functions.GetMainPrefix(); CmdPlayerPrefix = Settings.PlayerPrefix; CmdSpecialPrefix = Settings.SpecialPrefix; CmdSplitKey = Settings.SplitKey; }) end }; From 66dc8caba78222d3f0906fbece95158595079cf1 Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:34:58 -0500 Subject: [PATCH 10/13] fix all commands --- MainModule/Server/Commands/Admins.luau | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MainModule/Server/Commands/Admins.luau b/MainModule/Server/Commands/Admins.luau index f3312c057b..150654bf7e 100644 --- a/MainModule/Server/Commands/Admins.luau +++ b/MainModule/Server/Commands/Admins.luau @@ -684,7 +684,7 @@ return function(Vargs, env) Functions.Hint(string.format("Team '%s' already exists!", teamName), {plr}) return; end - + service.New("Team", { Parent = service.Teams; Name = teamName; @@ -1430,7 +1430,7 @@ return function(Vargs, env) CanBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.ServerBan.AdminLevel); CanTimeBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.TimeBan.AdminLevel); CanPermBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.PermanentBan.AdminLevel); - Prefix = Settings.Prefix; + Prefix = Functions.GetMainPrefix() }) end, }; From fe80179205c6986c12dbc3c8274ff551210a7c0b Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:41:41 -0500 Subject: [PATCH 11/13] fix admin core --- MainModule/Server/Commands/Admins.luau | 2939 ++++++++++++------------ 1 file changed, 1530 insertions(+), 1409 deletions(-) diff --git a/MainModule/Server/Commands/Admins.luau b/MainModule/Server/Commands/Admins.luau index 150654bf7e..c38d42b7ed 100644 --- a/MainModule/Server/Commands/Admins.luau +++ b/MainModule/Server/Commands/Admins.luau @@ -1,1598 +1,1719 @@ ---!nocheck -return function(Vargs, env) +server = nil +service = nil +Routine = nil +GetEnv = nil +origEnv = nil +logError = nil + +--// Admin +return function(Vargs, GetEnv) + local env = GetEnv(nil, {script = script}) + setfenv(1, env) + local server = Vargs.Server; local service = Vargs.Service; - local Settings = server.Settings - local Functions, Commands, Admin, Anti, Core, HTTP, Logs, Remote, Process, Variables, Deps = - server.Functions, server.Commands, server.Admin, server.Anti, server.Core, server.HTTP, server.Logs, server.Remote, server.Process, server.Variables, server.Deps - - if env then setfenv(1, env) end - - local Routine = env.Routine - - return { - SetRank = { - Prefix = Settings.Prefix; - Commands = {"setrank", "permrank", "permsetrank"}; - Args = {"player/user", "rank"}; - Description = "Sets the admin rank of the target user(s); THIS SAVES!"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - assert(args[1], "Missing target user (argument #1)") - local rankName = assert(args[2], "Missing rank name (argument #2)") - - local newRank = Settings.Ranks[rankName] - if not newRank then - for thisRankName, thisRank in Settings.Ranks do - if thisRankName:lower() == rankName:lower() then - rankName = thisRankName - newRank = thisRank - break + local Functions, Admin, Anti, Core, HTTP, Logs, Remote, Process, Variables, Settings, Commands + local AddLog, TrackTask, Defaults + local CreatorId = game.CreatorType == Enum.CreatorType.User and game.CreatorId or service.GetGroupCreatorId(game.CreatorId) + local function Init() + Functions = server.Functions; + Admin = server.Admin; + Anti = server.Anti; + Core = server.Core; + HTTP = server.HTTP; + Logs = server.Logs; + Remote = server.Remote; + Process = server.Process; + Variables = server.Variables; + Settings = server.Settings; + Commands = server.Commands; + Defaults = server.Defaults + + TrackTask = service.TrackTask + AddLog = Logs.AddLog; + + TrackTask("Thread: ChatServiceHandler", function() + + --// Support for modern TextChatService + if service.TextChatService and service.TextChatService.ChatVersion == Enum.ChatVersion.TextChatService then + local function onNewTextchannel(textchannel: TextChannel) + AddLog("Script", `Connected to TextChannel: {textchannel.Name}`) + + if Settings.OverrideChatCallbacks ~= false then --// Default to "on" this for all games + AddLog("Script", "Overriding ShouldDeliverCallback for " .. textchannel.Name) + textchannel.ShouldDeliverCallback = function(chatMessage, textSource) + if + chatMessage.Status == Enum.TextChatMessageStatus.Success + or chatMessage.Status == Enum.TextChatMessageStatus.Sending + then + local SenderId = chatMessage.TextSource.UserId + local SenderPlayer = service.Players:GetPlayerByUserId(SenderId) + local Receiver = service.Players:GetPlayerByUserId(textSource.UserId) + local slowCache = Admin.SlowCache + + local IsOriginalSender = SenderPlayer == Receiver + + if not SenderPlayer then + return true + elseif Admin.DoHideChatCmd(SenderPlayer, chatMessage.Text) then -- // Hide chat commands? + return false + elseif Admin.IsMuted(SenderPlayer) then -- // Mute handler + if IsOriginalSender then + server.Remote.Send(SenderPlayer, "Function", "DisplaySystemMessageInTextChat", nil, `[Adonis Chat]: You are muted! Other players cannot see your messages.`) + end + + return false + elseif Admin.SlowMode and not Admin.CheckAdmin(SenderPlayer) and slowCache[SenderPlayer] and os.time() - slowCache[SenderPlayer] < Admin.SlowMode then + if IsOriginalSender then --// Only show this for the person sending! Hide for others, however + --Functions.Notification("You are chatting too fast!", string.format("[Adonis] :: Slow mode enabled! (%g second(s) remaining)", Admin.SlowMode - (os.time() - slowCache[SenderPlayer])), {SenderPlayer}, 10) + + server.Remote.Send(SenderPlayer, "Function", "DisplaySystemMessageInTextChat", nil, `[Adonis Chat]: You are sending messages too fast! {string.format("(%g second(s) remaining)", Admin.SlowMode - (os.time() - slowCache[SenderPlayer]))}`) + end + + return false + end + + if Variables.DisguiseBindings[SenderId] then -- // Disguise command handler + chatMessage.PrefixText = Variables.DisguiseBindings[SenderId].TargetUsername..":" + end + + if Admin.SlowMode and IsOriginalSender then + slowCache[SenderPlayer] = os.time() + end + end + + return true end - end - end - assert(newRank, `No rank named '{rankName}' exists`) - - local newLevel = newRank.Level - local senderLevel = data.PlayerData.Level - - assert(newLevel < senderLevel, string.format("Rank level (%s) cannot be equal to or above your own level (%s)", newLevel, senderLevel)) - local v = Functions.GetPlayers(plr, args[1], {NoFakePlayer = false}) - for _, p in v do - if senderLevel > Admin.GetLevel(p) then - Admin.AddAdmin(p, rankName) - Functions.LogAdminAction(plr, "Set Rank", p.Name, `New rank: {rankName}, New level: {newLevel}`) - Functions.Notification( - "Notification", - `You are {if string.lower(string.sub(rankName, 1, 3)) == "the" then "" elseif string.match(rankName, "^[AEIOUaeiou]") and string.lower(string.sub(rankName, 1, 3)) ~= "uni" then "an " else "a "}{rankName}. Click to view commands.`, - {p}, 10, "MatIcon://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}cmds')`) - ) - Functions.Hint(`{service.FormatPlayer(p, true)} is now rank {rankName} (Permission Level: {newLevel})`, {plr}) else - Functions.Hint(`You do not have permission to set the rank of {service.FormatPlayer(p, true)}`, {plr}) + AddLog("Script", `Using the 'CanSend' method of handling chat connectivity in channel {textchannel.Name}`) + server.Variables.TextChatSpeakers = {} + local function AddUserToTextChatSpeakers(player: Player, speaker: TextSource) + if not server.Variables.TextChatSpeakers[player] then + server.Variables.TextChatSpeakers[player] = {} + end + table.insert(server.Variables.TextChatSpeakers[player], speaker) + --// Check if the player is muted or not + speaker:SetAttribute("OriginalCanSend", speaker.CanSend) + if server.Admin.IsMuted(player) then + speaker.CanSend = false + end + end + local function SpeakerAdded(speaker: TextSource) + if speaker.UserId and speaker.UserId > 0 then + local Player = service.Players:GetPlayerByUserId(speaker.UserId) + if Player then + AddUserToTextChatSpeakers(Player, speaker) + end + end + end + local function SpeakerRemoved(speaker: TextSource) + if speaker.UserId and speaker.UserId > 0 then + local Player = service.Players:GetPlayerByUserId(speaker.UserId) + local Tab = server.Variables.TextChatSpeakers[Player] + if Tab then + local index = table.find(Tab, speaker) + while index do + table.remove(Tab, index) + index = table.find(Tab, speaker) + end + task.defer(function() + if #Tab == 0 then + server.Variables.TextChatSpeakers[Player] = nil + end + end) + end + end + end + + textchannel.ChildAdded:Connect(function(textSource) + if textSource:IsA("TextSource") then + SpeakerAdded(textSource) + end + end) + + textchannel.ChildRemoved:Connect(function(textSource) + if textSource:IsA("TextSource") then + SpeakerRemoved(textSource) + end + end) + + for _,inst in textchannel:GetChildren() do + if inst:IsA("TextSource") then + SpeakerAdded(inst) + end + end + end end - end; - }; - - SetTempRank = { - Prefix = Settings.Prefix; - Commands = {"settemprank", "temprank", "tempsetrank"}; - Args = {"player", "rank"}; - Description = `Identical to {Settings.Prefix}setrank, but doesn't save`; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - assert(args[1], "Missing target player (argument #1)") - local rankName = assert(args[2], "Missing rank name (argument #2)") - - local newRank = Settings.Ranks[rankName] - if not newRank then - for thisRankName, thisRank in Settings.Ranks do - if thisRankName:lower() == rankName:lower() then - rankName = thisRankName - newRank = thisRank - break + + --// Only set this up once + --// This is for commands to tell us when a player should be muted + if not Settings.OverrideChatCallbacks then + service.Events.PlayerMuted:Connect(function(data) + local PlayerId = data.Target; + local ModId = data.Moderator; + + local Player = service.Players:GetPlayerByUserId(PlayerId) + --// Loop through CanSend of a speaker + for _,speakers : TextSource in if Player then server.Variables.TextChatSpeakers[Player] or {} else {} do + speakers.CanSend = false end - end + if Player then + AddLog("Script", `Muted player {Player.Name}:{Player.UserId} using CanSend method`) + end + end) + service.Events.PlayerUnMuted:Connect(function(data) + local PlayerId = data.Target; + local ModId = data.Moderator; + + local Player = service.Players:GetPlayerByUserId(PlayerId) + --// Loop through CanSend of a speaker + for _,speakers : TextSource in if Player then server.Variables.TextChatSpeakers[Player] or {} else {} do + local original = speakers:GetAttribute("OriginalCanSend") + speakers.CanSend = if original ~= nil then original else true + end + if Player then + AddLog("Script", `UnMuted player {Player.Name}:{Player.UserId} via CanSend method`) + end + end) + service.Events.MutedPlayerChat_UnFiltered:Connect(function(p, ...) + server.Remote.Send(p, "Function", "DisplaySystemMessageInTextChat", nil, `[Adonis Chat]: You are muted! Other players cannot see your messages.`) + end) end - assert(newRank, `No rank named '{rankName}' exists`) - local newLevel = newRank.Level - local senderLevel = data.PlayerData.Level - assert(newLevel < senderLevel, string.format("Rank level (%s) cannot be equal to or above your own level (%s)", newLevel, senderLevel)) + local function onTextChannelsAdded(textChannels) + textChannels.ChildAdded:Connect(function(child) + if child:IsA("TextChannel") then + task.spawn(onNewTextchannel, child) + end + end) - for _, v in service.GetPlayers(plr, args[1]) do - if senderLevel > Admin.GetLevel(v) then - Admin.AddAdmin(v, rankName, true) - Functions.LogAdminAction(plr, "Set Temporary Rank", v.Name, `Temporary rank set to: {rankName}`) - Functions.Notification("Notification", `You are a temp {rankName}. Click to view commands.`, {v}, 10, "MatIcon://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}cmds')`)) - Functions.Hint(`{service.FormatPlayer(v, true)} is now rank {rankName} (Permission Level: {newLevel})`, {plr}) - else - Functions.Hint(`You do not have permission to set the rank of {service.FormatPlayer(v, true)}`, {plr}) + for _, v in textChannels:GetChildren() do + if v:IsA("TextChannel") then + task.spawn(onNewTextchannel, v) + end end end - end; - }; - - SetLevel = { - Prefix = Settings.Prefix; - Commands = {"setlevel", "setadminlevel"}; - Args = {"player", "level"}; - Description = "Sets the target player(s) permission level for the current server; does not save"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - local senderLevel = data.PlayerData.Level - local newLevel = assert(tonumber(args[2]), "Level must be a number") - - assert(newLevel < senderLevel, `Level cannot be equal to or above your own permission level ({senderLevel})`); - - for _, v in service.GetPlayers(plr, args[1])do - if senderLevel > Admin.GetLevel(v) then - Admin.SetLevel(v, newLevel, args[3] == "true") - Functions.LogAdminAction(plr, "Set Level", v.Name, `New level: {newLevel}`) - Functions.Notification("Notification", `Your admin permission level was set to {newLevel} for this server only. Click to view commands.`, {v}, 10, "MatIcon://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}cmds')`)) - Functions.Hint(`{service.FormatPlayer(v, true)} is now permission level {newLevel}`, {plr}) - else - Functions.Hint(`You do not have permission to set the permission level of {service.FormatPlayer(v, true)}`, {plr}) + + service.TextChatService.ChildAdded:Connect(function(child) + if child.Name == "TextChannels" then + task.spawn(onTextChannelsAdded, child) end + end) + + if service.TextChatService:FindFirstChild("TextChannels") then + task.spawn(pcall, onTextChannelsAdded, service.TextChatService:FindFirstChild("TextChannels")) end - end; - }; - - UnAdmin = { - Prefix = Settings.Prefix; - Commands = {"unadmin", "unmod", "unowner", "unpadmin", "unheadadmin", "unrank"}; - Args = {"player/user / list entry", "temp? (true/false) (default: false)"}; - Description = "Removes admin/moderator ranks from the target player(s); saves unless is 'true'"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - local target = assert(args[1], "Missing target user (argument #1)") - local temp = args[2] and args[2]:lower() == "true" - local senderLevel = data.PlayerData.Level - local userFound = false - - if not string.find(target, ":") then - for _, v in service.GetPlayers(plr, target, { - UseFakePlayer = true; - DontError = true; - }) - do - userFound = true - local targLevel, targRank = Admin.GetLevel(v) - if targLevel > 0 then - if senderLevel > targLevel then - Admin.RemoveAdmin(v, temp) - Functions.LogAdminAction(plr, "Remove Admin", v.Name, `Temporary: {temp}`) - Functions.Hint(string.format("Removed %s from rank %s", service.FormatPlayer(v, true), targRank or "[unknown rank]"), {plr}) - Functions.Notification("Notification", `You are no longer a(n) {targRank or "admin"}`, {v}, 10, "MatIcon://Shield") - else - Functions.Hint(`You do not have permission to remove {service.FormatPlayer(v, true)}'s rank`, {plr}) - end - else - Functions.Hint(`{service.FormatPlayer(v, true)} does not already have any rank to remove`, {plr}) - end + + AddLog("Script", "TextChatService Handler Loaded") + end + + --// Support for legacy Lua chat system + --// ChatService mute handler (credit to Coasterteam) + AddLog("Script", "Starting loading of legacy chatservice handler") + local chatService = Functions.GetChatService(300) + if chatService then + chatService:RegisterProcessCommandsFunction("ADONIS_CMD", function(speakerName, message) + local speaker = chatService:GetSpeaker(speakerName) + local speakerPlayer = speaker and speaker:GetPlayer() + + if not speakerPlayer then + return false end - if userFound then - return - else - Functions.Hint("User not found in server; searching datastore", {plr}) + if Admin.DoHideChatCmd(speakerPlayer, message) then + return true end - end - for rankName, rankData in Settings.Ranks do - if senderLevel <= rankData.Level then - continue + return false + end) + + chatService:RegisterProcessCommandsFunction("ADONIS_MUTE_SERVER", function(speakerName, _, channelName) + local slowCache = Admin.SlowCache + + local speaker = chatService:GetSpeaker(speakerName) + local speakerPlayer = speaker and speaker:GetPlayer() + + if not speakerPlayer then + return false end - for i, user in rankData.Users do - if not (user:lower() == target:lower() or user:lower():match(`^{target:lower()}:`) or Admin.DoCheck(target, user)) then - continue - end - if - Remote.GetGui(plr, "YesNoPrompt", { - Question = `Remove '{user}' from '{rankName}'?`; - }) == "Yes" - then - table.remove(rankData.Users, i) - if not temp and Settings.SaveAdmins then - service.TrackTask("Thread: RemoveAdmin", Core.DoSave, false, { - Type = "TableRemove"; - Table = {"Settings", "Ranks", rankName, "Users"}; - Value = user; - }); - Functions.Hint(`Removed entry '{user}' from {rankName}`, {plr}) - Logs:AddLog("Script", `{plr} removed {user} from {rankName}`) - end - end - userFound = true + + if speakerPlayer and Admin.IsMuted(speakerPlayer) then + speaker:SendSystemMessage("[Adonis] :: You are muted!", channelName) + return true + elseif speakerPlayer and Admin.SlowMode and not Admin.CheckAdmin(speakerPlayer) and slowCache[speakerPlayer] and os.time() - slowCache[speakerPlayer] < Admin.SlowMode then + speaker:SendSystemMessage(string.format("[Adonis] :: Slow mode enabled! (%g second(s) remaining)", Admin.SlowMode - (os.time() - slowCache[speakerPlayer])), channelName) + return true end - end - assert(userFound, `No table entries matching '{args[1]}' were found`) - end - }; - - TempUnAdmin = { - Prefix = Settings.Prefix; - Commands = {"tempunadmin", "untempadmin", "tunadmin", "untadmin"}; - Args = {"player"}; - Description = "Removes the target players' admin powers for this server; does not save"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - local senderLevel = data.PlayerData.Level - - for _, v in service.GetPlayers(plr, assert(args[1], "Missing target player (argument #1)")) do - local targetLevel = Admin.GetLevel(v) - if targetLevel > 0 then - if senderLevel > targetLevel then - Admin.RemoveAdmin(v, true) - Functions.LogAdminAction(plr, "Temporary Unadmin", v.Name, "Admin powers temporarily removed") - Functions.Hint(`Removed {service.FormatPlayer(v)}'s admin powers`, {plr}) - Functions.Notification("Notification", "Your admin powers have been temporarily removed", {v}, 10, "MatIcons://Remove moderator") - else - Functions.Hint(`You do not have permission to remove {service.FormatPlayer(v, true)}'s admin powers`, {plr}) - end - else - Functions.Hint(`{service.FormatPlayer(v, true)} is not an admin`, {plr}) + + if Admin.SlowMode then + slowCache[speakerPlayer] = os.time() end - end + + return false + end) + + AddLog("Script", "ChatService Handler Loaded") + elseif chatService == false then + AddLog("Script", "Using TextChatService; Handler Loaded") + else + warn("Place is missing ChatService; Vanilla Roblox chat related features may not work") + AddLog("Script", "ChatService Handler Not Found") end - }; - - TempModerator = { - Prefix = Settings.Prefix; - Commands = {"tempmod", "tmod", "tempmoderator", "tmoderator"}; - Args = {"player"}; - Description = "Makes the target player(s) a temporary moderator; does not save"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - local senderLevel = data.PlayerData.Level - - for _, v in service.GetPlayers(plr, assert(args[1], "Missing target player (argument #1)")) do - if senderLevel > Admin.GetLevel(v) then - Admin.AddAdmin(v, "Moderators", true) - Functions.LogAdminAction(plr, "Temporary Moderator", v.Name, "N/A") - Functions.Notification("Notification", "You are a temp moderator. Click to view commands.", {v}, 10, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}cmds')`)) - Functions.Hint(`{service.FormatPlayer(v, true)} is now a temp moderator`, {plr}) - else - Functions.Hint(`{service.FormatPlayer(v, true)} is already the same admin level as you or higher`, {plr}) + end) + + --// Make sure the default ranks are always present for compatability with existing commands + local Ranks = Settings.Ranks + for rank, data in Defaults.Settings.Ranks do + if not Ranks[rank] then + for r, d in Ranks do + if d.Level == data.Level then + data.Hidden = true + break end end + Ranks[rank] = data end - }; + end - Moderator = { - Prefix = Settings.Prefix; - Commands = {"permmod", "pmod", "mod", "moderator", "pmoderator"}; - Args = {"player/user"}; - Description = "Makes the target player(s) a moderator; saves"; - AdminLevel = "Admins"; - Dangerous = true; - Function = function(plr: Player, args: {string}, data: {any}) - local senderLevel = data.PlayerData.Level + if Settings.CustomRanks then + local Ranks = Settings.Ranks + for name, users in Settings.CustomRanks do + if not Ranks[name] then + Ranks[name] = { + Level = 1; + Users = users; + }; + end + end + end - for _, v in service.GetPlayers(plr, assert(args[1], "Missing target player (argument #1)"), { - UseFakePlayer = true; - }) - do - if senderLevel > Admin.GetLevel(v) then - Admin.AddAdmin(v, "Moderators") - Functions.LogAdminAction(plr, "Promoted to Moderator", v.Name, "N/A") - Functions.Notification("Notification", "You are a moderator. Click to view commands.", {v}, 10, "MatIcons://Shield", Core.Bytecode(`client.Remote.Send('ProcessCommand','{Settings.Prefix}cmds')`)) - Functions.Hint(`{service.FormatPlayer(v, true)} is now a moderator`, {plr}) - else - Functions.Hint(`{service.FormatPlayer(v, true)} is already the same admin level as you or higher`, {plr}) + if Settings.CommandCooldowns then + for cmdName, cooldownData in Settings.CommandCooldowns do + local realCmd = Admin.GetCommand(cmdName) + + if realCmd then + if cooldownData.Player then + realCmd.PlayerCooldown = cooldownData.Player + end + + if cooldownData.Server then + realCmd.ServerCooldown = cooldownData.Server + end + + if cooldownData.Cross then + realCmd.CrossCooldown = cooldownData.Cross end end end - }; - - Broadcast = { - Prefix = Settings.Prefix; - Commands = {"broadcast", "bc"}; - Args = {"Message"}; - Filter = true; - Description = "Makes a message in the chat window"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers() do - if service.TextChatService and service.TextChatService.ChatVersion == Enum.ChatVersion.TextChatService then - local TextToUse = args[1] - if data.Options.Chat ~= true then - TextToUse = service.SanitizeXML(args[1] or "Hello world!") - end - Remote.Send( - v, "Function", "DisplaySystemMessageInTextChat", nil, `{ - string.format(`[%s] %s`, Settings.SystemTitle, service.Filter(TextToUse), plr, v) - }`) - else - Remote.Send(v, "Function", "ChatMessage", string.format("[%s] %s", Settings.SystemTitle, service.Filter(args[1], plr, v)), Color3.fromRGB(255,64,77)) + end + + Admin.Init = nil; + AddLog("Script", "Admin Module Initialized") + end; + + local function RunAfterPlugins(data) + --// Backup Map + if Settings.AutoBackup then + TrackTask("Thread: Initial Map Backup", Admin.RunCommand, false, `{Settings.Prefix}backupmap`) + end + + --// Run OnStartup Commands + for i,v in Settings.OnStartup do + print(`Running startup command {v}`) + TrackTask(`Thread: Startup_Cmd: {v}`, Admin.RunCommand, false, v) + AddLog("Script", { + Text = `Startup: Executed {v}`; + Desc = `Executed startup command; {v}`; + }) + end + + --// Check if Shutdownlogs is set and if not then set it + if Core.DataStore and not Core.GetData("ShutdownLogs") then + Core.SetData("ShutdownLogs", {}) + end + + Admin.RunAfterPlugins = nil; + AddLog("Script", "Admin Module RunAfterPlugins Finished") + end + + service.MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, id, purchased) + if Variables and player.Parent and table.find(Variables.DonorPass, id) and purchased then + Variables.CachedDonors[tostring(player.UserId)] = os.time() + end + end) + + local function stripArgPlaceholders(alias) + return service.Trim(string.gsub(alias, "<%S+>", "")) + end + + local function FormatAliasArgs(alias, aliasCmd, msg) + local uniqueArgs = {} + local argTab = {} + local numArgs = 0 + + --// First try to extract args info from the alias + for arg in string.gmatch(alias, "<(%S+)>") do + if arg ~= "" and arg ~= " " then + local arg = `<{arg}>` + if not uniqueArgs[arg] then + numArgs += 1 + uniqueArgs[arg] = true + table.insert(argTab, arg) + end + end + end + + --// If no args in alias string, check the command string instead and try to guess args based on order of appearance + if numArgs == 0 then + for arg in string.gmatch(aliasCmd, "<(%S+)>") do + if arg ~= "" and arg ~= " " then + local arg = `<{arg}>` + if not uniqueArgs[arg] then --// Get only unique placeholder args, repeats will be matched to the same arg pos + numArgs += 1 + uniqueArgs[arg] = true --// :cmd + table.insert(argTab, arg) end end end - }; + end - ShutdownLogs = { - Prefix = Settings.Prefix; - Commands = {"shutdownlogs", "shutdownlog", "slogs", "shutdowns"}; - Args = {}; - Description = "Shows who shutdown or restarted a server and when"; - AdminLevel = "Admins"; - ListUpdater = function(plr: Player) - local logs = Core.GetData("ShutdownLogs") or {} - local tab = {} - for i, v in logs do - if v.Restart then v.Time ..= " [RESTART]" end - tab[i] = { - Text = `{v.Time}: {v.User}`; - Desc = `Reason: {v.Reason}`; - } - end - return tab - end; - Function = function(plr: Player, args: {string}) - Remote.MakeGui(plr, "List", { - Title = "Shutdown Logs"; - Table = Logs.ListUpdaters.ShutdownLogs(plr); - Update = "ShutdownLogs"; - }) + local suppliedArgs = Admin.GetArgs(msg, numArgs) -- User supplied args (when running :alias arg) + local out = aliasCmd + + local SanitizePattern = service.SanitizePattern + for i,argType in argTab do + local replaceWith = suppliedArgs[i] + if replaceWith then + out = string.gsub(out, SanitizePattern(argType), replaceWith) end - }; - - ServerLock = { - Prefix = Settings.Prefix; - Commands = {"slock", "serverlock", "lockserver"}; - Args = {"on/off"}; - Description = "Enables/disables server lock"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local arg = args[1] and string.lower(args[1]) - Functions.LogAdminAction(plr, "Server Lock", "Server is now " .. (if (arg == "on" or arg == "true") then "locked" else "unlocked"), "") - if (not arg and Variables.ServerLock ~= true) or arg == "on" or arg == "true" then - Variables.ServerLock = true - Functions.Hint("Server Locked", service.Players:GetPlayers()) - elseif Variables.ServerLock == true or arg == "off" or arg == "false" then - Variables.ServerLock = false - Functions.Hint("Server Unlocked", service.Players:GetPlayers()) - end - end - }; - - Whitelist = { - Prefix = Settings.Prefix; - Commands = {"wl", "enablewhitelist", "whitelist"}; - Args = {"on/off/add/remove/list/clear", "optional player"}; - Description = "Enables/disables the server whitelist; :wl username to add them to the whitelist"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local sub = string.lower(args[1]) - - if sub == "on" or sub == "enable" then - Variables.Whitelist.Enabled = true - Functions.Hint("Enabled server whitelist", service.Players:GetPlayers()) - elseif sub == "off" or sub == "disable" then - Variables.Whitelist.Enabled = false - Functions.Hint("Disabled server whitelist", service.Players:GetPlayers()) - elseif sub == "add" then - if args[2] then - local plrs = service.GetPlayers(plr, args[2], { - DontError = true; - IsServer = false; - IsKicking = false; - NoFakePlayer = false; - }) - if #plrs>0 then - for _, v in plrs do - table.insert(Variables.Whitelist.Lists.Settings, `{v.Name}:{v.UserId}`) - Functions.Hint(`Added {service.FormatPlayer(v)} to the whitelist`, {plr}) - end - else - table.insert(Variables.Whitelist.Lists.Settings, args[2]) - end + end + + return out + end + + server.Admin = { + Init = Init; + RunAfterPlugins = RunAfterPlugins; + + SpecialLevels = {}; + TempAdmins = {}; + + PrefixCache = {}; + CommandCache = {}; + SlowCache = {}; + UserIdCache = {}; + UsernameCache = {}; + GroupsCache = {}; + + BlankPrefix = false; + + --// How long admin levels will be cached (unless forcibly updated via something like :admin user) + AdminLevelCacheTimeout = 30; + + CheckSlowMode = function(p: Player) + if Admin.SlowMode and Admin.SlowCache[p] and os.time() - Admin.SlowCache[p] < Admin.SlowMode then + return true + else + return false + end + end, + + DoHideChatCmd = function(p: Player, message: string, data: {[string]: any}?) + local pData = data or Core.GetPlayer(p) + if pData.Client.HideChatCommands then + if Admin.BlankPrefix and + + (string.sub(message,1,1) ~= Settings.Prefix or string.sub(message,1,1) ~= Settings.PlayerPrefix or (type(Settings.Prefix) == "table" and not table.find(Settings.Prefix, string.sub(message,1,1)))) then + local isCMD = Admin.GetCommand(message) + if isCMD then + return true else - error("Missing user argument") + return false end - elseif sub == "remove" then - if args[2] then - for i, v in Variables.Whitelist.Lists.Settings do - if string.sub(string.lower(v), 1,#args[2]) == string.lower(args[2])then - table.remove(Variables.Whitelist.Lists.Settings,i) - Functions.Hint(`Removed {v} from the whitelist`, {plr}) - end - end - else - error("Missing user argument") + elseif (string.sub(message,1,1) == Settings.Prefix or string.sub(message,1,1) == Settings.PlayerPrefix or (type(Settings.Prefix) == "table" and table.find(Settings.Prefix, string.sub(message,1,1)))) + and string.sub(message,2,2) ~= string.sub(message,1,1) then + return true; + end + end + end; + + GetPlayerGroups = function(p: Player) + if not p or p.Parent ~= service.Players then + return {} + end + return Admin.GetGroups(p.UserId) + end; + + GetPlayerGroup = function(p, group) + local groups = Admin.GetPlayerGroups(p) + local isId = type(group) == "number" + if groups and #groups > 0 then + for _, g in groups do + if (isId and g.Id == group) or (not isId and g.Name == group) then + return g end - elseif sub == "list" then - local Tab = {} - for Key, List in Variables.Whitelist.Lists do - local Prefix = Key == "Settings" and "" or `[{Key}] ` - for _, User in List do - table.insert(Tab, {Text = Prefix .. User, Desc = User}) - end + end + end + end; + + GetGroups = function(uid, updateCache) + uid = tonumber(uid) + + if type(uid) == "number" then + local existCache = Admin.GroupsCache[uid] + local canUpdate = false + + if not updateCache then + --> Feel free to adjust the time to update over or less than 300 seconds (5 minutes). + --> 300 seconds is recommended in the event of unexpected server breakdowns with Roblox and faster performance. + if (existCache and (os.time()-existCache.LastUpdated > 300)) or not existCache then + canUpdate = true end - Remote.MakeGui(plr, "List", {Title = "Whitelist List"; Tab = Tab;}) - elseif sub == "clear" then - Variables.Whitelist.Lists.Settings = {} - Functions.Hint("Cleared server whitelist", service.Players:GetPlayers()) else - error("Invalid subcommand (on/off/add/remove/list/clear)") - end - end - }; - - SystemNotify = { - Prefix = Settings.Prefix; - Commands = {"sn", "systemnotify", "sysnotif", "sysnotify", "systemsmallmessage", "snmessage", "snmsg", "ssmsg", "ssmessage"}; - Args = {"message"}; - Filter = true; - Description = "Makes a system small message"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[1], "Missing message") - for _, v in service.GetPlayers() do - Remote.RemoveGui(v, "Notify") - Functions.Notify(Settings.SystemTitle, service.Filter(args[1], plr, v), {v}) - end - end - }; - - Notif = { - Prefix = Settings.Prefix; - Commands = {"setmessage", "notif", "setmsg", "permhint"}; - Args = {"message OR off"}; - Filter = true; - Description = "Sets a small hint message at the top of the screen"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[1], "Missing message (or enter 'off' to disable)") - - if args[1] == "off" or args[1] == "false" then - Variables.NotifMessage = nil - for _, v in service.GetPlayers() do - Remote.RemoveGui(v, "Notif") + canUpdate = true + end + + if canUpdate then + local cacheTab = { + Groups = (existCache and existCache.Groups) or {}; + LastUpdated = os.time(); + } + Admin.GroupsCache[uid] = cacheTab + + local suc,groups = pcall(function() + return service.GroupService:GetGroupsAsync(uid) or {} + end) + + if suc and type(groups) == "table" then + cacheTab.Groups = groups + return cacheTab.Groups end + + Admin.GroupsCache[uid] = cacheTab + return table.clone(cacheTab.Groups) else - Variables.NotifMessage = args[1] - for _, v in service.GetPlayers() do - Remote.MakeGui(v, "Notif", { - Message = Variables.NotifMessage; - }) - end + return table.clone((existCache and existCache.Groups) or {}) end end - }; - - SetBanMessage = { - Prefix = Settings.Prefix; - Commands = {"setbanmessage", "setbmsg"}; - Args = {"message"}; - Filter = true; - Description = "Sets the ban message banned players see"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - Variables.BanMessage = assert(args[1], "Missing message (argument #1)") - end - }; - - SetLockMessage = { - Prefix = Settings.Prefix; - Commands = {"setlockmessage", "slockmsg", "setlmsg"}; - Args = {"message"}; - Filter = true; - Description = "Sets the lock message unwhitelisted players see if :whitelist or :slock is on"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - Variables.LockMessage = assert(args[1], "Missing message (argument #1)") - end - }; - - SystemMessage = { - Prefix = Settings.Prefix; - Commands = {"sm", "systemmessage", "sysmsg"}; - Args = {"message"}; - Filter = true; - Description = "Same as message but says SYSTEM MESSAGE instead of your name, or whatever system message title is server to..."; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - Functions.Message(Settings.SystemTitle, service.BroadcastFilter(assert(args[1], "Missing message (argument #1)"), plr), service.GetPlayers(), true) - end - }; - - SetCoreGuiEnabled = { - Prefix = Settings.Prefix; - Commands = {"setcoreguienabled", "setcoreenabled", "showcoregui", "setcoregui", "setcgui", "setcore", "setcge"}; - Args = {"player", "All/Backpack/Chat/EmotesMenu/Health/PlayerList", "true/false"}; - Description = "Enables or disables CoreGui elements for the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[3], "Missing state (argument #3)") - local enable = if args[3]:lower() == "on" or args[3]:lower() == "true" then true elseif args[3]:lower() == "off" or args[3]:lower() == "false" then false else nil - assert(enable ~= nil, `Invalid state '{args[3]}'; please supply 'true' or 'false' (argument #3)`) - for _,v in service.GetPlayers(plr, args[1]) do - if string.lower(args[3]) == "on" or string.lower(args[3]) == "true" then - Remote.Send(v, "Function", "SetCoreGuiEnabled", args[2], true) - elseif string.lower(args[3]) == 'off' or string.lower(args[3]) == "false" then - Remote.Send(v, "Function", "SetCoreGuiEnabled", args[2], false) + end; + + GetGroupLevel = function(uid, groupId) + groupId = tonumber(groupId) + + if groupId then + local groups = Admin.GetGroups(uid) or {} + + for _, group in groups do + if group.Id == groupId then + return group.Rank end end end - }; - - Alert = { - Prefix = Settings.Prefix; - Commands = {"alert", "alarm", "annoy"}; - Args = {"player", "message"}; - Filter = true; - Description = "Get someone's attention"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr,string.lower(args[1]))do - Remote.MakeGui(v, "Alert", {Message = args[2] and service.Filter(args[2],plr, v) or "Wake up; Your attention is required"}) - end - end - }; - - LockMap = { - Prefix = Settings.Prefix; - Commands = {"lockmap"}; - Args = {}; - Description = "Locks the map"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, obj in workspace:GetDescendants()do - if obj:IsA("BasePart")then - obj.Locked = true + + return 0 + end; + + CheckInGroup = function(uid, groupId) + local groups = Admin.GetGroups(uid) or {} + groupId = tonumber(groupId) + + if groupId then + for i,group in groups do + if group.Id == groupId then + return true end end end - }; - UnlockMap = { - Prefix = Settings.Prefix; - Commands = {"unlockmap"}; - Args = {}; - Description = "Unlocks the map"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, obj in workspace:GetDescendants()do - if obj:IsA("BasePart")then - obj.Locked = false - end + return false + end, + + IsLax = function(str) + for _, v in {"plr", "user", "player", "brickcolor"} do + if string.match(string.lower(str), v) then + return true end end - }; - - BuildingTools = { - Prefix = Settings.Prefix; - Commands = {"btools", "f3x", "buildtools", "buildingtools", "buildertools"}; - Args = {"player"}; - Description = "Gives the target player(s) F3X building tools."; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local F3X = Deps.Assets:FindFirstChild("F3X Deps") and (function(deps) - local F3X = service.New("Tool", { - GripPos = Vector3.new(0, 0, 0.4), - CanBeDropped = false, - ManualActivationOnly = false, - ToolTip = "Building Tools by F3X", - Name = "Building Tools" - }, true) - local clonedDeps = deps:Clone() - - for _, obj in clonedDeps:GetDescendants() do - if obj:IsA("BaseScript") then - obj.Disabled = false - end - end - for _, obj in clonedDeps:GetChildren() do - obj.Parent = F3X - end - clonedDeps:Destroy() - return F3X - end)(Deps.Assets:FindFirstChild("F3X Deps")) or Variables.F3XCached and Variables.F3XCached:Clone() or require(580330877)() - Variables.F3XCached = Variables.F3XCached or F3X:Clone() - service.New("StringValue", { - Name = `__ADONIS_VARIABLES_{Variables.CodeName}`, - Parent = F3X - }) + return false + end, - for _, v in service.GetPlayers(plr, args[1]) do - local Backpack = v:FindFirstChildOfClass("Backpack") + IsMuted = function(player) + local DoCheck = Admin.DoCheck + for _, v in Settings.Muted do + if DoCheck(player, v) then + return true + end + end - if Backpack then - F3X:Clone().Parent = Backpack - end + for _, v in HTTP.Trello.Mutes do + if DoCheck(player, v) then + return true end + end - F3X:Destroy() + if HTTP.WebPanel.Mutes then + for _, v in HTTP.WebPanel.Mutes do + if DoCheck(player, v) then + return true + end + end + end + end; + + DoCheck = function(pObj, check, banCheck) + local pType = typeof(pObj) + local cType = typeof(check) + + local pUnWrapped = service.UnWrap(pObj) + + local plr: Player = if pType == "userdata" and pObj:IsA("Player") then pObj + elseif pType == "number" then service.Players:GetPlayerByUserId(pObj) + elseif pType == "string" then service.Players:FindFirstChild(pObj) + elseif typeof(pUnWrapped) == "Instance" and pUnWrapped:IsA("Player") then pUnWrapped + elseif pType == "userdata" then service.Players:GetPlayerByUserId(pObj.UserId) + else nil + if not plr then + return false end - }; - Insert = { - Prefix = Settings.Prefix; - Commands = {"insert", "ins"}; - Args = {"id"}; - Description = "Inserts whatever object belongs to the ID you supply, the object must be in the place owner's or Roblox's inventory"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local id = string.lower(args[1]) + if cType == "number" then + return plr.UserId == check + elseif cType == "string" then + if plr.Name == check then + return true + end - for i, v in Variables.InsertList do - if id == string.lower(v.Name)then - id = v.ID - break + local filterName, filterData = string.match(check, "^(.-):(.+)$") + if filterName then + filterName = string.lower(filterName) + else + return false + end + if filterName == "group" then + local groupId = tonumber((string.match(filterData, "^%d+"))) + if groupId then + local plrRank = Admin.GetGroupLevel(plr.UserId, groupId) + local requiredRank,noRank = tonumber((string.match(filterData, "^%d+:(.+)$"))), string.match(filterData,"^%d+$") + if requiredRank then + if requiredRank < 0 then + return plrRank >= math.abs(requiredRank) + else + return plrRank == requiredRank + end + elseif noRank then + return plrRank > 0 + end + end + return false + elseif filterName == "item" then + local itemId = tonumber((string.match(filterData, "^%d+"))) + return itemId and service.CheckAssetOwnership(plr, itemId) + elseif filterName == "gamepass" then + local gamepassId = tonumber((string.match(filterData, "^%d+"))) + return gamepassId and service.CheckPassOwnership(plr, gamepassId) + elseif filterName == "subscription" then + local subscriptionId = string.match(filterData, "^EXP%-%d+$") + return subscriptionId and service.CheckSubscriptionStatus(plr, subscriptionId) + else + local username, userId = string.match(check, "^(.*):(.*)") + if username and userId and (plr.UserId == tonumber(userId) or string.lower(plr.Name) == string.lower(username)) then + return true end - end - for i, v in HTTP.Trello.InsertList do - if id == string.lower(v.Name) then - id = v.ID - break + if not banCheck and type(check) == "string" and not string.find(check, ":") then + local cache = Functions.GetUserIdFromNameAsync(check) + if cache and plr.UserId == cache then + return true + end end end - - local obj = service.Insert(tonumber(id), true) - if obj and plr.Character then - table.insert(Variables.InsertedObjects, obj) - obj.Parent = workspace - pcall(obj.MakeJoints, obj) - obj:PivotTo(plr.Character:GetPivot()) - end - end - }; - - SaveTool = { - Prefix = Settings.Prefix; - Commands = {"addtool", "savetool", "maketool"}; - Args = {"optional player", "optional new tool name"}; - Description = `Saves the equipped tool to the storage so that it can be inserted using {Settings.Prefix}give`; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - local tool = v.Character and v.Character:FindFirstChildWhichIsA("BackpackItem") - if tool then - tool = tool:Clone() - if args[2] then tool.Name = args[2] end - tool.Parent = service.UnWrap(Settings.Storage) - Variables.SavedTools[tool] = service.FormatPlayer(plr) - Functions.Hint(`Added tool: {tool.Name}`, {plr}) - elseif not args[1] then - error("You must have an equipped tool to add to the storage.") + elseif cType == "table" then + local groupId, rank = check.Group, check.Rank + if groupId and rank then + local plrGroupInfo = Admin.GetPlayerGroup(plr, groupId) + if plrGroupInfo then + local plrRank = plrGroupInfo.Rank + return plrRank == rank or (rank < 0 and plrRank >= math.abs(rank)) end end end - }; - - ClearSavedTools = { - Prefix = Settings.Prefix; - Commands = {"clraddedtools", "clearaddedtools", "clearsavedtools", "clrsavedtools"}; - Args = {}; - Description = `Removes any tools in the storage added using {Settings.Prefix}savetool`; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local count = 0 - for tool in Variables.SavedTools do - count += 1 - tool:Destroy() - end - table.clear(Variables.SavedTools) - Functions.Hint(string.format("Cleared %d saved tool%s.", count, count == 1 and "" or "s"), {plr}) - end - }; - - NewTeam = { - Prefix = Settings.Prefix; - Commands = {"newteam", "createteam", "maketeam"}; - Args = {"name", "BrickColor"}; - Filter = true; - Description = "Make a new team with the specified name and color"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local teamName = assert(args[1], "Missing team name (argument #1)") - local teamColor = Functions.ParseBrickColor(args[2]) - - if service.Teams:FindFirstChild(teamName) then - Functions.Hint(string.format("Team '%s' already exists!", teamName), {plr}) - return; - end - - service.New("Team", { - Parent = service.Teams; - Name = teamName; - TeamColor = teamColor; - AutoAssignable = false; - }) - if Settings.CommandFeedback then - Functions.Hint(string.format("Created new team '%s' (%s)", teamName, teamColor.Name), {plr}) - end - end - }; - - RemoveTeam = { - Prefix = Settings.Prefix; - Commands = {"removeteam", "deleteteam"}; - Args = {"name"}; - Description = "Remove the specified team"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.Teams:GetTeams() do - if string.sub(string.lower(v.Name), 1, #args[1]) == string.lower(args[1]) then - local ans = Remote.GetGui(plr, "YesNoPrompt", { Question = `Remove team: '{v.Name}'?` }) - - if ans == "Yes" then - v:Destroy() - return Functions.Hint(`Removed team {v.Name}`, {plr}) - else - return Functions.Hint("Cancelled team removal operation", {plr}) - end - end + + return check == plr + end; + + LevelToList = function(lvl) + local lvl = tonumber(lvl) + if not lvl then return nil end + local listName = Admin.LevelToListName(lvl) + if listName then + local list = Settings.Ranks[listName]; + if list then + return list.Users, listName, list; end end - }; + end; - RestoreMap = { - Prefix = Settings.Prefix; - Commands = {"restoremap", "maprestore", "rmap"}; - Args = {}; - Description = "Restore the map to the the way it was the last time it was backed up"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local plrName = plr and service.FormatPlayer(plr) or "" + LevelToListName = function(lvl) + if lvl > 999 then + return "Place Owner" + elseif lvl == 0 then + return "Players" + end - if not Variables.MapBackup then - error("Cannot restore when there are no backup maps!", 0) - return + --// Check if this is a default rank and if the level matches the default (so stuff like [Trello] Admins doesn't appear in the command list) + for i,v in server.Defaults.Settings.Ranks do + local tRank = Settings.Ranks[i]; + if tRank and tRank.Level == v.Level and v.Level == lvl then + return i end - if Variables.RestoringMap then - error("Map has not been backed up",0) - return + end + + for i,v in Settings.Ranks do + if v.Level == lvl then + return i end - if Variables.BackingupMap then - error("Cannot restore map while backing up map is in process!", 0) - return + end + end; + + UpdateCachedLevel = function(p, data) + local data = data or Core.GetPlayer(p) + local oLevel, oRank = data.AdminLevel, data.AdminRank + local level, rank = Admin.GetUpdatedLevel(p, data) + + data.AdminLevel = level + data.AdminRank = rank + data.LastLevelUpdate = os.time() + + AddLog("Script", { + Text = `Updating cached level for {p.Name}`; + Desc = `Updating the cached admin level for {p.Name}`; + Player = p; + }) + + if Settings.Console and (oLevel ~= level or oRank ~= rank) then + if not Settings.Console_AdminsOnly or (Settings.Console_AdminsOnly and level > 0) then + task.defer(Remote.RefreshGui, p, "Console") + else + task.defer(Remote.RemoveGui, p, "Console") end + end + + return level, rank + end; + + GetLevel = function(p) + local data = Core.GetPlayer(p) + local level = data.AdminLevel + local rank = data.AdminRank + local lastUpdate = data.LastLevelUpdate or 0 + local clients = Remote.Clients + local key = tostring(p.UserId) - Variables.RestoringMap = true - Functions.Hint("Restoring Map...", service.Players:GetPlayers()) - workspace.Gravity = Variables.OriginalGravity + local currentTime = os.time() - for _, obj in workspace:GetChildren() do - if obj.ClassName ~= "Terrain" and not service.Players:GetPlayerFromCharacter(obj) then - obj:Destroy() - service.RunService.Stepped:Wait() + if (not level or not lastUpdate or currentTime - lastUpdate > Admin.AdminLevelCacheTimeout) or lastUpdate > currentTime then + local newLevel, newRank = Admin.UpdateCachedLevel(p, data) + + if clients[key] and level and newLevel and type(p) == "userdata" and p:IsA("Player") then + if newLevel < level then + Functions.Hint(`Your admin level has been reduced to {newLevel} [{newRank or "Unknown"}]`, {p}) + elseif newLevel > level then + Functions.Hint(`Your admin level has been increased to {newLevel} [{newRank or "Unknown"}]`, {p}) end end - local new = Variables.MapBackup:Clone() - for _, obj in new:GetChildren() do - obj.Parent = workspace - if obj:IsA("Model") then - obj:MakeJoints() - end + return newLevel, newRank + end + + return level or 0, rank; + end; + + GetUpdatedLevel = function(p, data) + local checkTable = Admin.CheckTable + local doCheck = Admin.DoCheck + + for _, admin in Admin.SpecialLevels do + if doCheck(p, admin.Player) then + return admin.Level, admin.Rank end - new:Destroy() + end - local Terrain = workspace.Terrain or workspace:FindFirstChildOfClass("Terrain") - if Terrain and Variables.TerrainMapBackup then - Terrain:Clear() - Terrain:PasteRegion(Variables.TerrainMapBackup, Terrain.MaxExtents.Min, true) + local sortedRanks = {} + for rank, data in Settings.Ranks do + table.insert(sortedRanks, { + Rank = rank; + Users = data.Users; + Level = data.Level; + }); + end + + table.sort(sortedRanks, function(t1, t2) + return t1.Level > t2.Level + end) + + local highestLevel = 0 + local highestRank = nil + + for _, data in sortedRanks do + local level = data.Level + if level > highestLevel then + for _, v in data.Users do + if doCheck(p, v) then + highestLevel, highestRank = level, data.Rank + break + end + end end + end - task.wait() + if Admin.IsPlaceOwner(p) and highestLevel < 1000 then + return 1000, "Place Owner" + end - Admin.RunCommand(`{Settings.Prefix}fixlighting`) - Admin.RunCommand(`{Settings.Prefix}respawn`, "all") - Variables.RestoringMap = false - Functions.Hint('Map Restore Complete.',service.Players:GetPlayers()) + return highestLevel, highestRank + end; - Logs:AddLog("Script", { - Text = "Map Restoration Complete", - Desc = `{plrName} has restored the map.`, - }) + IsPlaceOwner = function(p) + if type(p) == "userdata" and p:IsA("Player") then + --// These are my accounts; Lately I've been using my game dev account(698712377) more so I'm adding it so I can debug without having to sign out and back in (it's really a pain) + --// Disable CreatorPowers in settings if you don't trust me. It's not like I lose or gain anything either way. Just re-enable it BEFORE telling me there's an issue with the script so I can go to your place and test it. + if Settings.CreatorPowers and table.find(Variables.DeveloperWhitelist, p.UserId) then + return true + end + + if tonumber(CreatorId) and p.UserId == CreatorId then + return true + end + + if p.UserId == -1 and Variables.IsStudio then --// To account for player emulators in multi-client Studio tests + return true + end end - }; - - ScriptBuilder = { - Prefix = Settings.Prefix; - Commands = {"scriptbuilder", "scriptb", "sb"}; - Args = {"create/remove/edit/close/clear/append/run/stop/list", "localscript/script", "scriptName", "data"}; - Description = "[Deprecated] Script Builder; make a script, then edit it and chat it's code or use :sb append "; - AdminLevel = "Admins"; - Hidden = true; - NoFilter = true; - CrossServerDenied = true; - Function = function(plr: Player, args: {string}) - assert(Settings.CodeExecution, "CodeExecution must be enabled for this command to work") - local sb = Variables.ScriptBuilder[tostring(plr.UserId)] - if not sb then - sb = { - Script = {}; - LocalScript = {}; - Events = {}; + end; + + CheckAdmin = function(p) + return Admin.GetLevel(p) > 0 + end; + + SetLevel = function(p, level, doSave, rankName) + local current, rank = Admin.GetLevel(p) + + if tonumber(level) then + if current >= 1000 then + return false + else + Admin.SpecialLevels[tostring(p.UserId)] = { + Player = p.UserId, + Level = level, + Rank = rankName } - Variables.ScriptBuilder[tostring(plr.UserId)] = sb end + elseif level == "Reset" then + Admin.SpecialLevels[tostring(p.UserId)] = nil + end - local action = string.lower(args[1]) - local class = args[2] or "LocalScript" - local name = args[3] + Admin.UpdateCachedLevel(p) + end; - if string.lower(class) == "script" or string.lower(class) == "s" then - class = "Script" - elseif string.lower(class) == "clientscript" or string.lower(class) == "cs" then - class = "ClientScript" - --elseif string.lower(class) == "localscript" or string.lower(class) == "ls" then - -- class = "LocalScript" - else - class = "LocalScript" + IsTempAdmin = function(p) + local DoCheck = Admin.DoCheck + for i,v in Admin.TempAdmins do + if DoCheck(p,v) then + return true, i end + end + end; - if action == "create" then - assert(args[1] and args[2] and args[3], "Missing arguments") - local code = args[4] or " " + RemoveAdmin = function(p, temp, override) + local current, rank = Admin.GetLevel(p) + local listData = rank and Settings.Ranks[rank] + local listName = listData and rank + local list = listData and listData.Users - if sb[class][name] then - pcall(function() - sb[class][name].Script.Disabled = true - sb[class][name].Script:Destroy() - end) - if sb.ChatEvent then - sb.ChatEvent:Disconnect() - end - end + local isTemp,tempInd = Admin.IsTempAdmin(p) - local wrapped,scr = Core.NewScript(class,code,false,true) + if isTemp then + temp = true + table.remove(Admin.TempAdmins, tempInd) + end - sb[class][name] = { - Wrapped = wrapped; - Script = scr; - } + if override then + temp = false + end - if args[4] then - Functions.Hint(`Created {class} {name} and appended text`, {plr}) - else - Functions.Hint(`Created {class} {name}`, {plr}) - end - elseif action == "edit" then - assert(args[1] and args[2] and args[3], "Missing arguments") - if sb[class][name] then - local scr = sb[class][name].Script - local tab = Core.GetScript(scr) - if scr and tab then - sb[class][name].Event = plr.Chatted:Connect(function(msg) - if string.sub(msg, 1,#(`{Settings.Prefix}sb`)) ~= `{Settings.Prefix}sb` then - tab.Source ..= `\n{msg}` - Functions.Hint(`Appended message to {class} {name}`, {plr}) - end - end) - Functions.Hint(`Now editing {class} {name}; Chats will be appended`, {plr}) - end - else - error(`{class} {name} not found!`) - end - elseif action == "close" then - assert(args[1] and args[2] and args[3], "Missing arguments") - local scr = sb[class][name].Script - local tab = Core.GetScript(scr) - if sb[class][name] then - if sb[class][name].Event then - sb[class][name].Event:Disconnect() - sb[class][name].Event = nil - Functions.Hint(`No longer editing {class} {name}`, {plr}) + if type(p) == "userdata" then + Admin.SetLevel(p, 0) + end + + if list then + local DoCheck = Admin.DoCheck + for ind,check in list do + if DoCheck(p, check) and not (type(check) == "string" and (string.match(check,"^Group:") or string.match(check,"^Item:"))) then + table.remove(list, ind) + + if not temp and Settings.SaveAdmins then + TrackTask("Thread: RemoveAdmin", Core.DoSave, false, { + Type = "TableRemove"; + Table = {"Settings", "Ranks", listName, "Users"}; + Value = check; + }) end - else - error(`{class} {name} not found!`) end - elseif action == "clear" then - assert(args[1] and args[2] and args[3], "Missing arguments") - local scr = sb[class][name].Script - local tab = Core.GetScript(scr) - if scr and tab then - tab.Source = " " - Functions.Hint(`Cleared {class} {name}`, {plr}) - else - error(`{class} {name} not found!`) + end + end + + Admin.UpdateCachedLevel(p) + end; + + AddAdmin = function(p, level, temp) + local current, rank = Admin.GetLevel(p) + local list = rank and Settings.Ranks[rank] + local levelName, newRank, newList + + if type(level) == "string" then + local newRank = Settings.Ranks[level] + levelName = newRank and level + newList = newRank and newRank.Users + level = (newRank and newRank.Level) or Admin.StringToComLevel(levelName) or level + else + local nL, nLN = Admin.LevelToList(level) + levelName = nLN + newRank = nLN + newList = nL + end + + Admin.RemoveAdmin(p, temp) + Admin.SetLevel(p, level, nil, levelName) + + if temp then + table.insert(Admin.TempAdmins, p) + end + + if list and type(list) == "table" then + local index,value + + for ind,ent in list do + if (type(ent)=="number" or type(ent)=="string") and (ent==p.UserId or string.lower(ent)==string.lower(p.Name) or string.lower(ent)==string.lower(`{p.Name}:{p.UserId}`)) then + index = ind + value = ent end - elseif action == "remove" then - assert(args[1] and args[2] and args[3], "Missing arguments") - if sb[class][name] then - pcall(function() - sb[class][name].Script.Disabled = true - sb[class][name].Script:Destroy() - end) - if sb.ChatEvent then - sb.ChatEvent:Disconnect() - sb.ChatEvent = nil - end - sb[class][name] = nil - else - error(`{class} {name} not found!`) + end + + if index and value then + table.remove(list, index) + end + end + + local value = `{p.Name}:{p.UserId}` + + if newList then + table.insert(newList, value) + + if Settings.SaveAdmins and levelName and not temp then + TrackTask("Thread: SaveAdmin", Core.DoSave, false, { + Type = "TableAdd"; + Table = {"Settings", "Ranks", levelName, "Users"}; + Value = value + }) + end + end + + Admin.UpdateCachedLevel(p) + end; + + CheckDonor = function(p) + local key = tostring(p.UserId) + if Variables.CachedDonors[key] then + return true + else + local pGroup = Admin.GetPlayerGroup(p, 886423) + for _, pass in Variables.DonorPass do + if p.Parent ~= service.Players then + return false end - elseif action == "append" then - assert(args[1] and args[2] and args[3] and args[4], "Missing arguments") - if sb[class][name] then - local scr = sb[class][name].Script - local tab = Core.GetScript(scr) - if scr and tab then - tab.Source ..= `\n{args[4]}` - Functions.Hint(`Appended message to {class} {name}`, {plr}) - end - else - error(`{class} {name} not found!`) + + local ran, ret + if type(pass) == "number" then + ran, ret = pcall(service.MarketPlace.UserOwnsGamePassAsync, service.MarketPlace, p.UserId, pass) + elseif type(pass) == "string" and tonumber(pass) then + ran, ret = pcall(service.MarketPlace.PlayerOwnsAsset, service.MarketPlace, p, tonumber(pass)) end - elseif action == "run" then - assert(args[1] and args[2] and args[3], "Missing arguments") - if sb[class][name] then - if class == "LocalScript" then - sb[class][name].Script.Parent = plr:FindFirstChildOfClass("Backpack") - else - sb[class][name].Script.Parent = service.ServerScriptService - end - sb[class][name].Script.Disabled = true - task.wait(0.03) - sb[class][name].Script.Disabled = false - Functions.Hint(`Running {class} {name}`, {plr}) - else - error(`{class} {name} not found!`) + + if (ran and ret) or (pGroup and pGroup.Rank >= 10) then --// Complimentary donor access is given to Adonis contributors & developers. + Variables.CachedDonors[key] = os.time() + return true end - elseif action == "stop" then - assert(args[1] and args[2] and args[3], "Missing arguments") - if sb[class][name] then - sb[class][name].Script.Disabled = true - Functions.Hint(`Stopped {class} {name}`, {plr}) + end + end + end; + + CheckBan = function(p) + local doCheck = Admin.DoCheck + local banCheck = Admin.DoBanCheck + + for ind, admin in Settings.Banned do + if (type(admin) == "table" and ((admin.UserId and doCheck(p, admin.UserId, true)) or (admin.Name and not admin.UserId and doCheck(p, admin.Name, true)))) or doCheck(p, admin, true) then + return true, (type(admin) == "table" and admin.Reason) + end + end + + for ind, ban in Core.Variables.TimeBans do + if p.UserId == ban.UserId then + if ban.EndTime-os.time() <= 0 then + table.remove(Core.Variables.TimeBans, ind) else - error(`{class} {name} not found!`) - end - elseif action == "list" then - local tab = {} - for i, v in sb.Script do - table.insert(tab, {Text = `Script: {i}`, Desc = `Running: {v.Script.Disabled}`}) + return true, `\n {ban.Reason or "(No reason provided.)"}\n | Banned until {service.FormatTime(ban.EndTime, {WithWrittenDate = true})}` end + end + end - for i, v in sb.LocalScript do - table.insert(tab, {Text = `LocalScript: {i}`, Desc = `Running: {v.Script.Disabled}`}) - end + for ind, admin in HTTP.Trello.Bans do + local name = type(admin) == "table" and admin.Name or admin + if doCheck(p, name) or banCheck(p, name) then + return true, (type(admin) == "table" and admin.Reason and service.Filter(admin.Reason, p, p)) + end + end - Remote.MakeGui(plr, "List", {Title = "SB Scripts", Table = tab}) - end - end - }; - - MakeScript = { - Prefix = Settings.Prefix; - Commands = {"s", "ss", "serverscript", "sscript", "script", "makescript"}; - Args = {"code"}; - Description = "Executes the given Lua code on the server"; - AdminLevel = "Admins"; - NoFilter = true; - CrossServerDenied = true; - Function = function(plr: Player, args: {string}) - assert(Settings.CodeExecution, "CodeExecution config must be enabled for this command to work") - local bytecode = Core.Bytecode(assert(args[1], "Missing Script code (argument #2)")) - assert(string.find(bytecode, "\27Lua"), `Script unable to be created: {string.gsub(bytecode, "Loadstring%.LuaX:%d+:", "")}`) - - local cl = Core.NewScript("Script", args[1], true) - cl.Name = "[Adonis] Script" - cl.Parent = service.ServerScriptService - task.wait() - cl.Disabled = false - Functions.Hint("Ran Script", {plr}) - end - }; - - MakeLocalScript = { - Prefix = Settings.Prefix; - Commands = {"ls", "localscript", "lscript"}; - Args = {"code"}; - Description = "Executes the given code on your client"; - AdminLevel = "Admins"; - NoFilter = true; - Function = function(plr: Player, args: {string}) - Commands.LoadLocalScript.Function(plr, {`@{plr.Name}`, args[1]}) - end - }; - - LoadLocalScript = { - Prefix = Settings.Prefix; - Commands = {"cs", "cscript", "clientscript"}; - Args = {"player", "code"}; - Description = "Executes the given code on the client of the target player(s)"; - AdminLevel = "Admins"; - NoFilter = true; - Function = function(plr: Player, args: {string}) - assert(args[2], "Missing LocalScript code (argument #2)") - - local bytecode = Core.Bytecode(args[2]) - assert(string.find(bytecode, "\27Lua"), `LocalScript unable to be created: {string.gsub(bytecode, "Loadstring%.LuaX:%d+:", "")}`) - - local new = Core.NewScript("LocalScript", `script.Parent = game:GetService('Players').LocalPlayer.PlayerScripts; {args[2]}`, true) - local function cloneScript(targetPlayer) - local playerName = if targetPlayer == plr then "your client" else service.FormatPlayer(targetPlayer) - - local backpack = targetPlayer:FindFirstChildOfClass("Backpack") - if not backpack then - Functions.Hint(`Couldn't run LocalScript on {playerName} (Backpack missing?)`, {plr}) - return + if HTTP.WebPanel.Bans then + for ind, admin in HTTP.WebPanel.Bans do + if doCheck(p, admin) or banCheck(p, admin) then + return true, (type(admin) == "table" and admin.Reason) end + end + end + end; + + AddBan = function(p, reason, doSave, moderator, banType) + local value = { + Name = p.Name; + UserId = p.UserId; + Reason = reason; + Moderator = if moderator then service.FormatPlayer(moderator) else "%SYSTEM%"; + BanType = banType + } + + table.insert(Settings.Banned, value) + + if doSave then + Core.DoSave({ + Type = "TableAdd"; + Table = "Banned"; + Value = value; + }) - local cl = new:Clone() - cl.Name = "[Adonis] LocalScript" - cl.Disabled = true - cl.Parent = targetPlayer:FindFirstChildOfClass("Backpack") - task.wait(.1) - cl.Disabled = false - Functions.Hint(`Ran LocalScript on {playerName}`, {plr}) - end - - for i, v in service.GetPlayers(plr, args[1]) do - task.spawn(cloneScript, v) - end - end - }; - - CreateStarterScript = { - Prefix = Settings.Prefix; - Commands = {"starterscript", "clientstarterscript", "starterclientscript", "createstarterscript"}; - Args = {"name", "code"}; - Description = "Executes the given code on everyone's client upon respawn"; - AdminLevel = "Admins"; - NoFilter = true; - Function = function(plr: Player, args: {string}) - assert(args[1], "Missing starter script name (argument #1)") - assert(args[2], "Missing LocalScript code (argument #2)") - - local bytecode = Core.Bytecode(args[2]) - assert(string.find(bytecode, "\27Lua"), `LocalScript unable to be created: {string.gsub(bytecode, "Loadstring%.LuaX:%d+:", "")}`) - - local new = Core.NewScript("LocalScript", args[2], true) - new.Name = `[Adonis] {args[1]}` - new.Parent = service.StarterGui - new.Disabled = false - Functions.Hint("Created starter script", {plr}) - end - }; - - - StarterScripts = { - Prefix = Settings.Prefix; - Commands = {"starterscripts", "clientstarterscripts", "starterclientscripts"}; - Args = {}; - Description = "Show existing starterscripts"; - AdminLevel = "Admins"; - NoFilter = true; - Function = function(plr: Player, args: {string}) - local result = {} - - for _,v : Instance in service.StarterGui:GetChildren() do - if v:IsA("LocalScript") and v.Name:find("[Adonis]") then - table.insert(result, (v.Name:gsub("%[Adonis%] ", ""))) - end + Core.CrossServer("RemovePlayer", p.Name, Variables.BanMessage, value.Reason or "No reason provided") + end + + if type(p) ~= "table" then + if not service.Players:FindFirstChild(p.Name) then + Remote.Send(p,'Function','KillClient') + else + if p then pcall(function() p:Kick(`{Variables.BanMessage} | Reason: {value.Reason or "No reason provided"}`) end) end end + end + service.Events.PlayerBanned:Fire(p, reason, doSave, moderator) + end; - Remote.MakeGui(plr,"List",{ - Title = "Starter Scripts"; - Tab = result; - }) + AddTimeBan = function(p : Player | {[string]: any}, duration: number, reason: string, moderator: Player?) + local value = { + Name = p.Name; + UserId = p.UserId; + EndTime = os.time() + tonumber(duration); + Reason = reason; + Moderator = if moderator then service.FormatPlayer(moderator) else "%SYSTEM%"; + } + + table.insert(Core.Variables.TimeBans, value) + + Core.DoSave({ + Type = "TableAdd"; + Table = {"Core", "Variables", "TimeBans"}; + Value = value; + }) + + Core.CrossServer("RemovePlayer", p.Name, Variables.BanMessage, value.Reason or "No reason provided") + + if type(p) ~= "table" then + if not service.Players:FindFirstChild(p.Name) then + Remote.Send(p, "Function", "KillClient") + else + if p then pcall(function() p:Kick(`{Variables.BanMessage} | Reason: {value.Reason or "No reason provided"}`) end) end + end end - }; + service.Events.PlayerBanned:Fire(p, reason, true, moderator) + end, - RemoveStarterScript = { - Prefix = Settings.Prefix; - Commands = {"removestarterscript", "removeclientstarterscripts", "removestarterclientscripts", "unstarterscript"}; - Args = {"name"}; - Description = "Remove a starterscript"; - AdminLevel = "Admins"; - NoFilter = true; - Function = function(plr: Player, args: {string}) - assert(args[1], "No starterscript name provided!") + DoBanCheck = function(name: string | number | Instance, check: string | {[string]: any}) + local id = type(name) == "number" and name - for _,v : Instance in service.StarterGui:GetChildren() do - if v:IsA("LocalScript") and v.Name:find("[Adonis]") then - if v.Name:gsub("%[Adonis%] ", ""):lower() == args[1]:lower() or args[1]:lower() == "all" then - service.Delete(v) - Functions.Hint("Removed starter script "..v.Name, {plr}) - end + if type(name) == "userdata" and name:IsA("Player") then + id = name.UserId + name = name.Name + end + + if type(check) == "table" then + if type(name) == "string" and check.Name and string.lower(check.Name) == string.lower(name) then + return true + elseif id and check.UserId and check.UserId == id then + return true + end + elseif type(check) == "string" then + local cName, cId = string.match(check, "(.*):(.*)") + if not cName and cId then cName = check end + + if cName then + if string.lower(cName) == string.lower(name) then + return true + elseif id and cId and id == tonumber(cId) then + return true end + else + return string.lower(tostring(check)) == string.lower(tostring(name)) end end - }; - - Note = { - Prefix = Settings.Prefix; - Commands = {"note", "writenote", "makenote"}; - Args = {"player", "note"}; - Filter = true; - Description = "Makes a note on the target player(s) that says "; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[2], "Missing note (argument #2)") - for _, v in service.GetPlayers(plr, args[1]) do - local PlayerData = Core.GetPlayer(v) - if not PlayerData.AdminNotes then PlayerData.AdminNotes = {} end - table.insert(PlayerData.AdminNotes, args[2]) - Functions.Hint(`Added {service.FormatPlayer(v)} Note {args[2]}`, {plr}) - Core.SavePlayer(v, PlayerData) - end - end - }; - - DeleteNote = { - Prefix = Settings.Prefix; - Commands = {"removenote", "remnote", "deletenote", "clearnote"}; - Args = {"player", "note (specify 'all' to delete all notes)"}; - Description = "Removes a note on the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[2], "Missing note (argument #2)") - for _, v in service.GetPlayers(plr, args[1]) do - local PlayerData = Core.GetPlayer(v) - if PlayerData.AdminNotes then - if string.lower(args[2]) == "all" then - PlayerData.AdminNotes = {} - else - for k, m in PlayerData.AdminNotes do - if string.sub(string.lower(m), 1, #args[2]) == string.lower(args[2]) then - Functions.Hint(`Removed {service.FormatPlayer(v)} Note {m}`, {plr}) - table.remove(PlayerData.AdminNotes, k) - end + + return false + end; + + RemoveBan = function(name, doSave) + local ret + for i,v in Settings.Banned do + if Admin.DoBanCheck(name, v) then + ret = table.remove(Settings.Banned, i) + if doSave then + Core.DoSave({ + Type = "TableRemove"; + Table = "Banned"; + Value = ret; + LaxCheck = true; + }) + end + end + end + return ret + end; + + RemoveTimeBan = function(name : string | number | Instance) + local ret + for i,v in Core.Variables.TimeBans do + if Admin.DoBanCheck(name, v) then + table.remove(Core.Variables.TimeBans, i) + ret = v + Core.DoSave({ + Type = "TableRemove"; + Table = {"Core", "Variables", "TimeBans"}; + Value = v; + LaxCheck = true; + }) + end + end + return ret + end, + + RunCommand = function(coma: string, ...) + local _, com = Admin.GetCommand(coma) + if com then + local cmdArgs = com.Args or com.Arguments + local args = Admin.GetArgs(coma, #cmdArgs, ...) + + TrackTask(`Command: {coma}`, com.Function, function(err) + warn(`Encountered an error while running a command: {coma}\n{err}\n{debug.traceback()}`) + end, false, args) + end + end; + + RunCommandAsPlayer = function(coma, plr, ...) + local ind, com = Admin.GetCommand(coma) + if com then + local adminLvl = Admin.GetLevel(plr) + + local cmdArgs = com.Args or com.Arguments + local args = Admin.GetArgs(coma, #cmdArgs, ...) + local ran, error = TrackTask( + `{plr.Name}: {coma}`, + com.Function, + function(err) + err = string.match(err, ":(.+)$") or "Unknown error" + Remote.MakeGui(plr, "Output", { + Title = "Error", + Message = error, + Color = Color3.new(1, 0, 0), + }) + warn(`Encountered an error while running a command: {coma}\n{err}\n{debug.traceback()}`) + end, + plr, + args, + { + PlayerData = { + Player = plr, + Level = adminLvl, + isDonor = ((Settings.DonorCommands or com.AllowDonors) and Admin.CheckDonor(plr)) or false, + }, + } + ) + end + end; + + RunCommandAsNonAdmin = function(coma, plr, ...) + local ind, com = Admin.GetCommand(coma) + if com and com.AdminLevel == 0 then + local cmdArgs = com.Args or com.Arguments + local args = Admin.GetArgs(coma, #cmdArgs, ...) + local _, error = TrackTask( + `{plr.Name}: {coma}`, + com.Function, + function(err) + err = string.match(err, ":(.+)$") or "Unknown error" + Remote.MakeGui(plr, "Output", { + Title = "", + Message = error, + Color = Color3.new(1, 0, 0), + }) + warn(`Encountered an error while running a command: {coma}\n{err}\n{debug.traceback()}`) + end, + plr, + args, + { PlayerData = { + Player = plr, + Level = 0, + isDonor = false, + } } + ) + end + end; + + CacheCommands = function() + local tempTable = {} + local tempPrefix = {} + for ind, data in Commands do + if type(data) == "table" then + for i,cmd in data.Commands do + if type(data.Prefix) ~= "table" and data.Prefix == "" then Admin.BlankPrefix = true end + if type(data.Prefix) == "table" then + for _,p in data.Prefix do + tempPrefix[p] = true + tempTable[string.lower(p..cmd)] = ind end + else + tempPrefix[data.Prefix] = true + tempTable[string.lower(data.Prefix..cmd)] = ind end - Core.SavePlayer(v, PlayerData) end end end - }; - - ShowNotes = { - Prefix = Settings.Prefix; - Commands = {"notes", "viewnotes"}; - Args = {"player"}; - Description = "Views notes on the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - local PlayerData = Core.GetPlayer(v) - local notes = PlayerData.AdminNotes - if not notes then - Functions.Hint(`No notes found on {service.FormatPlayer(v)}`, {plr}) - continue + + Admin.PrefixCache = tempPrefix + Admin.CommandCache = tempTable + + if Variables.ChatCreateRobloxCommands then + -- // Support for commands to be ran via TextChat + task.spawn(function() + local container = service.TextChatService.ChatVersion == Enum.ChatVersion.TextChatService and service.TextChatService:WaitForChild("TextChatCommands", 9e9) + + if container then + for _, v in container:GetChildren() do + if string.sub(v.Name, 1, 7) == "Adonis_" then + v:Destroy() + end + end + + local blacklistedCommands = {} + + for _, v in container:GetDescendants() do + if v:IsA("TextChatCommand") then + blacklistedCommands[v.PrimaryAlias] = true + blacklistedCommands[v.SecondaryAlias] = true + end + end + + for name, data in Commands do + local command1, command2 = nil, nil + + if type(data) ~= "table" or data.Hidden then + continue + end + + for _, v in data.Commands do + if type(data.Prefix) == "table" then + for _, p in data.Prefix do + if not blacklistedCommands["/"..p..v] then + if not command1 then + command1 = "/"..p..v + else + command2 = "/"..p..v + end + end + end + else + if not blacklistedCommands["/"..data.Prefix..v] then + if not command1 then + command1 = "/"..data.Prefix..v + else + command2 = "/"..data.Prefix..v + end + end + + end + + end + + if command1 then + local command = service.New("TextChatCommand") + + command.Name = "Adonis_"..name + command.PrimaryAlias = command1 + command.SecondaryAlias = command2 or "" + command.Archivable = false + command:SetAttribute("AdminLevel", tonumber(data.AdminLevel) or data.AdminLevel or nil) + command.Parent = container + command.Triggered:Connect(function(textSource, text) + local player = service.Players:GetPlayerByUserId(textSource.UserId) + + if player then + Process.Command(player, string.sub(text, 2)) + end + end) + end + end end - Remote.MakeGui(plr, "List", {Title = service.FormatPlayer(v), Table = notes}) - end - end - }; - - LoopKill = { - Prefix = Settings.Prefix; - Commands = {"loopkill"}; - Args = {"player", "num (optional)"}; - Description = "Repeatedly kills the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local num = tonumber(args[2]) or 9999 - - for _, v in service.GetPlayers(plr, args[1]) do - service.StopLoop(`{v.UserId}LOOPKILL`) - local count = 0 - Routine(service.StartLoop, `{v.UserId}LOOPKILL`, 3, function() - local hum = v.Character and v.Character:FindFirstChildOfClass("Humanoid") - if hum and hum.Health > 0 then - hum.Health = 0 - count += 1 + end) + end + end; + + GetCommand = function(Command) + if Admin.PrefixCache[string.sub(Command, 1, 1)] or Admin.BlankPrefix then + local matched + matched = if string.find(Command, Settings.SplitKey) then + string.match(Command, `^(%S+){Settings.SplitKey}`) + else string.match(Command, "^(%S+)") + + if matched then + local found = Admin.CommandCache[string.lower(matched)] + if found then + local real = Commands[found] + if real then + return found,real,matched end - if count == num then - service.StopLoop(`{v.UserId}LOOPKILL`) + end + end + end + end; + + FindCommands = function(Command) + local prefixChar = string.sub(Command, 1, 1) + local checkPrefix = Admin.PrefixCache[prefixChar] and prefixChar + local matched + + if checkPrefix then + Command = string.sub(Command, 2) + end + + if string.find(Command, Settings.SplitKey) then + matched = string.match(Command, `^(%S+){Settings.SplitKey}`) + else + matched = string.match(Command, "^(%S+)") + end + + if matched then + local foundCmds = {} + matched = string.lower(matched) + + for ind,cmd in Commands do + if type(cmd) == "table" and ((checkPrefix and prefixChar == cmd.Prefix) or not checkPrefix) then + for _, alias in cmd.Commands do + if string.lower(alias) == matched then + foundCmds[ind] = cmd + break + end end - end) + end end + + return foundCmds end - }; + end; - UnLoopKill = { - Prefix = Settings.Prefix; - Commands = {"unloopkill"}; - Args = {"player"}; - Description = "Un-Loop Kill"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - service.StopLoop(`{v.UserId}LOOPKILL`) + SetPermission = function(comString, newLevel) + local cmds = Admin.FindCommands(comString) + if cmds then + for ind, cmd in cmds do + cmd.AdminLevel = newLevel end end - }; + end; - Lag = { - Prefix = Settings.Prefix; - Commands = {"lag", "fpslag"}; - Args = {"player"}; - Description = "Makes the target player(s)'s FPS drop"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers(plr, args[1]) do - if Admin.CheckAuthority(plr, v, "lag") then - Remote.Send(v, "Function", "SetFPS", 5.6) - end + FormatCommandArguments = function(command) + local text = table.create(#command.Args) + for i, arg in command.Args do + text[i] = `<{arg}>` + end + return table.concat(text, Settings.SplitKey) + end; + + FormatCommand = function(command, cmdn) + return table.concat({ + (if type(command.Prefix) == "table" then command.Prefix[1] else command.Prefix or ""), + tostring(command.Commands[cmdn or 1]), + #command.Args > 0 and Settings.SplitKey or "", + #command.Args > 0 and Admin.FormatCommandArguments(command) or "" + }) + end; + + FormatCommandAdminLevel = function(command) + local levels = if type(command.AdminLevel) == "table" + then table.clone(command.AdminLevel) + else {command.AdminLevel} + local permissionDesc = table.create(#levels) + + for i, lvl in levels do + if type(lvl) == "number" then + local list, name, data = Admin.LevelToList(lvl) + permissionDesc[i] = `{name or "No Rank"}; Level {lvl}` + elseif type(lvl) == "string" then + local numLvl = Admin.StringToComLevel(lvl) + permissionDesc[i] = `{lvl}; Level {numLvl or "Unknown"}` + else + permissionDesc[i] = "N/A" end end - }; - UnLag = { - Prefix = Settings.Prefix; - Commands = {"unlag", "unfpslag"}; - Args = {"player"}; - Description = "Un-Lag"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - Remote.Send(v, "Function", "RestoreFPS") + return table.concat(permissionDesc, ", ") + end; + + CheckTable = function(p, tab) + local doCheck = Admin.DoCheck + for i,v in tab do + if doCheck(p, v) then + return true end end - }; + end; + + --// Make it so you can't accidentally overwrite certain existing commands... resulting in being unable to add/edit/remove aliases (and other stuff) + CheckAliasBlacklist = function(alias) + local playerPrefix = Settings.PlayerPrefix; + local prefix = Settings.Prefix + local blacklist = { + [`{playerPrefix}alias`] = true; + [`{playerPrefix}newalias`] = true; + [`{playerPrefix}removealias`] = true; + [`{playerPrefix}client`] = true; + [`{playerPrefix}userpanel`] = true; + [":adonissettings"] = true; + } + --return Admin.CommandCache[alias:lower()] --// Alternatively, we could make it so you can't overwrite ANY existing commands... + return blacklist[alias]; + end; + + GetArgs = function(msg, num, ...) + local newArgs = table.pack(...) + local args = Functions.Split(string.match(msg, `^.-{Settings.SplitKey}(.+)`) or "", Settings.SplitKey, num) or table.create(newArgs.n) + for i = 1, newArgs.n do + table.insert(args, newArgs[i]) + end + return args + end; - Crash = { - Prefix = Settings.Prefix; - Commands = {"crash"}; - Args = {"player"}; - Description = "Crashes the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if Admin.CheckAuthority(plr, v, "crash") then - Remote.Send(v, "Function", "Crash") + AliasFormat = function(aliases, msg) + local foundPlayerAlias = false --// Check if there's a player-defined alias first then otherwise check settings aliases + + local CheckAliasBlacklist, SanitizePattern = Admin.CheckAliasBlacklist, service.SanitizePattern + + if aliases then + for alias, cmd in aliases do + local tAlias = stripArgPlaceholders(alias) + if not Admin.CheckAliasBlacklist(tAlias) then + local escAlias = SanitizePattern(tAlias) + --// Ignore any "empty" aliases, aka aliases that would basically match any command + if string.len(Functions.Trim(escAlias)) == 0 then + continue + end + local trimmedMsg = Functions.Trim(msg) + --// Use Adonis split to better support various characters that string.split can't handle properly + local aliasCharacters = Functions.Split(trimmedMsg, Settings.SplitKey) + --// Matching an alias can result in an infinite loop like running !fire with the alias !f, it will infinitely run the !f alias + --// If you have an alias !f + if escAlias == aliasCharacters[1] or string.match(trimmedMsg, `%s{escAlias}`) then + msg = FormatAliasArgs(alias, cmd, msg) + end end end end - }; - HardCrash = { - Prefix = Settings.Prefix; - Commands = {"hardcrash"}; - Args = {"player"}; - Description = "Hard-crashes the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if Admin.CheckAuthority(plr, v, "hard-crash") then - Remote.Send(v, "Function", "HardCrash") + for alias, cmd in Variables.Aliases do + local tAlias = stripArgPlaceholders(alias) + if not CheckAliasBlacklist(tAlias) then + local escAlias = SanitizePattern(tAlias) + if string.match(msg, `^{escAlias}`) or string.match(msg, `%s{escAlias}`) then + msg = FormatAliasArgs(alias, cmd, msg) end end end - }; - RAMCrash = { - Prefix = Settings.Prefix; - Commands = {"ramcrash", "memcrash"}; - Args = {"player"}; - Description = "RAM-crashes the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if Admin.CheckAuthority(plr, v, "RAM-crash") then - Remote.Send(v, "Function", "RAMCrash") + return msg + end; + + StringToComLevel = function(str) + local strType = type(str) + if strType == "string" and string.lower(str) == "players" then + return 0 + end + if strType == "number" then + return str + end + + local lvl = Settings.Ranks[str] + return (lvl and lvl.Level) or tonumber(str) + end; + + CheckComLevel = function(plrAdminLevel, comLevel) + if type(comLevel) == "string" then + comLevel = Admin.StringToComLevel(comLevel) + elseif type(comLevel) == "table" then + for _, level in comLevel do + if Admin.CheckComLevel(plrAdminLevel, level) then + return true end end + return false end - }; - GPUCrash = { - Prefix = Settings.Prefix; - Commands = {"gpucrash"}; - Args = {"player"}; - Description = "GPU crashes the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if Admin.CheckAuthority(plr, v, "GPU-crash") then - Remote.Send(v, "Function", "GPUCrash") - end + return type(comLevel) == "number" and plrAdminLevel >= comLevel + end; + + IsBlacklisted = function(p) + local CheckTable = Admin.CheckTable + for _, list in Variables.Blacklist.Lists do + if CheckTable(p, list) then + return true end end - }; + end; - CustomKick = { - Prefix = Settings.Prefix; - Commands = {"ckick", "customkick", "customcrash"}; - Args = {"player", "title", "message"}; - Description = "Disconnects (crashes) the target player with a custom Roblox dialog"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[3], "Argument(s) missing or nil") + CheckPermission = function(pDat, cmd, ignoreCooldown, opts) + opts = opts or {} - local title = service.BroadcastFilter(args[2], plr) - assert(title == args[2], "Title was filtered: "..title) + local adminLevel = pDat.Level + local comLevel = cmd.AdminLevel - local msg = service.BroadcastFilter(args[3], plr) - assert(msg == args[3], "Message was filtered: "..msg) + if cmd.Disabled then + return false, "This command has been disabled." + end - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if not Admin.CheckAuthority(plr, v, "custom-kick") then - continue - end + if Variables.IsStudio and cmd.NoStudio then + return false, "This command cannot be used in Roblox Studio." + end - local plrgui = v:FindFirstChildOfClass("PlayerGui") - if not plrgui then - Remote.MakeGui(plr, "Output", { - Message = `Failed to custom-kick {service.FormatPlayer(v)} (PlayerGui not found)`; - }) - continue - end + if opts.CrossServer and cmd.CrossServerDenied then -- Ignore when disabled then + return false, "This command may not be run across servers (cross-server-blacklisted)." + end - local promptGui = Deps.Assets.RobloxPromptGui:Clone() - promptGui.promptOverlay.ErrorPrompt.TitleFrame.ErrorTitle.Text = title - promptGui.promptOverlay.ErrorPrompt.MessageArea.ErrorFrame.ErrorMessage.Text = msg - promptGui.Parent = plrgui + if cmd.CrossServer and not Settings.CrossServerCommands then + return false, "This command has been disabled due to CrossServerCommands being disabled" + end - Remote.Send(v, "Function", "CustomKick") - task.delay(5, function() - if v.Parent == service.Players then - -- make sure they're really kicked - v:Kick("Unexpected Error") - end - end) - Functions.Hint(`Custom-kicking {service.FormatPlayer(v)}`, {plr}) - end - end - }; - - Shutdown = { - Prefix = Settings.Prefix; - Commands = {"shutdown"}; - Args = {"reason"}; - Description = "Shuts the server down"; - Filter = true; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - if Core.DataStore then - Core.UpdateData("ShutdownLogs", function(logs) - table.insert(logs, 1, { - User = plr and plr.Name or "[Server]", - Time = os.time(), - Reason = args[1] or "N/A" - }) + if Admin.IsPlaceOwner(pDat.Player) or adminLevel >= Settings.Ranks.Creators.Level then + return true, nil + end - local nlogs = #logs - if nlogs > Logs.OldCommandLogsLimit then - table.remove(logs, nlogs) - end + if Admin.IsBlacklisted(pDat.Player) then + return false, "You are blacklisted from running commands." + end - return logs - end) - end + if (comLevel == 0 or comLevel == "Players") and adminLevel <= 0 and not Settings.PlayerCommands then + return false, "Player commands are disabled in this game." + end - Functions.Shutdown(args[1]) + if cmd.Fun and not Settings.FunCommands then + return false, "Fun commands are disabled in this game." end - }; - ServerBan = { - Prefix = Settings.Prefix; - Commands = {"serverban", "ban"}; - Args = {"player/user", "reason"}; - Description = "Bans the target player(s) from the server"; - AdminLevel = "Admins"; - Filter = true; - Function = function(plr: Player, args: {string}, data: {any}) - local reason = args[2] or "No reason provided" - for _, v in service.GetPlayers(plr, args[1], { - IsKicking = true; - NoFakePlayer = false; - }) - do - if Admin.CheckAuthority(plr, v, "server-ban", false) then - Admin.AddBan(v, reason, false, plr, "Server") - Functions.LogAdminAction(plr, "Server Ban", v.Name, `Reason: {reason}`) - Functions.Hint(`Server-banned {service.FormatPlayer(v, true)}`, {plr}) + if opts.Chat and cmd.Chattable == false then + return false, "This command is not permitted as chat message (non-chattable command)." + end + + local permAllowed = (cmd.Donors and (pDat.isDonor and (Settings.DonorCommands or cmd.AllowDonors))) or (cmd.Agent and HTTP.Trello.CheckAgent) and HTTP.Trello.CheckAgent(pDat.Player) + or Admin.CheckComLevel(adminLevel, comLevel) + + if permAllowed and not ignoreCooldown and type(pDat.Player) == "userdata" then + local playerCooldown = tonumber(cmd.PlayerCooldown) + local serverCooldown = tonumber(cmd.ServerCooldown) + local crossCooldown = tonumber(cmd.CrossCooldown) + + local cmdFullName = cmd._fullName or (function() + local aliases = cmd.Aliases or cmd.Commands or {} + cmd._fullName = `{cmd.Prefix}{aliases[1] or `{service.HttpService:GenerateGUID(false)}-RANDOM_COMMAND`}` + return cmd._fullName + end)() + + local pCooldown_Cache = cmd._playerCooldownCache or (function() + local tab = {} + cmd._playerCooldownCache = tab + return tab + end)() + + local sCooldown_Cache = cmd._serverCooldownCache or (function() + local tab = {} + cmd._serverCooldownCache = tab + return tab + end)() + + local crossCooldown_Cache = cmd._crossCooldownCache or (function() + local tab = {} + cmd._crossCooldownCache = tab + return tab + end)() + + local cooldownIndex = tostring(pDat.Player.UserId) + local pCooldown_playerCache = pCooldown_Cache[cooldownIndex] + local sCooldown_playerCache = sCooldown_Cache[cooldownIndex] + + if playerCooldown and pCooldown_playerCache then + local secsTillPass = os.clock() - pCooldown_playerCache + if secsTillPass < playerCooldown then + return false, string.format("[PlayerCooldown] You must wait %.0f seconds to run the command.", playerCooldown - secsTillPass) end end - end - }; - UnBan = { - Prefix = Settings.Prefix; - Commands = {"unserverban", "unban"}; - Args = {"user"}; - Description = "Unbans the target user(s) from the server"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, assert(args[1], "Missing user (argument #1)"), { - UseFakePlayer = true; - }) - do - if Admin.RemoveBan(v) then - Functions.LogAdminAction(plr, "Unban", v.Name, "User has been unbanned.") - Functions.Hint(`{service.FormatPlayer(v, true)} has been unbanned`, {plr}) - else - Functions.Hint(`{service.FormatPlayer(v, true)} is not currently banned`, {plr}) + if serverCooldown and sCooldown_playerCache then + local secsTillPass = os.clock() - sCooldown_playerCache + if secsTillPass < serverCooldown then + return false, string.format("[ServerCooldown] You must wait %.0f seconds to run the command.", serverCooldown - secsTillPass) end end - end - }; - - BanMenu = { - Prefix = Settings.Prefix; - Commands = {"banmenu"}; - Args = {}; - Description = "Opens the ban menu"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}, data: {any}) - Remote.MakeGui(plr,"BanMenu",{ - AdminLevel = Admin.GetLevel(plr); - CanBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.ServerBan.AdminLevel); - CanTimeBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.TimeBan.AdminLevel); - CanPermBan = Admin.CheckComLevel(Admin.GetLevel(plr),Commands.PermanentBan.AdminLevel); - Prefix = Functions.GetMainPrefix() - }) - end, - }; - - CustomMessage = { - Prefix = Settings.Prefix; - Commands = {"cm", "custommessage"}; - Args = {"Upper message", "message"}; - Filter = true; - Description = "Same as message but says whatever you want upper message to be instead of your name."; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[1], "Missing message title (argument #1)") - assert(args[2], "Missing message (argument #2)") - for _, v in service.Players:GetPlayers() do - Remote.RemoveGui(v, "Message") - Remote.MakeGui(v, "Message", { - Title = args[1]; - Message = args[2]; - Time = (#tostring(args[1]) / 19) + 2.5; - --service.Filter(args[1],plr, v); - }) + + if crossCooldown then + local playerData = Core.GetPlayer(pDat.Player) or {} + local crossCooldown_Cache = playerData._crossCooldownCache or (function() + local tab = {} + playerData._crossCooldownCache = tab + return tab + end)() + local crossCooldown_playerCache = crossCooldown_Cache[cmdFullName] + + if crossCooldown_playerCache then + local secsTillPass = os.clock() - crossCooldown_playerCache + if secsTillPass < crossCooldown then + return false, string.format("[CrossServerCooldown] You must wait %.0f seconds to run the command.", crossCooldown - secsTillPass) + end + end end end - }; - - Nil = { - Prefix = Settings.Prefix; - Commands = {"nil"}; - Args = {"player"}; - Hidden = true; - Description = `Deletes the player forcefully, causing them to be kicked for "Player has been removed from the DataModel"`; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - v.Character = nil - v.Parent = nil - Functions.Hint(`Sent {service.FormatPlayer(v)} to nil`, {plr}) - end - end - }; - - PromptPremiumPurchase = { - Prefix = Settings.Prefix; - Commands = {"promptpremiumpurchase", "premiumpurchaseprompt"}; - Args = {"player"}; - Description = "Opens the Roblox Premium purchase prompt for the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - service.MarketplaceService:PromptPremiumPurchase(v) - end - end - }; - - RobloxNotify = { - Prefix = Settings.Prefix; - Commands = {"rbxnotify", "robloxnotify", "robloxnotif", "rblxnotify", "rnotif", "rn"}; - Args = {"player", "duration (seconds)", "text"}; - Filter = true; - Description = "Sends a Roblox-styled notification for the target player(s)"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - for _, v in service.GetPlayers(plr, args[1]) do - Remote.Send(v, "Function", "SetCore", "SendNotification", { - Title = "Notification"; - Text = args[3] or "Hello, from Adonis!"; - Duration = tonumber(args[2]) or 5; - }) - end + + return permAllowed, nil + end; + + UpdateCooldown = function(pDat, cmd) + if pDat.Player == "SYSTEM" then return end + local playerCooldown = tonumber(cmd.PlayerCooldown) + local serverCooldown = tonumber(cmd.ServerCooldown) + local crossCooldown = tonumber(cmd.CrossCooldown) + + local cmdFullName = cmd._fullName or (function() + local aliases = cmd.Aliases or cmd.Commands or {} + cmd._fullName = `{cmd.Prefix}{aliases[1] or `{service.HttpService:GenerateGUID(false)}-RANDOM_COMMAND`}` + return cmd._fullName + end)() + + local pCooldown_Cache = cmd._playerCooldownCache or (function() + local tab = {} + cmd._playerCooldownCache = tab + return tab + end)() + + local sCooldown_Cache = cmd._serverCooldownCache or (function() + local tab = {} + cmd._serverCooldownCache = tab + return tab + end)() + + local crossCooldown_Cache = cmd._crossCooldownCache or (function() + local tab = {} + cmd._crossCooldownCache = tab + return tab + end)() + + local cooldownIndex = tostring(pDat.Player.UserId) + local pCooldown_playerCache = pCooldown_Cache[cooldownIndex] + local sCooldown_playerCache = sCooldown_Cache[cooldownIndex] + local lastUsed = os.clock() + + if playerCooldown then + pCooldown_Cache[cooldownIndex] = lastUsed end - }; - Disguise = { - Prefix = Settings.Prefix; - Commands = {"disguise", "masquerade"}; - Args = {"player", "username"}; - Description = "Names the player, chars the player, and modifies the player's chat tag"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - assert(args[2], "Argument missing or nil") - local userId = Functions.GetUserIdFromNameAsync(args[2]) - assert(userId, "Invalid username supplied/user not found") + if serverCooldown then + sCooldown_Cache[cooldownIndex] = lastUsed + end - local username = select(2, xpcall(function() - return service.Players:GetNameFromUserIdAsync(userId) - end, function() return args[2] end)) + --// Cross cooldown + do + local playerData = Core.GetPlayer(pDat.Player) + local crossCooldown_Cache = playerData._crossCooldownCache or {} + local crossCooldown_playerCache = crossCooldown_Cache[cmdFullName] - if service.Players:GetPlayerByUserId(userId) then - error("You cannot disguise as this player (currently in server)") + if not crossCooldown and crossCooldown_playerCache then + crossCooldown_playerCache[cmdFullName] = nil + elseif crossCooldown then + crossCooldown_Cache[cmdFullName] = lastUsed end + end + end; - Commands.Char.Function(plr, args) - Commands.DisplayName.Function(plr, {args[1], username}) - - local ChatService = Functions.GetChatService() + SearchCommands = function(p, search) + local checkPerm = Admin.CheckPermission + local tab = {} + local pDat = { + Player = p; + Level = Admin.GetLevel(p); + isDonor = Admin.CheckDonor(p); + } - for _, v in service.GetPlayers(plr, args[1]) do - if Variables.DisguiseBindings[v.UserId] then - Variables.DisguiseBindings[v.UserId].Rename:Disconnect() - Variables.DisguiseBindings[v.UserId].Rename = nil - if ChatService then - ChatService:RemoveSpeaker(Variables.DisguiseBindings[v.UserId].TargetUsername) - ChatService:UnregisterProcessCommandsFunction(`Disguise_{v.Name}`) - end - end + for ind, cmd in Commands do + if checkPerm(pDat, cmd, true) then + tab[ind] = cmd + end + end - Variables.DisguiseBindings[v.UserId] = { - TargetUsername = username; - Rename = v.CharacterAppearanceLoaded:Connect(function(char) - Commands.DisplayName.Function(v, {v.Name, username}) - end); - } + return tab + end; - if ChatService then - local disguiseSpeaker = ChatService:AddSpeaker(username) - disguiseSpeaker:JoinChannel("All") - ChatService:RegisterProcessCommandsFunction(`Disguise_{v.Name}`, function(speaker, message, channelName) - if speaker == v.Name then - local filteredMessage = select(2, xpcall(function() - return service.TextService:FilterStringAsync(message, v.UserId, Enum.TextFilterContext.PrivateChat):GetChatForUserAsync(v.UserId) - end, function() - Remote.Send(v, "Function", "ChatMessage", "A message filtering error occurred.", Color3.new(1, 64/255, 77/255)) - return - end)) - if filteredMessage and not server.Admin.DoHideChatCmd(v, message) then - disguiseSpeaker:SayMessage(filteredMessage, channelName) - if v.Character then - service.Chat:Chat(v.Character, filteredMessage, Enum.ChatColor.White) - end - end - return true - end - return false - end) - end + CheckAuthority = function(p, target, actionName, allowSelf) + if p == target then + if allowSelf == false then + Functions.Hint(`You cannot {actionName} yourself`, {p}) + return false end + + return allowSelf or Remote.GetGui(p, "YesNoPrompt", { + Question = `Are you sure you want to {actionName} yourself?`; + }) == "Yes" + + elseif Admin.GetLevel(p) > Admin.GetLevel(target) then + return true end - }; - - UnDisguise = { - Prefix = Settings.Prefix; - Commands = {"undisguise", "removedisguise", "cleardisguise", "nodisguise"}; - Args = {"player"}; - Description = "Removes the player's disguise"; - AdminLevel = "Admins"; - Function = function(plr: Player, args: {string}) - local ChatService = Functions.GetChatService() - for _, v in service.GetPlayers(plr, args[1]) do - if Variables.DisguiseBindings[v.UserId] then - Variables.DisguiseBindings[v.UserId].Rename:Disconnect() - Variables.DisguiseBindings[v.UserId].Rename = nil - pcall(function() - ChatService:RemoveSpeaker(Variables.DisguiseBindings[v.UserId].TargetUsername) - ChatService:UnregisterProcessCommandsFunction(`Disguise_{v.Name}`) - end) - end - Variables.DisguiseBindings[v.UserId] = nil + + Functions.Hint(`You don't have permission to {actionName} {service.FormatPlayer(target)}`, {p}) + return false + end; + + GetAliases = function(player) + local aliases = table.clone(Variables.Aliases) + local pData = player and Core.GetPlayer(player) + + if pData and pData.Aliases then + for alias, command in pData.Aliases do + aliases[alias] = command end - Commands.UnChar.Function(plr, args) - Commands.UnDisplayName.Function(plr, args) end - }; + + return aliases + end; } end From 74e46e671e6b7d42de0e3eab4fd6996dda80420d Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:55:42 -0500 Subject: [PATCH 12/13] fixed __concat --- MainModule/Server/Server.luau | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MainModule/Server/Server.luau b/MainModule/Server/Server.luau index 393b3ea1aa..3294a0e6e6 100644 --- a/MainModule/Server/Server.luau +++ b/MainModule/Server/Server.luau @@ -601,7 +601,7 @@ return service.NewProxy({ if type(server.Settings.Prefix) == "table" then setmetatable(server.Settings.Prefix, { __concat = function(self, value) - return self[1] + return `{self[1]}{value}` end, __tostring = function(self) return self[1] @@ -613,6 +613,7 @@ return service.NewProxy({ + --// Bind cleanup service.DataModel:BindToClose(function(...) server.CleanUp(...) From ace859349073729155cf85588a75084b8f3ed1cf Mon Sep 17 00:00:00 2001 From: SuperCater Date: Sun, 26 Jan 2025 14:59:16 -0500 Subject: [PATCH 13/13] fix userpanel --- MainModule/Client/UI/Default/UserPanel.luau | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MainModule/Client/UI/Default/UserPanel.luau b/MainModule/Client/UI/Default/UserPanel.luau index 7d50c7f385..363fcfbdd8 100644 --- a/MainModule/Client/UI/Default/UserPanel.luau +++ b/MainModule/Client/UI/Default/UserPanel.luau @@ -497,7 +497,8 @@ return function(data, env) chatMod = Remote.Get("Setting",{"Prefix","SpecialPrefix","BatchKey","AnyPrefix","DonorCommands","DonorCapes"}) settingsData = Remote.Get("AllSettings") Variables.Aliases = playerData.Aliases or {} - commandPrefix = chatMod.Prefix + commandPrefix = if type(chatMod.Prefix) == "table" then chatMod.Prefix[1] else chatMod.Prefix + for _, v in loadingIcons do v:Destroy()