AdminNotesManager.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. using System.Text;
  2. using System.Threading.Tasks;
  3. using Content.Server.Administration.Managers;
  4. using Content.Server.Database;
  5. using Content.Server.EUI;
  6. using Content.Server.GameTicking;
  7. using Content.Shared.Administration;
  8. using Content.Shared.Administration.Notes;
  9. using Content.Shared.CCVar;
  10. using Content.Shared.Database;
  11. using Content.Shared.Players.PlayTimeTracking;
  12. using Robust.Shared.Configuration;
  13. using Robust.Shared.Network;
  14. using Robust.Shared.Player;
  15. namespace Content.Server.Administration.Notes;
  16. public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
  17. {
  18. [Dependency] private readonly IAdminManager _admins = default!;
  19. [Dependency] private readonly IServerDbManager _db = default!;
  20. [Dependency] private readonly ILogManager _logManager = default!;
  21. [Dependency] private readonly EuiManager _euis = default!;
  22. [Dependency] private readonly IEntitySystemManager _systems = default!;
  23. [Dependency] private readonly IConfigurationManager _config = default!;
  24. public const string SawmillId = "admin.notes";
  25. public event Action<SharedAdminNote>? NoteAdded;
  26. public event Action<SharedAdminNote>? NoteModified;
  27. public event Action<SharedAdminNote>? NoteDeleted;
  28. private ISawmill _sawmill = default!;
  29. public bool CanCreate(ICommonSession admin)
  30. {
  31. return CanEdit(admin);
  32. }
  33. public bool CanDelete(ICommonSession admin)
  34. {
  35. return CanEdit(admin);
  36. }
  37. public bool CanEdit(ICommonSession admin)
  38. {
  39. return _admins.HasAdminFlag(admin, AdminFlags.EditNotes);
  40. }
  41. public bool CanView(ICommonSession admin)
  42. {
  43. return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes);
  44. }
  45. public async Task OpenEui(ICommonSession admin, Guid notedPlayer)
  46. {
  47. var ui = new AdminNotesEui();
  48. _euis.OpenEui(ui, admin);
  49. await ui.ChangeNotedPlayer(notedPlayer);
  50. }
  51. public async Task OpenUserNotesEui(ICommonSession player)
  52. {
  53. var ui = new UserNotesEui();
  54. _euis.OpenEui(ui, player);
  55. await ui.UpdateNotes();
  56. }
  57. public async Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
  58. {
  59. message = message.Trim();
  60. // There's a foreign key constraint in place here. If there's no player record, it will fail.
  61. // Not like there's much use in adding notes on accounts that have never connected.
  62. // You can still ban them just fine, which is why we should allow admins to view their bans with the notes panel
  63. if (await _db.GetPlayerRecordByUserId((NetUserId) player) is null)
  64. return;
  65. var sb = new StringBuilder($"{createdBy.Name} added a");
  66. if (secret && type == NoteType.Note)
  67. {
  68. sb.Append(" secret");
  69. }
  70. sb.Append($" {type} with message {message}");
  71. switch (type)
  72. {
  73. case NoteType.Note:
  74. sb.Append($" with {severity} severity");
  75. break;
  76. case NoteType.Message:
  77. severity = null;
  78. secret = false;
  79. break;
  80. case NoteType.Watchlist:
  81. severity = null;
  82. secret = true;
  83. break;
  84. case NoteType.ServerBan:
  85. case NoteType.RoleBan:
  86. default:
  87. throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
  88. }
  89. if (expiryTime is not null)
  90. {
  91. sb.Append($" which expires on {expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
  92. }
  93. _sawmill.Info(sb.ToString());
  94. _systems.TryGetEntitySystem(out GameTicker? ticker);
  95. int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
  96. var serverName = _config.GetCVar(CCVars.AdminLogsServerName); // This could probably be done another way, but this is fine. For displaying only.
  97. var createdAt = DateTime.UtcNow;
  98. var playtime = (await _db.GetPlayTimes(player)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
  99. int noteId;
  100. bool? seen = null;
  101. switch (type)
  102. {
  103. case NoteType.Note:
  104. if (severity is null)
  105. throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
  106. noteId = await _db.AddAdminNote(roundId, player, playtime, message, severity.Value, secret, createdBy.UserId, createdAt, expiryTime);
  107. break;
  108. case NoteType.Watchlist:
  109. secret = true;
  110. noteId = await _db.AddAdminWatchlist(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
  111. break;
  112. case NoteType.Message:
  113. noteId = await _db.AddAdminMessage(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
  114. seen = false;
  115. break;
  116. case NoteType.ServerBan: // Add bans using the ban panel, not note edit
  117. case NoteType.RoleBan:
  118. default:
  119. throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
  120. }
  121. var note = new SharedAdminNote(
  122. noteId,
  123. (NetUserId) player,
  124. roundId,
  125. serverName,
  126. playtime,
  127. type,
  128. message,
  129. severity,
  130. secret,
  131. createdBy.Name,
  132. createdBy.Name,
  133. createdAt,
  134. createdAt,
  135. expiryTime,
  136. null,
  137. null,
  138. null,
  139. seen
  140. );
  141. NoteAdded?.Invoke(note);
  142. }
  143. private async Task<SharedAdminNote?> GetAdminRemark(int id, NoteType type)
  144. {
  145. return type switch
  146. {
  147. NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
  148. NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
  149. NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
  150. NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(),
  151. NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(),
  152. _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
  153. };
  154. }
  155. public async Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession deletedBy)
  156. {
  157. var note = await GetAdminRemark(noteId, type);
  158. if (note == null)
  159. {
  160. _sawmill.Warning($"Player {deletedBy.Name} has tried to delete non-existent {type} {noteId}");
  161. return;
  162. }
  163. var deletedAt = DateTime.UtcNow;
  164. switch (type)
  165. {
  166. case NoteType.Note:
  167. await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
  168. break;
  169. case NoteType.Watchlist:
  170. await _db.DeleteAdminWatchlist(noteId, deletedBy.UserId, deletedAt);
  171. break;
  172. case NoteType.Message:
  173. await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
  174. break;
  175. case NoteType.ServerBan:
  176. await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt);
  177. break;
  178. case NoteType.RoleBan:
  179. await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt);
  180. break;
  181. default:
  182. throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
  183. }
  184. _sawmill.Info($"{deletedBy.Name} has deleted {type} {noteId}");
  185. NoteDeleted?.Invoke(note);
  186. }
  187. public async Task ModifyAdminRemark(int noteId, NoteType type, ICommonSession editedBy, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
  188. {
  189. message = message.Trim();
  190. var note = await GetAdminRemark(noteId, type);
  191. // If the note doesn't exist or is the same, we skip updating it
  192. if (note == null ||
  193. note.Message == message &&
  194. note.NoteSeverity == severity &&
  195. note.Secret == secret &&
  196. note.ExpiryTime == expiryTime)
  197. {
  198. return;
  199. }
  200. var sb = new StringBuilder($"{editedBy.Name} has modified {type} {noteId}");
  201. if (note.Message != message)
  202. {
  203. sb.Append($", modified message from {note.Message} to {message}");
  204. }
  205. if (note.Secret != secret)
  206. {
  207. sb.Append($", made it {(secret ? "secret" : "visible")}");
  208. }
  209. if (note.NoteSeverity != severity)
  210. {
  211. sb.Append($", updated the severity from {note.NoteSeverity} to {severity}");
  212. }
  213. if (note.ExpiryTime != expiryTime)
  214. {
  215. sb.Append(", updated the expiry time from ");
  216. if (note.ExpiryTime is null)
  217. sb.Append("never");
  218. else
  219. sb.Append($"{note.ExpiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
  220. sb.Append(" to ");
  221. if (expiryTime is null)
  222. sb.Append("never");
  223. else
  224. sb.Append($"{expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
  225. }
  226. _sawmill.Info(sb.ToString());
  227. var editedAt = DateTime.UtcNow;
  228. switch (type)
  229. {
  230. case NoteType.Note:
  231. if (severity is null)
  232. throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
  233. await _db.EditAdminNote(noteId, message, severity.Value, secret, editedBy.UserId, editedAt, expiryTime);
  234. break;
  235. case NoteType.Watchlist:
  236. await _db.EditAdminWatchlist(noteId, message, editedBy.UserId, editedAt, expiryTime);
  237. break;
  238. case NoteType.Message:
  239. await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
  240. break;
  241. case NoteType.ServerBan:
  242. if (severity is null)
  243. throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
  244. await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
  245. break;
  246. case NoteType.RoleBan:
  247. if (severity is null)
  248. throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity));
  249. await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
  250. break;
  251. default:
  252. throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
  253. }
  254. var newNote = note with
  255. {
  256. Message = message,
  257. NoteSeverity = severity,
  258. Secret = secret,
  259. LastEditedAt = editedAt,
  260. EditedByName = editedBy.Name,
  261. ExpiryTime = expiryTime
  262. };
  263. NoteModified?.Invoke(newNote);
  264. }
  265. public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
  266. {
  267. return await _db.GetAllAdminRemarks(player);
  268. }
  269. public async Task<List<IAdminRemarksRecord>> GetVisibleRemarks(Guid player)
  270. {
  271. if (_config.GetCVar(CCVars.SeeOwnNotes))
  272. {
  273. return await _db.GetVisibleAdminNotes(player);
  274. }
  275. _sawmill.Warning($"Someone tried to call GetVisibleNotes for {player} when see_own_notes was false");
  276. return new List<IAdminRemarksRecord>();
  277. }
  278. public async Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player)
  279. {
  280. return await _db.GetActiveWatchlists(player);
  281. }
  282. public async Task<List<AdminMessageRecord>> GetNewMessages(Guid player)
  283. {
  284. return await _db.GetMessages(player);
  285. }
  286. public async Task MarkMessageAsSeen(int id, bool dismissedToo)
  287. {
  288. await _db.MarkMessageAsSeen(id, dismissedToo);
  289. }
  290. public void PostInject()
  291. {
  292. _sawmill = _logManager.GetSawmill(SawmillId);
  293. }
  294. }