From 23c5137a5fe476b23ee5b153dee0fdb2e1361b58 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 18 Jan 2016 04:36:31 -0400 Subject: [PATCH] Added Before/After to update events, added dynamic IL generation --- src/Discord.Net.Modules/ModuleManager.cs | 27 ++++---- src/Discord.Net.Net45/Discord.Net.csproj | 78 ++++++++++++++--------- src/Discord.Net/DiscordClient.Events.cs | 46 ++++++------- src/Discord.Net/DiscordClient.cs | 26 +++++--- src/Discord.Net/DiscordConfig.cs | 3 + src/Discord.Net/DynamicIL.cs | 37 +++++++++++ src/Discord.Net/Events/ChannelUpdatedEventArgs.cs | 18 ++++++ src/Discord.Net/Events/MessageUpdatedEventArgs.cs | 20 ++++++ src/Discord.Net/Events/RoleUpdatedEventArgs.cs | 18 ++++++ src/Discord.Net/Events/ServerUpdatedEventArgs.cs | 16 +++++ src/Discord.Net/Events/UserUpdatedEventArgs.cs | 17 +++++ src/Discord.Net/Models/Channel.cs | 12 +++- src/Discord.Net/Models/Color.cs | 14 +++- src/Discord.Net/Models/Emoji.cs | 9 --- src/Discord.Net/Models/Invite.cs | 18 ++++-- src/Discord.Net/Models/Message.cs | 23 ++++--- src/Discord.Net/Models/Permissions.cs | 44 ++++++++++--- src/Discord.Net/Models/Profile.cs | 10 +++ src/Discord.Net/Models/Region.cs | 2 +- src/Discord.Net/Models/Role.cs | 14 +++- src/Discord.Net/Models/Server.cs | 26 +++++--- src/Discord.Net/Models/User.cs | 14 +++- src/Discord.Net/project.json | 2 + 23 files changed, 367 insertions(+), 127 deletions(-) create mode 100644 src/Discord.Net/DynamicIL.cs create mode 100644 src/Discord.Net/Events/ChannelUpdatedEventArgs.cs create mode 100644 src/Discord.Net/Events/MessageUpdatedEventArgs.cs create mode 100644 src/Discord.Net/Events/RoleUpdatedEventArgs.cs create mode 100644 src/Discord.Net/Events/ServerUpdatedEventArgs.cs create mode 100644 src/Discord.Net/Events/UserUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/Models/Emoji.cs diff --git a/src/Discord.Net.Modules/ModuleManager.cs b/src/Discord.Net.Modules/ModuleManager.cs index 091529229..bd6f27519 100644 --- a/src/Discord.Net.Modules/ModuleManager.cs +++ b/src/Discord.Net.Modules/ModuleManager.cs @@ -15,31 +15,31 @@ namespace Discord.Modules public event EventHandler ChannelDisabled = delegate { }; public event EventHandler LeftServer = delegate { }; - public event EventHandler ServerUpdated = delegate { }; + public event EventHandler ServerUpdated = delegate { }; public event EventHandler ServerUnavailable = delegate { }; public event EventHandler ServerAvailable = delegate { }; public event EventHandler ChannelCreated = delegate { }; public event EventHandler ChannelDestroyed = delegate { }; - public event EventHandler ChannelUpdated = delegate { }; + public event EventHandler ChannelUpdated = delegate { }; public event EventHandler RoleCreated = delegate { }; - public event EventHandler RoleUpdated = delegate { }; + public event EventHandler RoleUpdated = delegate { }; public event EventHandler RoleDeleted = delegate { }; public event EventHandler UserBanned = delegate { }; public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; - public event EventHandler UserUpdated = delegate { }; - public event EventHandler UserPresenceUpdated = delegate { }; - public event EventHandler UserVoiceStateUpdated = delegate { }; + public event EventHandler UserUpdated = delegate { }; + //public event EventHandler UserPresenceUpdated = delegate { }; + //public event EventHandler UserVoiceStateUpdated = delegate { }; public event EventHandler UserUnbanned = delegate { }; - public event EventHandler UserIsTypingUpdated = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler MessageReceived = delegate { }; public event EventHandler MessageSent = delegate { }; public event EventHandler MessageDeleted = delegate { }; - public event EventHandler MessageUpdated = delegate { }; + public event EventHandler MessageUpdated = delegate { }; public event EventHandler MessageReadRemotely = delegate { }; private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; @@ -79,11 +79,12 @@ namespace Discord.Modules if (_allowAll || _useServerWhitelist) //Server-only events { client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; - client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; + //TODO: This *is* a channel update if the before/after voice channel is whitelisted + //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; } client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); }; - client.ChannelUpdated += (s, e) => { if (HasChannel(e.Channel)) ChannelUpdated(s, e); }; + client.ChannelUpdated += (s, e) => { if (HasChannel(e.After)) ChannelUpdated(s, e); }; client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); }; client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); }; @@ -96,16 +97,16 @@ namespace Discord.Modules client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); }; client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } }; - client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.Server)) ServerUpdated(s, e); }; + client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.After)) ServerUpdated(s, e); }; client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); }; client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); }; client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; - client.UserIsTypingUpdated += (s, e) => { if (HasChannel(e.Channel)) UserIsTypingUpdated(s, e); }; + client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist - client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; + //client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; } diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 30baabd50..3f3932a95 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -385,18 +385,9 @@ API\Status\Rest\UpcomingMaintenances.cs - - ChannelEventArgs.cs - - - ChannelUserEventArgs.cs - Config.cs - - DisconnectedEventArgs.cs - DiscordClient.cs @@ -406,6 +397,9 @@ DiscordConfig.cs + + DynamicIL.cs + Enums\ChannelType.cs @@ -418,12 +412,57 @@ Enums\PermissionTarget.cs + + Enums\Relative.cs + Enums\StringEnum.cs Enums\UserStatus.cs + + Events\ChannelEventArgs.cs + + + Events\ChannelUpdatedEventArgs.cs + + + Events\ChannelUserEventArgs.cs + + + Events\DisconnectedEventArgs.cs + + + Events\LogMessageEventArgs.cs + + + Events\MessageEventArgs.cs + + + Events\MessageUpdatedEventArgs.cs + + + Events\ProfileUpdatedEventArgs.cs + + + Events\RoleEventArgs.cs + + + Events\RoleUpdatedEventArgs.cs + + + Events\ServerEventArgs.cs + + + Events\ServerUpdatedEventArgs.cs + + + Events\UserEventArgs.cs + + + Events\UserUpdatedEventArgs.cs + Extensions.cs @@ -445,12 +484,6 @@ Logging\LogManager.cs - - LogMessageEventArgs.cs - - - MessageEventArgs.cs - MessageQueue.cs @@ -532,27 +565,12 @@ Net\WebSockets\WS4NetEngine.cs - - ProfileEventArgs.cs - - - Relative.cs - - - RoleEventArgs.cs - - - ServerEventArgs.cs - ServiceManager.cs TaskManager.cs - - UserEventArgs.cs - diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 5b620ef38..e34c4efef 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -9,29 +9,27 @@ namespace Discord public event EventHandler Disconnected = delegate { }; public event EventHandler ChannelCreated = delegate { }; public event EventHandler ChannelDestroyed = delegate { }; - public event EventHandler ChannelUpdated = delegate { }; + public event EventHandler ChannelUpdated = delegate { }; public event EventHandler MessageAcknowledged = delegate { }; public event EventHandler MessageDeleted = delegate { }; public event EventHandler MessageReceived = delegate { }; public event EventHandler MessageSent = delegate { }; - public event EventHandler MessageUpdated = delegate { }; - public event EventHandler ProfileUpdated = delegate { }; + public event EventHandler MessageUpdated = delegate { }; + public event EventHandler ProfileUpdated = delegate { }; public event EventHandler RoleCreated = delegate { }; - public event EventHandler RoleUpdated = delegate { }; + public event EventHandler RoleUpdated = delegate { }; public event EventHandler RoleDeleted = delegate { }; public event EventHandler JoinedServer = delegate { }; public event EventHandler LeftServer = delegate { }; public event EventHandler ServerAvailable = delegate { }; - public event EventHandler ServerUpdated = delegate { }; + public event EventHandler ServerUpdated = delegate { }; public event EventHandler ServerUnavailable = delegate { }; public event EventHandler UserBanned = delegate { }; - public event EventHandler UserIsTypingUpdated = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; - public event EventHandler UserPresenceUpdated = delegate { }; - public event EventHandler UserUpdated = delegate { }; + public event EventHandler UserUpdated = delegate { }; public event EventHandler UserUnbanned = delegate { }; - public event EventHandler UserVoiceStateUpdated = delegate { }; private void OnConnected() => OnEvent(Connected); @@ -42,8 +40,8 @@ namespace Discord => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); private void OnChannelDestroyed(Channel channel) => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); - private void OnChannelUpdated(Channel channel) - => OnEvent(ChannelUpdated, new ChannelEventArgs(channel)); + private void OnChannelUpdated(Channel before, Channel after) + => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); private void OnMessageAcknowledged(Message msg) => OnEvent(MessageAcknowledged, new MessageEventArgs(msg)); @@ -53,18 +51,18 @@ namespace Discord => OnEvent(MessageReceived, new MessageEventArgs(msg)); internal void OnMessageSent(Message msg) => OnEvent(MessageSent, new MessageEventArgs(msg)); - private void OnMessageUpdated(Message msg) - => OnEvent(MessageUpdated, new MessageEventArgs(msg)); + private void OnMessageUpdated(Message before, Message after) + => OnEvent(MessageUpdated, new MessageUpdatedEventArgs(before, after)); - private void OnProfileUpdated(Profile profile) - => OnEvent(ProfileUpdated, new ProfileEventArgs(profile)); + private void OnProfileUpdated(Profile before, Profile after) + => OnEvent(ProfileUpdated, new ProfileUpdatedEventArgs(before, after)); private void OnRoleCreated(Role role) => OnEvent(RoleCreated, new RoleEventArgs(role)); private void OnRoleDeleted(Role role) => OnEvent(RoleDeleted, new RoleEventArgs(role)); - private void OnRoleUpdated(Role role) - => OnEvent(RoleUpdated, new RoleEventArgs(role)); + private void OnRoleUpdated(Role before, Role after) + => OnEvent(RoleUpdated, new RoleUpdatedEventArgs(before, after)); private void OnJoinedServer(Server server) => OnEvent(JoinedServer, new ServerEventArgs(server)); @@ -72,27 +70,23 @@ namespace Discord => OnEvent(LeftServer, new ServerEventArgs(server)); private void OnServerAvailable(Server server) => OnEvent(ServerAvailable, new ServerEventArgs(server)); - private void OnServerUpdated(Server server) - => OnEvent(ServerUpdated, new ServerEventArgs(server)); + private void OnServerUpdated(Server before, Server after) + => OnEvent(ServerUpdated, new ServerUpdatedEventArgs(before, after)); private void OnServerUnavailable(Server server) => OnEvent(ServerUnavailable, new ServerEventArgs(server)); private void OnUserBanned(User user) => OnEvent(UserBanned, new UserEventArgs(user)); private void OnUserIsTypingUpdated(Channel channel, User user) - => OnEvent(UserIsTypingUpdated, new ChannelUserEventArgs(channel, user)); + => OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user)); private void OnUserJoined(User user) => OnEvent(UserJoined, new UserEventArgs(user)); private void OnUserLeft(User user) => OnEvent(UserLeft, new UserEventArgs(user)); - private void OnUserPresenceUpdated(User user) - => OnEvent(UserPresenceUpdated, new UserEventArgs(user)); private void OnUserUnbanned(User user) => OnEvent(UserUnbanned, new UserEventArgs(user)); - private void OnUserUpdated(User user) - => OnEvent(UserUpdated, new UserEventArgs(user)); - private void OnUserVoiceStateUpdated(User user) - => OnEvent(UserVoiceStateUpdated, new UserEventArgs(user)); + private void OnUserUpdated(User before, User after) + => OnEvent(UserUpdated, new UserUpdatedEventArgs(before, after)); private void OnEvent(EventHandler handler, T eventArgs, [CallerMemberName] string callerName = null) { diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 282606031..a5f1317c4 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -546,10 +546,11 @@ namespace Discord var server = GetServer(data.Id); if (server != null) { + var before = Config.EnablePreUpdateEvents ? server.Clone() : null; server.Update(data); if (Config.LogEvents) Logger.Info($"Server Updated: {server.Name}"); - OnServerUpdated(server); + OnServerUpdated(before, server); } else Logger.Warning("GUILD_UPDATE referenced an unknown guild."); @@ -609,10 +610,11 @@ namespace Discord var channel = GetChannel(data.Id); if (channel != null) { + var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; channel.Update(data); if (Config.LogEvents) Logger.Info($"Channel Updated: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); - OnChannelUpdated(channel); + OnChannelUpdated(before, channel); } else Logger.Warning("CHANNEL_UPDATE referenced an unknown channel."); @@ -660,10 +662,11 @@ namespace Discord var user = server.GetUser(data.User.Id); if (user != null) { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); if (Config.LogEvents) Logger.Info($"User Updated: {server.Name}/{user.Name}"); - OnUserUpdated(user); + OnUserUpdated(before, user); } else Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); @@ -721,7 +724,7 @@ namespace Discord role.Update(data.Data); if (Config.LogEvents) Logger.Info($"Role Created: {server.Name}/{role.Name}"); - OnRoleUpdated(role); + OnRoleCreated(role); } else Logger.Warning("GUILD_ROLE_CREATE referenced an unknown guild."); @@ -736,10 +739,11 @@ namespace Discord var role = server.GetRole(data.Data.Id); if (role != null) { + var before = Config.EnablePreUpdateEvents ? role.Clone() : null; role.Update(data.Data); if (Config.LogEvents) Logger.Info($"Role Updated: {server.Name}/{role.Name}"); - OnRoleUpdated(role); + OnRoleUpdated(before, role); } else Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); @@ -860,10 +864,11 @@ namespace Discord if (channel != null) { var msg = channel.GetMessage(data.Id, data.Author?.Id); + var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; msg.Update(data); if (Config.LogEvents) Logger.Verbose($"Message Update: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); - OnMessageUpdated(msg); + OnMessageUpdated(before, msg); } else Logger.Warning("MESSAGE_UPDATE referenced an unknown channel."); @@ -936,9 +941,10 @@ namespace Discord if (user != null) { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); //Logger.Verbose($"Presence Updated: {server.Name}/{user.Name}"); - OnUserPresenceUpdated(user); + OnUserUpdated(before, user); } /*else //Occurs when a user leaves a server Logger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ @@ -982,9 +988,10 @@ namespace Discord var user = server.GetUser(data.UserId); if (user != null) { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); - OnUserVoiceStateUpdated(user); + OnUserUpdated(user, user); } /*else //Occurs when a user leaves a server Logger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");*/ @@ -1000,13 +1007,14 @@ namespace Discord var data = e.Payload.ToObject(Serializer); if (data.Id == CurrentUser.Id) { + var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; CurrentUser.Update(data); PrivateUser.Update(data); foreach (var server in _servers) server.Value.CurrentUser.Update(data); if (Config.LogEvents) Logger.Info("Profile Updated"); - OnProfileUpdated(CurrentUser); + OnProfileUpdated(before, CurrentUser); } } break; diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs index f3ff9567f..cf2f59f0d 100644 --- a/src/Discord.Net/DiscordConfig.cs +++ b/src/Discord.Net/DiscordConfig.cs @@ -103,6 +103,9 @@ namespace Discord /// Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members public bool UsePermissionsCache { get { return _usePermissionsCache; } set { SetValue(ref _usePermissionsCache, value); } } private bool _usePermissionsCache = true; + /// Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed. + public bool EnablePreUpdateEvents { get { return _enablePreUpdateEvents; } set { SetValue(ref _enablePreUpdateEvents, value); } } + private bool _enablePreUpdateEvents = true; public DiscordConfig() { diff --git a/src/Discord.Net/DynamicIL.cs b/src/Discord.Net/DynamicIL.cs new file mode 100644 index 000000000..18e58cb78 --- /dev/null +++ b/src/Discord.Net/DynamicIL.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace Discord +{ + internal static class DynamicIL + { + public static Action CreateCloner() + { + var method = new DynamicMethod("CopyFields", null, new[] { typeof(T), typeof(T) }, typeof(T), true); + var generator = method.GetILGenerator(); + var typeInfo = typeof(T).GetTypeInfo(); + + CopyFields(generator, typeInfo); + + generator.Emit(OpCodes.Ret); + + return method.CreateDelegate(typeof(Action)) as Action; + } + private static void CopyFields(ILGenerator generator, TypeInfo typeInfo) + { + foreach (var field in typeInfo.DeclaredFields.Where(x => !x.IsStatic)) + { + generator.Emit(OpCodes.Ldarg_1); //Stack: TargetRef + generator.Emit(OpCodes.Ldarg_0); //Stack: TargetRef, SourceRef + generator.Emit(OpCodes.Ldfld, field); //Stack: TargetRef, Value + generator.Emit(OpCodes.Stfld, field); //Stack: + } + + var baseType = typeInfo.BaseType; + if (baseType != null && baseType.AssemblyQualifiedName == typeInfo.AssemblyQualifiedName) + CopyFields(generator, baseType.GetTypeInfo()); + } + } +} diff --git a/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs new file mode 100644 index 000000000..fa8da98ea --- /dev/null +++ b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord +{ + public class ChannelUpdatedEventArgs : EventArgs + { + public Channel Before { get; } + public Channel After { get; } + + public Server Server => After.Server; + + public ChannelUpdatedEventArgs(Channel before, Channel after) + { + Before = before; + After = after; + } + } +} diff --git a/src/Discord.Net/Events/MessageUpdatedEventArgs.cs b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs new file mode 100644 index 000000000..849f234e1 --- /dev/null +++ b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Discord +{ + public class MessageUpdatedEventArgs : EventArgs + { + public Message Before { get; } + public Message After { get; } + + public User User => After.User; + public Channel Channel => After.Channel; + public Server Server => After.Server; + + public MessageUpdatedEventArgs(Message before, Message after) + { + Before = before; + After = after; + } + } +} diff --git a/src/Discord.Net/Events/RoleUpdatedEventArgs.cs b/src/Discord.Net/Events/RoleUpdatedEventArgs.cs new file mode 100644 index 000000000..26151c98b --- /dev/null +++ b/src/Discord.Net/Events/RoleUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord +{ + public class RoleUpdatedEventArgs : EventArgs + { + public Role Before { get; } + public Role After { get; } + + public Server Server => After.Server; + + public RoleUpdatedEventArgs(Role before, Role after) + { + Before = before; + After = after; + } + } +} diff --git a/src/Discord.Net/Events/ServerUpdatedEventArgs.cs b/src/Discord.Net/Events/ServerUpdatedEventArgs.cs new file mode 100644 index 000000000..8532f72dc --- /dev/null +++ b/src/Discord.Net/Events/ServerUpdatedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord +{ + public class ServerUpdatedEventArgs : EventArgs + { + public Server Before { get; } + public Server After { get; } + + public ServerUpdatedEventArgs(Server before, Server after) + { + Before = before; + After = after; + } + } +} diff --git a/src/Discord.Net/Events/UserUpdatedEventArgs.cs b/src/Discord.Net/Events/UserUpdatedEventArgs.cs new file mode 100644 index 000000000..89e8cce0c --- /dev/null +++ b/src/Discord.Net/Events/UserUpdatedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +namespace Discord +{ + public class UserUpdatedEventArgs : EventArgs + { + public User Before { get; } + public User After { get; } + + public Server Server => After.Server; + + public UserUpdatedEventArgs(User before, User after) + { + Before = before; + After = after; + } + } +} diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index cb41988fa..b1cfb0e6b 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -14,6 +14,8 @@ namespace Discord { public sealed class Channel : IMentionable { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + private struct Member { public readonly User User; @@ -102,7 +104,7 @@ namespace Discord return Enumerable.Empty(); } } - + internal Channel(DiscordClient client, ulong id, Server server) : this(client, id) { @@ -598,6 +600,14 @@ namespace Discord } #endregion + internal Channel Clone() + { + var result = new Channel(); + _cloner(this, result); + return result; + } + private Channel() { } + public override string ToString() => Name ?? Id.ToIdString(); } } diff --git a/src/Discord.Net/Models/Color.cs b/src/Discord.Net/Models/Color.cs index 1ae922bd7..c62bfecbb 100644 --- a/src/Discord.Net/Models/Color.cs +++ b/src/Discord.Net/Models/Color.cs @@ -3,8 +3,10 @@ namespace Discord { public sealed class Color : IEquatable - { - public static readonly Color Default = PresetColor(0); + { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + public static readonly Color Default = PresetColor(0); public static readonly Color Teal = PresetColor(0x1ABC9C); public static readonly Color DarkTeal = PresetColor(0x11806A); @@ -83,6 +85,14 @@ namespace Discord public override bool Equals(object obj) => (obj as Color)?.Equals(this) ?? false; public bool Equals(Color color) => color != null && color._rawValue == _rawValue; + internal Color Clone() + { + var result = new Color(); + _cloner(this, result); + return result; + } + private Color() { } //Used for cloning + public override string ToString() => '#' + _rawValue.ToString("X"); } } diff --git a/src/Discord.Net/Models/Emoji.cs b/src/Discord.Net/Models/Emoji.cs deleted file mode 100644 index cd9b8fcd4..000000000 --- a/src/Discord.Net/Models/Emoji.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Discord.API.Client; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.Models -{ -} diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index 326b1bf82..3bd300098 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -9,8 +9,10 @@ using APIInvite = Discord.API.Client.Invite; namespace Discord { public sealed class Invite - { - public sealed class ServerInfo + { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + public sealed class ServerInfo { /// Returns the unique identifier of this server. public ulong Id { get; } @@ -126,8 +128,14 @@ namespace Discord public Task Accept() => Client.ClientAPI.Send(new AcceptInviteRequest(Code)); - public override bool Equals(object obj) => obj is Invite && (obj as Invite).Code == Code; - public override int GetHashCode() => unchecked(Code.GetHashCode() + 9980); - public override string ToString() => XkcdCode ?? Code; + internal Invite Clone() + { + var result = new Invite(); + _cloner(this, result); + return result; + } + private Invite() { } //Used for cloning + + public override string ToString() => XkcdCode ?? Code; } } diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index 9e66111a2..2568806eb 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -1,12 +1,9 @@ using Discord.API.Client.Rest; using Discord.Net; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using APIMessage = Discord.API.Client.Message; @@ -27,9 +24,11 @@ namespace Discord public sealed class Message { - private static readonly Regex _userRegex = new Regex(@"<@[0-9]+>", RegexOptions.Compiled); - private static readonly Regex _channelRegex = new Regex(@"<#[0-9]+>", RegexOptions.Compiled); - private static readonly Regex _roleRegex = new Regex(@"@everyone", RegexOptions.Compiled); + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + private static readonly Regex _userRegex = new Regex(@"<@[0-9]+>"); + private static readonly Regex _channelRegex = new Regex(@"<#[0-9]+>"); + private static readonly Regex _roleRegex = new Regex(@"@everyone"); private static readonly Attachment[] _initialAttachments = new Attachment[0]; private static readonly Embed[] _initialEmbeds = new Embed[0]; @@ -369,8 +368,14 @@ namespace Discord return Resolve(Channel, text); } - public override bool Equals(object obj) => obj is Message && (obj as Message).Id == Id; - public override int GetHashCode() => unchecked(Id.GetHashCode() + 9979); - public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; + internal Message Clone() + { + var result = new Message(); + _cloner(this, result); + return result; + } + private Message() { } //Used for cloning + + public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; } } diff --git a/src/Discord.Net/Models/Permissions.cs b/src/Discord.Net/Models/Permissions.cs index 1d678bfbe..4d4d148ff 100644 --- a/src/Discord.Net/Models/Permissions.cs +++ b/src/Discord.Net/Models/Permissions.cs @@ -32,16 +32,24 @@ namespace Discord } public sealed class ServerPermissions : Permissions - { - public static ServerPermissions None { get; } = new ServerPermissions(); + { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + public static ServerPermissions None { get; } = new ServerPermissions(); public static ServerPermissions All { get; } = new ServerPermissions(Convert.ToUInt32("00000011111100111111110000111111", 2)); public ServerPermissions() : base() { } public ServerPermissions(uint rawValue) : base(rawValue) { } public ServerPermissions Copy() => new ServerPermissions(RawValue); + internal ServerPermissions Clone() + { + var result = new ServerPermissions(); + _cloner(this, result); + return result; + } - /// If True, a user may ban users from the server. - public bool BanMembers { get { return GetBit(PermissionsBits.BanMembers); } set { SetBit(PermissionsBits.BanMembers, value); } } + /// If True, a user may ban users from the server. + public bool BanMembers { get { return GetBit(PermissionsBits.BanMembers); } set { SetBit(PermissionsBits.BanMembers, value); } } /// If True, a user may kick users from the server. public bool KickMembers { get { return GetBit(PermissionsBits.KickMembers); } set { SetBit(PermissionsBits.KickMembers, value); } } /// If True, a user may adjust roles. This also implictly grants all other permissions. @@ -53,8 +61,10 @@ namespace Discord } public sealed class ChannelPermissions : Permissions - { - public static ChannelPermissions None { get; } = new ChannelPermissions(); + { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + public static ChannelPermissions None { get; } = new ChannelPermissions(); public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); @@ -70,9 +80,15 @@ namespace Discord public ChannelPermissions() : base() { } public ChannelPermissions(uint rawValue) : base(rawValue) { } public ChannelPermissions Copy() => new ChannelPermissions(RawValue); + internal ChannelPermissions Clone() + { + var result = new ChannelPermissions(); + _cloner(this, result); + return result; + } - /// If True, a user may adjust permissions. This also implictly grants all other permissions. - public bool ManagePermissions { get { return GetBit(PermissionsBits.ManageRolesOrPermissions); } set { SetBit(PermissionsBits.ManageRolesOrPermissions, value); } } + /// If True, a user may adjust permissions. This also implictly grants all other permissions. + public bool ManagePermissions { get { return GetBit(PermissionsBits.ManageRolesOrPermissions); } set { SetBit(PermissionsBits.ManageRolesOrPermissions, value); } } /// If True, a user may create, delete and modify this channel. public bool ManageChannel { get { return GetBit(PermissionsBits.ManageChannel); } set { SetBit(PermissionsBits.ManageChannel, value); } } } @@ -152,8 +168,10 @@ namespace Discord } public sealed class DualChannelPermissions - { - public ChannelPermissions Allow { get; } + { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + + public ChannelPermissions Allow { get; } public ChannelPermissions Deny { get; } public DualChannelPermissions(uint allow = 0, uint deny = 0) @@ -233,6 +251,12 @@ namespace Discord Deny.Lock(); } public DualChannelPermissions Copy() => new DualChannelPermissions(Allow.RawValue, Deny.RawValue); + internal DualChannelPermissions Clone() + { + var result = new DualChannelPermissions(); + _cloner(this, result); + return result; + } public static bool operator ==(DualChannelPermissions a, DualChannelPermissions b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); public static bool operator !=(DualChannelPermissions a, DualChannelPermissions b) => !(a == b); diff --git a/src/Discord.Net/Models/Profile.cs b/src/Discord.Net/Models/Profile.cs index c6eeb6657..08e462f1d 100644 --- a/src/Discord.Net/Models/Profile.cs +++ b/src/Discord.Net/Models/Profile.cs @@ -8,6 +8,8 @@ namespace Discord { public sealed class Profile { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + internal DiscordClient Client { get; } /// Gets the unique identifier for this user. @@ -75,6 +77,14 @@ namespace Discord } } + internal Profile Clone() + { + var result = new Profile(); + _cloner(this, result); + return result; + } + private Profile() { } //Used for cloning + public override string ToString() => Id.ToIdString(); } } diff --git a/src/Discord.Net/Models/Region.cs b/src/Discord.Net/Models/Region.cs index 839d20907..dcb8de12b 100644 --- a/src/Discord.Net/Models/Region.cs +++ b/src/Discord.Net/Models/Region.cs @@ -16,5 +16,5 @@ Port = port; Vip = vip; } - } + } } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index fefe0f0fd..a7aa805d2 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -11,6 +11,8 @@ namespace Discord { public sealed class Role : IMentionable { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + internal DiscordClient Client => Server.Client; /// Gets the unique identifier for this role. @@ -117,7 +119,15 @@ namespace Discord try { await Client.ClientAPI.Send(new DeleteRoleRequest(Server.Id, Id)).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } - - public override string ToString() => Name ?? Id.ToIdString(); + + internal Role Clone() + { + var result = new Role(); + _cloner(this, result); + return result; + } + private Role() { } //Used for cloning + + public override string ToString() => Name ?? Id.ToIdString(); } } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index c19e23c46..f0ef11bcd 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -14,6 +14,8 @@ namespace Discord /// Represents a Discord server (also known as a guild). public sealed class Server { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + internal static string GetIconUrl(ulong serverId, string iconId) => iconId != null ? $"{DiscordConfig.ClientAPIUrl}guilds/${serverId}/icons/${iconId}.jpg" : null; internal static string GetSplashUrl(ulong serverId, string splashId) @@ -85,8 +87,9 @@ namespace Discord public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; /// Gets the current user in this server. public User CurrentUser => GetUser(Client.CurrentUser.Id); - /// Gets the URL to this user's current avatar. + /// Gets the URL to this server's current icon. public string IconUrl => GetIconUrl(Id, IconId); + /// Gets the URL to this servers's splash image. public string SplashUrl => GetSplashUrl(Id, SplashId); /// Gets a collection of all channels in this server. @@ -143,16 +146,15 @@ namespace Discord Roles = x.RoleIds.Select(y => GetRole(y)).Where(y => y != null).ToArray() }).ToArray(); } - - //Can be null - _afkChannelId = model.AFKChannelId; - SplashId = model.Splash; - if (model.Roles != null) { foreach (var x in model.Roles) AddRole(x.Id).Update(x); } + + //Can be null + _afkChannelId = model.AFKChannelId; + SplashId = model.Splash; } internal void Update(ExtendedGuild model) { @@ -498,7 +500,15 @@ namespace Discord public void RequestOfflineUsers() => Client.GatewaySocket.SendRequestMembers(Id, "", 0); #endregion - - public override string ToString() => Name ?? Id.ToIdString(); + + internal Server Clone() + { + var result = new Server(); + _cloner(this, result); + return result; + } + private Server() { } //Used for cloning + + public override string ToString() => Name ?? Id.ToIdString(); } } diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 910b07faa..ef9200b10 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -9,8 +9,10 @@ using APIMember = Discord.API.Client.Member; namespace Discord { - public sealed class User : IMentionable + public sealed class User { + private readonly static Action _cloner = DynamicIL.CreateCloner(); + internal static string GetAvatarUrl(ulong userId, string avatarId) => avatarId != null ? $"{DiscordConfig.ClientAPIUrl}users/{userId}/avatars/{avatarId}.jpg" : null; @@ -338,6 +340,14 @@ namespace Discord => Edit(roles: Roles.Except(roles)); #endregion - public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); + internal User Clone() + { + var result = new User(); + _cloner(this, result); + return result; + } + private User() { } //Used for cloning + + public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); } } \ No newline at end of file diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index be0791385..c9feaae46 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -50,6 +50,8 @@ "System.Net.Sockets": "4.1.0-beta-23409", "System.Net.Requests": "4.0.11-beta-23516", "System.Net.WebSockets.Client": "4.0.0-beta-23516", + "System.Reflection": "4.1.0-beta-23516", + "System.Reflection.Emit.Lightweight": "4.0.1-beta-23516", "System.Runtime.InteropServices": "4.0.21-beta-23516", "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", "System.Text.RegularExpressions": "4.0.11-beta-23516",