using Content.Shared.Mind;
using Content.Shared.Players.PlayTimeTracking;
using Prometheus;
+using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Configuration;
+using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Server.Administration.Logs;
Players = players,
};
- DoAdminAlerts(players, message, impact);
+ DoAdminAlerts(players, message, impact, handler);
if (preRound)
{
return players;
}
+ /// <summary>
+ /// Get a list of coordinates from the <see cref="LogStringHandler"/>s values. Will transform all coordinate types
+ /// to map coordinates!
+ /// </summary>
+ /// <returns>A list of map coordinates that were found in the value input, can return an empty list.</returns>
+ private List<MapCoordinates> GetCoordinates(Dictionary<string, object?> values)
+ {
+ List<MapCoordinates> coordList = new();
+ EntityManager.TrySystem(out TransformSystem? transform);
+
+ foreach (var value in values.Values)
+ {
+ switch (value)
+ {
+ case EntityCoordinates entCords:
+ if (transform != null)
+ coordList.Add(transform.ToMapCoordinates(entCords));
+ continue;
+
+ case MapCoordinates mapCord:
+ coordList.Add(mapCord);
+ continue;
+ }
+ }
+
+ return coordList;
+ }
+
private void AddPlayer(List<AdminLogPlayer> players, Guid user, int logId)
{
// The majority of logs have a single player, or maybe two. Instead of allocating a List<AdminLogPlayer> and
});
}
- private void DoAdminAlerts(List<AdminLogPlayer> players, string message, LogImpact impact)
+ private void DoAdminAlerts(List<AdminLogPlayer> players, string message, LogImpact impact, LogStringHandler handler)
{
var adminLog = false;
var logMessage = message;
+ var playerNetEnts = new List<(NetEntity, string)>();
foreach (var player in players)
{
("name", cachedInfo.CharacterName),
("subtype", subtype));
}
+ if (cachedInfo != null && cachedInfo.NetEntity != null)
+ playerNetEnts.Add((cachedInfo.NetEntity.Value, cachedInfo.CharacterName));
}
if (adminLog)
}
if (adminLog)
+ {
_chat.SendAdminAlert(logMessage);
+
+ if (CreateTpLinks(playerNetEnts, out var tpLinks))
+ _chat.SendAdminAlertNoFormatOrEscape(tpLinks);
+
+ var coords = GetCoordinates(handler.Values);
+
+ if (CreateCordLinks(coords, out var cordLinks))
+ _chat.SendAdminAlertNoFormatOrEscape(cordLinks);
+ }
+ }
+
+ /// <summary>
+ /// Creates a list of tpto command links of the given players
+ /// </summary>
+ private bool CreateTpLinks(List<(NetEntity NetEnt, string CharacterName)> players, out string outString)
+ {
+ outString = string.Empty;
+
+ if (players.Count == 0)
+ return false;
+
+ outString = Loc.GetString("admin-alert-tp-to-players-header");
+
+ for (var i = 0; i < players.Count; i++)
+ {
+ var player = players[i];
+ outString += $"[cmdlink=\"{EscapeText(player.CharacterName)}\" command=\"tpto {player.NetEnt}\"/]";
+
+ if (i < players.Count - 1)
+ outString += ", ";
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Creates a list of toto command links for the given map coordinates.
+ /// </summary>
+ private bool CreateCordLinks(List<MapCoordinates> cords, out string outString)
+ {
+ outString = string.Empty;
+
+ if (cords.Count == 0)
+ return false;
+
+ outString = Loc.GetString("admin-alert-tp-to-coords-header");
+
+ for (var i = 0; i < cords.Count; i++)
+ {
+ var cord = cords[i];
+ outString += $"[cmdlink=\"{cord.ToString()}\" command=\"tp {cord.X} {cord.Y} {cord.MapId}\"/]";
+
+ if (i < cords.Count - 1)
+ outString += ", ";
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Escape the given text to not allow breakouts of the cmdlink tags.
+ /// </summary>
+ private string EscapeText(string text)
+ {
+ return FormattedMessage.EscapeText(text).Replace("\"", "\\\"").Replace("'", "\\'");
}
public async Task<List<SharedAdminLog>> All(LogFilter? filter = null, Func<List<SharedAdminLog>>? listProvider = null)
public void SendAdminAlert(string message)
{
- var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
-
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message)));
- ChatMessageToMany(ChatChannel.AdminAlert, message, wrappedMessage, default, false, true, clients);
+ SendAdminAlertNoFormatOrEscape(wrappedMessage);
+ }
+
+ public void SendAdminAlertNoFormatOrEscape(string message)
+ {
+ var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
+
+ ChatMessageToMany(ChatChannel.AdminAlert, message, message, default, false, true, clients);
}
+
public void SendAdminAlert(EntityUid player, string message)
{
var mindSystem = _entityManager.System<SharedMindSystem>();
public interface ISharedChatManager
{
void Initialize();
+
+ /// <summary>
+ /// Send an admin alert to the admin chat channel.
+ /// </summary>
+ /// <param name="message">The message to send.</param>
void SendAdminAlert(string message);
+
+ /// <summary>
+ /// Send an admin alert to the admin chat channel specifically about the given player.
+ /// Will include info extra like their antag status and name.
+ /// </summary>
+ /// <param name="player">The player that the message is about.</param>
+ /// <param name="message">The message to send.</param>
void SendAdminAlert(EntityUid player, string message);
+
+ /// <summary>
+ /// This is a dangerous function! Only pass in property escaped text.
+ /// See: <see cref="SendAdminAlert(string)"/>
+ /// <br/><br/>
+ /// Use this for things that need to be unformatted (like tpto links) but ensure that everything else
+ /// is formated properly. If it's not, players could sneak in ban links or other nasty commands that the admins
+ /// could clink on.
+ /// </summary>
+ void SendAdminAlertNoFormatOrEscape(string message);
}