From dee2c1a9f4f81bf603bc35a40ee8e010d7c86c09 Mon Sep 17 00:00:00 2001 From: stevenh Date: Thu, 16 Feb 2012 12:03:08 +0000 Subject: [PATCH] Refactored output processing to always return RestObject's Refactored method names to be consistent with function e.g. Off -> ServerOff Added new endpoints:- /v2/server/status - Status query including player and rules matching common query tool requirements such as qstat. The json returned uses native types e.g. int's where possible /v2/groups/list - Lists groups /v2/groups/read - Query group detail /v2/groups/destroy - Remove a group /v2/groups/create - Create a group /v2/players/list - Lists players including basic details /v2/users/create - Create a user /v2/users/list - Lists users basic user details applying a filter if specified. Refactored missing parameter and error handling to use common utility methods RestError, RestMissingParam which ensures consistency across all functions and protects against null object references Removed maxplayers from /status to ensure 100% compatibility, this and more details can be found in the new end point /v2/server/status Protected against null names in user/activelist Refactored variable checks to use string.IsNullOrWhiteSpace where applicable to capture errors early in the RestAPI Added the ability to location users by "id" matching the underlying API. Refactored locating players, bans & users to utility methods to ensure consitency across the api and eliminate duplicate code making for easier maintainence NOTE: Duplicate and invalid DB entries currently can still be made if the DB format hasn't been updated to with missing unique and not null constaints Removed { RequiresToken = true } from RestCommand constructors as this is the default anyway. Optimised function calls so that tests are processed sequentially avoiding unnessasary operations in failure cases Cleaned up formatting ensuring consistent line endings, indentation and single line if layout Fixed ServerCommand duplicate key issue for multi line returns, now uses a single "response" string with embeded newlines Fixed PlayerKill output using verb instead of parameters for "from" option --- TShockAPI/Rest/RestManager.cs | 1041 +++++++++++++++++++-------------- 1 file changed, 588 insertions(+), 453 deletions(-) diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 4fd20273..ed79492e 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using HttpServer; @@ -36,395 +37,393 @@ namespace TShockAPI public void RegisterRestfulCommands() { - Rest.Register(new RestCommand("/status", Status) {RequiresToken = false}); - Rest.Register(new RestCommand("/tokentest", TokenTest) {RequiresToken = true}); + // Server Commands + Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); + Rest.Register(new RestCommand("/v2/server/off", ServerOff)); + Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); + Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); + Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); + Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); - Rest.Register(new RestCommand("/v2/users/activelist", UserListV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/read", UserInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2) { RequiresToken = true }); + // User Commands + Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); + Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); + Rest.Register(new RestCommand("/v2/users/list", UserListV2)); + Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); + Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); + Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); - Rest.Register(new RestCommand("/bans/create", BanCreate) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2) { RequiresToken = true }); + // Ban Commands + Rest.Register(new RestCommand("/bans/create", BanCreate)); + Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); + Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); + Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); - Rest.Register(new RestCommand("/v2/lists/bans", BanListIPs) { RequiresToken = true }); - Rest.Register(new RestCommand("/lists/players", PlayerList) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/lists/players", PlayerListV2) { RequiresToken = true }); + // World Commands + Rest.Register(new RestCommand("/world/read", WorldRead)); + Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); + Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); + Rest.Register(new RestCommand("/v2/world/save", WorldSave)); + Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); + Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); - Rest.Register(new RestCommand("/world/read", WorldRead) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/meteor", WorldMeteor) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/save", WorldSave) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", ChangeWorldSaveSettings) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/world/butcher", Butcher) {RequiresToken = true}); + // Player Commands + Rest.Register(new RestCommand("/lists/players", PlayerList)); + Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); + Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); + Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); + Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); + Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); + Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); + Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); - Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kill", PlayerKill) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/mute", PlayerMute) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute) {RequiresToken = true}); - - Rest.Register(new RestCommand("/v2/server/broadcast", Broadcast) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/off", Off) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand) {RequiresToken = true}); + // Group Commands + Rest.Register(new RestCommand("/v2/groups/list", GroupList)); + Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); + Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); + Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); + Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); } #region RestServerMethods private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["cmd"] != null && parameters["cmd"].Trim() != "") - { - TSRestPlayer tr = new TSRestPlayer(); - RestObject ro = new RestObject("200"); - Commands.HandleCommand(tr, parameters["cmd"]); - foreach (string s in tr.GetCommandOutput()) - { - ro.Add("response", s); - } - return ro; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Missing or blank cmd parameter."; - return fail; + if (string.IsNullOrWhiteSpace(parameters["cmd"])) + return RestMissingParam("cmd"); + + TSRestPlayer tr = new TSRestPlayer(); + Commands.HandleCommand(tr, parameters["cmd"]); + return RestResponse(string.Join("\n", tr.GetCommandOutput())); } - private object Off(RestVerbs verbs, IParameterCollection parameters) + private object ServerOff(RestVerbs verbs, IParameterCollection parameters) { - bool confirm; - bool.TryParse(parameters["confirm"], out confirm); - bool nosave; - bool.TryParse(parameters["nosave"], out nosave); + if (!GetBool(parameters["confirm"], false)) + return RestInvalidParam("confirm"); - if (confirm == true) + if (!GetBool(parameters["nosave"], false)) + WorldGen.saveWorld(); + Netplay.disconnect = true; + + // Inform players the server is shutting down + var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; + foreach (TSPlayer player in TShock.Players.Where(p => null != p)) { - if (!nosave) - WorldGen.saveWorld(); - Netplay.disconnect = true; - RestObject reply = new RestObject("200"); - reply["response"] = "The server is shutting down."; - return reply; + TShock.Utils.ForceKick(player, msg); } - RestObject fail = new RestObject("400"); - fail["response"] = "Invalid/missing confirm switch, and/or missing nosave switch."; - return fail; + return RestResponse("The server is shutting down"); } - private object Broadcast(RestVerbs verbs, IParameterCollection parameters) + private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["msg"] != null && parameters["msg"].Trim() != "") - { - TShock.Utils.Broadcast(parameters["msg"]); - RestObject reply = new RestObject("200"); - reply["response"] = "The message was broadcasted successfully."; - return reply; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Broadcast failed."; - return fail; + var msg = parameters["msg"]; + if (string.IsNullOrWhiteSpace(msg)) + return RestMissingParam("msg"); + TShock.Utils.Broadcast(msg); + return RestResponse("The message was broadcasted successfully"); } - #endregion - - #region RestMethods - - private object TokenTest(RestVerbs verbs, IParameterCollection parameters) - { - return new Dictionary - {{"status", "200"}, {"response", "Token is valid and was passed through correctly."}}; - } - - private object Status(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatus(RestVerbs verbs, IParameterCollection parameters) { if (TShock.Config.EnableTokenEndpointAuthentication) - return new RestObject("403") {Error = "Server settings require a token for this API call."}; + return RestError("Server settings require a token for this API call"); - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", Convert.ToString(TShock.Config.ServerPort)}, + {"playercount", Convert.ToString(activeplayers.Count())}, + {"players", string.Join(", ", activeplayers.Select(p => p.name))}, + }; + } - var ret = new RestObject("200"); - ret["name"] = TShock.Config.ServerNickname; - ret["port"] = Convert.ToString(TShock.Config.ServerPort); - ret["playercount"] = Convert.ToString(activeplayers.Count()); - ret["players"] = currentPlayers; + private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters) + { + if (TShock.Config.EnableTokenEndpointAuthentication) + return RestError("Server settings require a token for this API call"); - ret["maxplayers"] = TShock.Config.MaxSlots; + var ret = new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", TShock.Config.ServerPort}, + {"playercount", Main.player.Where(p => null != p && p.active).Count()}, + {"maxplayers", TShock.Config.MaxSlots}, + {"world", Main.worldName} + }; + + if (GetBool(parameters["players"], false)) + { + var players = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + players.Add(p); + } + ret.Add("players", players); + } + + if (GetBool(parameters["rules"], false)) + { + var rules = new Dictionary(); + rules.Add("AutoSave", TShock.Config.AutoSave); + rules.Add("DisableBuild", TShock.Config.DisableBuild); + rules.Add("DisableClownBombs", TShock.Config.DisableClownBombs); + rules.Add("DisableDungeonGuardian", TShock.Config.DisableDungeonGuardian); + rules.Add("DisableInvisPvP", TShock.Config.DisableInvisPvP); + rules.Add("DisableSnowBalls", TShock.Config.DisableSnowBalls); + rules.Add("DisableTombstones", TShock.Config.DisableTombstones); + rules.Add("EnableWhitelist", TShock.Config.EnableWhitelist); + rules.Add("HardcoreOnly", TShock.Config.HardcoreOnly); + rules.Add("PvPMode", TShock.Config.PvPMode); + rules.Add("SpawnProtection", TShock.Config.SpawnProtection); + rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + + ret.Add("rules", rules); + } return ret; } + private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) + { + return RestResponse("Token is valid and was passed through correctly"); + } + #endregion #region RestUserMethods + private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters) + { + return new RestObject() { { "activeusers", string.Join("\t", TShock.Players.Where(p => null != p && null != p.UserAccountName && p.Active).Select(p => p.UserAccountName)) } }; + } + private object UserListV2(RestVerbs verbs, IParameterCollection parameters) { - string playerlist = ""; - foreach (var TSPlayer in TShock.Players) + return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary(){ + {"name", p.Name}, + {"id", p.ID}, + {"group", p.Group}, + {"ip", p.Address}, + }) } }; + } + + private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) + { + var username = parameters["user"]; + if (string.IsNullOrWhiteSpace(username)) + return RestMissingParam("user"); + + var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group)) + return RestMissingParam("group"); + + var password = parameters["password"]; + if (string.IsNullOrWhiteSpace(password)) + return RestMissingParam("password"); + + // NOTE: ip can be blank + User user = new User(parameters["ip"], username, password, group); + try { - if (TSPlayer == null) - { - continue; - } - playerlist += playerlist == "" ? TSPlayer.UserAccountName : "\t" + TSPlayer.UserAccountName; + TShock.Users.AddUser(user); } - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("activeusers", playerlist); - return returnBlock; + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("User was successfully created"); } private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + var password = parameters["password"]; var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group) && string.IsNullOrWhiteSpace(password)) + return RestMissingParam("group", "password"); - if (group == null && password == null) + User user = (User)ret; + var response = new RestObject(); + if (!string.IsNullOrWhiteSpace(password)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameters were passed."); - return returnBlock; + try + { + TShock.Users.SetUserPassword(user, password); + response.Add("password-response", "Password updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user password (" + e.Message + ")"); + } } - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) + if (!string.IsNullOrWhiteSpace(group)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specefied user doesn't exist."); - return returnBlock; + try + { + TShock.Users.SetUserGroup(user, group); + response.Add("group-response", "Group updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user group (" + e.Message + ")"); + } } - if (password != null) - { - TShock.Users.SetUserPassword(user, password); - returnBlock.Add("password-response", "Password updated successfully."); - } - - if (group != null) - { - TShock.Users.SetUserGroup(user, group); - returnBlock.Add("group-response", "Group updated successfully."); - } - - returnBlock.Add("status", "200"); - return returnBlock; + return response; } private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + try { - TShock.Users.RemoveUser(user); + TShock.Users.RemoveUser((User)ret); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified user was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "User deleted successfully."); - return returnBlock; + + return RestResponse("User deleted successfully"); } private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("group", user.Group); - returnBlock.Add("id", user.ID.ToString()); - return returnBlock; + User user = (User)ret; + return new RestObject() { { "group", user.Group }, { "id", user.ID.ToString() } }; } #endregion #region RestBanMethods - private object BanListIPs(RestVerbs verbs, IParameterCollection parameters) - { - RestObject returnItem = new RestObject("200"); - returnItem.Add("bans", TShock.Bans.GetBans()); - - return returnItem; - } - private object BanCreate(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); var ip = parameters["ip"]; var name = parameters["name"]; - var reason = parameters["reason"]; - if (ip == null && name == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Required parameters were missing from this API endpoint."); - return returnBlock; - } - - if (ip == null) - { - ip = ""; - } - - if (name == null) - { - name = ""; - } - - if (reason == null) - { - reason = ""; - } + if (string.IsNullOrWhiteSpace(ip) && string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ip", "name"); try { - TShock.Bans.AddBan(ip, name, reason); + TShock.Bans.AddBan(ip, name, parameters["reason"], true); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be created."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban created successfully."); - return returnBlock; + return RestResponse("Ban created successfully"); } private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - - var type = parameters["type"]; - if (type == null) - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified ban does not exist."}}; - } + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; try { - TShock.Bans.RemoveBan(ban.IP); + Ban ban = (Ban)ret; + switch (parameters["type"]) + { + case "ip": + if (!TShock.Bans.RemoveBan(ban.IP, false, false, true)) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + case "name": + if (!TShock.Bans.RemoveBan(ban.Name, true, GetBool(parameters["caseinsensitive"], true))) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + default: + return RestError("Invalid Type: '" + parameters["type"] + "'"); + } + } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban deleted successfully."); - return returnBlock; + + return RestResponse("Ban deleted successfully"); } private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; - var type = parameters["type"]; - if (type == null) + Ban ban = (Ban)ret; + return new RestObject() { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + }; + } + + private object BanListV2(RestVerbs verbs, IParameterCollection parameters) + { + var banList = new ArrayList(); + foreach (var ban in TShock.Bans.GetBans()) { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; + banList.Add( + new Dictionary + { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + } + ); } - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary { { "status", "400" }, { "error", "The specified ban does not exist." } }; - } - - returnBlock.Add("status", "200"); - returnBlock.Add("name", ban.Name); - returnBlock.Add("ip", ban.IP); - returnBlock.Add("reason", ban.Reason); - return returnBlock; + return new RestObject() { { "bans", banList } }; } #endregion #region RestWorldMethods - private object ChangeWorldSaveSettings(RestVerbs verbs, IParameterCollection parameters) + private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) { - bool state; - bool.TryParse(verbs["state"], out state); + bool autoSave; + if (!bool.TryParse(verbs["bool"], out autoSave)) + return RestInvalidParam("state"); + TShock.Config.AutoSave = autoSave; - if (state == true) - { - TShock.Config.AutoSave = true; - } - else - { - TShock.Config.AutoSave = false; - } - - RestObject rj = new RestObject("200"); - rj["response"] = "Value changed"; - rj["state"] = state; - - return rj; + return RestResponse("AutoSave has been set to " + autoSave); } private object WorldSave(RestVerbs verbs, IParameterCollection parameters) { TShock.Utils.SaveWorld(); - RestObject rj = new RestObject("200"); - rj["response"] = "World saved."; - return rj; + return RestResponse("World saved"); } - private object Butcher(RestVerbs verbs, IParameterCollection parameters) + private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) { bool killFriendly; if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) - { - RestObject fail = new RestObject("400"); - fail["response"] = "The given value for killfriendly wasn't a boolean value."; - return fail; - } + return RestInvalidParam("killfriendly"); + if (killFriendly) - { killFriendly = !killFriendly; - } int killcount = 0; for (int i = 0; i < Main.npc.Length; i++) @@ -436,54 +435,38 @@ namespace TShockAPI } } - RestObject rj = new RestObject("200"); - rj["response"] = killcount + " NPCs have been killed."; - return rj; + return RestResponse(killcount + " NPCs have been killed"); } private object WorldRead(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("name", Main.worldName); - returnBlock.Add("size", Main.maxTilesX + "*" + Main.maxTilesY); - returnBlock.Add("time", Main.time); - returnBlock.Add("daytime", Main.dayTime); - returnBlock.Add("bloodmoon", Main.bloodMoon); - returnBlock.Add("invasionsize", Main.invasionSize); - return returnBlock; + return new RestObject() + { + {"name", Main.worldName}, + {"size", Main.maxTilesX + "*" + Main.maxTilesY}, + {"time", Main.time}, + {"daytime", Main.dayTime}, + {"bloodmoon", Main.bloodMoon}, + {"invasionsize", Main.invasionSize} + }; } private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) { - if (WorldGen.genRand == null) + if (null == WorldGen.genRand) WorldGen.genRand = new Random(); WorldGen.dropMeteor(); - var returnBlock = new Dictionary {{"status", "200"}, {"response", "Meteor has been spawned."}}; - return returnBlock; + return RestResponse("Meteor has been spawned"); } private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var bloodmoonVerb = verbs["bool"]; bool bloodmoon; - if (bloodmoonVerb == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameter was passed."); - return returnBlock; - } - if (!bool.TryParse(bloodmoonVerb, out bloodmoon)) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Unable to parse parameter."); - return returnBlock; - } + if (!bool.TryParse(verbs["bool"], out bloodmoon)) + return RestInvalidParam("bloodmoon"); Main.bloodMoon = bloodmoon; - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Blood Moon has been set to " + bloodmoon); - return returnBlock; + + return RestResponse("Blood Moon has been set to " + bloodmoon); } #endregion @@ -492,190 +475,342 @@ namespace TShockAPI private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = false; - player.SendMessage("You have been remotely unmuted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; + return PlayerSetMute(parameters, false); } private object PlayerMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = true; - player.SendMessage("You have been remotely muted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; - } - - private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) - { - RestObject returnBlock = new RestObject("200"); - returnBlock.Add("players", TShock.Players.Where(p => p != null && p.Active)); - return returnBlock; + return PlayerSetMute(parameters, true); } private object PlayerList(RestVerbs verbs, IParameterCollection parameters) { - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); - var ret = new RestObject("200"); - ret["players"] = currentPlayers; - ret.Add("deprecated", "This endpoint is deprecated and has been replaced with /v2/lists/players."); - return ret; + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; + } + + private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) + { + var playerList = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + playerList.Add(p); + } + return new RestObject() { { "players", playerList } }; } private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); + return new RestObject() { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - returnBlock.Add("status", "200"); - returnBlock.Add("nickname", player.Name); - returnBlock.Add("username", player.UserAccountName == null ? "" : player.UserAccountName); - returnBlock.Add("ip", player.IP); - returnBlock.Add("group", player.Group.Name); - returnBlock.Add("position", player.TileX + "," + player.TileY); - var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); - returnBlock.Add("inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))); - returnBlock.Add("buffs", string.Join(", ", player.TPlayer.buffType)); - } - return returnBlock; + {"nickname", player.Name}, + {"username", null == player.UserAccountName ? "" : player.UserAccountName}, + {"ip", player.IP}, + {"group", player.Group.Name}, + {"position", player.TileX + "," + player.TileY}, + {"inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))}, + {"buffs", string.Join(", ", player.TPlayer.buffType)} + }; } private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - if (playerParam == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Unspecified name."); - return returnBlock; - } - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Utils.ForceKick(player, reason == null ? "Kicked via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was kicked"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + TShock.Utils.ForceKick(player, null == parameters["reason"] ? "Kicked via web" : parameters["reason"]); + return RestResponse("Player " + player.Name + " was kicked"); } private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Bans.AddBan(player.IP, player.Name, reason == null ? "Banned via web" : reason); - TShock.Utils.ForceKick(player, reason == null ? "Banned via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was banned"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var reason = null == parameters["reason"] ? "Banned via web" : parameters["reason"]; + TShock.Bans.AddBan(player.IP, player.Name, reason); + TShock.Utils.ForceKick(player, reason); + return RestResponse("Player " + player.Name + " was banned"); } private object PlayerKill(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var from = verbs["from"]; - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.DamagePlayer(999999); + var from = string.IsNullOrWhiteSpace(parameters["from"]) ? "Server Admin" : parameters["from"]; + player.SendMessage(string.Format("{0} just killed you!", from)); + return RestResponse("Player " + player.Name + " was killed"); + } + + #endregion + + #region RestGroupMethods + + private object GroupList(RestVerbs verbs, IParameterCollection parameters) + { + var groups = new ArrayList(); + foreach (Group group in TShock.Groups) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); + groups.Add(new Dictionary {{"name", group.Name}, {"parent", group.ParentName}, {"chatcolor", group.ChatColor}}); } - else if (found.Count > 1) + return new RestObject() { { "groups", groups } }; + } + + private object GroupInfo(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + return new RestObject() { + {"name", group.Name}, + {"parent", group.ParentName}, + {"chatcolor", group.ChatColor}, + {"permissions", group.permissions}, + {"negatedpermissions", group.negatedpermissions} + }; + } + + private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + try { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); + TShock.Groups.DeleteGroup(group.Name, true); } - else if (found.Count == 1) + catch (Exception e) { - var player = found[0]; - player.DamagePlayer(999999); - player.SendMessage(string.Format("{0} just killed you!", from)); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was killed."); + return RestError(e.Message); } - return returnBlock; + + return RestResponse("Group '" + group.Name + "' deleted successfully"); + } + + private object GroupCreate(RestVerbs verbs, IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + try + { + TShock.Groups.AddGroup(name, parameters["parent"], parameters["permissions"], parameters["chatcolor"], true); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + name + "' created successfully"); + } + + private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + var parent = (null == parameters["parent"]) ? group.ParentName : parameters["parent"]; + var chatcolor = (null == parameters["chatcolor"]) ? group.ChatColor : parameters["chatcolor"]; + var permissions = (null == parameters["permissions"]) ? group.Permissions : parameters["permissions"]; + try + { + TShock.Groups.UpdateGroup(group.Name, parent, permissions, chatcolor); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + group.Name + "' updated successfully"); + } + + #endregion + + #region Utility Methods + + private RestObject RestError(string message, string status = "400") + { + return new RestObject(status) {Error = message}; + } + + private RestObject RestResponse(string message, string status = "200") + { + return new RestObject(status) {Response = message}; + } + + private RestObject RestMissingParam(string var) + { + return RestError("Missing or empty " + var + " parameter"); + } + + private RestObject RestMissingParam(params string[] vars) + { + return RestMissingParam(string.Join(", ", vars)); + } + + private RestObject RestInvalidParam(string var) + { + return RestError("Missing or invalid " + var + " parameter"); + } + + private bool GetBool(string val, bool def) + { + bool ret; + return bool.TryParse(val, out ret) ? ret : def; + } + + private object PlayerFind(IParameterCollection parameters) + { + string name = parameters["player"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("player"); + + var found = TShock.Utils.FindPlayer(name); + switch(found.Count) + { + case 1: + return found[0]; + case 0: + return RestError("Player " + name + " was not found"); + default: + return RestError("Player " + name + " matches " + found.Count + " players"); + } + } + + private object UserFind(IParameterCollection parameters) + { + string name = parameters["user"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("user"); + + User user; + string type = parameters["type"]; + try + { + switch (type) + { + case null: + case "name": + type = "name"; + user = TShock.Users.GetUserByName(name); + break; + case "id": + user = TShock.Users.GetUserByID(Convert.ToInt32(name)); + break; + case "ip": + user = TShock.Users.GetUserByIP(name); + + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + } + catch (Exception e) + { + return RestError(e.Message); + } + + if (null == user) + return RestError(String.Format("User {0} '{1}' doesn't exist", type, name)); + + return user; + } + + private object BanFind(IParameterCollection parameters) + { + string name = parameters["ban"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ban"); + + string type = parameters["type"]; + if (string.IsNullOrWhiteSpace(type)) + return RestMissingParam("type"); + + Ban ban; + switch (type) + { + case "ip": + ban = TShock.Bans.GetBanByIp(name); + break; + case "name": + ban = TShock.Bans.GetBanByName(name, GetBool(parameters["caseinsensitive"], true)); + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + + if (null == ban) + return RestError("Ban " + type + " '" + name + "' doesn't exist"); + + return ban; + } + + private object GroupFind(IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + + var group = TShock.Groups.GetGroupByName(name); + if (null == group) + return RestError("Group '" + name + "' doesn't exist"); + + return group; + } + + private Dictionary PlayerFilter(TSPlayer tsPlayer, IParameterCollection parameters) + { + var player = new Dictionary + { + {"nickname", tsPlayer.Name}, + {"username", null == tsPlayer.UserAccountName ? "" : tsPlayer.UserAccountName}, + {"ip", tsPlayer.IP}, + {"group", tsPlayer.Group.Name}, + {"active", tsPlayer.Active}, + {"state", tsPlayer.State}, + {"team", tsPlayer.Team}, + }; + foreach (IParameter filter in parameters) + { + if (player.ContainsKey(filter.Name) && !player[filter.Name].Equals(filter.Value)) + return null; + } + return player; + } + + private object PlayerSetMute(IParameterCollection parameters, bool mute) + { + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.mute = mute; + var verb = mute ? "muted" : "unmuted"; + player.SendMessage("You have been remotely " + verb); + return RestResponse("Player " + player.Name + " was " + verb); } #endregion