· 6 years ago · Oct 06, 2019, 03:04 PM
1using Newtonsoft.Json;
2using Newtonsoft.Json.Linq;
3using Oxide.Core;
4using Oxide.Core.Libraries.Covalence;
5using System;
6using System.Collections.Generic;
7using System.Linq;
8using System.Text.RegularExpressions;
9
10namespace Oxide.Plugins
11{
12 [Info("BetterChat Mute", "LaserHydra", "1.1.2", ResourceId = 118491460)]
13 [Description("Simple mute system, made for use with Better Chat")]
14 internal class BetterChatMute : CovalencePlugin
15 {
16 private static Dictionary<string, MuteInfo> _mutes;
17 private bool _isDataDirty, _globalMute;
18
19 #region Hooks
20
21 private void Loaded()
22 {
23 permission.RegisterPermission("betterchatmute.permanent", this);
24
25 LoadData(out _mutes);
26 SaveData(_mutes);
27
28 lang.RegisterMessages(new Dictionary<string, string>
29 {
30 ["No Permission"] = "You don't have permission to use this command.",
31 ["No Reason"] = "Unknown reason",
32 ["Muted"] = "{player} was muted by {initiator}: {reason}.",
33 ["Muted Time"] = "{player} was muted by {initiator} for {time}: {reason}.",
34 ["Unmuted"] = "{player} was unmuted by {initiator}.",
35 ["Not Muted"] = "{player} is currently not muted.",
36 ["Mute Expired"] = "{player} is no longer muted.",
37 ["Invalid Time Format"] = "Invalid time format. Example: 1d2h3m4s = 1 day, 2 hours, 3 min, 4 sec",
38 ["Nobody Muted"] = "There is nobody muted at the moment.",
39 ["Invalid Syntax Mute"] = "/mute <player|steamid> \"[reason]\" [time: 1d1h1m1s]",
40 ["Invalid Syntax Unmute"] = "/unmute <player|steamid>",
41 ["Player Name Not Found"] = "Could not find player with name '{name}'",
42 ["Player ID Not Found"] = "Could not find player with ID '{id}'",
43 ["Multiple Players Found"] = "Multiple matching players found: \n{matches}",
44 ["Time Muted Player Joined"] = "{player} is temporarily muted. Remaining time: {time}",
45 ["Time Muted Player Chat"] = "You may not chat, you are temporarily muted. Remaining time: {time}",
46 ["Muted Player Joined"] = "{player} is permanently muted.",
47 ["Muted Player Chat"] = "You may not chat, you are permanently muted.",
48 ["Global Mute Enabled"] = "Global mute was enabled. Nobody can chat while global mute is active.",
49 ["Global Mute Disabled"] = "Global mute was disabled. Everybody can chat again.",
50 ["Global Mute Active"] = "Global mute is active, you may not chat."
51 }, this);
52
53 timer.Repeat(10, 0, () =>
54 {
55 List<string> expired = _mutes.Where(m => m.Value.Expired).Select(m => m.Key).ToList();
56
57 foreach (string id in expired)
58 {
59 var player = players.FindPlayerById(id);
60
61 _mutes.Remove(id);
62 PublicMessage("Mute Expired", new KeyValuePair<string, string>("player", player?.Name));
63
64 Interface.CallHook("OnBetterChatMuteExpired", player);
65
66 if (!_isDataDirty)
67 _isDataDirty = true;
68 }
69
70 if (_isDataDirty)
71 {
72 SaveData(_mutes);
73 _isDataDirty = false;
74 }
75 });
76 }
77
78 private object OnUserChat(IPlayer player, string message)
79 {
80 object result = HandleChat(player);
81
82 if (result is bool && !(bool)result)
83 {
84 if (!MuteInfo.IsMuted(player) && _globalMute)
85 {
86 player.Reply(lang.GetMessage("Global Mute Active", this, player.Id));
87 }
88 else if (_mutes[player.Id].Timed)
89 {
90 player.Reply(
91 lang.GetMessage("Time Muted Player Chat", this, player.Id)
92 .Replace("{time}", FormatTime(_mutes[player.Id].ExpireDate - DateTime.UtcNow))
93 );
94 }
95 else
96 {
97 player.Reply(lang.GetMessage("Muted Player Chat", this, player.Id));
98 }
99 }
100
101 return result;
102 }
103
104 private object OnBetterChat(Dictionary<string, object> messageData) => HandleChat((IPlayer)messageData["Player"]);
105
106 private void OnUserInit(IPlayer player)
107 {
108 UpdateMuteStatus(player);
109
110 if (MuteInfo.IsMuted(player))
111 {
112 if (_mutes[player.Id].Timed)
113 PublicMessage("Time Muted Player Joined",
114 new KeyValuePair<string, string>("player", player.Name),
115 new KeyValuePair<string, string>("time", FormatTime(_mutes[player.Id].ExpireDate - DateTime.UtcNow)));
116 else
117 PublicMessage("Muted Player Joined", new KeyValuePair<string, string>("player", player.Name));
118 }
119 }
120
121 #endregion
122
123 #region Commands
124
125 [Command("toggleglobalmute"), Permission("betterchatmute.use.global")]
126 private void CmdGlobalMute(IPlayer player, string cmd, string[] args)
127 {
128 _globalMute = !_globalMute;
129 PublicMessage(_globalMute ? "Global Mute Enabled" : "Global Mute Disabled");
130 }
131
132 [Command("mutelist"), Permission("betterchatmute.use")]
133 private void CmdMuteList(IPlayer player, string cmd, string[] args)
134 {
135 if (_mutes.Count == 0)
136 player.Reply(lang.GetMessage("Nobody Muted", this, player.Id));
137 else
138 {
139 player.Reply(string.Join(Environment.NewLine,
140 _mutes.Select(kvp =>
141 $"{players.FindPlayerById(kvp.Key).Name}: {FormatTime(kvp.Value.ExpireDate - DateTime.UtcNow)}"
142 ).ToArray()
143 ));
144 }
145 }
146
147 [Command("mute"), Permission("betterchatmute.use")]
148 private void CmdMute(IPlayer player, string cmd, string[] args)
149 {
150 if (args.Length == 0)
151 {
152 player.Reply(lang.GetMessage("Invalid Syntax Mute", this, player.Id));
153 return;
154 }
155
156 string reason = string.Empty;
157 TimeSpan? timeSpan = null;
158
159 var target = GetPlayer(args[0], player);
160
161 if (target == null)
162 return;
163
164 var forInd = args.ToList().LastIndexOf("for");
165
166 if (forInd >= 0 && TryParseTimeSpan(args.Skip(forInd + 1).ToSentence(), out timeSpan))
167 reason = args.Skip(1).Take(forInd - 1).ToSentence();
168 else
169 args.Skip(1).ToSentence();
170
171 // No time given; make sure user has permanent muting permission
172 if (timeSpan == null && !permission.UserHasPermission(player.Id, "betterchatmute.permanent") && player.Id != "server_console")
173 {
174 player.Reply(lang.GetMessage("No Permission", this, player.Id));
175 return;
176 }
177
178 var expireDate = timeSpan == null ? MuteInfo.NonTimedExpireDate : DateTime.UtcNow + (TimeSpan)timeSpan;
179
180 _mutes[target.Id] = new MuteInfo(expireDate, reason);
181 SaveData(_mutes);
182
183 if (timeSpan == null)
184 {
185 Interface.CallHook("OnBetterChatMuted", target, player, reason);
186
187 PublicMessage("Muted",
188 new KeyValuePair<string, string>("initiator", player.Name),
189 new KeyValuePair<string, string>("player", target.Name),
190 new KeyValuePair<string, string>("reason", reason));
191 }
192 else
193 {
194 Interface.CallHook("OnBetterChatTimeMuted", target, player, (TimeSpan)timeSpan, reason);
195
196 PublicMessage("Muted Time",
197 new KeyValuePair<string, string>("initiator", player.Name),
198 new KeyValuePair<string, string>("player", target.Name),
199 new KeyValuePair<string, string>("time", FormatTime((TimeSpan)timeSpan)),
200 new KeyValuePair<string, string>("reason", reason));
201 }
202 }
203
204 [Command("unmute"), Permission("betterchatmute.use")]
205 private void CmdUnmute(IPlayer player, string cmd, string[] args)
206 {
207 if (args.Length != 1)
208 {
209 player.Reply(lang.GetMessage("Invalid Syntax Unmute", this, player.Id));
210 return;
211 }
212
213 IPlayer target = GetPlayer(args[0], player);
214
215 if (target == null)
216 return;
217
218 if (!MuteInfo.IsMuted(target))
219 {
220 player.Reply(lang.GetMessage("Not Muted", this, player.Id).Replace("{player}", target.Name));
221 return;
222 }
223
224 _mutes.Remove(target.Id);
225 SaveData(_mutes);
226
227 Interface.CallHook("OnBetterChatUnmuted", target, player);
228
229 PublicMessage("Unmuted",
230 new KeyValuePair<string, string>("initiator", player.Name),
231 new KeyValuePair<string, string>("player", target.Name));
232 }
233
234 #endregion
235
236 #region API Methods
237
238 private void API_Mute(IPlayer target, IPlayer player, string reason = "", bool callHook = true, bool broadcast = true)
239 {
240 _mutes[target.Id] = new MuteInfo(MuteInfo.NonTimedExpireDate, reason);
241 SaveData(_mutes);
242
243 reason = string.IsNullOrEmpty(reason) ? lang.GetMessage("No Reason", this) : reason;
244
245 if (callHook)
246 Interface.CallHook("OnBetterChatMuted", target, player, reason);
247
248 if (broadcast)
249 {
250 PublicMessage("Muted",
251 new KeyValuePair<string, string>("initiator", player.Name),
252 new KeyValuePair<string, string>("player", target.Name),
253 new KeyValuePair<string, string>("reason", reason));
254 }
255 }
256
257 private void API_TimeMute(IPlayer target, IPlayer player, TimeSpan timeSpan, string reason = "", bool callHook = true, bool broadcast = true)
258 {
259 _mutes[target.Id] = new MuteInfo(DateTime.UtcNow + timeSpan, reason);
260 SaveData(_mutes);
261
262 reason = string.IsNullOrEmpty(reason) ? lang.GetMessage("No Reason", this) : reason;
263
264 if (callHook)
265 Interface.CallHook("OnBetterChatTimeMuted", target, player, timeSpan,
266 string.IsNullOrEmpty(reason) ? lang.GetMessage("No Reason", this) : reason);
267
268 if (broadcast)
269 {
270 PublicMessage("Muted Time",
271 new KeyValuePair<string, string>("initiator", player.Name),
272 new KeyValuePair<string, string>("player", target.Name),
273 new KeyValuePair<string, string>("time", FormatTime(timeSpan)),
274 new KeyValuePair<string, string>("reason", reason));
275 }
276 }
277
278 private bool API_Unmute(IPlayer target, IPlayer player, bool callHook = true, bool broadcast = true)
279 {
280 if (!MuteInfo.IsMuted(target))
281 return false;
282
283 _mutes.Remove(target.Id);
284 SaveData(_mutes);
285
286 if (callHook)
287 Interface.CallHook("OnBetterChatUnmuted", target, player);
288
289 if (broadcast)
290 {
291 PublicMessage("Unmuted",
292 new KeyValuePair<string, string>("initiator", player.Name),
293 new KeyValuePair<string, string>("player", target.Name));
294 }
295
296 return true;
297 }
298
299 private void API_SetGlobalMuteState(bool state, bool broadcast = true)
300 {
301 _globalMute = state;
302
303 if (broadcast)
304 PublicMessage(_globalMute ? "Global Mute Enabled" : "Global Mute Disabled");
305 }
306
307 private bool API_GetGlobalMuteState() => _globalMute;
308
309 private bool API_IsMuted(IPlayer player) => _mutes.ContainsKey(player.Id);
310
311 private List<string> API_GetMuteList() => _mutes.Keys.ToList();
312
313 #endregion
314
315 #region Helpers
316
317 private void PublicMessage(string key, params KeyValuePair<string, string>[] replacements)
318 {
319 var message = lang.GetMessage(key, this);
320
321 foreach (var replacement in replacements)
322 message = message.Replace($"{{{replacement.Key}}}", replacement.Value);
323
324 server.Broadcast(message);
325 Puts(message);
326 }
327
328 private object HandleChat(IPlayer player)
329 {
330 UpdateMuteStatus(player);
331
332 var result = Interface.CallHook("OnBetterChatMuteHandle", player, MuteInfo.IsMuted(player) ? JObject.FromObject(_mutes[player.Id]) : null);
333
334 if (result != null)
335 return null;
336
337 if (MuteInfo.IsMuted(player))
338 return false;
339
340 if (_globalMute && !permission.UserHasPermission(player.Id, "betterchatmute.use.global"))
341 return false;
342
343 return null;
344 }
345
346 private void UpdateMuteStatus(IPlayer player)
347 {
348 if (MuteInfo.IsMuted(player) && _mutes[player.Id].Expired)
349 {
350 _mutes.Remove(player.Id);
351 SaveData(_mutes);
352
353 PublicMessage("Mute Expired", new KeyValuePair<string, string>("player", players.FindPlayerById(player.Id)?.Name));
354
355 Interface.CallHook("OnBetterChatMuteExpired", player);
356 }
357 }
358
359 private IPlayer GetPlayer(string nameOrId, IPlayer requestor)
360 {
361 if (nameOrId.IsSteamId())
362 {
363 IPlayer player = players.All.ToList().Find(p => p.Id == nameOrId);
364
365 if (player == null)
366 requestor.Reply(lang.GetMessage("Player ID Not Found", this, requestor.Id).Replace("{id}", nameOrId));
367
368 return player;
369 }
370
371 List<IPlayer> foundPlayers = new List<IPlayer>();
372
373 foreach (var player in players.Connected)
374 {
375 if (string.Equals(player.Name, nameOrId, StringComparison.CurrentCultureIgnoreCase))
376 return player;
377
378 if (player.Name.ToLower().Contains(nameOrId.ToLower()))
379 foundPlayers.Add(player);
380 }
381
382 switch (foundPlayers.Count)
383 {
384 case 0:
385 requestor.Reply(lang.GetMessage("Player Name Not Found", this, requestor.Id).Replace("{name}", nameOrId));
386 break;
387
388 case 1:
389 return foundPlayers[0];
390
391 default:
392 var names = (from current in foundPlayers select current.Name).ToArray();
393 requestor.Reply(lang.GetMessage("Multiple Players Found", this, requestor.Id).Replace("{matches}", string.Join(", ", names)));
394 break;
395 }
396
397 return null;
398 }
399
400 #region DateTime Helper
401
402 private static string FormatTime(TimeSpan time)
403 {
404 var values = new List<string>();
405
406 if (time.Days != 0)
407 values.Add($"{time.Days} day(s)");
408
409 if (time.Hours != 0)
410 values.Add($"{time.Hours} hour(s)");
411
412 if (time.Minutes != 0)
413 values.Add($"{time.Minutes} minute(s)");
414
415 if (time.Seconds != 0)
416 values.Add($"{time.Seconds} second(s)");
417
418 return values.ToSentence();
419 }
420
421 private static bool TryParseTimeSpan(string source, out TimeSpan? timeSpan)
422 {
423 if (source.Contains("today"))
424 {
425 timeSpan = DateTime.Today.AddDays(1) - DateTime.Now;
426 return true;
427 }
428
429 int seconds = 0, minutes = 0, hours = 0, days = 0;
430
431 var s = Regex.Match(source, "(\\d+) ?s(?:ec)?(?:ond)?s?");
432 var m = Regex.Match(source, "(\\d+) ?m(?:in)?s?");
433 var h = Regex.Match(source, "(\\d+) ?h(?:our)?s?");
434 var d = Regex.Match(source, "(\\d+) ?d(?:ay)?s?");
435
436 if (s.Success)
437 seconds = Convert.ToInt32(s.Groups[1].ToString());
438
439 if (m.Success)
440 minutes = Convert.ToInt32(m.Groups[1].ToString());
441
442 if (h.Success)
443 hours = Convert.ToInt32(h.Groups[1].ToString());
444
445 if (d.Success)
446 days = Convert.ToInt32(d.Groups[1].ToString());
447
448 if (s.Success || m.Success || h.Success || d.Success)
449 {
450 timeSpan = null;
451 return false;
452 }
453
454 timeSpan = new TimeSpan(days, hours, minutes, seconds);
455 return true;
456 }
457
458 #endregion
459
460 #region Data & Config Helper
461
462 private string DataFileName => Title.Replace(" ", "");
463
464 private void LoadData<T>(out T data, string filename = null) => data = Interface.Oxide.DataFileSystem.ReadObject<T>(filename ?? DataFileName);
465
466 private void SaveData<T>(T data, string filename = null) => Interface.Oxide.DataFileSystem.WriteObject(filename ?? DataFileName, data);
467
468 #endregion
469
470 #endregion
471
472 #region Classes
473
474 public class MuteInfo
475 {
476 public DateTime ExpireDate = DateTime.MinValue;
477
478 [JsonIgnore]
479 public bool Timed => ExpireDate != DateTime.MinValue;
480
481 [JsonIgnore]
482 public bool Expired => Timed && ExpireDate < DateTime.UtcNow;
483
484 public string Reason { get; set; }
485
486 public static bool IsMuted(IPlayer player) => _mutes.ContainsKey(player.Id);
487
488 public static readonly DateTime NonTimedExpireDate = DateTime.MinValue;
489
490 public MuteInfo()
491 {
492 }
493
494 public MuteInfo(DateTime expireDate, string reason)
495 {
496 ExpireDate = expireDate;
497 Reason = reason;
498 }
499 }
500
501 #endregion
502 }
503}