diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 156d79e21..bfbbe24d9 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -218,6 +218,9 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco case .presenceUpdate: handlePresenceUpdate(with: eventData) case .messageCreate: handleMessageCreate(with: eventData) case .messageUpdate: handleMessageUpdate(with: eventData) + case .messageReactionAdd: handleMessageReactionAdd(with: eventData) + case .messageReactionRemove: handleMessageReactionRemove(with: eventData) + case .messageReactionRemoveAll: handleMessageReactionRemoveAll(with: eventData) case .guildMemberAdd: handleGuildMemberAdd(with: eventData) case .guildMembersChunk: handleGuildMembersChunk(with: eventData) case .guildMemberUpdate: handleGuildMemberUpdate(with: eventData) @@ -819,6 +822,88 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco delegate?.client(self, didCreateMessage: message) } + /// Used to get fields for reaction notifications since add and remove are very similar + /// - parameter mode: A string to identify add/remove when logging errors + private func getReactionInfo(mode: String, from data: [String: Any]) -> (UserID, DiscordTextChannel, MessageID, DiscordEmoji)? { + guard let userID = UserID(data["user_id"] as? String), + let channelID = ChannelID(data["channel_id"] as? String), + let messageID = MessageID(data["message_id"] as? String), + let emoji = (data["emoji"] as? [String: Any]).map(DiscordEmoji.init(emojiObject:)) + else { + DefaultDiscordLogger.Logger.log("Failed to get required fields from reaction \(mode)", type: logType) + return nil + } + guard let channel = findChannel(fromId: channelID) as? DiscordTextChannel else { + DefaultDiscordLogger.Logger.log("Failed to get channel from ID in reaction \(mode)", type: logType) + return nil + } + return (userID, channel, messageID, emoji) + } + + /// + /// Handles reaction adds from Discord. You shouldn't need to call this method directly. + /// + /// Override to provide additional customization around this event. + /// + /// Calls the `didAddReaction` delegate method. + /// + /// - parameter with: The data from the event + /// + open func handleMessageReactionAdd(with data: [String: Any]) { + DefaultDiscordLogger.Logger.log("Handling message reaction add", type: logType) + + guard let (userID, channel, messageID, emoji) = getReactionInfo(mode: "add", from: data) else { return } + + if let guildID = GuildID(data["guild_id"] as? String), + let guild = guilds[guildID], + let member = (data["member"] as? [String: Any]).map({ DiscordGuildMember(guildMemberObject: $0, guildId: guildID) }) { + guild.members[member.user.id] = member + } + + delegate?.client(self, didAddReaction: emoji, toMessage: messageID, onChannel: channel, user: userID) + } + + /// + /// Handles reaction removals from Discord. You shouldn't need to call this method directly. + /// + /// Override to provide additional customization around this event. + /// + /// Calls the `didRemoveReaction` delegate method. + /// + /// - parameter with: The data from the event + /// + open func handleMessageReactionRemove(with data: [String: Any]) { + DefaultDiscordLogger.Logger.log("Handling message reaction remove", type: logType) + + guard let (userID, channel, messageID, emoji) = getReactionInfo(mode: "remove", from: data) else { return } + + delegate?.client(self, didRemoveReaction: emoji, fromMessage: messageID, onChannel: channel, user: userID) + } + + /// + /// Handles reaction remove alls from Discord. You shouldn't need to call this method directly. + /// + /// Override to provide additional customization around this event. + /// + /// Calls the `didRemoveAllReactionsFrom` delegate method. + /// + /// - parameter with: The data from the event + /// + open func handleMessageReactionRemoveAll(with data: [String: Any]) { + guard let channelID = ChannelID(data["channel_id"] as? String), + let messageID = MessageID(data["message_id"] as? String) + else { + DefaultDiscordLogger.Logger.log("Failed to get required fields from reaction remove all", type: logType) + return + } + guard let channel = findChannel(fromId: channelID) as? DiscordTextChannel else { + DefaultDiscordLogger.Logger.log("Failed to get channel from ID in reaction remove all", type: logType) + return + } + + delegate?.client(self, didRemoveAllReactionsFrom: messageID, onChannel: channel) + } + /// /// Handles presence updates from Discord. You shouldn't need to call this method directly. /// diff --git a/Sources/SwiftDiscord/DiscordClientDelegate.swift b/Sources/SwiftDiscord/DiscordClientDelegate.swift index 126879fc5..6c8510e85 100644 --- a/Sources/SwiftDiscord/DiscordClientDelegate.swift +++ b/Sources/SwiftDiscord/DiscordClientDelegate.swift @@ -126,6 +126,37 @@ public protocol DiscordClientDelegate : class { /// func client(_ client: DiscordClient, didCreateMessage message: DiscordMessage) + /// + /// Called when a user adds a reaction to a message. + /// + /// - parameter client: The client that is calling. + /// - parameter reaction: The reaction that was added. + /// - parameter messageID: The ID of the message the reaction was added to. + /// - parameter channel: The channel the message was on. + /// - parameter userID: The ID of the user who added the reaction. + /// + func client(_ client: DiscordClient, didAddReaction reaction: DiscordEmoji, toMessage messageID: MessageID, onChannel channel: DiscordTextChannel, user userID: UserID) + + /// + /// Called when a user removes a reaction to a message. + /// + /// - parameter client: The client that is calling. + /// - parameter reaction: The reaction that was added. + /// - parameter messageID: The ID of the message the reaction was removed from. + /// - parameter channel: The channel the message was on. + /// - parameter userID: The ID of the user who added the reaction. + /// + func client(_ client: DiscordClient, didRemoveReaction reaction: DiscordEmoji, fromMessage messageID: MessageID, onChannel channel: DiscordTextChannel, user userID: UserID) + + /// + /// Called when all reactions are removed from a message. + /// + /// - parameter client: The client that is calling. + /// - parameter messageID: The ID of the message all + /// - parameter channel: The channel the message was on + /// + func client(_ client: DiscordClient, didRemoveAllReactionsFrom messageID: MessageID, onChannel channel: DiscordTextChannel) + /// /// Called when the client adds a new role. /// @@ -304,6 +335,15 @@ public extension DiscordClientDelegate { /// Default. func client(_ client: DiscordClient, didCreateMessage message: DiscordMessage) { } + /// Default. + func client(_ client: DiscordClient, didAddReaction reaction: DiscordEmoji, toMessage messageID: MessageID, onChannel channel: DiscordTextChannel, user userID: UserID) { } + + /// Default. + func client(_ client: DiscordClient, didRemoveReaction reaction: DiscordEmoji, fromMessage messageID: MessageID, onChannel channel: DiscordTextChannel, user userID: UserID) { } + + /// Default. + func client(_ client: DiscordClient, didRemoveAllReactionsFrom messageID: MessageID, onChannel channel: DiscordTextChannel) { } + /// Default. func client(_ client: DiscordClient, didReceivePresenceUpdate presence: DiscordPresence) { } diff --git a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift index 8301b1fb7..13d2b1641 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift @@ -39,13 +39,13 @@ public enum DiscordDispatchEvent : String { /// Message Delete Bulk (Not handled) case messageDeleteBulk = "MESSAGE_DELETE_BULK" - /// Message Reaction Add (Not handled) + /// Message Reaction Add (Handled) case messageReactionAdd = "MESSAGE_REACTION_ADD" - /// Message Reaction Remove All (Not handled) + /// Message Reaction Remove All (Handled) case messageReactionRemoveAll = "MESSAGE_REACTION_REMOVE_ALL" - /// Message Reaction Remove (Not handled) + /// Message Reaction Remove (Handled) case messageReactionRemove = "MESSAGE_REACTION_REMOVE" /// Message Update (Not handled)