Merge pull request #2170 from Pryaxis/new-bans
New bans + some other stuff
This commit is contained in:
commit
3b1502c28f
12 changed files with 1250 additions and 904 deletions
|
|
@ -13,6 +13,14 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
|
|||
* If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change.
|
||||
|
||||
## Upcoming Changes
|
||||
* Overhauled Bans system. Bans are now based on 'identifiers'. (@QuiCM)
|
||||
* The old Bans table (`Bans`) has been deprecated. New bans will go in `PlayerBans`. Old bans will be converted automatically to the new system.
|
||||
* All old ban routes in REST are now redirected. Please use `/v3/bans/*` for REST-based ban management.
|
||||
* TShock recognizes and acts upon 4 main identifiers: UUID, IP, Player Name, Account name. This can be extended by plugins. New identifiers can be added to the `ban help identifiers` output by registering them in `TShockAPI.DB.Identifier.Register(string, string)`
|
||||
* By default, bans are no longer removed upon expiry or 'deletion'. Instead, they remain in the system. A new ban for an indentifier can be added once an existing ban has expired.
|
||||
* Server Console now understands Terraria color codes (e.g., `[c/FF00FF:Words]`) and prints the colored text to the console. Note that console colors are limited and thus only approximations. (@QuiCM)
|
||||
* Fixed a bug in `/sudo` that prevented quoted arguments being forwarded properly. Example: `/sudo /user group "user name" "user group"` should now work correctly. (@QuiCM)
|
||||
* Shutting down the server should now correctly display the shutdown message to players rather than 'Lost connection'. (@QuiCM)
|
||||
|
||||
## TShock 4.4.0 (Pre-release 14)
|
||||
* Terraria v1.4.1.2 (Thanks @Patrikkk and @DeathCradle <3)
|
||||
|
|
|
|||
|
|
@ -1267,328 +1267,369 @@ namespace TShockAPI
|
|||
|
||||
private static void Ban(CommandArgs args)
|
||||
{
|
||||
//Ban syntax:
|
||||
// ban add <target> [reason] [duration] [flags (default: -a -u -ip)]
|
||||
// Duration is in the format 0d0h0m0s. Any part can be ignored. E.g., 1s is a valid ban time, as is 1d1s, etc. If no duration is specified, ban is permanent
|
||||
// Valid flags: -a (ban account name), -u (ban UUID), -n (ban character name), -ip (ban IP address), -e (exact, ban the identifier provided as 'target')
|
||||
// Unless -e is passed to the command, <target> is assumed to be a player or player index.
|
||||
// ban del <ban ID>
|
||||
// Target is expected to be a ban Unique ID
|
||||
// ban list [page]
|
||||
// Displays a paginated list of bans
|
||||
// ban details <ban ID>
|
||||
// Target is expected to be a ban Unique ID
|
||||
//ban help [command]
|
||||
// Provides extended help on specific ban commands
|
||||
|
||||
void Help()
|
||||
{
|
||||
if (args.Parameters.Count > 1)
|
||||
{
|
||||
MoreHelp(args.Parameters[1].ToLower());
|
||||
return;
|
||||
}
|
||||
|
||||
args.Player.SendMessage("TShock Ban Help", Color.White);
|
||||
args.Player.SendMessage("Available Ban commands:", Color.White);
|
||||
args.Player.SendMessage($"ban {"add".Color(Utils.RedHighlight)} <Target> [Flags]", Color.White);
|
||||
args.Player.SendMessage($"ban {"del".Color(Utils.RedHighlight)} <Ban ID>", Color.White);
|
||||
args.Player.SendMessage($"ban {"list".Color(Utils.RedHighlight)}", Color.White);
|
||||
args.Player.SendMessage($"ban {"details".Color(Utils.RedHighlight)} <Ban ID>", Color.White);
|
||||
args.Player.SendMessage($"Quick usage: {"ban add".Color(Utils.BoldHighlight)} {args.Player.Name.Color(Utils.RedHighlight)} \"Griefing\"", Color.White);
|
||||
args.Player.SendMessage($"For more info, use {"ban help".Color(Utils.BoldHighlight)} {"command".Color(Utils.RedHighlight)}", Color.White);
|
||||
}
|
||||
|
||||
void MoreHelp(string cmd)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case "add":
|
||||
args.Player.SendMessage("", Color.White);
|
||||
args.Player.SendMessage("Ban Add Syntax", Color.White);
|
||||
args.Player.SendMessage($"{"ban add".Color(Utils.BoldHighlight)} <{"Target".Color(Utils.RedHighlight)}> [{"Reason".Color(Utils.BoldHighlight)}] [{"Duration".Color(Utils.PinkHighlight)}] [{"Flags".Color(Utils.GreenHighlight)}]", Color.White);
|
||||
args.Player.SendMessage($"- {"Duration".Color(Utils.PinkHighlight)}: uses the format {"0d0m0s".Color(Utils.PinkHighlight)} to determine the length of the ban.", Color.White);
|
||||
args.Player.SendMessage($" Eg a value of {"10d30m0s".Color(Utils.PinkHighlight)} would represent 10 days, 30 minutes, 0 seconds.", Color.White);
|
||||
args.Player.SendMessage($" If no duration is provided, the ban will be permanent.", Color.White);
|
||||
args.Player.SendMessage($"- {"Flags".Color(Utils.GreenHighlight)}: -a (account name), -u (UUID), -n (character name), -ip (IP address), -e (exact, {"Target".Color(Utils.RedHighlight)} will be treated as identifier)", Color.White);
|
||||
args.Player.SendMessage($" Unless {"-e".Color(Utils.GreenHighlight)} is passed to the command, {"Target".Color(Utils.RedHighlight)} is assumed to be a player or player index", Color.White);
|
||||
args.Player.SendMessage($" If no {"Flags".Color(Utils.GreenHighlight)} are specified, the command uses {"-a -u -ip".Color(Utils.GreenHighlight)} by default.", Color.White);
|
||||
args.Player.SendMessage($"Example usage: {"ban add".Color(Utils.BoldHighlight)} {args.Player.Name.Color(Utils.RedHighlight)} {"\"Cheating\"".Color(Utils.BoldHighlight)} {"10d30m0s".Color(Utils.PinkHighlight)} {"-a -u -ip".Color(Utils.GreenHighlight)}", Color.White);
|
||||
break;
|
||||
|
||||
case "del":
|
||||
args.Player.SendMessage("", Color.White);
|
||||
args.Player.SendMessage("Ban Del Syntax", Color.White);
|
||||
args.Player.SendMessage($"{"ban del".Color(Utils.BoldHighlight)} <{"Ticket Number".Color(Utils.RedHighlight)}>", Color.White);
|
||||
args.Player.SendMessage($"- {"Ticket Number".Color(Utils.RedHighlight)}s are provided when you add a ban, and can also be viewed with the {"ban list".Color(Utils.BoldHighlight)} command.", Color.White);
|
||||
args.Player.SendMessage($"Example usage: {"ban del".Color(Utils.BoldHighlight)} {"12345".Color(Utils.RedHighlight)}", Color.White);
|
||||
break;
|
||||
|
||||
case "list":
|
||||
args.Player.SendMessage("", Color.White);
|
||||
args.Player.SendMessage("Ban List Syntax", Color.White);
|
||||
args.Player.SendMessage($"{"ban list".Color(Utils.BoldHighlight)} [{"Page".Color(Utils.PinkHighlight)}]", Color.White);
|
||||
args.Player.SendMessage("- Lists active bans. Color trends towards green as the ban approaches expiration", Color.White);
|
||||
args.Player.SendMessage($"Example usage: {"ban list".Color(Utils.BoldHighlight)}", Color.White);
|
||||
break;
|
||||
|
||||
case "details":
|
||||
args.Player.SendMessage("", Color.White);
|
||||
args.Player.SendMessage("Ban Details Syntax", Color.White);
|
||||
args.Player.SendMessage($"{"ban details".Color(Utils.BoldHighlight)} <{"Ticket Number".Color(Utils.RedHighlight)}>", Color.White);
|
||||
args.Player.SendMessage($"- {"Ticket Number".Color(Utils.RedHighlight)}s are provided when you add a ban, and can be found with the {"ban list".Color(Utils.BoldHighlight)} command.", Color.White);
|
||||
args.Player.SendMessage($"Example usage: {"ban details".Color(Utils.BoldHighlight)} {"12345".Color(Utils.RedHighlight)}", Color.White);
|
||||
break;
|
||||
|
||||
case "identifiers":
|
||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 2, args.Player, out int pageNumber))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid page number. Page number should be numeric.", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
var idents = from ident in Identifier.Available
|
||||
select $"{ident.Color(Utils.RedHighlight)} - {ident.Description}";
|
||||
|
||||
args.Player.SendMessage("", Color.White);
|
||||
PaginationTools.SendPage(args.Player, pageNumber, idents.ToList(),
|
||||
new PaginationTools.Settings
|
||||
{
|
||||
HeaderFormat = "Available identifiers ({0}/{1}):",
|
||||
FooterFormat = "Type {0}ban help identifiers {{0}} for more.".SFormat(Specifier),
|
||||
NothingToDisplayString = "There are currently no available identifiers.",
|
||||
HeaderTextColor = Color.White,
|
||||
LineTextColor = Color.White
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
args.Player.SendMessage($"Unknown ban command. Try {"add".Color(Utils.RedHighlight)}, {"del".Color(Utils.RedHighlight)}, {"list".Color(Utils.RedHighlight)}, or {"details".Color(Utils.RedHighlight)}.", Color.White);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayBanDetails(Ban ban)
|
||||
{
|
||||
args.Player.SendMessage($"{"Ban Details".Color(Utils.BoldHighlight)} - Ticket Number: {ban.TicketNumber.Color(Utils.GreenHighlight)}", Color.White);
|
||||
args.Player.SendMessage($"{"Identifier:".Color(Utils.BoldHighlight)} {ban.Identifier}", Color.White);
|
||||
args.Player.SendMessage($"{"Reason:".Color(Utils.BoldHighlight)} {ban.Reason}", Color.White);
|
||||
args.Player.SendMessage($"{"Banned by:".Color(Utils.BoldHighlight)} {ban.BanningUser.Color(Utils.GreenHighlight)} on {ban.BanDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({ban.GetPrettyTimeSinceBanString().Color(Utils.YellowHighlight)} ago)", Color.White);
|
||||
if (ban.ExpirationDateTime < DateTime.UtcNow)
|
||||
{
|
||||
args.Player.SendMessage($"{"Ban expired:".Color(Utils.BoldHighlight)} {ban.ExpirationDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({ban.GetPrettyExpirationString().Color(Utils.YellowHighlight)} ago)", Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
string remaining;
|
||||
if (ban.ExpirationDateTime == DateTime.MaxValue)
|
||||
{
|
||||
remaining = "Never".Color(Utils.YellowHighlight);
|
||||
}
|
||||
else
|
||||
{
|
||||
remaining = $"{ban.GetPrettyExpirationString().Color(Utils.YellowHighlight)} remaining";
|
||||
}
|
||||
|
||||
args.Player.SendMessage($"{"Ban expires:".Color(Utils.BoldHighlight)} {ban.ExpirationDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({remaining})", Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
AddBanResult DoBan(string ident, string reason, DateTime expiration)
|
||||
{
|
||||
AddBanResult banResult = TShock.Bans.InsertBan(ident, reason, args.Player.Account.Name, DateTime.UtcNow, expiration);
|
||||
if (banResult.Ban != null)
|
||||
{
|
||||
args.Player.SendSuccessMessage($"Ban added. Ticket Number {banResult.Ban.TicketNumber.Color(Utils.GreenHighlight)} was created for identifier {ident.Color(Utils.WhiteHighlight)}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Player.SendWarningMessage($"Failed to add ban for identifier: {ident.Color(Utils.WhiteHighlight)}");
|
||||
args.Player.SendWarningMessage($"Reason: {banResult.Message}");
|
||||
}
|
||||
|
||||
return banResult;
|
||||
}
|
||||
|
||||
void AddBan()
|
||||
{
|
||||
if (!args.Parameters.TryGetValue(1, out string target))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ban Add syntax. Refer to {"ban help add".Color(Utils.BoldHighlight)} for details on how to use the {"ban add".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
bool exactTarget = args.Parameters.Any(p => p == "-e");
|
||||
bool banAccount = args.Parameters.Any(p => p == "-a");
|
||||
bool banUuid = args.Parameters.Any(p => p == "-u");
|
||||
bool banName = args.Parameters.Any(p => p == "-n");
|
||||
bool banIp = args.Parameters.Any(p => p == "-ip");
|
||||
|
||||
List<string> flags = new List<string>() { "-e", "-a", "-u", "-n", "-ip" };
|
||||
|
||||
string reason = "Banned.";
|
||||
string duration = null;
|
||||
DateTime expiration = DateTime.MaxValue;
|
||||
|
||||
//This is hacky. We want flag values to be independent of order so we must force the consecutive ordering of the 'reason' and 'duration' parameters,
|
||||
//while still allowing them to be placed arbitrarily in the parameter list.
|
||||
//As an example, the following parameter lists (and more) should all be acceptable:
|
||||
//-u "reason" -a duration -ip
|
||||
//"reason" duration -u -a -ip
|
||||
//-u -a -ip "reason" duration
|
||||
//-u -a -ip
|
||||
for (int i = 2; i < args.Parameters.Count; i++)
|
||||
{
|
||||
var param = args.Parameters[i];
|
||||
if (!flags.Contains(param))
|
||||
{
|
||||
reason = param;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 3; i < args.Parameters.Count; i++)
|
||||
{
|
||||
var param = args.Parameters[i];
|
||||
if (!flags.Contains(param))
|
||||
{
|
||||
duration = param;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (TShock.Utils.TryParseTime(duration, out int seconds))
|
||||
{
|
||||
expiration = DateTime.UtcNow.AddSeconds(seconds);
|
||||
}
|
||||
|
||||
//If no flags were specified, default to account, uuid, and IP
|
||||
if (!exactTarget && !banAccount && !banUuid && !banName && !banIp)
|
||||
{
|
||||
banAccount = banUuid = banIp = true;
|
||||
}
|
||||
|
||||
reason = reason ?? "Banned";
|
||||
|
||||
if (exactTarget)
|
||||
{
|
||||
DoBan(target, reason, expiration);
|
||||
return;
|
||||
}
|
||||
|
||||
var players = TSPlayer.FindByNameOrID(target);
|
||||
|
||||
if (players.Count > 1)
|
||||
{
|
||||
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
if (players.Count < 1)
|
||||
{
|
||||
args.Player.SendErrorMessage("Could not find the target specified. Check that you have the correct spelling.");
|
||||
return;
|
||||
}
|
||||
|
||||
var player = players[0];
|
||||
AddBanResult banResult = null;
|
||||
|
||||
if (banAccount)
|
||||
{
|
||||
if (player.Account != null)
|
||||
{
|
||||
banResult = DoBan($"{Identifier.Account}{player.Account.Name}", reason, expiration);
|
||||
}
|
||||
}
|
||||
|
||||
if (banUuid)
|
||||
{
|
||||
banResult = DoBan($"{Identifier.UUID}{player.UUID}", reason, expiration);
|
||||
}
|
||||
|
||||
if (banName)
|
||||
{
|
||||
banResult = DoBan($"{Identifier.Name}{player.Name}", reason, expiration);
|
||||
}
|
||||
|
||||
if (banIp)
|
||||
{
|
||||
banResult = DoBan($"{Identifier.IP}{player.IP}", reason, expiration);
|
||||
}
|
||||
|
||||
if (banResult?.Ban != null)
|
||||
{
|
||||
player.Disconnect($"You have been banned: {banResult.Ban.Reason}.");
|
||||
}
|
||||
}
|
||||
|
||||
void DelBan()
|
||||
{
|
||||
if (!args.Parameters.TryGetValue(1, out string target))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ban Del syntax. Refer to {"ban help del".Color(Utils.BoldHighlight)} for details on how to use the {"ban del".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(target, out int banId))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ticket Number. Refer to {"ban help del".Color(Utils.BoldHighlight)} for details on how to use the {"ban del".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TShock.Bans.RemoveBan(banId))
|
||||
{
|
||||
TShock.Log.ConsoleInfo($"Ban {banId} has been revoked by {args.Player.Account.Name}.");
|
||||
args.Player.SendSuccessMessage($"Ban {banId.Color(Utils.GreenHighlight)} has now been marked as expired.");
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Player.SendErrorMessage("Failed to remove ban.");
|
||||
}
|
||||
}
|
||||
|
||||
void ListBans()
|
||||
{
|
||||
string PickColorForBan(Ban ban)
|
||||
{
|
||||
double hoursRemaining = (ban.ExpirationDateTime - DateTime.UtcNow).TotalHours;
|
||||
double hoursTotal = (ban.ExpirationDateTime - ban.BanDateTime).TotalHours;
|
||||
double percentRemaining = TShock.Utils.Clamp(hoursRemaining / hoursTotal, 100, 0);
|
||||
|
||||
int red = TShock.Utils.Clamp((int)(255 * 2.0f * percentRemaining), 255, 0);
|
||||
int green = TShock.Utils.Clamp((int)(255 * (2.0f * (1 - percentRemaining))), 255, 0);
|
||||
|
||||
return $"{red:X2}{green:X2}{0:X2}";
|
||||
}
|
||||
|
||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out int pageNumber))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ban List syntax. Refer to {"ban help list".Color(Utils.BoldHighlight)} for details on how to use the {"ban list".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
var bans = from ban in TShock.Bans.Bans
|
||||
where ban.Value.ExpirationDateTime > DateTime.UtcNow
|
||||
orderby ban.Value.ExpirationDateTime ascending
|
||||
select $"[{ban.Key.Color(Utils.GreenHighlight)}] {ban.Value.Identifier.Color(PickColorForBan(ban.Value))}";
|
||||
|
||||
PaginationTools.SendPage(args.Player, pageNumber, bans.ToList(),
|
||||
new PaginationTools.Settings
|
||||
{
|
||||
HeaderFormat = "Bans ({0}/{1}):",
|
||||
FooterFormat = "Type {0}ban list {{0}} for more.".SFormat(Specifier),
|
||||
NothingToDisplayString = "There are currently no active bans."
|
||||
});
|
||||
}
|
||||
|
||||
void BanDetails()
|
||||
{
|
||||
if (!args.Parameters.TryGetValue(1, out string target))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ban Details syntax. Refer to {"ban help details".Color(Utils.BoldHighlight)} for details on how to use the {"ban details".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(target, out int banId))
|
||||
{
|
||||
args.Player.SendMessage($"Invalid Ticket Number. Refer to {"ban help details".Color(Utils.BoldHighlight)} for details on how to use the {"ban details".Color(Utils.BoldHighlight)} command", Color.White);
|
||||
return;
|
||||
}
|
||||
|
||||
Ban ban = TShock.Bans.GetBanById(banId);
|
||||
|
||||
if (ban == null)
|
||||
{
|
||||
args.Player.SendErrorMessage("No bans found matching the provided ticket number");
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayBanDetails(ban);
|
||||
}
|
||||
|
||||
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
|
||||
switch (subcmd)
|
||||
{
|
||||
case "add":
|
||||
#region Add Ban
|
||||
{
|
||||
if (args.Parameters.Count < 2)
|
||||
{
|
||||
args.Player.SendErrorMessage("Invalid command. Format: {0}ban add <player> [time] [reason]", Specifier);
|
||||
args.Player.SendErrorMessage("Example: {0}ban add Shank 10d Hacking and cheating", Specifier);
|
||||
args.Player.SendErrorMessage("Example: {0}ban add Ash", Specifier);
|
||||
args.Player.SendErrorMessage("Use the time 0 (zero) for a permanent ban.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Used only to notify if a ban was successful and who the ban was about
|
||||
bool success = false;
|
||||
string targetGeneralizedName = "";
|
||||
|
||||
// Effective ban target assignment
|
||||
List<TSPlayer> players = TSPlayer.FindByNameOrID(args.Parameters[1]);
|
||||
|
||||
// Bad case: Players contains more than 1 person so we can't ban them
|
||||
if (players.Count > 1)
|
||||
{
|
||||
//Fail fast
|
||||
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
UserAccount offlineUserAccount = TShock.UserAccounts.GetUserAccountByName(args.Parameters[1]);
|
||||
|
||||
// Storage variable to determine if the command executor is the server console
|
||||
// If it is, we assume they have full control and let them override permission checks
|
||||
bool callerIsServerConsole = args.Player is TSServerPlayer;
|
||||
|
||||
// The ban reason the ban is going to have
|
||||
string banReason = "Unknown.";
|
||||
|
||||
// The default ban length
|
||||
// 0 is permanent ban, otherwise temp ban
|
||||
int banLengthInSeconds = 0;
|
||||
|
||||
// Figure out if param 2 is a time or 0 or garbage
|
||||
if (args.Parameters.Count >= 3)
|
||||
{
|
||||
bool parsedOkay = false;
|
||||
if (args.Parameters[2] != "0")
|
||||
{
|
||||
parsedOkay = TShock.Utils.TryParseTime(args.Parameters[2], out banLengthInSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedOkay = true;
|
||||
}
|
||||
|
||||
if (!parsedOkay)
|
||||
{
|
||||
args.Player.SendErrorMessage("Invalid time format. Example: 10d 5h 3m 2s.");
|
||||
args.Player.SendErrorMessage("Use 0 (zero) for a permanent ban.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a reason exists, use the given reason.
|
||||
if (args.Parameters.Count > 3)
|
||||
{
|
||||
banReason = String.Join(" ", args.Parameters.Skip(3));
|
||||
}
|
||||
|
||||
// Good case: Online ban for matching character.
|
||||
if (players.Count == 1)
|
||||
{
|
||||
TSPlayer target = players[0];
|
||||
|
||||
if (target.HasPermission(Permissions.immunetoban) && !callerIsServerConsole)
|
||||
{
|
||||
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", target.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
targetGeneralizedName = target.Name;
|
||||
success = TShock.Bans.AddBan(target.IP, target.Name, target.UUID, target.Account?.Name ?? "", banReason, false, args.Player.Account.Name,
|
||||
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
|
||||
|
||||
// Since this is an online ban, we need to dc the player and tell them now.
|
||||
if (success)
|
||||
{
|
||||
if (banLengthInSeconds == 0)
|
||||
{
|
||||
target.Disconnect(String.Format("Permanently banned for {0}", banReason));
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Disconnect(String.Format("Banned for {0} seconds for {1}", banLengthInSeconds, banReason));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case: Players & user are invalid, could be IP?
|
||||
// Note: Order matters. If this method is above the online player check,
|
||||
// This enables you to ban an IP even if the player exists in the database as a player.
|
||||
// You'll get two bans for the price of one, in theory, because both IP and user named IP will be banned.
|
||||
// ??? edge cases are weird, but this is going to happen
|
||||
// The only way around this is to either segregate off the IP code or do something else.
|
||||
if (players.Count == 0)
|
||||
{
|
||||
// If the target is a valid IP...
|
||||
string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
|
||||
Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
|
||||
if (r.IsMatch(args.Parameters[1]))
|
||||
{
|
||||
targetGeneralizedName = "IP: " + args.Parameters[1];
|
||||
success = TShock.Bans.AddBan(args.Parameters[1], "", "", "", banReason,
|
||||
false, args.Player.Account.Name, banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
|
||||
if (success && offlineUserAccount != null)
|
||||
{
|
||||
args.Player.SendSuccessMessage("Target IP {0} was banned successfully.", targetGeneralizedName);
|
||||
args.Player.SendErrorMessage("Note: An account named with this IP address also exists.");
|
||||
args.Player.SendErrorMessage("Note: It will also be banned.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apparently there is no way to not IP ban someone
|
||||
// This means that where we would normally just ban a "character name" here
|
||||
// We can't because it requires some IP as a primary key.
|
||||
if (offlineUserAccount == null)
|
||||
{
|
||||
args.Player.SendErrorMessage("Unable to ban target {0}.", args.Parameters[1]);
|
||||
args.Player.SendErrorMessage("Target is not a valid IP address, a valid online player, or a known offline user.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Case: Offline ban
|
||||
if (players.Count == 0 && offlineUserAccount != null)
|
||||
{
|
||||
// Catch: we don't know an offline player's last login character name
|
||||
// This means that we're banning their *user name* on the assumption that
|
||||
// user name == character name
|
||||
// (which may not be true)
|
||||
// This needs to be fixed in a future implementation.
|
||||
targetGeneralizedName = offlineUserAccount.Name;
|
||||
|
||||
if (TShock.Groups.GetGroupByName(offlineUserAccount.Group).HasPermission(Permissions.immunetoban) &&
|
||||
!callerIsServerConsole)
|
||||
{
|
||||
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", targetGeneralizedName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offlineUserAccount.KnownIps == null)
|
||||
{
|
||||
args.Player.SendErrorMessage("Unable to ban target {0} because they have no valid IP to ban.", targetGeneralizedName);
|
||||
return;
|
||||
}
|
||||
|
||||
string lastIP = JsonConvert.DeserializeObject<List<string>>(offlineUserAccount.KnownIps).Last();
|
||||
|
||||
success =
|
||||
TShock.Bans.AddBan(lastIP,
|
||||
"", offlineUserAccount.UUID, offlineUserAccount.Name, banReason, false, args.Player.Account.Name,
|
||||
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
args.Player.SendSuccessMessage("{0} was successfully banned.", targetGeneralizedName);
|
||||
args.Player.SendInfoMessage("Length: {0}", banLengthInSeconds == 0 ? "Permanent." : banLengthInSeconds + " seconds.");
|
||||
args.Player.SendInfoMessage("Reason: {0}", banReason);
|
||||
if (!args.Silent)
|
||||
{
|
||||
if (banLengthInSeconds == 0)
|
||||
{
|
||||
TSPlayer.All.SendErrorMessage("{0} was permanently banned by {1} for: {2}",
|
||||
targetGeneralizedName, args.Player.Account.Name, banReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSPlayer.All.SendErrorMessage("{0} was temp banned for {1} seconds by {2} for: {3}",
|
||||
targetGeneralizedName, banLengthInSeconds, args.Player.Account.Name, banReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Player.SendErrorMessage("{0} was NOT banned due to a database error or other system problem.", targetGeneralizedName);
|
||||
args.Player.SendErrorMessage("If this player is online, they have NOT been kicked.");
|
||||
args.Player.SendErrorMessage("Check the system logs for details.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
case "del":
|
||||
#region Delete ban
|
||||
{
|
||||
if (args.Parameters.Count != 2)
|
||||
{
|
||||
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban del <player>", Specifier);
|
||||
return;
|
||||
}
|
||||
|
||||
string plStr = args.Parameters[1];
|
||||
Ban ban = TShock.Bans.GetBanByName(plStr, false);
|
||||
if (ban != null)
|
||||
{
|
||||
if (TShock.Bans.RemoveBan(ban.Name, true))
|
||||
args.Player.SendSuccessMessage("Unbanned {0} ({1}).", ban.Name, ban.IP);
|
||||
else
|
||||
args.Player.SendErrorMessage("Failed to unban {0} ({1}), check logs.", ban.Name, ban.IP);
|
||||
}
|
||||
else
|
||||
args.Player.SendErrorMessage("No bans for {0} exist.", plStr);
|
||||
}
|
||||
#endregion
|
||||
return;
|
||||
case "delip":
|
||||
#region Delete IP ban
|
||||
{
|
||||
if (args.Parameters.Count != 2)
|
||||
{
|
||||
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban delip <ip>", Specifier);
|
||||
return;
|
||||
}
|
||||
|
||||
string ip = args.Parameters[1];
|
||||
Ban ban = TShock.Bans.GetBanByIp(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
if (TShock.Bans.RemoveBan(ban.IP, false))
|
||||
args.Player.SendSuccessMessage("Unbanned IP {0} ({1}).", ban.IP, ban.Name);
|
||||
else
|
||||
args.Player.SendErrorMessage("Failed to unban IP {0} ({1}), check logs.", ban.IP, ban.Name);
|
||||
}
|
||||
else
|
||||
args.Player.SendErrorMessage("IP {0} is not banned.", ip);
|
||||
}
|
||||
#endregion
|
||||
return;
|
||||
case "help":
|
||||
#region Help
|
||||
{
|
||||
int pageNumber;
|
||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
|
||||
return;
|
||||
Help();
|
||||
break;
|
||||
|
||||
var lines = new List<string>
|
||||
{
|
||||
"add <target> <time> [reason] - Bans a player or user account if the player is not online.",
|
||||
"del <player> - Unbans a player.",
|
||||
"delip <ip> - Unbans an IP.",
|
||||
"list [page] - Lists all player bans.",
|
||||
"listip [page] - Lists all IP bans."
|
||||
};
|
||||
case "add":
|
||||
AddBan();
|
||||
break;
|
||||
|
||||
case "del":
|
||||
DelBan();
|
||||
break;
|
||||
|
||||
PaginationTools.SendPage(args.Player, pageNumber, lines,
|
||||
new PaginationTools.Settings
|
||||
{
|
||||
HeaderFormat = "Ban Sub-Commands ({0}/{1}):",
|
||||
FooterFormat = "Type {0}ban help {{0}} for more sub-commands.".SFormat(Specifier)
|
||||
}
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
return;
|
||||
case "list":
|
||||
#region List bans
|
||||
{
|
||||
int pageNumber;
|
||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ListBans();
|
||||
break;
|
||||
|
||||
List<Ban> bans = TShock.Bans.GetBans();
|
||||
case "details":
|
||||
BanDetails();
|
||||
break;
|
||||
|
||||
var nameBans = from ban in bans
|
||||
where !String.IsNullOrEmpty(ban.Name)
|
||||
select ban.Name;
|
||||
|
||||
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(nameBans),
|
||||
new PaginationTools.Settings
|
||||
{
|
||||
HeaderFormat = "Bans ({0}/{1}):",
|
||||
FooterFormat = "Type {0}ban list {{0}} for more.".SFormat(Specifier),
|
||||
NothingToDisplayString = "There are currently no bans."
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
return;
|
||||
case "listip":
|
||||
#region List IP bans
|
||||
{
|
||||
int pageNumber;
|
||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Ban> bans = TShock.Bans.GetBans();
|
||||
|
||||
var ipBans = from ban in bans
|
||||
where String.IsNullOrEmpty(ban.Name)
|
||||
select ban.IP;
|
||||
|
||||
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(ipBans),
|
||||
new PaginationTools.Settings
|
||||
{
|
||||
HeaderFormat = "IP Bans ({0}/{1}):",
|
||||
FooterFormat = "Type {0}ban listip {{0}} for more.".SFormat(Specifier),
|
||||
NothingToDisplayString = "There are currently no IP bans."
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
return;
|
||||
default:
|
||||
args.Player.SendErrorMessage("Invalid subcommand! Type {0}ban help for more information.", Specifier);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1844,7 +1885,7 @@ namespace TShockAPI
|
|||
return;
|
||||
}
|
||||
|
||||
string replacementCommand = String.Join(" ", args.Parameters);
|
||||
string replacementCommand = String.Join(" ", args.Parameters.Select(p => p.Contains(" ") ? $"\"{p}\"" : p));
|
||||
args.Player.tempGroup = new SuperAdminGroup();
|
||||
HandleCommand(args.Player, replacementCommand);
|
||||
args.Player.tempGroup = null;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -46,7 +46,6 @@ namespace TShockAPI.DB
|
|||
com.CommandText = query;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
com.AddParameter("@" + i, args[i]);
|
||||
|
||||
return com.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +80,39 @@ namespace TShockAPI.DB
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a query on a database, returning the first column of the first row of the result set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="olddb">Database to query</param>
|
||||
/// <param name="query">Query string with parameters as @0, @1, etc.</param>
|
||||
/// <param name="args">Parameters to be put in the query</param>
|
||||
/// <returns></returns>
|
||||
public static T QueryScalar<T>(this IDbConnection olddb, string query, params object[] args)
|
||||
{
|
||||
using (var db = olddb.CloneEx())
|
||||
{
|
||||
db.Open();
|
||||
using (var com = db.CreateCommand())
|
||||
{
|
||||
com.CommandText = query;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
com.AddParameter("@" + i, args[i]);
|
||||
|
||||
object output = com.ExecuteScalar();
|
||||
if (output.GetType() != typeof(T))
|
||||
{
|
||||
if (typeof(IConvertible).IsAssignableFrom(output.GetType()))
|
||||
{
|
||||
return (T)Convert.ChangeType(output, typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
return (T)output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static QueryResult QueryReaderDict(this IDbConnection olddb, string query, Dictionary<string, object> values)
|
||||
{
|
||||
var db = olddb.CloneEx();
|
||||
|
|
@ -210,12 +242,24 @@ namespace TShockAPI.DB
|
|||
public static T Get<T>(this IDataReader reader, int column)
|
||||
{
|
||||
if (reader.IsDBNull(column))
|
||||
return default(T);
|
||||
return default;
|
||||
|
||||
if (ReadFuncs.ContainsKey(typeof(T)))
|
||||
return (T)ReadFuncs[typeof(T)](reader, column);
|
||||
|
||||
throw new NotImplementedException();
|
||||
Type t;
|
||||
if (typeof(T) != (t = reader.GetFieldType(column)))
|
||||
{
|
||||
string columnName = reader.GetName(column);
|
||||
throw new InvalidCastException($"Received type '{typeof(T).Name}', however column '{columnName}' expects type '{t.Name}'");
|
||||
}
|
||||
|
||||
if (reader.IsDBNull(column))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return (T)reader.GetValue(column);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TShockAPI
|
||||
{
|
||||
|
|
@ -31,5 +32,25 @@ namespace TShockAPI
|
|||
foreach (T item in source)
|
||||
action(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the value at the given index from the enumerable
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="enumerable"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryGetValue<T>(this IEnumerable<T> enumerable, int index, out T value)
|
||||
{
|
||||
if (index < enumerable.Count())
|
||||
{
|
||||
value = enumerable.ElementAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace TShockAPI
|
||||
|
|
@ -27,5 +28,16 @@ namespace TShockAPI
|
|||
{
|
||||
return String.Format(str, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the string representation of an object with a Terraria color code for the given color
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="color"></param>
|
||||
/// <returns></returns>
|
||||
public static string Color(this object obj, string color)
|
||||
{
|
||||
return $"[c/{color}:{obj}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ namespace TShockAPI
|
|||
[Description("Prevents you from being kicked.")]
|
||||
public static readonly string immunetokick = "tshock.admin.nokick";
|
||||
|
||||
[Obsolete("Ban immunity is no longer available.")]
|
||||
[Description("Prevents you from being banned.")]
|
||||
public static readonly string immunetoban = "tshock.admin.noban";
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ namespace TShockAPI
|
|||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Token"/>
|
||||
/// </summary>
|
||||
public Token() : base("token", true, "The REST authentication token.", typeof(String)){}
|
||||
public Token() : base("token", true, "The REST authentication token.", typeof(String)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -216,9 +216,13 @@ namespace TShockAPI
|
|||
Rest.RegisterRedirect("/users/update", "/v2/users/update");
|
||||
|
||||
//ban commands
|
||||
Rest.RegisterRedirect("/bans/list", "/v2/bans/list");
|
||||
Rest.RegisterRedirect("/bans/read", "/v2/bans/read");
|
||||
Rest.RegisterRedirect("/bans/destroy", "/v2/bans/destroy");
|
||||
Rest.RegisterRedirect("/bans/create", "/v3/bans/create");
|
||||
Rest.RegisterRedirect("/bans/list", "/v3/bans/list");
|
||||
Rest.RegisterRedirect("/bans/read", "/v3/bans/read");
|
||||
Rest.RegisterRedirect("/bans/destroy", "/v3/bans/destroy");
|
||||
Rest.RegisterRedirect("/v2/bans/list", "/v3/bans/list");
|
||||
Rest.RegisterRedirect("/v2/bans/read", "/v3/bans/read");
|
||||
Rest.RegisterRedirect("/v2/bans/destroy", "/v3/bans/destroy");
|
||||
|
||||
//world commands
|
||||
Rest.RegisterRedirect("/world/bloodmoon", "v3/world/bloodmoon");
|
||||
|
|
@ -258,10 +262,10 @@ namespace TShockAPI
|
|||
Rest.Register(new SecureRestCommand("/v2/users/update", UserUpdateV2, RestPermissions.restmanageusers) { DoLog = false });
|
||||
|
||||
// Ban Commands
|
||||
Rest.Register(new SecureRestCommand("/bans/create", BanCreate, RestPermissions.restmanagebans));
|
||||
Rest.Register(new SecureRestCommand("/v2/bans/list", BanListV2, RestPermissions.restviewbans));
|
||||
Rest.Register(new SecureRestCommand("/v2/bans/read", BanInfoV2, RestPermissions.restviewbans));
|
||||
Rest.Register(new SecureRestCommand("/v2/bans/destroy", BanDestroyV2, RestPermissions.restmanagebans));
|
||||
Rest.Register(new SecureRestCommand("/v3/bans/create", BanCreateV3, RestPermissions.restban, RestPermissions.restmanagebans));
|
||||
Rest.Register(new SecureRestCommand("/v3/bans/list", BanListV3, RestPermissions.restviewbans));
|
||||
Rest.Register(new SecureRestCommand("/v3/bans/read", BanInfoV3, RestPermissions.restviewbans));
|
||||
Rest.Register(new SecureRestCommand("/v3/bans/destroy", BanDestroyV3, RestPermissions.restmanagebans));
|
||||
|
||||
// World Commands
|
||||
Rest.Register(new SecureRestCommand("/world/read", WorldRead));
|
||||
|
|
@ -279,7 +283,6 @@ namespace TShockAPI
|
|||
Rest.Register(new SecureRestCommand("/v3/players/read", PlayerReadV3, RestPermissions.restuserinfo));
|
||||
Rest.Register(new SecureRestCommand("/v4/players/read", PlayerReadV4, RestPermissions.restuserinfo));
|
||||
Rest.Register(new SecureRestCommand("/v2/players/kick", PlayerKickV2, RestPermissions.restkick));
|
||||
Rest.Register(new SecureRestCommand("/v2/players/ban", PlayerBanV2, RestPermissions.restban, RestPermissions.restmanagebans));
|
||||
Rest.Register(new SecureRestCommand("/v2/players/kill", PlayerKill, RestPermissions.restkill));
|
||||
Rest.Register(new SecureRestCommand("/v2/players/mute", PlayerMute, RestPermissions.restmute));
|
||||
Rest.Register(new SecureRestCommand("/v2/players/unmute", PlayerUnMute, RestPermissions.restmute));
|
||||
|
|
@ -420,7 +423,7 @@ namespace TShockAPI
|
|||
|
||||
if (GetBool(args.Parameters["rules"], false))
|
||||
{
|
||||
var rules = new Dictionary<string,object>();
|
||||
var rules = new Dictionary<string, object>();
|
||||
rules.Add("AutoSave", TShock.Config.AutoSave);
|
||||
rules.Add("DisableBuild", TShock.Config.DisableBuild);
|
||||
rules.Add("DisableClownBombs", TShock.Config.DisableClownBombs);
|
||||
|
|
@ -492,8 +495,8 @@ namespace TShockAPI
|
|||
return RestMissingParam("user");
|
||||
|
||||
var group = args.Parameters["group"];
|
||||
if (string.IsNullOrWhiteSpace(group))
|
||||
group = TShock.Config.DefaultRegistrationGroupName;
|
||||
if (string.IsNullOrWhiteSpace(group))
|
||||
group = TShock.Config.DefaultRegistrationGroupName;
|
||||
|
||||
var password = args.Parameters["password"];
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
|
|
@ -609,115 +612,154 @@ namespace TShockAPI
|
|||
#region Rest Ban Methods
|
||||
|
||||
[Description("Create a new ban entry.")]
|
||||
[Route("/bans/create")]
|
||||
[Route("/v3/bans/create")]
|
||||
[Permission(RestPermissions.restmanagebans)]
|
||||
[Noun("ip", false, "The IP to ban, at least this or name must be specified.", typeof(String))]
|
||||
[Noun("name", false, "The name to ban, at least this or ip must be specified.", typeof(String))]
|
||||
[Noun("identifier", true, "The identifier to ban.", typeof(String))]
|
||||
[Noun("reason", false, "The reason to assign to the ban.", typeof(String))]
|
||||
[Noun("start", false, "The datetime at which the ban should start.", typeof(String))]
|
||||
[Noun("end", false, "The datetime at which the ban should end.", typeof(String))]
|
||||
[Token]
|
||||
private object BanCreate(RestRequestArgs args)
|
||||
private object BanCreateV3(RestRequestArgs args)
|
||||
{
|
||||
var ip = args.Parameters["ip"];
|
||||
var name = args.Parameters["name"];
|
||||
string identifier = args.Parameters["identifier"];
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
return RestMissingParam("identifier");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ip) && string.IsNullOrWhiteSpace(name))
|
||||
return RestMissingParam("ip", "name");
|
||||
string reason = args.Parameters["reason"];
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
reason = "Banned";
|
||||
|
||||
try
|
||||
if (!DateTime.TryParse(args.Parameters["start"], out DateTime startDate))
|
||||
startDate = DateTime.UtcNow;
|
||||
|
||||
if (!DateTime.TryParse(args.Parameters["end"], out DateTime endDate))
|
||||
endDate = DateTime.MaxValue;
|
||||
|
||||
AddBanResult banResult = TShock.Bans.InsertBan(identifier, reason, args.TokenData.Username, startDate, endDate);
|
||||
if (banResult.Ban != null)
|
||||
{
|
||||
TShock.Bans.AddBan(ip, name, "", "", args.Parameters["reason"], true, args.TokenData.Username);
|
||||
TSPlayer player = null;
|
||||
if (identifier.StartsWith(Identifier.IP.Prefix))
|
||||
{
|
||||
player = TShock.Players.FirstOrDefault(p => p.IP == identifier.Substring(Identifier.IP.Prefix.Length));
|
||||
}
|
||||
else if (identifier.StartsWith(Identifier.Name.Prefix))
|
||||
{
|
||||
//Character names may not necessarily be unique, so kick all matches
|
||||
foreach (var ply in TShock.Players.Where(p => p.Name == identifier.Substring(Identifier.Name.Prefix.Length)))
|
||||
{
|
||||
ply.Kick(reason, true);
|
||||
}
|
||||
}
|
||||
else if (identifier.StartsWith(Identifier.Account.Prefix))
|
||||
{
|
||||
player = TShock.Players.FirstOrDefault(p => p.Account?.Name == identifier.Substring(Identifier.Account.Prefix.Length));
|
||||
}
|
||||
else if (identifier.StartsWith(Identifier.UUID.Prefix))
|
||||
{
|
||||
player = TShock.Players.FirstOrDefault(p => p.UUID == identifier.Substring(Identifier.UUID.Prefix.Length));
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
{
|
||||
player.Kick(reason, true);
|
||||
}
|
||||
|
||||
return RestResponse($"Ban added. Ticket number: {banResult.Ban.TicketNumber}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return RestError(e.Message);
|
||||
}
|
||||
return RestResponse("Ban created successfully");
|
||||
|
||||
return RestError($"Failed to add ban. {banResult.Message}", status: "500");
|
||||
}
|
||||
|
||||
[Description("Delete an existing ban entry.")]
|
||||
[Route("/v2/bans/destroy")]
|
||||
[Route("/v3/bans/destroy")]
|
||||
[Permission(RestPermissions.restmanagebans)]
|
||||
[Noun("ban", true, "The search criteria, either an IP address or a name.", typeof(String))]
|
||||
[Noun("type", true, "The type of search criteria, 'ip' or 'name'. Also used as the method of removing from the database.", typeof(String))]
|
||||
[Noun("caseinsensitive", false, "Name lookups should be case insensitive.", typeof(bool))]
|
||||
[Noun("ticketNumber", true, "The ticket number of the ban to delete.", typeof(String))]
|
||||
[Noun("fullDelete", false, "Whether or not to completely remove the ban from the system.", typeof(bool))]
|
||||
[Token]
|
||||
private object BanDestroyV2(RestRequestArgs args)
|
||||
private object BanDestroyV3(RestRequestArgs args)
|
||||
{
|
||||
var ret = BanFind(args.Parameters);
|
||||
if (ret is RestObject)
|
||||
return ret;
|
||||
string id = args.Parameters["ticketNumber"];
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
return RestMissingParam("ticketNumber");
|
||||
|
||||
try
|
||||
if (!int.TryParse(id, out int ticketNumber))
|
||||
{
|
||||
Ban ban = (Ban)ret;
|
||||
switch (args.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(args.Parameters["caseinsensitive"], true)))
|
||||
return RestResponse("Failed to delete ban (already deleted?)");
|
||||
break;
|
||||
default:
|
||||
return RestError("Invalid Type: '" + args.Parameters["type"] + "'");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return RestError(e.Message);
|
||||
return RestInvalidParam("ticketNumber");
|
||||
}
|
||||
|
||||
return RestResponse("Ban deleted successfully");
|
||||
bool.TryParse(args.Parameters["fullDelete"], out bool fullDelete);
|
||||
|
||||
if (TShock.Bans.RemoveBan(ticketNumber, fullDelete))
|
||||
{
|
||||
return RestResponse("Ban removed.");
|
||||
}
|
||||
|
||||
return RestError("Failed to remove ban.", status: "500");
|
||||
}
|
||||
|
||||
[Description("View the details of a specific ban.")]
|
||||
[Route("/v2/bans/read")]
|
||||
[Route("/v3/bans/read")]
|
||||
[Permission(RestPermissions.restviewbans)]
|
||||
[Noun("ban", true, "The search criteria, either an IP address or a name.", typeof(String))]
|
||||
[Noun("type", true, "The type of search criteria, 'ip' or 'name'.", typeof(String))]
|
||||
[Noun("caseinsensitive", false, "Name lookups should be case insensitive.", typeof(bool))]
|
||||
[Noun("ticketNumber", true, "The ticket number to search for.", typeof(String))]
|
||||
[Token]
|
||||
private object BanInfoV2(RestRequestArgs args)
|
||||
private object BanInfoV3(RestRequestArgs args)
|
||||
{
|
||||
var ret = BanFind(args.Parameters);
|
||||
if (ret is RestObject)
|
||||
return ret;
|
||||
string id = args.Parameters["ticketNumber"];
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
return RestMissingParam("ticketNumber");
|
||||
|
||||
Ban ban = (Ban)ret;
|
||||
return new RestObject() {
|
||||
{"name", null == ban.Name ? "" : ban.Name},
|
||||
{"ip", null == ban.IP ? "" : ban.IP},
|
||||
{"banning_user", null == ban.BanningUser ? "" : ban.BanningUser},
|
||||
{"date", null == ban.BanDateTime ? "" : ban.BanDateTime.Value.ToString()},
|
||||
{"reason", null == ban.Reason ? "" : ban.Reason},
|
||||
if (!int.TryParse(id, out int ticketNumber))
|
||||
{
|
||||
return RestInvalidParam("ticketNumber");
|
||||
}
|
||||
|
||||
Ban ban = TShock.Bans.GetBanById(ticketNumber);
|
||||
|
||||
if (ban == null)
|
||||
{
|
||||
return RestResponse("No matching bans found.");
|
||||
}
|
||||
|
||||
return new RestObject
|
||||
{
|
||||
{ "ticket_number", ban.TicketNumber },
|
||||
{ "identifier", ban.Identifier },
|
||||
{ "reason", ban.Reason },
|
||||
{ "banning_user", ban.BanningUser },
|
||||
{ "start_date_ticks", ban.BanDateTime.Ticks },
|
||||
{ "end_date_ticks", ban.ExpirationDateTime.Ticks },
|
||||
};
|
||||
}
|
||||
|
||||
[Description("View all bans in the TShock database.")]
|
||||
[Route("/v2/bans/list")]
|
||||
[Route("/v3/bans/list")]
|
||||
[Permission(RestPermissions.restviewbans)]
|
||||
[Token]
|
||||
private object BanListV2(RestRequestArgs args)
|
||||
private object BanListV3(RestRequestArgs args)
|
||||
{
|
||||
IEnumerable<Ban> bans = TShock.Bans.Bans.Select(kvp => kvp.Value);
|
||||
|
||||
var banList = new ArrayList();
|
||||
foreach (var ban in TShock.Bans.GetBans())
|
||||
foreach (var ban in bans)
|
||||
{
|
||||
banList.Add(
|
||||
new Dictionary<string, string>
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{"name", null == ban.Name ? "" : ban.Name},
|
||||
{"ip", null == ban.IP ? "" : ban.IP},
|
||||
{"banning_user", null == ban.BanningUser ? "" : ban.BanningUser},
|
||||
{"date", null == ban.BanDateTime ? "" : ban.BanDateTime.Value.ToString()},
|
||||
{"reason", null == ban.Reason ? "" : ban.Reason},
|
||||
{ "ticket_number", ban.TicketNumber },
|
||||
{ "identifier", ban.Identifier },
|
||||
{ "reason", ban.Reason },
|
||||
{ "banning_user", ban.BanningUser },
|
||||
{ "start_date_ticks", ban.BanDateTime.Ticks },
|
||||
{ "end_date_ticks", ban.ExpirationDateTime.Ticks },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new RestObject() { { "bans", banList } };
|
||||
return new RestObject
|
||||
{
|
||||
{ "bans", banList }
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -987,26 +1029,6 @@ namespace TShockAPI
|
|||
return RestResponse("Player " + player.Name + " was kicked");
|
||||
}
|
||||
|
||||
[Description("Add a ban to the database.")]
|
||||
[Route("/v2/players/ban")]
|
||||
[Permission(RestPermissions.restban)]
|
||||
[Permission(RestPermissions.restmanagebans)]
|
||||
[Noun("player", true, "The player to kick.", typeof(String))]
|
||||
[Noun("reason", false, "The reason the user was banned.", typeof(String))]
|
||||
[Token]
|
||||
private object PlayerBanV2(RestRequestArgs args)
|
||||
{
|
||||
var ret = PlayerFind(args.Parameters);
|
||||
if (ret is RestObject)
|
||||
return ret;
|
||||
|
||||
TSPlayer player = (TSPlayer)ret;
|
||||
var reason = null == args.Parameters["reason"] ? "Banned via web" : args.Parameters["reason"];
|
||||
TShock.Bans.AddBan(player.IP, player.Name, "", "", reason);
|
||||
player.Kick(reason, true, false, null, true);
|
||||
return RestResponse("Player " + player.Name + " was banned");
|
||||
}
|
||||
|
||||
[Description("Kill a player.")]
|
||||
[Route("/v2/players/kill")]
|
||||
[Permission(RestPermissions.restkill)]
|
||||
|
|
@ -1039,7 +1061,7 @@ namespace TShockAPI
|
|||
var groups = new ArrayList();
|
||||
foreach (Group group in TShock.Groups)
|
||||
{
|
||||
groups.Add(new Dictionary<string, object> {{"name", group.Name}, {"parent", group.ParentName}, {"chatcolor", group.ChatColor}});
|
||||
groups.Add(new Dictionary<string, object> { { "name", group.Name }, { "parent", group.ParentName }, { "chatcolor", group.ChatColor } });
|
||||
}
|
||||
return new RestObject() { { "groups", groups } };
|
||||
}
|
||||
|
|
@ -1200,7 +1222,7 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
sb.AppendLine("Example Usage: {0}?{1}".SFormat(routeattr.Route,
|
||||
string.Join("&", nouns.Select(n => String.Format("{0}={0}", ((Noun) n).Name)))));
|
||||
string.Join("&", nouns.Select(n => String.Format("{0}={0}", ((Noun)n).Name)))));
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
|
@ -1210,12 +1232,12 @@ namespace TShockAPI
|
|||
|
||||
private RestObject RestError(string message, string status = "400")
|
||||
{
|
||||
return new RestObject(status) {Error = message};
|
||||
return new RestObject(status) { Error = message };
|
||||
}
|
||||
|
||||
private RestObject RestResponse(string message, string status = "200")
|
||||
{
|
||||
return new RestObject(status) {Response = message};
|
||||
return new RestObject(status) { Response = message };
|
||||
}
|
||||
|
||||
private RestObject RestMissingParam(string var)
|
||||
|
|
@ -1246,7 +1268,7 @@ namespace TShockAPI
|
|||
return RestMissingParam("player");
|
||||
|
||||
var found = TSPlayer.FindByNameOrID(name);
|
||||
switch(found.Count)
|
||||
switch (found.Count)
|
||||
{
|
||||
case 1:
|
||||
return found[0];
|
||||
|
|
@ -1292,35 +1314,6 @@ namespace TShockAPI
|
|||
return account;
|
||||
}
|
||||
|
||||
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"];
|
||||
|
|
|
|||
|
|
@ -1636,11 +1636,15 @@ namespace TShockAPI
|
|||
{
|
||||
if (!ConnectionAlive)
|
||||
return true;
|
||||
if (force || !HasPermission(Permissions.immunetoban))
|
||||
if (force)
|
||||
{
|
||||
string ip = IP;
|
||||
string uuid = UUID;
|
||||
TShock.Bans.AddBan(ip, Name, uuid, "", reason, false, adminUserName);
|
||||
TShock.Bans.InsertBan($"{Identifier.IP}{IP}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||
TShock.Bans.InsertBan($"{Identifier.IP}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||
if (Account != null)
|
||||
{
|
||||
TShock.Bans.InsertBan($"{Identifier.Account}{Account.Name}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
Disconnect(string.Format("Banned: {0}", reason));
|
||||
string verb = force ? "force " : "";
|
||||
if (string.IsNullOrWhiteSpace(adminUserName))
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using Terraria.Utilities;
|
|||
using TShockAPI;
|
||||
using TShockAPI.DB;
|
||||
using Terraria.Localization;
|
||||
using System.Linq;
|
||||
|
||||
namespace TShockAPI
|
||||
{
|
||||
|
|
@ -41,30 +42,22 @@ namespace TShockAPI
|
|||
|
||||
public override void SendErrorMessage(string msg)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
SendConsoleMessage(msg, 255, 0, 0);
|
||||
}
|
||||
|
||||
public override void SendInfoMessage(string msg)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
SendConsoleMessage(msg, 255, 250, 170);
|
||||
}
|
||||
|
||||
public override void SendSuccessMessage(string msg)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
SendConsoleMessage(msg, 0, 255, 0);
|
||||
}
|
||||
|
||||
public override void SendWarningMessage(string msg)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkRed;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
SendConsoleMessage(msg, 139, 0, 0);
|
||||
}
|
||||
|
||||
public override void SendMessage(string msg, Color color)
|
||||
|
|
@ -74,7 +67,28 @@ namespace TShockAPI
|
|||
|
||||
public override void SendMessage(string msg, byte red, byte green, byte blue)
|
||||
{
|
||||
Console.WriteLine(msg);
|
||||
SendConsoleMessage(msg, red, green, blue);
|
||||
}
|
||||
|
||||
public void SendConsoleMessage(string msg, byte red, byte green, byte blue)
|
||||
{
|
||||
var snippets = Terraria.UI.Chat.ChatManager.ParseMessage(msg, new Color(red, green, blue));
|
||||
|
||||
foreach (var snippet in snippets)
|
||||
{
|
||||
if (snippet.Color != null)
|
||||
{
|
||||
Console.ForegroundColor = PickNearbyConsoleColor(snippet.Color);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
Console.Write(snippet.Text);
|
||||
}
|
||||
Console.WriteLine();
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void SetFullMoon()
|
||||
|
|
@ -179,5 +193,47 @@ namespace TShockAPI
|
|||
All.SendTileSquare((int)coords.X, (int)coords.Y, 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly Dictionary<Color, ConsoleColor> _consoleColorMap = new Dictionary<Color, ConsoleColor>
|
||||
{
|
||||
{ Color.Red, ConsoleColor.Red },
|
||||
{ Color.Green, ConsoleColor.Green },
|
||||
{ Color.Blue, ConsoleColor.Cyan },
|
||||
{ new Color(255, 250, 170), ConsoleColor.Yellow },
|
||||
{ new Color(170, 170, 255), ConsoleColor.Cyan },
|
||||
{ new Color(255, 170, 255), ConsoleColor.Magenta },
|
||||
{ new Color(170, 255, 170), ConsoleColor.Green },
|
||||
{ new Color(255, 170, 170), ConsoleColor.Red },
|
||||
{ new Color(139, 0, 0), ConsoleColor.DarkRed }, // This is the console warning color
|
||||
{ Color.PaleVioletRed, ConsoleColor.Magenta }, // This is the command logging color
|
||||
{ Color.White, ConsoleColor.White }
|
||||
};
|
||||
|
||||
private ConsoleColor PickNearbyConsoleColor(Color color)
|
||||
{
|
||||
//Grabs an integer difference between two colors in euclidean space
|
||||
int ColorDiff(Color c1, Color c2)
|
||||
{
|
||||
return (int)Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
|
||||
+ (c1.G - c2.G) * (c1.G - c2.G)
|
||||
+ (c1.B - c2.B) * (c1.B - c2.B));
|
||||
}
|
||||
|
||||
var diffs = _consoleColorMap.Select(kvp => ColorDiff(kvp.Key, color));
|
||||
int index = 0;
|
||||
int min = int.MaxValue;
|
||||
|
||||
for (int i = 0; i < _consoleColorMap.Count; i++)
|
||||
{
|
||||
if (diffs.ElementAt(i) < min)
|
||||
{
|
||||
index = i;
|
||||
min = diffs.ElementAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return _consoleColorMap.Values.ElementAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,27 +478,7 @@ namespace TShockAPI
|
|||
args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
|
||||
UserAccounts.UpdateLogin(args.Player.Account);
|
||||
|
||||
Ban potentialBan = Bans.GetBanByAccountName(args.Player.Account.Name);
|
||||
|
||||
if (potentialBan != null)
|
||||
{
|
||||
// A user just signed in successfully despite being banned by account name.
|
||||
// We should fix the ban database so that all of their ban info is up to date.
|
||||
Bans.AddBan(args.Player.IP, args.Player.Name, args.Player.UUID, args.Player.Account.Name,
|
||||
potentialBan.Reason, false, potentialBan.BanningUser, potentialBan.Expiration);
|
||||
|
||||
// And then get rid of them.
|
||||
if (potentialBan.Expiration == "")
|
||||
{
|
||||
args.Player.Kick(String.Format("Permanently banned by {0} for {1}", potentialBan.BanningUser
|
||||
,potentialBan.Reason), true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Player.Kick(String.Format("Still banned by {0} for {1}", potentialBan.BanningUser,
|
||||
potentialBan.Reason), true, true);
|
||||
}
|
||||
}
|
||||
Bans.CheckBan(args.Player);
|
||||
}
|
||||
|
||||
/// <summary>OnAccountDelete - Internal hook fired on account delete.</summary>
|
||||
|
|
@ -816,7 +796,7 @@ namespace TShockAPI
|
|||
Console.WriteLine("Startup parameter overrode REST port.");
|
||||
}
|
||||
})
|
||||
.AddFlags(playerSet, (p)=>
|
||||
.AddFlags(playerSet, (p) =>
|
||||
{
|
||||
int slots;
|
||||
if (int.TryParse(p, out slots))
|
||||
|
|
@ -1100,7 +1080,7 @@ namespace TShockAPI
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if(!OnCreep(args.Grass))
|
||||
if (!OnCreep(args.Grass))
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
|
|
@ -1209,66 +1189,7 @@ namespace TShockAPI
|
|||
return;
|
||||
}
|
||||
|
||||
Ban ban = null;
|
||||
if (Config.EnableBanOnUsernames)
|
||||
{
|
||||
var newban = Bans.GetBanByName(player.Name);
|
||||
if (null != newban)
|
||||
ban = newban;
|
||||
}
|
||||
|
||||
if (Config.EnableIPBans && null == ban)
|
||||
{
|
||||
ban = Bans.GetBanByIp(player.IP);
|
||||
}
|
||||
|
||||
if (Config.EnableUUIDBans && null == ban && !String.IsNullOrWhiteSpace(player.UUID))
|
||||
{
|
||||
ban = Bans.GetBanByUUID(player.UUID);
|
||||
}
|
||||
|
||||
if (ban != null)
|
||||
{
|
||||
if (!Bans.RemoveBanIfExpired(ban))
|
||||
{
|
||||
DateTime exp;
|
||||
if (!DateTime.TryParse(ban.Expiration, out exp))
|
||||
{
|
||||
player.Disconnect("Permanently banned for: " + ban.Reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimeSpan ts = exp - DateTime.UtcNow;
|
||||
int months = ts.Days / 30;
|
||||
if (months > 0)
|
||||
{
|
||||
player.Disconnect(String.Format("You are banned for {0} month{1} and {2} day{3}: {4}",
|
||||
months, months == 1 ? "" : "s", ts.Days, ts.Days == 1 ? "" : "s", ban.Reason));
|
||||
}
|
||||
else if (ts.Days > 0)
|
||||
{
|
||||
player.Disconnect(String.Format("You are banned for {0} day{1} and {2} hour{3}: {4}",
|
||||
ts.Days, ts.Days == 1 ? "" : "s", ts.Hours, ts.Hours == 1 ? "" : "s", ban.Reason));
|
||||
}
|
||||
else if (ts.Hours > 0)
|
||||
{
|
||||
player.Disconnect(String.Format("You are banned for {0} hour{1} and {2} minute{3}: {4}",
|
||||
ts.Hours, ts.Hours == 1 ? "" : "s", ts.Minutes, ts.Minutes == 1 ? "" : "s", ban.Reason));
|
||||
}
|
||||
else if (ts.Minutes > 0)
|
||||
{
|
||||
player.Disconnect(String.Format("You are banned for {0} minute{1} and {2} second{3}: {4}",
|
||||
ts.Minutes, ts.Minutes == 1 ? "" : "s", ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
|
||||
}
|
||||
else
|
||||
{
|
||||
player.Disconnect(String.Format("You are banned for {0} second{1}: {2}",
|
||||
ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
|
||||
}
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
Bans.CheckBan(player);
|
||||
}
|
||||
|
||||
/// <summary>OnLeave - Called when a player leaves the server.</summary>
|
||||
|
|
@ -1647,7 +1568,8 @@ namespace TShockAPI
|
|||
player.RecentlyCreatedProjectiles.RemoveAll(p => p.Index == e.number && p.Killed);
|
||||
}
|
||||
|
||||
if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number)) {
|
||||
if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number))
|
||||
{
|
||||
player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct()
|
||||
{
|
||||
Index = e.number,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ using System.Text.RegularExpressions;
|
|||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using Terraria.Utilities;
|
||||
using TShockAPI.DB;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Terraria.Localization;
|
||||
using TShockAPI.Localization;
|
||||
|
|
@ -39,6 +38,31 @@ namespace TShockAPI
|
|||
/// </summary>
|
||||
public class Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Hex code for a blue pastel color
|
||||
/// </summary>
|
||||
public const string BoldHighlight = "AAAAFF";
|
||||
/// <summary>
|
||||
/// Hex code for a red pastel color
|
||||
/// </summary>
|
||||
public const string RedHighlight = "FFAAAA";
|
||||
/// <summary>
|
||||
/// Hex code for a green pastel color
|
||||
/// </summary>
|
||||
public const string GreenHighlight = "AAFFAA";
|
||||
/// <summary>
|
||||
/// Hex code for a pink pastel color
|
||||
/// </summary>
|
||||
public const string PinkHighlight = "FFAAFF";
|
||||
/// <summary>
|
||||
/// Hex code for a yellow pastel color
|
||||
/// </summary>
|
||||
public const string YellowHighlight = "FFFAAA";
|
||||
/// <summary>
|
||||
/// Hex code for a white highlight
|
||||
/// </summary>
|
||||
public const string WhiteHighlight = "FFFFFF";
|
||||
|
||||
/// <summary>
|
||||
/// The lowest id for a prefix.
|
||||
/// </summary>
|
||||
|
|
@ -465,7 +489,14 @@ namespace TShockAPI
|
|||
if (save)
|
||||
SaveManager.Instance.SaveWorld();
|
||||
|
||||
TSPlayer.All.Kick(reason, true, true, null, true);
|
||||
foreach (var player in TShock.Players.Where(p => p != null))
|
||||
{
|
||||
if (player.IsLoggedIn)
|
||||
{
|
||||
player.SaveServerCharacter();
|
||||
}
|
||||
player.Disconnect(reason);
|
||||
}
|
||||
|
||||
// Broadcast so console can see we are shutting down as well
|
||||
TShock.Utils.Broadcast(reason, Color.Red);
|
||||
|
|
@ -531,6 +562,11 @@ namespace TShockAPI
|
|||
{
|
||||
seconds = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(3);
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue