summaryrefslogtreecommitdiffstats
path: root/api/post.go
diff options
context:
space:
mode:
Diffstat (limited to 'api/post.go')
-rw-r--r--api/post.go315
1 files changed, 164 insertions, 151 deletions
diff --git a/api/post.go b/api/post.go
index d32d1c6a5..a873e16a8 100644
--- a/api/post.go
+++ b/api/post.go
@@ -239,9 +239,6 @@ func handlePostEvents(c *Context, post *model.Post, triggerWebhooks bool) {
tchan := Srv.Store.Team().Get(c.TeamId)
cchan := Srv.Store.Channel().Get(post.ChannelId)
uchan := Srv.Store.User().Get(post.UserId)
- pchan := Srv.Store.User().GetProfiles(c.TeamId)
- dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
- mchan := Srv.Store.Channel().GetMembers(post.ChannelId)
var team *model.Team
if result := <-tchan; result.Err != nil {
@@ -259,34 +256,7 @@ func handlePostEvents(c *Context, post *model.Post, triggerWebhooks bool) {
channel = result.Data.(*model.Channel)
}
- var profiles map[string]*model.User
- if result := <-pchan; result.Err != nil {
- l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
- return
- } else {
- profiles = result.Data.(map[string]*model.User)
- }
-
- if result := <-dpchan; result.Err != nil {
- l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
- return
- } else {
- dps := result.Data.(map[string]*model.User)
- for k, v := range dps {
- profiles[k] = v
- }
- }
-
- var members []model.ChannelMember
- if result := <-mchan; result.Err != nil {
- l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
- return
- } else {
- members = result.Data.([]model.ChannelMember)
- }
-
- go sendNotifications(c, post, team, channel, profiles, members)
- go checkForOutOfChannelMentions(c, post, channel, profiles, members)
+ go sendNotifications(c, post, team, channel)
var user *model.User
if result := <-uchan; result.Err != nil {
@@ -477,110 +447,166 @@ func handleWebhookEvents(c *Context, post *model.Post, team *model.Team, channel
}
}
-func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel, profileMap map[string]*model.User, members []model.ChannelMember) {
- if _, ok := profileMap[post.UserId]; !ok {
- l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
- return
- }
-
- mentionedUserIds := make(map[string]bool)
- alwaysNotifyUserIds := []string{}
- hereNotification := false
- updateMentionChans := []store.StoreChannel{}
+// Given a map of user IDs to profiles and a map of user IDs of channel members, returns a list of mention
+// keywords for all users on the team. Users that are members of the channel will have all their mention
+// keywords returned while users that aren't in the channel will only have their @mentions returned.
+func getMentionKeywords(profiles map[string]*model.User, members map[string]string) map[string][]string {
+ keywords := make(map[string][]string)
- if channel.Type == model.CHANNEL_DIRECT {
+ for id, profile := range profiles {
+ _, inChannel := members[id]
- var otherUserId string
- if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
- otherUserId = userIds[1]
- } else {
- otherUserId = userIds[0]
- }
-
- mentionedUserIds[otherUserId] = true
-
- } else {
- // Find out who is a member of the channel, only keep those profiles
- tempProfileMap := make(map[string]*model.User)
- for _, member := range members {
- if profile, ok := profileMap[member.UserId]; ok {
- tempProfileMap[member.UserId] = profile
- }
- }
-
- profileMap = tempProfileMap
-
- // Build map for keywords
- keywordMap := make(map[string][]string)
- for _, profile := range profileMap {
+ if inChannel {
if len(profile.NotifyProps["mention_keys"]) > 0 {
-
// Add all the user's mention keys
splitKeys := strings.Split(profile.NotifyProps["mention_keys"], ",")
for _, k := range splitKeys {
- keywordMap[k] = append(keywordMap[strings.ToLower(k)], profile.Id)
+ // note that these are made lower case so that we can do a case insensitive check for them
+ key := strings.ToLower(k)
+ keywords[key] = append(keywords[key], id)
}
}
// If turned on, add the user's case sensitive first name
if profile.NotifyProps["first_name"] == "true" {
- keywordMap[profile.FirstName] = append(keywordMap[profile.FirstName], profile.Id)
+ keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
}
// Add @channel and @all to keywords if user has them turned on
if profile.NotifyProps["channel"] == "true" {
- keywordMap["@channel"] = append(keywordMap["@channel"], profile.Id)
- keywordMap["@all"] = append(keywordMap["@all"], profile.Id)
+ keywords["@channel"] = append(keywords["@channel"], profile.Id)
+ keywords["@all"] = append(keywords["@all"], profile.Id)
}
+ } else {
+ // user isn't in channel, so just look for @mentions
+ key := "@" + strings.ToLower(profile.Username)
+ keywords[key] = append(keywords[key], id)
+ }
+ }
- if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
- (post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
- !post.IsSystemMessage() {
- alwaysNotifyUserIds = append(alwaysNotifyUserIds, profile.Id)
- }
+ return keywords
+}
+
+// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
+// users and whether or not @here was mentioned.
+func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, bool) {
+ mentioned := make(map[string]bool)
+ hereMentioned := false
+
+ addMentionedUsers := func(ids []string) {
+ for _, id := range ids {
+ mentioned[id] = true
}
+ }
+
+ for _, word := range strings.Fields(message) {
+ isMention := false
- // Build a map as a list of unique user_ids that are mentioned in this post
- splitF := func(c rune) bool {
- return model.SplitRunes[c]
+ if word == "@here" {
+ hereMentioned = true
}
- splitMessage := strings.Fields(post.Message)
- var userIds []string
- for _, word := range splitMessage {
- if word == "@here" {
- hereNotification = true
- continue
- }
- // Non-case-sensitive check for regular keys
- if ids, match := keywordMap[strings.ToLower(word)]; match {
- userIds = append(userIds, ids...)
- }
+ // Non-case-sensitive check for regular keys
+ if ids, match := keywords[strings.ToLower(word)]; match {
+ addMentionedUsers(ids)
+ isMention = true
+ }
- // Case-sensitive check for first name
- if ids, match := keywordMap[word]; match {
- userIds = append(userIds, ids...)
- }
+ // Case-sensitive check for first name
+ if ids, match := keywords[word]; match {
+ addMentionedUsers(ids)
+ isMention = true
+ }
- if len(userIds) == 0 {
- // No matches were found with the string split just on whitespace so try further splitting
- // the message on punctuation
- splitWords := strings.FieldsFunc(word, splitF)
+ if !isMention {
+ // No matches were found with the string split just on whitespace so try further splitting
+ // the message on punctuation
+ splitWords := strings.FieldsFunc(word, func(c rune) bool {
+ return model.SplitRunes[c]
+ })
- for _, splitWord := range splitWords {
- // Non-case-sensitive check for regular keys
- if ids, match := keywordMap[strings.ToLower(splitWord)]; match {
- userIds = append(userIds, ids...)
- }
+ for _, splitWord := range splitWords {
+ if splitWord == "@here" {
+ hereMentioned = true
+ }
- // Case-sensitive check for first name
- if ids, match := keywordMap[splitWord]; match {
- userIds = append(userIds, ids...)
- }
+ // Non-case-sensitive check for regular keys
+ if ids, match := keywords[strings.ToLower(splitWord)]; match {
+ addMentionedUsers(ids)
}
+
+ // Case-sensitive check for first name
+ if ids, match := keywords[splitWord]; match {
+ addMentionedUsers(ids)
+ }
+ }
+ }
+ }
+
+ return mentioned, hereMentioned
+}
+
+func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
+ // get profiles for all users we could be mentioning
+ pchan := Srv.Store.User().GetProfiles(c.TeamId)
+ dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
+
+ var profileMap map[string]*model.User
+ if result := <-pchan; result.Err != nil {
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
+ return
+ } else {
+ profileMap = result.Data.(map[string]*model.User)
+ }
+
+ if result := <-dpchan; result.Err != nil {
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
+ return
+ } else {
+ dps := result.Data.(map[string]*model.User)
+ for k, v := range dps {
+ profileMap[k] = v
+ }
+ }
+
+ if _, ok := profileMap[post.UserId]; !ok {
+ l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
+ return
+ }
+
+ mentionedUserIds := make(map[string]bool)
+ alwaysNotifyUserIds := []string{} // ???
+ hereNotification := false
+ updateMentionChans := []store.StoreChannel{}
+
+ if channel.Type == model.CHANNEL_DIRECT {
+ var otherUserId string
+ if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
+ otherUserId = userIds[1]
+ } else {
+ otherUserId = userIds[0]
+ }
+
+ mentionedUserIds[otherUserId] = true
+ } else {
+ // using a map as a pseudo-set since we're checking for containment a lot
+ members := make(map[string]string)
+ if result := <-Srv.Store.Channel().GetMembers(post.ChannelId); result.Err != nil {
+ l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
+ return
+ } else {
+ for _, member := range result.Data.([]model.ChannelMember) {
+ members[member.UserId] = member.UserId
}
}
+ keywords := getMentionKeywords(profileMap, members)
+
+ // get users that are explicitly mentioned
+ var mentioned map[string]bool
+ mentioned, hereNotification = getExplicitMentions(post.Message, keywords)
+
+ // get users that have comment thread mentions enabled
if len(post.RootId) > 0 {
if result := <-Srv.Store.Post().Get(post.RootId); result.Err != nil {
l4g.Error(utils.T("api.post.send_notifications_and_forget.comment_thread.error"), post.RootId, result.Err)
@@ -591,22 +617,41 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
for _, threadPost := range list.Posts {
profile := profileMap[threadPost.UserId]
if profile.NotifyProps["comments"] == "any" || (profile.NotifyProps["comments"] == "root" && threadPost.Id == list.Order[0]) {
- userIds = append(userIds, threadPost.UserId)
+ mentioned[threadPost.UserId] = true
}
}
}
}
- for _, userId := range userIds {
- if post.UserId == userId && post.Props["from_webhook"] != "true" {
- continue
- }
+ // prevent the user from mentioning themselves
+ if post.Props["from_webhook"] != "true" {
+ delete(mentioned, post.UserId)
+ }
- mentionedUserIds[userId] = true
+ outOfChannelMentions := make(map[string]bool)
+ for id := range mentioned {
+ if _, inChannel := members[id]; inChannel {
+ mentionedUserIds[id] = true
+ } else {
+ outOfChannelMentions[id] = true
+ }
}
- for id := range mentionedUserIds {
- updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id))
+ go sendOutOfChannelMentions(c, post, profileMap, outOfChannelMentions)
+
+ // find which users in the channel are set up to always receive mobile notifications
+ for id := range members {
+ profile := profileMap[id]
+ if profile == nil {
+ l4g.Warn(utils.T("api.post.notification.member_profile.warn"), id)
+ continue
+ }
+
+ if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
+ (post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
+ !post.IsSystemMessage() {
+ alwaysNotifyUserIds = append(alwaysNotifyUserIds, profile.Id)
+ }
}
}
@@ -882,20 +927,14 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha
}
}
-func checkForOutOfChannelMentions(c *Context, post *model.Post, channel *model.Channel, allProfiles map[string]*model.User, members []model.ChannelMember) {
- // don't check for out of channel mentions in direct channels
- if channel.Type == model.CHANNEL_DIRECT {
+func sendOutOfChannelMentions(c *Context, post *model.Post, profiles map[string]*model.User, outOfChannelMentions map[string]bool) {
+ if len(outOfChannelMentions) == 0 {
return
}
- mentioned := getOutOfChannelMentions(post, allProfiles, members)
- if len(mentioned) == 0 {
- return
- }
-
- usernames := make([]string, len(mentioned))
- for i, user := range mentioned {
- usernames[i] = user.Username
+ var usernames []string
+ for id := range outOfChannelMentions {
+ usernames = append(usernames, profiles[id].Username)
}
sort.Strings(usernames)
@@ -922,32 +961,6 @@ func checkForOutOfChannelMentions(c *Context, post *model.Post, channel *model.C
)
}
-// Gets a list of users that were mentioned in a given post that aren't in the channel that the post was made in
-func getOutOfChannelMentions(post *model.Post, allProfiles map[string]*model.User, members []model.ChannelMember) []*model.User {
- // copy the profiles map since we'll be removing items from it
- profiles := make(map[string]*model.User)
- for id, profile := range allProfiles {
- profiles[id] = profile
- }
-
- // only keep profiles which aren't in the current channel
- for _, member := range members {
- delete(profiles, member.UserId)
- }
-
- var mentioned []*model.User
-
- for _, profile := range profiles {
- if pattern, err := regexp.Compile(`(\W|^)@` + regexp.QuoteMeta(profile.Username) + `(\W|$)`); err != nil {
- l4g.Error(utils.T("api.post.get_out_of_channel_mentions.regex.error"), profile.Id, err)
- } else if pattern.MatchString(post.Message) {
- mentioned = append(mentioned, profile)
- }
- }
-
- return mentioned
-}
-
func SendEphemeralPost(teamId, userId string, post *model.Post) {
post.Type = model.POST_EPHEMERAL