Whisper/Tags.cs
Astra c414e8a770 Whisper 0.1.0: private messages between players
/pm (/w) with names, #userid, SteamIDs, @me, @aim, @random, @randomt and
@randomct, /r to reply, and /block, /blocklist, /unblock. Lines use
CS2-Tags' rank prefixes and colours: >[[M] Kalus -> me] message. Gagged
players can't send; blocks stop everyone but staff. Messages are kept in
CS2-SimpleAdmin's database while History is on (css_whisper_history).
2026-10-05 13:13:58 +01:00

97 lines
3.7 KiB
C#

using System.Reflection;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Utils;
namespace Whisper;
// Reads CS2-Tags' tags.json so private messages show the same rank prefix and colours as chat, and
// picks a player's tag the way CS2-Tags does: their SteamID64 first, then the first "#group" or
// "@permission" key they match in file order, then "everyone". Reread when the file changes.
public sealed class Tags(string path, Action<string> warn)
{
public sealed record Tag(string Prefix, string NickColor, string MessageColor);
private List<KeyValuePair<string, Tag>> _tags = [];
private DateTime _loadedWrite;
public Tag? Find(CCSPlayerController player)
{
Refresh();
if (player.IsBot || player.IsHLTV || player.AuthorizedSteamID == null)
return null;
var steamid = player.AuthorizedSteamID.SteamId64.ToString();
foreach (var (key, tag) in _tags)
if (key == steamid)
return tag;
foreach (var (key, tag) in _tags)
{
if (key.StartsWith('#') && AdminManager.PlayerInGroup(player, key)) return tag;
if (key.StartsWith('@') && AdminManager.PlayerHasPermissions(player, key)) return tag;
}
foreach (var (key, tag) in _tags)
if (key == "everyone")
return tag;
return null;
}
private void Refresh()
{
DateTime written;
try
{
written = File.GetLastWriteTimeUtc(path);
}
catch (Exception)
{
return;
}
// GetLastWriteTimeUtc gives 1601 for a missing file.
if (written == _loadedWrite || written.Year < 1700)
{
if (written.Year < 1700 && _tags.Count > 0)
_tags = [];
_loadedWrite = written;
return;
}
_loadedWrite = written;
try
{
using var doc = JsonDocument.Parse(File.ReadAllText(path),
new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true });
var tags = new List<KeyValuePair<string, Tag>>();
if (doc.RootElement.TryGetProperty("tags", out var section))
foreach (var entry in section.EnumerateObject())
tags.Add(new(entry.Name, new Tag(Str(entry.Value, "prefix"), Str(entry.Value, "nick_color"), Str(entry.Value, "message_color"))));
_tags = tags;
}
catch (Exception ex)
{
// Keep the tags from before, as CS2-Tags does with a broken file.
warn($"Couldn't read {path}: {ex.Message}; keeping the previous tags");
}
}
private static string Str(JsonElement e, string name) =>
e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() ?? "" : "";
public static string TeamColor(int team) => team switch
{
(int)CsTeam.Terrorist => ChatColors.Gold.ToString(),
(int)CsTeam.CounterTerrorist => ChatColors.Blue.ToString(),
_ => "",
};
// Colour tags in config strings only, never in names or messages: {Red}, {Default}, {TEAMCOLOR}.
public static string Colors(string text, int team = 0)
{
if (!text.Contains('{'))
return text;
foreach (var field in typeof(ChatColors).GetFields(BindingFlags.Public | BindingFlags.Static))
if (field.FieldType == typeof(char))
text = text.Replace($"{{{field.Name}}}", field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase);
return text.Replace("{TEAMCOLOR}", TeamColor(team), StringComparison.OrdinalIgnoreCase);
}
}