Better notes and bans (#14228)
Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
This commit is contained in:
63
Content.Server/Administration/Notes/AdminMessageEui.cs
Normal file
63
Content.Server/Administration/Notes/AdminMessageEui.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Content.Server.Database;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Configuration;
|
||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
|
||||
public sealed class AdminMessageEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
private readonly float _closeWait;
|
||||
private AdminMessage? _message;
|
||||
private DateTime _startTime;
|
||||
|
||||
public AdminMessageEui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
|
||||
}
|
||||
|
||||
public void SetMessage(AdminMessage message)
|
||||
{
|
||||
_message = message;
|
||||
_startTime = DateTime.UtcNow;
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
if (_message == null)
|
||||
return new AdminMessageEuiState(float.MaxValue, "An error has occurred.", string.Empty, DateTime.MinValue);
|
||||
return new AdminMessageEuiState(
|
||||
_closeWait,
|
||||
_message.Message,
|
||||
_message.CreatedBy?.LastSeenUserName ?? "[System]",
|
||||
_message.CreatedAt
|
||||
);
|
||||
}
|
||||
|
||||
public override async void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case Accept:
|
||||
if (_message == null)
|
||||
break;
|
||||
// No escape
|
||||
if (DateTime.UtcNow - _startTime >= TimeSpan.FromSeconds(_closeWait))
|
||||
await _notesMan.MarkMessageAsSeen(_message.Id);
|
||||
Close();
|
||||
break;
|
||||
case Dismiss:
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Eui;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Robust.Shared.Network;
|
||||
using static Content.Shared.Administration.Notes.AdminNoteEuiMsg;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
@@ -11,6 +15,8 @@ public sealed class AdminNotesEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _admins = default!;
|
||||
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||
[Dependency] private readonly IPlayerLocator _locator = default!;
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
|
||||
public AdminNotesEui()
|
||||
{
|
||||
@@ -19,7 +25,8 @@ public sealed class AdminNotesEui : BaseEui
|
||||
|
||||
private Guid NotedPlayer { get; set; }
|
||||
private string NotedPlayerName { get; set; } = string.Empty;
|
||||
private Dictionary<int, SharedAdminNote> Notes { get; set; } = new();
|
||||
private bool HasConnectedBefore { get; set; }
|
||||
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
|
||||
|
||||
public override async void Opened()
|
||||
{
|
||||
@@ -46,7 +53,7 @@ public sealed class AdminNotesEui : BaseEui
|
||||
return new AdminNotesEuiState(
|
||||
NotedPlayerName,
|
||||
Notes,
|
||||
_notesMan.CanCreate(Player),
|
||||
_notesMan.CanCreate(Player) && HasConnectedBefore,
|
||||
_notesMan.CanDelete(Player),
|
||||
_notesMan.CanEdit(Player)
|
||||
);
|
||||
@@ -58,49 +65,51 @@ public sealed class AdminNotesEui : BaseEui
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case CreateNoteRequest {Message: var message}:
|
||||
{
|
||||
if (!_notesMan.CanCreate(Player))
|
||||
case CreateNoteRequest request:
|
||||
{
|
||||
Close();
|
||||
if (!_notesMan.CanCreate(Player))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Message))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (request.ExpiryTime is not null && request.ExpiryTime <= DateTime.UtcNow)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.AddAdminRemark(Player, NotedPlayer, request.NoteType, request.Message, request.NoteSeverity, request.Secret, request.ExpiryTime);
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.AddNote(Player, NotedPlayer, message);
|
||||
break;
|
||||
}
|
||||
case DeleteNoteRequest request:
|
||||
{
|
||||
if (!_notesMan.CanDelete(Player))
|
||||
{
|
||||
Close();
|
||||
if (!_notesMan.CanDelete(Player))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.DeleteAdminRemark(request.Id, request.Type, Player);
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.DeleteNote(request.Id, Player);
|
||||
break;
|
||||
}
|
||||
case EditNoteRequest request:
|
||||
{
|
||||
if (!_notesMan.CanEdit(Player))
|
||||
{
|
||||
Close();
|
||||
if (!_notesMan.CanEdit(Player))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Message))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.ModifyAdminRemark(request.Id, request.Type, Player, request.Message, request.NoteSeverity, request.Secret, request.ExpiryTime);
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Message))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await _notesMan.ModifyNote(request.Id, Player, request.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +124,7 @@ public sealed class AdminNotesEui : BaseEui
|
||||
if (note.Player != NotedPlayer)
|
||||
return;
|
||||
|
||||
Notes[note.Id] = note;
|
||||
Notes[(note.Id, note.NoteType)] = note;
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
@@ -124,28 +133,29 @@ public sealed class AdminNotesEui : BaseEui
|
||||
if (note.Player != NotedPlayer)
|
||||
return;
|
||||
|
||||
Notes.Remove(note.Id);
|
||||
Notes.Remove((note.Id, note.NoteType));
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
private async Task LoadFromDb()
|
||||
{
|
||||
NotedPlayerName = await _notesMan.GetPlayerName(NotedPlayer);
|
||||
|
||||
var notes = new Dictionary<int, SharedAdminNote>();
|
||||
foreach (var note in await _notesMan.GetNotes(NotedPlayer))
|
||||
{
|
||||
notes.Add(note.Id, note.ToShared());
|
||||
}
|
||||
|
||||
Notes = notes;
|
||||
|
||||
var locatedPlayer = await _locator.LookupIdAsync((NetUserId) NotedPlayer);
|
||||
NotedPlayerName = locatedPlayer?.Username ?? string.Empty;
|
||||
HasConnectedBefore = locatedPlayer?.LastAddress is not null;
|
||||
Notes = (from note in await _notesMan.GetAllAdminRemarks(NotedPlayer)
|
||||
select note.ToShared())
|
||||
.ToDictionary(sharedNote => (sharedNote.Id, sharedNote.NoteType));
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
private void OnPermsChanged(AdminPermsChangedEventArgs args)
|
||||
{
|
||||
if (args.Player == Player && !_notesMan.CanView(Player))
|
||||
if (args.Player != Player)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_notesMan.CanView(Player))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1,21 +1,75 @@
|
||||
using Content.Server.Database;
|
||||
using System.Diagnostics;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
|
||||
public static class AdminNotesExtensions
|
||||
{
|
||||
public static SharedAdminNote ToShared(this AdminNote note)
|
||||
public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
|
||||
{
|
||||
NoteSeverity? severity = null;
|
||||
var secret = false;
|
||||
NoteType type;
|
||||
string[]? bannedRoles = null;
|
||||
string? unbannedByName = null;
|
||||
DateTime? unbannedTime = null;
|
||||
bool? seen = null;
|
||||
switch (note)
|
||||
{
|
||||
case AdminNote adminNote:
|
||||
type = NoteType.Note;
|
||||
severity = adminNote.Severity;
|
||||
secret = adminNote.Secret;
|
||||
break;
|
||||
case AdminWatchlist:
|
||||
type = NoteType.Watchlist;
|
||||
secret = true;
|
||||
break;
|
||||
case AdminMessage adminMessage:
|
||||
type = NoteType.Message;
|
||||
seen = adminMessage.Seen;
|
||||
break;
|
||||
case ServerBanNote ban:
|
||||
type = NoteType.ServerBan;
|
||||
severity = ban.Severity;
|
||||
unbannedTime = ban.UnbanTime;
|
||||
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||
break;
|
||||
case ServerRoleBanNote roleBan:
|
||||
type = NoteType.RoleBan;
|
||||
severity = roleBan.Severity;
|
||||
bannedRoles = roleBan.Roles;
|
||||
unbannedTime = roleBan.UnbanTime;
|
||||
unbannedByName = roleBan.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), note.GetType(), "Unknown note type");
|
||||
}
|
||||
|
||||
// There may be bans without a user, but why would we ever be converting them to shared notes?
|
||||
if (note.PlayerUserId is null)
|
||||
throw new ArgumentNullException(nameof(note.PlayerUserId), "Player user ID cannot be null for a note");
|
||||
return new SharedAdminNote(
|
||||
note.Id,
|
||||
note.PlayerUserId.Value,
|
||||
note.RoundId,
|
||||
note.PlayerUserId,
|
||||
note.Round?.Server.Name,
|
||||
note.PlaytimeAtNote,
|
||||
type,
|
||||
note.Message,
|
||||
note.CreatedBy.LastSeenUserName,
|
||||
note.LastEditedBy.LastSeenUserName,
|
||||
severity,
|
||||
secret,
|
||||
note.CreatedBy?.LastSeenUserName ?? Loc.GetString("system-user"),
|
||||
note.LastEditedBy?.LastSeenUserName ?? string.Empty,
|
||||
note.CreatedAt,
|
||||
note.LastEditedAt
|
||||
note.LastEditedAt,
|
||||
note.ExpirationTime,
|
||||
bannedRoles,
|
||||
unbannedTime,
|
||||
unbannedByName,
|
||||
seen
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
@@ -17,6 +23,7 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly EuiManager _euis = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
public const string SawmillId = "admin.notes";
|
||||
|
||||
@@ -54,91 +61,280 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
||||
await ui.ChangeNotedPlayer(notedPlayer);
|
||||
}
|
||||
|
||||
public async Task AddNote(IPlayerSession createdBy, Guid player, string message)
|
||||
public async Task OpenUserNotesEui(IPlayerSession player)
|
||||
{
|
||||
_sawmill.Info($"Player {createdBy.Name} added note with message {message}");
|
||||
var ui = new UserNotesEui();
|
||||
_euis.OpenEui(ui, player);
|
||||
|
||||
await ui.UpdateNotes();
|
||||
}
|
||||
|
||||
public async Task AddAdminRemark(IPlayerSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
|
||||
{
|
||||
message = message.Trim();
|
||||
|
||||
// There's a foreign key constraint in place here. If there's no player record, it will fail.
|
||||
// Not like there's much use in adding notes on accounts that have never connected.
|
||||
// You can still ban them just fine, which is why we should allow admins to view their bans with the notes panel
|
||||
if (await _db.GetPlayerRecordByUserId((NetUserId) player) is null)
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder($"{createdBy.Name} added a");
|
||||
|
||||
if (secret && type == NoteType.Note)
|
||||
{
|
||||
sb.Append(" secret");
|
||||
}
|
||||
|
||||
sb.Append($" {type} with message {message}");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NoteType.Note:
|
||||
sb.Append($" with {severity} severity");
|
||||
break;
|
||||
case NoteType.Message:
|
||||
severity = null;
|
||||
secret = false;
|
||||
break;
|
||||
case NoteType.Watchlist:
|
||||
severity = null;
|
||||
secret = true;
|
||||
break;
|
||||
case NoteType.ServerBan:
|
||||
case NoteType.RoleBan:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||
}
|
||||
|
||||
if (expiryTime is not null)
|
||||
{
|
||||
sb.Append($" which expires on {expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
|
||||
}
|
||||
|
||||
_sawmill.Info(sb.ToString());
|
||||
|
||||
_systems.TryGetEntitySystem(out GameTicker? ticker);
|
||||
int? round = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
|
||||
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
|
||||
var serverName = _config.GetCVar(CCVars.AdminLogsServerName); // This could probably be done another way, but this is fine. For displaying only.
|
||||
var createdAt = DateTime.UtcNow;
|
||||
var noteId = await _db.AddAdminNote(round, player, message, createdBy.UserId, createdAt);
|
||||
var playtime = (await _db.GetPlayTimes(player)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
|
||||
int noteId;
|
||||
bool? seen = null;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NoteType.Note:
|
||||
if (severity is null)
|
||||
throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
|
||||
noteId = await _db.AddAdminNote(roundId, player, playtime, message, severity.Value, secret, createdBy.UserId, createdAt, expiryTime);
|
||||
break;
|
||||
case NoteType.Watchlist:
|
||||
secret = true;
|
||||
noteId = await _db.AddAdminWatchlist(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
|
||||
break;
|
||||
case NoteType.Message:
|
||||
noteId = await _db.AddAdminMessage(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
|
||||
seen = false;
|
||||
break;
|
||||
case NoteType.ServerBan: // Add bans using the ban panel, not note edit
|
||||
case NoteType.RoleBan:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||
}
|
||||
|
||||
var note = new SharedAdminNote(
|
||||
noteId,
|
||||
round,
|
||||
player,
|
||||
roundId,
|
||||
serverName,
|
||||
playtime,
|
||||
type,
|
||||
message,
|
||||
severity,
|
||||
secret,
|
||||
createdBy.Name,
|
||||
createdBy.Name,
|
||||
createdAt,
|
||||
createdAt
|
||||
createdAt,
|
||||
expiryTime,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
seen
|
||||
);
|
||||
NoteAdded?.Invoke(note);
|
||||
}
|
||||
|
||||
public async Task DeleteNote(int noteId, IPlayerSession deletedBy)
|
||||
private async Task<SharedAdminNote?> GetAdminRemark(int id, NoteType type)
|
||||
{
|
||||
var note = await _db.GetAdminNote(noteId);
|
||||
return type switch
|
||||
{
|
||||
NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
|
||||
NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
|
||||
NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
|
||||
NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(),
|
||||
NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
|
||||
};
|
||||
}
|
||||
|
||||
public async Task DeleteAdminRemark(int noteId, NoteType type, IPlayerSession deletedBy)
|
||||
{
|
||||
var note = await GetAdminRemark(noteId, type);
|
||||
if (note == null)
|
||||
{
|
||||
_sawmill.Info($"Player {deletedBy.Name} tried to delete non-existent note {noteId}");
|
||||
_sawmill.Warning($"Player {deletedBy.Name} has tried to delete non-existent {type} {noteId}");
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Info($"Player {deletedBy.Name} deleted note {noteId}");
|
||||
|
||||
var deletedAt = DateTime.UtcNow;
|
||||
await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
|
||||
|
||||
var sharedNote = new SharedAdminNote(
|
||||
noteId,
|
||||
note.RoundId,
|
||||
note.PlayerUserId,
|
||||
note.Message,
|
||||
note.CreatedBy.LastSeenUserName,
|
||||
note.LastEditedBy.LastSeenUserName,
|
||||
note.CreatedAt,
|
||||
note.LastEditedAt
|
||||
);
|
||||
NoteDeleted?.Invoke(sharedNote);
|
||||
switch (type)
|
||||
{
|
||||
case NoteType.Note:
|
||||
await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
|
||||
break;
|
||||
case NoteType.Watchlist:
|
||||
await _db.DeleteAdminWatchlist(noteId, deletedBy.UserId, deletedAt);
|
||||
break;
|
||||
case NoteType.Message:
|
||||
await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
|
||||
break;
|
||||
case NoteType.ServerBan:
|
||||
await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt);
|
||||
break;
|
||||
case NoteType.RoleBan:
|
||||
await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||
}
|
||||
|
||||
_sawmill.Info($"{deletedBy.Name} has deleted {type} {noteId}");
|
||||
NoteDeleted?.Invoke(note);
|
||||
}
|
||||
|
||||
public async Task ModifyNote(int noteId, IPlayerSession editedBy, string message)
|
||||
public async Task ModifyAdminRemark(int noteId, NoteType type, IPlayerSession editedBy, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
|
||||
{
|
||||
message = message.Trim();
|
||||
|
||||
var note = await _db.GetAdminNote(noteId);
|
||||
if (note == null || note.Message == message)
|
||||
var note = await GetAdminRemark(noteId, type);
|
||||
|
||||
// If the note doesn't exist or is the same, we skip updating it
|
||||
if (note == null ||
|
||||
note.Message == message &&
|
||||
note.NoteSeverity == severity &&
|
||||
note.Secret == secret &&
|
||||
note.ExpiryTime == expiryTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Info($"Player {editedBy.Name} modified note {noteId} with message {message}");
|
||||
var sb = new StringBuilder($"{editedBy.Name} has modified {type} {noteId}");
|
||||
|
||||
if (note.Message != message)
|
||||
{
|
||||
sb.Append($", modified message from {note.Message} to {message}");
|
||||
}
|
||||
|
||||
if (note.Secret != secret)
|
||||
{
|
||||
sb.Append($", made it {(secret ? "secret" : "visible")}");
|
||||
}
|
||||
|
||||
if (note.NoteSeverity != severity)
|
||||
{
|
||||
sb.Append($", updated the severity from {note.NoteSeverity} to {severity}");
|
||||
}
|
||||
|
||||
if (note.ExpiryTime != expiryTime)
|
||||
{
|
||||
sb.Append(", updated the expiry time from ");
|
||||
if (note.ExpiryTime is null)
|
||||
sb.Append("never");
|
||||
else
|
||||
sb.Append($"{note.ExpiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
|
||||
|
||||
sb.Append(" to ");
|
||||
|
||||
if (expiryTime is null)
|
||||
sb.Append("never");
|
||||
else
|
||||
sb.Append($"{expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
|
||||
}
|
||||
|
||||
_sawmill.Info(sb.ToString());
|
||||
|
||||
var editedAt = DateTime.UtcNow;
|
||||
await _db.EditAdminNote(noteId, message, editedBy.UserId, editedAt);
|
||||
|
||||
var sharedNote = new SharedAdminNote(
|
||||
noteId,
|
||||
note.RoundId,
|
||||
note.PlayerUserId,
|
||||
message,
|
||||
note.CreatedBy.LastSeenUserName,
|
||||
editedBy.Name,
|
||||
note.CreatedAt,
|
||||
note.LastEditedAt
|
||||
);
|
||||
NoteModified?.Invoke(sharedNote);
|
||||
switch (type)
|
||||
{
|
||||
case NoteType.Note:
|
||||
if (severity is null)
|
||||
throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
|
||||
await _db.EditAdminNote(noteId, message, severity.Value, secret, editedBy.UserId, editedAt, expiryTime);
|
||||
break;
|
||||
case NoteType.Watchlist:
|
||||
await _db.EditAdminWatchlist(noteId, message, editedBy.UserId, editedAt, expiryTime);
|
||||
break;
|
||||
case NoteType.Message:
|
||||
await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
|
||||
break;
|
||||
case NoteType.ServerBan:
|
||||
if (severity is null)
|
||||
throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
|
||||
await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
|
||||
break;
|
||||
case NoteType.RoleBan:
|
||||
if (severity is null)
|
||||
throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity));
|
||||
await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||
}
|
||||
|
||||
var newNote = note with
|
||||
{
|
||||
Message = message,
|
||||
NoteSeverity = severity,
|
||||
Secret = secret,
|
||||
LastEditedAt = editedAt,
|
||||
EditedByName = editedBy.Name,
|
||||
ExpiryTime = expiryTime
|
||||
};
|
||||
NoteModified?.Invoke(newNote);
|
||||
}
|
||||
|
||||
public async Task<List<AdminNote>> GetNotes(Guid player)
|
||||
public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player)
|
||||
{
|
||||
return await _db.GetAdminNotes(player);
|
||||
return await _db.GetAllAdminRemarks(player);
|
||||
}
|
||||
|
||||
public async Task<string> GetPlayerName(Guid player)
|
||||
public async Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player)
|
||||
{
|
||||
return (await _db.GetPlayerRecordByUserId(new NetUserId(player)))?.LastSeenUserName ?? string.Empty;
|
||||
if (_config.GetCVar(CCVars.SeeOwnNotes))
|
||||
{
|
||||
return await _db.GetVisibleAdminNotes(player);
|
||||
}
|
||||
_sawmill.Warning($"Someone tried to call GetVisibleNotes for {player} when see_own_notes was false");
|
||||
return new List<IAdminRemarksCommon>();
|
||||
}
|
||||
|
||||
public async Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player)
|
||||
{
|
||||
return await _db.GetActiveWatchlists(player);
|
||||
}
|
||||
|
||||
public async Task<List<AdminMessage>> GetNewMessages(Guid player)
|
||||
{
|
||||
return await _db.GetMessages(player);
|
||||
}
|
||||
|
||||
public async Task MarkMessageAsSeen(int id)
|
||||
{
|
||||
await _db.MarkMessageAsSeen(id);
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
|
||||
public sealed class AdminNotesSystem : EntitySystem
|
||||
public sealed class AdminNotesSystem : EntitySystem, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IAdminNotesManager _notes = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly EuiManager _euis = default!;
|
||||
|
||||
public const string SawmillId = "admin.notes_system";
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddVerbs);
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void AddVerbs(GetVerbsEvent<Verb> ev)
|
||||
@@ -41,4 +54,43 @@ public sealed class AdminNotesSystem : EntitySystem
|
||||
|
||||
ev.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.Connected)
|
||||
return;
|
||||
|
||||
var messages = await _notes.GetNewMessages(e.Session.UserId);
|
||||
var watchlists = await _notes.GetActiveWatchlists(e.Session.UserId);
|
||||
|
||||
if (!_playerManager.TryGetPlayerData(e.Session.UserId, out var playerData))
|
||||
{
|
||||
_sawmill.Error($"Could not get player data for ID {e.Session.UserId}");
|
||||
}
|
||||
|
||||
var username = playerData?.UserName ?? e.Session.UserId.ToString();
|
||||
foreach (var watchlist in watchlists)
|
||||
{
|
||||
_chat.SendAdminAlert(Loc.GetString("admin-notes-watchlist", ("player", username), ("message", watchlist.Message)));
|
||||
}
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var messageString = Loc.GetString("admin-notes-new-message", ("admin", message.CreatedBy?.LastSeenUserName ?? "[System]"), ("message", message.Message));
|
||||
// Only open the popup if the user hasn't seen it yet
|
||||
if (!message.Seen)
|
||||
{
|
||||
var ui = new AdminMessageEui();
|
||||
_euis.OpenEui(ui, e.Session);
|
||||
ui.SetMessage(message);
|
||||
}
|
||||
// Send the message anyway
|
||||
_chat.DispatchServerMessage(e.Session, messageString);
|
||||
}
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill(SawmillId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
@@ -16,9 +17,33 @@ public interface IAdminNotesManager
|
||||
bool CanEdit(IPlayerSession admin);
|
||||
bool CanView(IPlayerSession admin);
|
||||
Task OpenEui(IPlayerSession admin, Guid notedPlayer);
|
||||
Task AddNote(IPlayerSession createdBy, Guid player, string message);
|
||||
Task DeleteNote(int noteId, IPlayerSession deletedBy);
|
||||
Task ModifyNote(int noteId, IPlayerSession editedBy, string message);
|
||||
Task<List<AdminNote>> GetNotes(Guid player);
|
||||
Task<string> GetPlayerName(Guid player);
|
||||
Task OpenUserNotesEui(IPlayerSession player);
|
||||
Task AddAdminRemark(IPlayerSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
|
||||
Task DeleteAdminRemark(int noteId, NoteType type, IPlayerSession deletedBy);
|
||||
Task ModifyAdminRemark(int noteId, NoteType type, IPlayerSession editedBy, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
|
||||
/// <summary>
|
||||
/// Queries the database and retrieves all notes, secret and visible
|
||||
/// </summary>
|
||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||
/// <returns>ALL non-deleted notes, secret or not</returns>
|
||||
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player);
|
||||
/// <summary>
|
||||
/// Queries the database and retrieves the notes a player should see
|
||||
/// </summary>
|
||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||
/// <returns>All player-visible notes</returns>
|
||||
Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player);
|
||||
/// <summary>
|
||||
/// Queries the database and retrieves watchlists that may have been placed on the player
|
||||
/// </summary>
|
||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||
/// <returns>Active watchlists</returns>
|
||||
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player);
|
||||
/// <summary>
|
||||
/// Queries the database and retrieves new messages a player has gotten
|
||||
/// </summary>
|
||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||
/// <returns>All unread messages</returns>
|
||||
Task<List<AdminMessage>> GetNewMessages(Guid player);
|
||||
Task MarkMessageAsSeen(int id);
|
||||
}
|
||||
|
||||
49
Content.Server/Administration/Notes/UserNotesEui.cs
Normal file
49
Content.Server/Administration/Notes/UserNotesEui.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
|
||||
public sealed class UserNotesEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
private readonly bool _seeOwnNotes;
|
||||
|
||||
public UserNotesEui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_seeOwnNotes = _cfg.GetCVar(CCVars.SeeOwnNotes);
|
||||
|
||||
if (!_seeOwnNotes)
|
||||
{
|
||||
Logger.WarningS("admin.notes", "User notes initialized when see_own_notes set to false");
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
return new UserNotesEuiState(
|
||||
Notes
|
||||
);
|
||||
}
|
||||
|
||||
public async Task UpdateNotes()
|
||||
{
|
||||
if (!_seeOwnNotes)
|
||||
{
|
||||
Logger.WarningS("admin.notes", $"User {Player.Name} with ID {Player.UserId} tried to update their own user notes when see_own_notes was set to false");
|
||||
return;
|
||||
}
|
||||
|
||||
Notes = (await _notesMan.GetVisibleRemarks(Player.UserId)).Select(note => note.ToShared()).ToDictionary(note => (note.Id, note.NoteType));
|
||||
StateDirty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user