Skip to content

Commit

Permalink
feat: Ignore invalid message reactions
Browse files Browse the repository at this point in the history
Ignore reactions on messages from apps and bots
- Apps and bots will come back with `nil` as their User ID. Previously,
posts from non-users, like messages from bots or Slack, were able to be
reacted to. This would cause messages to be posted without proper
attribution.

Don't allow users to react to .webm
- The Slack attachments API doesn't currently support .webm. In the
  future, we can add additional formats that are not allowed.

Alert user when messages cannot post
- If a message cannot be posted, then send a DM to the user so they know
  exactly why their message reaction has not posted.
  • Loading branch information
jordanleven committed Feb 12, 2022
1 parent 6a9f3b7 commit fb2e3db
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 4 deletions.
19 changes: 19 additions & 0 deletions internal/slackclient/slack_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,22 @@ func PostSlackMessage(client *SlackClient, channel string, opts SlackPostMessage

return ts, error
}

// PostSlackMessage allows posting messages to specific channels
func SendDirectMessage(client *SlackClient, userId string, message string) (timestamp string, error error) {
reactedMessageBlock := slack.NewTextBlockObject(slack.MarkdownType, message, false, false)

blocks := []slack.Block{
slack.NewSectionBlock(reactedMessageBlock, nil, nil),
}

_, ts, error := client.PostMessage(
userId,
slack.MsgOptionBlocks(blocks...),
slack.MsgOptionText(message, true),
slack.MsgOptionAsUser(false),
slack.MsgOptionParse(true),
)

return ts, error
}
113 changes: 109 additions & 4 deletions reactionbot/events.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,119 @@
package reactionbot

import "github.com/jordanleven/reaction-bot/internal/slackclient"
import (
"fmt"
"path/filepath"
"strings"

func (r reactionBot) messageShouldBePosted(event slackclient.ReactionEvent) bool {
return r.reactionIsRegistered(event.ReactionEmoji) && event.ReactionCount == 1
"github.com/fatih/color"
"github.com/jordanleven/reaction-bot/internal/slackclient"
)

type eventReactionStatus int

// Enums for handling eligible and ineligible posts
const (
eventReactionEligible eventReactionStatus = iota
eventReactionIneligibleUserReactedToDoesNotExist
eventReactionIneligibleMessageContainsDisallowedAttachmentType
eventReactionIneligibleUnknownReason
)

const (
eventReactionIneligibleMessageIntro = "Hey {user}, Reaction Bot here :robot_face:! It looks like you recently added a \":{reactionType}:\" reaction to a message (\"{message}\") that I'm unable to work with."
eventReactionIneligibleMessageOutro = "Sorry about that, but I hope we can still be friends! :heart:"
)

var eventReactionStatusErrorMessages = map[eventReactionStatus]string{
eventReactionIneligibleUserReactedToDoesNotExist: "Posting a reaction on messages that originate from Slack Bots and Apps is unsupported.",
eventReactionIneligibleMessageContainsDisallowedAttachmentType: "Posting a reaction on messages that contain `{attachmentExtension}` attachments is currently not supported by Slack.",
eventReactionIneligibleUnknownReason: "",
}

var eventReactionDisallowedAttachmentTypes = []string{
"webm",
}

func eventReactionMessageContainsDisallowedAttachmentType(event slackclient.ReactionEvent) bool {
// Early exist if no attachment is contained
if event.MessageAttachment == nil {
return false
}

attachmentExtension := filepath.Ext(event.MessageAttachment.Name)

for _, v := range eventReactionDisallowedAttachmentTypes {
vF := fmt.Sprintf(".%s", v)
if attachmentExtension == vF {
return true
}
}

return false
}

func (r reactionBot) eventReactionIsEligibleForPost(event slackclient.ReactionEvent) (bool, eventReactionStatus) {
// User IDs will only exist for messages posted by real users
userIdDoesNotExist := event.UserIDReactedTo == ""
messageContainsDisallowedAttachment := eventReactionMessageContainsDisallowedAttachmentType(event)

messageIsEligibleForPost := !userIdDoesNotExist && !messageContainsDisallowedAttachment

switch {
case messageIsEligibleForPost:
return messageIsEligibleForPost, eventReactionEligible
case userIdDoesNotExist:
return messageIsEligibleForPost, eventReactionIneligibleUserReactedToDoesNotExist
case messageContainsDisallowedAttachment:
return messageIsEligibleForPost, eventReactionIneligibleMessageContainsDisallowedAttachmentType
default:
return messageIsEligibleForPost, eventReactionIneligibleUnknownReason
}
}

func (r reactionBot) eventReactionIsRegistered(event slackclient.ReactionEvent) bool {
return r.reactionIsRegistered(event.ReactionEmoji)
}

func eventReactionIsUnique(event slackclient.ReactionEvent) bool {
return event.ReactionCount == 1
}

func (r reactionBot) sentEventIneligibleMessageToUser(status eventReactionStatus, event slackclient.ReactionEvent) {
allUsers := r.Users
reactedByUser := getUserByUserID(*allUsers, event.UserIDReactedBy)
var attachmentExtension string

if event.MessageAttachment != nil {
attachmentExtension = filepath.Ext(event.MessageAttachment.Name)
}

statusSpecificErrorMessage := eventReactionStatusErrorMessages[status]
errorMessage := fmt.Sprintf("%s %s \n \n %s", eventReactionIneligibleMessageIntro, statusSpecificErrorMessage, eventReactionIneligibleMessageOutro)

errorMessageReplacements := strings.NewReplacer(
"{user}", reactedByUser.DisplayName,
"{message}", event.Message,
"{attachmentExtension}", attachmentExtension,
"{reactionType}", event.ReactionEmoji,
)
errorMessageF := errorMessageReplacements.Replace(errorMessage)

slackclient.SendDirectMessage(r.SlackClient, event.UserIDReactedBy, errorMessageF)
color.Yellow("Sent error message to %s. Message unable to reacted to (code %d). Original message was \"%s\". (dated %s).\n", reactedByUser.DisplayName, status, event.Message, event.ReactionTimestamp)
}

func (r reactionBot) handleReaction(event slackclient.ReactionEvent) {
if r.messageShouldBePosted(event) {
// Early exist if the reaction isn't something we need to deal with
if !r.eventReactionIsRegistered(event) || !eventReactionIsUnique(event) {
return
}

isEligibleForPost, status := r.eventReactionIsEligibleForPost(event)
if isEligibleForPost {
r.maybePostReactedMessageToChannel(event)
} else {
r.sentEventIneligibleMessageToUser(status, event)
}
}

Expand Down

0 comments on commit fb2e3db

Please sign in to comment.