diff --git a/DisCatSharp/Clients/DiscordClient.InteractionEventHandlers.cs b/DisCatSharp/Clients/DiscordClient.InteractionEventHandlers.cs index 497a1c603..301c755ad 100644 --- a/DisCatSharp/Clients/DiscordClient.InteractionEventHandlers.cs +++ b/DisCatSharp/Clients/DiscordClient.InteractionEventHandlers.cs @@ -1,226 +1,239 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2023 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace DisCatSharp; /// /// A Discord API wrapper. /// public sealed partial class DiscordClient { private readonly Dictionary<(object?, Type, bool), List<(EventInfo, Delegate, bool)[]>> _registrationInteractionToDelegate = new(); private readonly Dictionary> _typeInteractionToAnonymousHandlers = new(); /// /// Registers all methods annotated with from the given object. /// /// The event handler object. /// Whether to consider static methods. public void RegisterInteractionHandler(object handler, bool registerStatic = false) => this.RegisterInteractionHandlerImpl(handler, handler.GetType(), registerStatic); /// /// Registers all static methods annotated with from the given type. /// /// The static event handler type. public void RegisterStaticInteractionHandler(Type t) => this.RegisterInteractionHandlerImpl(null, t); /// /// . /// /// Type to register. public void RegisterStaticInteractionHandler() => this.RegisterStaticInteractionHandler(typeof(T)); /// /// If abstract, registers all static methods of the type. /// If non-abstract, tries to instantiate it, optionally using the provided /// and registers all instance and static methods. /// /// Type to register. public void RegisterInteractionHandler(Type type) { if (type.IsAbstract) this.RegisterStaticInteractionHandler(type); else { var anon = ActivatorUtilities.CreateInstance(this.Configuration.ServiceProvider, type); this._typeInteractionToAnonymousHandlers[type] = this._typeInteractionToAnonymousHandlers.TryGetValue(type, out var anonObjs) ? anonObjs : (anonObjs = new()); anonObjs.Add(anon); this.RegisterInteractionHandlerImpl(anon, type); } } /// /// . /// /// Type to register. public void RegisterInteractionHandler() => this.RegisterInteractionHandler(typeof(T)); /// /// Registers all types associated with the provided assembly that have the attribute. /// /// The assembly from which to get the types. public void RegisterInteractionHandlers(Assembly assembly) { + this.Logger.LogError("Registering event handlers from assembly"); foreach (var t in GetInteractionHandlersFromAssembly(assembly)) this.RegisterInteractionHandler(t); } /// /// Perfectly mirrors . /// /// The event handler object. /// Whether it considered static methods. public void UnregisterInteractionHandler(object handler, bool wasRegisteredWithStatic = false) => this.UnregisterInteractionHandlerImpl(handler, handler.GetType(), wasRegisteredWithStatic); /// /// Perfectly mirrors . /// /// Type to unregister. public void UnregisterStaticInteractionHandler(Type t) => this.UnregisterInteractionHandlerImpl(null, t); /// /// Perfectly mirrors . /// /// Type to unregister. public void UnregisterStaticInteractionHandler() => this.UnregisterInteractionHandler(typeof(T)); /// /// Perfectly mirrors . /// /// Type to unregister. public void UnregisterInteractionHandler(Type t) { if (t.IsAbstract) this.UnregisterStaticInteractionHandler(t); else { if (!this._typeInteractionToAnonymousHandlers.TryGetValue(t, out var anonObjs) || anonObjs.Count == 0) return; var anon = anonObjs[0]; anonObjs.RemoveAt(0); if (anonObjs.Count == 0) this._typeInteractionToAnonymousHandlers.Remove(t); this.UnregisterInteractionHandlerImpl(anon, t); } } /// /// Perfectly mirrors . /// /// The type to unregister public void UnregisterInteractionHandler() => this.UnregisterInteractionHandler(typeof(T)); /// /// Perfectly mirrors . /// /// The assembly to unregister. public void UnregisterInteractionHandlers(Assembly assembly) { foreach (var t in GetInteractionHandlersFromAssembly(assembly)) this.UnregisterInteractionHandler(t); } /// /// Gets the event handlers from the assembly. /// /// The assembly to get the event handlers from. private static IEnumerable GetInteractionHandlersFromAssembly(Assembly assembly) - => assembly.GetTypes().Where(t => t.GetCustomAttribute() is not null); + => assembly.GetTypes().Where(t => t.GetCustomAttribute() is not null); /// /// Unregisters event handler implementations. /// /// The event handler object. /// The type. /// Whether it considereded static methods. - private void UnregisterInteractionHandlerImpl(object? handler, Type type, bool wasRegisteredWithStatic = true) { if (!this._registrationInteractionToDelegate.TryGetValue((handler, type, wasRegisteredWithStatic), out var delegateLists) || delegateLists.Count == 0) return; foreach (var (evnt, dlgt, prv) in delegateLists[0]) evnt.RemoveEventHandler(this, dlgt); delegateLists.RemoveAt(0); if (delegateLists.Count == 0) this._registrationInteractionToDelegate.Remove((handler, type, wasRegisteredWithStatic)); } /// /// Rregisters event handler implementations. /// /// The event handler object. /// The type. /// Whether to consider static methods. private void RegisterInteractionHandlerImpl(object? handler, Type type, bool registerStatic = true) { - var delegates = ( + this.Logger.LogError("Trying to register event handlers"); + try + { + var delegates = ( from method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) let attribute = method.GetCustomAttribute() where attribute is not null && ((registerStatic && method.IsStatic) || handler is not null) let eventName = attribute.EventName let eventInfo = this.GetType().GetEvent(eventName) ?? throw new ArgumentException($"Tried to register handler to non-existent event \"{eventName}\"") let eventHandlerType = eventInfo.EventHandlerType let prefixed = attribute.IsPrefixed let dlgt = (method.IsStatic ? Delegate.CreateDelegate(eventHandlerType, method, false) : Delegate.CreateDelegate(eventHandlerType, handler, method, false)) ?? throw new ArgumentException($"Method \"{method}\" does not adhere to event specification \"{eventHandlerType}\"") select (eventInfo, dlgt, prefixed) ).ToArray(); this._registrationInteractionToDelegate[(handler, type, registerStatic)] = this._registrationInteractionToDelegate.TryGetValue((handler, type, registerStatic), out var delList) ? delList : (delList = new()); delList.Add(delegates); foreach (var (evnt, dlgt, prv) in delegates) if (!prv) evnt.AddEventHandler(this, dlgt); else evnt.AddEventHandler(this, dlgt); + + this.Logger.LogError("Registered {count} interaction event handlers", delegates.Length); + } + catch (Exception ex) + { + this.Logger.LogError(ex.Message); + this.Logger.LogError(ex.StackTrace); + throw ex; + } } } diff --git a/DisCatSharp/Enums/Discord/DiscordEvent.cs b/DisCatSharp/Enums/Discord/DiscordEvent.cs index 6f51a2e64..f083684b9 100644 --- a/DisCatSharp/Enums/Discord/DiscordEvent.cs +++ b/DisCatSharp/Enums/Discord/DiscordEvent.cs @@ -1,299 +1,305 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2023 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #nullable enable using System; namespace DisCatSharp.Enums; /// /// Methods marked with this attribute will be registered as modal handling methods. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ModalInteractionAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomId; /// /// Registers a method as modal handler. /// /// The custom id of the modal to handle. public ModalInteractionAttribute(string custom_id) : base("InteractionCreated") { this.CustomId = custom_id; } } /// /// Methods marked with this attribute will be registered as modal handling methods. /// Modal custom ids will be checked with . /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ModalInteractionWithPrefixAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomIdPrefix; /// /// Registers a method as modal handler. /// /// The custom id prefix of the modal to handle. public ModalInteractionWithPrefixAttribute(string custom_id_prefix) : base("InteractionCreated", true) { this.CustomIdPrefix = custom_id_prefix; } } /// /// Methods marked with this attribute will be registered as button handling methods. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ButtonInteractionAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomId; /// /// Registers a method as button handler. /// /// The custom id of the button to handle. public ButtonInteractionAttribute(string custom_id) : base("ComponentInteractionCreated") { this.CustomId = custom_id; } } /// /// Methods marked with this attribute will be registered as button handling methods. /// Modal custom ids will be checked with . /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ButtonInteractionWithPrefixAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomIdPrefix; /// /// Registers a method as button handler. /// /// The custom id prefix of the button to handle. public ButtonInteractionWithPrefixAttribute(string custom_id_prefix) : base("ComponentInteractionCreated", true) { this.CustomIdPrefix = custom_id_prefix; } } /// /// Methods marked with this attribute will be registered as select menu handling methods. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class SelectInteractionAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomId; /// /// Registers a method as select menu handler. /// /// The custom id of the select menu to handle. public SelectInteractionAttribute(string custom_id) : base("ComponentInteractionCreated") { this.CustomId = custom_id; } } /// /// Methods marked with this attribute will be registered as select menu handling methods. /// Modal custom ids will be checked with . /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class SelectMenuInteractionWithPrefixAttribute : InteractionAttribute { /// /// The custom id to listen for. /// internal readonly string CustomIdPrefix; /// /// Registers a method as select menu handler. /// /// The custom id prefix of the select menu to handle. public SelectMenuInteractionWithPrefixAttribute(string custom_id_prefix) : base("ComponentInteractionCreated", true) { this.CustomIdPrefix = custom_id_prefix; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class InteractionAttribute : Attribute { internal readonly string EventName; internal readonly bool IsPrefixed; public InteractionAttribute(string event_name, bool prefixed = false) { this.EventName = event_name; this.IsPrefixed = prefixed; } } +/// +/// Classes marked with this attribute will be considered for event handler registration from an assembly. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class InteractionHandlerAttribute : Attribute { } + /// /// Methods marked with this attribute will be registered as event handling methods /// if the associated type / an associated instance is being registered. /// [AttributeUsage(AttributeTargets.Method)] public class EventAttribute : Attribute { /// /// The event name to listen for. /// internal readonly string? EventName; public EventAttribute() { } /// /// Registers a method as event handler. /// /// The name of the event. /// The attributed method's name will be used if null. public EventAttribute(DiscordEvent evtn) { this.EventName = evtn.ToString(); } } /// /// Classes marked with this attribute will be considered for event handler registration from an assembly. /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class EventHandlerAttribute : Attribute { } /// /// All events available in for use with . /// public enum DiscordEvent { ApplicationCommandCreated, ApplicationCommandDeleted, ApplicationCommandPermissionsUpdated, ApplicationCommandUpdated, ChannelCreated, ChannelDeleted, ChannelPinsUpdated, ChannelUpdated, ClientErrored, ComponentInteractionCreated, ContextMenuInteractionCreated, DmChannelDeleted, EmbeddedActivityUpdated, GuildApplicationCommandCountUpdated, GuildAvailable, GuildBanAdded, GuildBanRemoved, GuildCreated, GuildDeleted, GuildDownloadCompleted, GuildEmojisUpdated, GuildIntegrationCreated, GuildIntegrationDeleted, GuildIntegrationsUpdated, GuildIntegrationUpdated, GuildMemberAdded, GuildMemberRemoved, GuildMembersChunked, GuildMemberTimeoutAdded, GuildMemberTimeoutChanged, GuildMemberTimeoutRemoved, GuildMemberUpdated, GuildRoleCreated, GuildRoleDeleted, GuildRoleUpdated, GuildScheduledEventCreated, GuildScheduledEventDeleted, GuildScheduledEventUpdated, GuildScheduledEventUserAdded, GuildScheduledEventUserRemoved, GuildStickersUpdated, GuildUnavailable, GuildUpdated, Heartbeated, InteractionCreated, InviteCreated, InviteDeleted, MessageAcknowledged, MessageCreated, MessageDeleted, MessageReactionAdded, MessageReactionRemoved, MessageReactionRemovedEmoji, MessageReactionsCleared, MessagesBulkDeleted, MessageUpdated, PayloadReceived, PresenceUpdated, RateLimitHit, Ready, Resumed, SocketClosed, SocketErrored, SocketOpened, StageInstanceCreated, StageInstanceDeleted, StageInstanceUpdated, ThreadCreated, ThreadDeleted, ThreadListSynced, ThreadMembersUpdated, ThreadMemberUpdated, ThreadUpdated, TypingStarted, UnknownEvent, UserSettingsUpdated, UserUpdated, VoiceServerUpdated, VoiceStateUpdated, WebhooksUpdated, Zombied, AutomodRuleCreated, AutomodRuleUpdated, AutomodRuleDeleted, AutomodActionExecuted, GuildAuditLogEntryCreated }