using Discord.WebSocket; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics; namespace MinecraftDiscordBot.Models; public abstract class Message { public static Message Deserialize(string strMessage) { var obj = JObject.Parse(strMessage); var typeName = GetKey(obj, "type"); if (!Parsers.TryGetValue(typeName, out var type)) throw new FormatException($"Unknown message type '{typeName}'!"); if (obj.ToObject(type) is not Message message) throw new FormatException($"Message cannot be casted to '{type}'!"); return message; } private static readonly Dictionary Parsers = GetMessageTypes(); private static Dictionary GetMessageTypes() { var types = new Dictionary(); var messageTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(domainAssembly => domainAssembly.GetTypes()) .Where(typeof(Message).IsAssignableFrom); foreach (var type in messageTypes) if (GetTypeAttribute(type) is MessageTypeAttribute attr) types.Add(attr.Name, type); return types; } private static MessageTypeAttribute? GetTypeAttribute(Type type) => type.GetCustomAttributes(typeof(MessageTypeAttribute), false).OfType().FirstOrDefault(); private static T GetKey(JObject msg, string key) => (msg.TryGetValue(key, out var type) ? type : throw new FormatException($"Message has no '{key}' param!")) .ToObject() ?? throw new FormatException($"'{key}' param is not of expected type '{typeof(T).Name}'!"); [JsonProperty("type")] public abstract string Type { get; } } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class CapabilityMessage : Message { private const string TYPE = "roles"; public override string Type => TYPE; [JsonProperty("role", Required = Required.Always)] public string[] Role { get; set; } = default!; public override string ToString() => $"Capabilities: {string.Join(", ", Role)}"; } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class ReplyMessage : Message { private const string TYPE = "reply"; public override string Type => TYPE; [JsonProperty("id", Required = Required.Always)] public int AnswerId { get; set; } [JsonProperty("result", Required = Required.Always)] public string Result { get; set; } = default!; [JsonProperty("chunk", Required = Required.DisallowNull)] public int Chunk { get; set; } = 1; [JsonProperty("total", Required = Required.DisallowNull)] public int Total { get; set; } = 1; [JsonProperty("success", Required = Required.DisallowNull)] public ResultState State { get; set; } = ResultState.Successful; public override string ToString() => $"Reply [{AnswerId}] {State} ({Chunk}/{Total}) Length {Result.Length}"; } public abstract class EventMessage : Message { } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class PeripheralDetachEvent : EventMessage { private const string TYPE = "peripheral_detach"; public override string Type => TYPE; [JsonProperty("side", Required = Required.Always)] public string Side { get; set; } = default!; public override string ToString() => $"Detached '{Side}'!"; } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class PlayerStatusEvent : EventMessage { private const string TYPE = "playerstatus"; public override string Type => TYPE; [JsonProperty("player", Required = Required.Always)] public string Player { get; set; } = default!; [JsonProperty("status", Required = Required.Always)] public bool Online { get; set; } public override string ToString() => $"{Player} is now {(Online ? "on" : "off")}line!"; } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class PeripheralAttachEvent : EventMessage { private const string TYPE = "peripheral"; public override string Type => TYPE; [JsonIgnore] public string Side => Peripheral.Side; [JsonProperty("peripheral", Required = Required.Always)] public Peripheral Peripheral { get; set; } = default!; public override string ToString() => $"Attached {Peripheral}!"; } public class Peripheral { [JsonProperty("side", Required = Required.Always)] public string Side { get; set; } = default!; [JsonProperty("type", Required = Required.Always)] public string Type { get; set; } = default!; [JsonProperty("methods", Required = Required.Always)] public string[] Methods { get; set; } = default!; public override string ToString() => $"{Type} at '{Side}'"; } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class ChatEvent : EventMessage { private const string TYPE = "chat"; public override string Type => TYPE; [JsonProperty("username", Required = Required.Always)] public string Username { get; set; } = default!; [JsonProperty("message", Required = Required.Always)] public string Message { get; set; } = default!; [JsonProperty("uuid", Required = Required.Always)] public string UUID { get; set; } = default!; [JsonProperty("hidden", Required = Required.Always)] public bool IsHidden { get; set; } public override string ToString() => $"{(IsHidden ? "HIDDEN: " : string.Empty)}[{Username}] {Message} ({UUID})"; } [MessageType(TYPE)] [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] public class RequestMessage : Message { private const string TYPE = "request"; public override string Type => TYPE; public RequestMessage(int answerId, string method, Dictionary? parameters = null) { AnswerId = answerId; Method = method; Parameters = (parameters ?? Enumerable.Empty>()) .ToDictionary(i => i.Key, i => i.Value); } [JsonProperty("id")] public int AnswerId { get; set; } [JsonProperty("method")] public string Method { get; set; } [JsonProperty("params")] public Dictionary Parameters { get; } public override string ToString() => $"Request [{AnswerId}] {Method}({JsonConvert.SerializeObject(Parameters)})"; }