PLT-2664 Split out push notifications from email notifications (#3049)
* Split out push and email notification logic, always send push notifications on mention * Add user setting to control push notifications * Minor changeto simplify code
Этот коммит содержится в:
391
api/post.go
391
api/post.go
@@ -465,39 +465,28 @@ 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) {
|
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel, profileMap map[string]*model.User, members []model.ChannelMember) {
|
||||||
var channelName string
|
if post.IsSystemMessage() {
|
||||||
var bodyText string
|
return
|
||||||
var subjectText string
|
}
|
||||||
|
|
||||||
var mentionedUsers []string
|
|
||||||
|
|
||||||
if _, ok := profileMap[post.UserId]; !ok {
|
if _, ok := profileMap[post.UserId]; !ok {
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
senderName := profileMap[post.UserId].Username
|
|
||||||
|
|
||||||
toEmailMap := make(map[string]bool)
|
mentionedUserIds := make(map[string]bool)
|
||||||
|
alwaysNotifyUserIds := []string{}
|
||||||
|
|
||||||
if channel.Type == model.CHANNEL_DIRECT {
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
|
||||||
var otherUserId string
|
var otherUserId string
|
||||||
if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
|
if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
|
||||||
otherUserId = userIds[1]
|
otherUserId = userIds[1]
|
||||||
channelName = profileMap[userIds[1]].Username
|
|
||||||
} else {
|
} else {
|
||||||
otherUserId = userIds[0]
|
otherUserId = userIds[0]
|
||||||
channelName = profileMap[userIds[0]].Username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
otherUser := profileMap[otherUserId]
|
mentionedUserIds[otherUserId] = true
|
||||||
sendEmail := true
|
|
||||||
if _, ok := otherUser.NotifyProps["email"]; ok && otherUser.NotifyProps["email"] == "false" {
|
|
||||||
sendEmail = false
|
|
||||||
}
|
|
||||||
if sendEmail && (otherUser.IsOffline() || otherUser.IsAway()) {
|
|
||||||
toEmailMap[otherUserId] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Find out who is a member of the channel, only keep those profiles
|
// Find out who is a member of the channel, only keep those profiles
|
||||||
@@ -534,6 +523,10 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
if profile.NotifyProps["channel"] == "true" {
|
if profile.NotifyProps["channel"] == "true" {
|
||||||
keywordMap["@channel"] = append(keywordMap["@channel"], profile.Id)
|
keywordMap["@channel"] = append(keywordMap["@channel"], profile.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL && (post.UserId != profile.Id || post.Props["from_webhook"] == "true") {
|
||||||
|
alwaysNotifyUserIds = append(alwaysNotifyUserIds, profile.Id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a map as a list of unique user_ids that are mentioned in this post
|
// Build a map as a list of unique user_ids that are mentioned in this post
|
||||||
@@ -576,179 +569,54 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
if post.UserId == userId && post.Props["from_webhook"] != "true" {
|
if post.UserId == userId && post.Props["from_webhook"] != "true" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sendEmail := true
|
|
||||||
if _, ok := profileMap[userId].NotifyProps["email"]; ok && profileMap[userId].NotifyProps["email"] == "false" {
|
mentionedUserIds[userId] = true
|
||||||
sendEmail = false
|
|
||||||
}
|
|
||||||
if sendEmail && (profileMap[userId].IsAway() || profileMap[userId].IsOffline()) {
|
|
||||||
toEmailMap[userId] = true
|
|
||||||
} else {
|
|
||||||
toEmailMap[userId] = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for id := range toEmailMap {
|
for id := range mentionedUserIds {
|
||||||
go updateMentionCount(post.ChannelId, id)
|
go updateMentionCount(post.ChannelId, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(toEmailMap) != 0 {
|
mentionedUsersList := make([]string, 0, len(mentionedUserIds))
|
||||||
mentionedUsers = make([]string, 0, len(toEmailMap))
|
|
||||||
for k := range toEmailMap {
|
senderName := profileMap[post.UserId].Username
|
||||||
mentionedUsers = append(mentionedUsers, k)
|
|
||||||
|
for id := range mentionedUserIds {
|
||||||
|
mentionedUsersList = append(mentionedUsersList, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.Cfg.EmailSettings.SendEmailNotifications {
|
||||||
|
for _, id := range mentionedUsersList {
|
||||||
|
userAllowsEmails := profileMap[id].NotifyProps["email"] != "false"
|
||||||
|
|
||||||
|
if userAllowsEmails && (profileMap[id].IsAway() || profileMap[id].IsOffline()) {
|
||||||
|
sendNotificationEmail(c, post, profileMap[id], channel, team, senderName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
teamURL := c.GetSiteURL() + "/" + team.Name
|
sendPushNotifications := false
|
||||||
|
if *utils.Cfg.EmailSettings.SendPushNotifications {
|
||||||
|
pushServer := *utils.Cfg.EmailSettings.PushNotificationServer
|
||||||
|
if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) {
|
||||||
|
l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
|
||||||
|
sendPushNotifications = false
|
||||||
|
} else {
|
||||||
|
sendPushNotifications = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build and send the emails
|
if sendPushNotifications {
|
||||||
tm := time.Unix(post.CreateAt/1000, 0)
|
for _, id := range mentionedUsersList {
|
||||||
|
if profileMap[id].NotifyProps["push"] != "none" {
|
||||||
for id, doSend := range toEmailMap {
|
sendPushNotification(post, profileMap[id], channel, senderName, true)
|
||||||
|
|
||||||
if !doSend {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// skip if inactive
|
for _, id := range alwaysNotifyUserIds {
|
||||||
if profileMap[id].DeleteAt > 0 {
|
if _, ok := mentionedUserIds[id]; !ok {
|
||||||
continue
|
sendPushNotification(post, profileMap[id], channel, senderName, false)
|
||||||
}
|
|
||||||
|
|
||||||
userLocale := utils.GetUserTranslations(profileMap[id].Locale)
|
|
||||||
|
|
||||||
if channel.Type == model.CHANNEL_DIRECT {
|
|
||||||
bodyText = userLocale("api.post.send_notifications_and_forget.message_body")
|
|
||||||
subjectText = userLocale("api.post.send_notifications_and_forget.message_subject")
|
|
||||||
} else {
|
|
||||||
bodyText = userLocale("api.post.send_notifications_and_forget.mention_body")
|
|
||||||
subjectText = userLocale("api.post.send_notifications_and_forget.mention_subject")
|
|
||||||
channelName = channel.DisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
month := userLocale(tm.Month().String())
|
|
||||||
day := fmt.Sprintf("%d", tm.Day())
|
|
||||||
year := fmt.Sprintf("%d", tm.Year())
|
|
||||||
zone, _ := tm.Zone()
|
|
||||||
|
|
||||||
subjectPage := utils.NewHTMLTemplate("post_subject", profileMap[id].Locale)
|
|
||||||
subjectPage.Props["Subject"] = userLocale("api.templates.post_subject",
|
|
||||||
map[string]interface{}{"SubjectText": subjectText, "TeamDisplayName": team.DisplayName,
|
|
||||||
"Month": month[:3], "Day": day, "Year": year})
|
|
||||||
subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
|
|
||||||
|
|
||||||
bodyPage := utils.NewHTMLTemplate("post_body", profileMap[id].Locale)
|
|
||||||
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
|
||||||
bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message)
|
|
||||||
bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name
|
|
||||||
bodyPage.Props["BodyText"] = bodyText
|
|
||||||
bodyPage.Props["Button"] = userLocale("api.templates.post_body.button")
|
|
||||||
bodyPage.Html["Info"] = template.HTML(userLocale("api.templates.post_body.info",
|
|
||||||
map[string]interface{}{"ChannelName": channelName, "SenderName": senderName,
|
|
||||||
"Hour": fmt.Sprintf("%02d", tm.Hour()), "Minute": fmt.Sprintf("%02d", tm.Minute()),
|
|
||||||
"TimeZone": zone, "Month": month, "Day": day}))
|
|
||||||
|
|
||||||
// attempt to fill in a message body if the post doesn't have any text
|
|
||||||
if len(strings.TrimSpace(bodyPage.Props["PostMessage"])) == 0 && len(post.Filenames) > 0 {
|
|
||||||
// extract the filenames from their paths and determine what type of files are attached
|
|
||||||
filenames := make([]string, len(post.Filenames))
|
|
||||||
onlyImages := true
|
|
||||||
for i, filename := range post.Filenames {
|
|
||||||
var err error
|
|
||||||
if filenames[i], err = url.QueryUnescape(filepath.Base(filename)); err != nil {
|
|
||||||
// this should never error since filepath was escaped using url.QueryEscape
|
|
||||||
filenames[i] = filepath.Base(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := filepath.Ext(filename)
|
|
||||||
onlyImages = onlyImages && model.IsFileExtImage(ext)
|
|
||||||
}
|
|
||||||
filenamesString := strings.Join(filenames, ", ")
|
|
||||||
|
|
||||||
var attachmentPrefix string
|
|
||||||
if onlyImages {
|
|
||||||
attachmentPrefix = "Image"
|
|
||||||
} else {
|
|
||||||
attachmentPrefix = "File"
|
|
||||||
}
|
|
||||||
if len(post.Filenames) > 1 {
|
|
||||||
attachmentPrefix += "s"
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyPage.Props["PostMessage"] = userLocale("api.post.send_notifications_and_forget.sent",
|
|
||||||
map[string]interface{}{"Prefix": attachmentPrefix, "Filenames": filenamesString})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.SendMail(profileMap[id].Email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.send.error"), profileMap[id].Email, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *utils.Cfg.EmailSettings.SendPushNotifications {
|
|
||||||
sessionChan := Srv.Store.Session().GetSessions(id)
|
|
||||||
if result := <-sessionChan; result.Err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.sessions.error"), id, result.Err)
|
|
||||||
} else {
|
|
||||||
sessions := result.Data.([]*model.Session)
|
|
||||||
alreadySeen := make(map[string]string)
|
|
||||||
|
|
||||||
pushServer := *utils.Cfg.EmailSettings.PushNotificationServer
|
|
||||||
if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) {
|
|
||||||
l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
|
|
||||||
} else {
|
|
||||||
for _, session := range sessions {
|
|
||||||
if len(session.DeviceId) > 0 && alreadySeen[session.DeviceId] == "" &&
|
|
||||||
(strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":")) {
|
|
||||||
alreadySeen[session.DeviceId] = session.DeviceId
|
|
||||||
|
|
||||||
msg := model.PushNotification{}
|
|
||||||
if badge := <-Srv.Store.User().GetUnreadCount(id); badge.Err != nil {
|
|
||||||
msg.Badge = 1
|
|
||||||
l4g.Error(utils.T("store.sql_user.get_unread_count.app_error"), id, badge.Err)
|
|
||||||
} else {
|
|
||||||
msg.Badge = int(badge.Data.(int64))
|
|
||||||
}
|
|
||||||
msg.ServerId = utils.CfgDiagnosticId
|
|
||||||
msg.ChannelId = channel.Id
|
|
||||||
msg.ChannelName = channel.Name
|
|
||||||
|
|
||||||
if strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":") {
|
|
||||||
msg.Platform = model.PUSH_NOTIFY_APPLE
|
|
||||||
msg.DeviceId = strings.TrimPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":")
|
|
||||||
} else if strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":") {
|
|
||||||
msg.Platform = model.PUSH_NOTIFY_ANDROID
|
|
||||||
msg.DeviceId = strings.TrimPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *utils.Cfg.EmailSettings.PushNotificationContents == model.FULL_NOTIFICATION {
|
|
||||||
if channel.Type == model.CHANNEL_DIRECT {
|
|
||||||
msg.Category = model.CATEGORY_DM
|
|
||||||
msg.Message = "@" + senderName + ": " + model.ClearMentionTags(post.Message)
|
|
||||||
} else {
|
|
||||||
msg.Message = "@" + senderName + " @ " + channelName + ": " + model.ClearMentionTags(post.Message)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if channel.Type == model.CHANNEL_DIRECT {
|
|
||||||
msg.Category = model.CATEGORY_DM
|
|
||||||
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_message")
|
|
||||||
} else {
|
|
||||||
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_mention") + channelName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
|
|
||||||
}
|
|
||||||
httpClient := &http.Client{Transport: tr}
|
|
||||||
request, _ := http.NewRequest("POST", pushServer+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson()))
|
|
||||||
|
|
||||||
l4g.Debug(utils.T("api.post.send_notifications_and_forget.push_notification.debug"), msg.DeviceId, msg.Message)
|
|
||||||
if _, err := httpClient.Do(request); err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.push_notification.error"), id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -756,6 +624,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
message := model.NewMessage(c.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
|
message := model.NewMessage(c.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
|
||||||
message.Add("post", post.ToJson())
|
message.Add("post", post.ToJson())
|
||||||
message.Add("channel_type", channel.Type)
|
message.Add("channel_type", channel.Type)
|
||||||
|
message.Add("sender_name", senderName)
|
||||||
|
|
||||||
if len(post.Filenames) != 0 {
|
if len(post.Filenames) != 0 {
|
||||||
message.Add("otherFile", "true")
|
message.Add("otherFile", "true")
|
||||||
@@ -769,13 +638,173 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(mentionedUsers) != 0 {
|
if len(mentionedUsersList) != 0 {
|
||||||
message.Add("mentions", model.ArrayToJson(mentionedUsers))
|
message.Add("mentions", model.ArrayToJson(mentionedUsersList))
|
||||||
}
|
}
|
||||||
|
|
||||||
go Publish(message)
|
go Publish(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendNotificationEmail(c *Context, post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string) {
|
||||||
|
// skip if inactive
|
||||||
|
if user.DeleteAt > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelName string
|
||||||
|
var bodyText string
|
||||||
|
var subjectText string
|
||||||
|
|
||||||
|
teamURL := c.GetSiteURL() + "/" + team.Name
|
||||||
|
tm := time.Unix(post.CreateAt/1000, 0)
|
||||||
|
|
||||||
|
userLocale := utils.GetUserTranslations(user.Locale)
|
||||||
|
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
bodyText = userLocale("api.post.send_notifications_and_forget.message_body")
|
||||||
|
subjectText = userLocale("api.post.send_notifications_and_forget.message_subject")
|
||||||
|
channelName = senderName
|
||||||
|
} else {
|
||||||
|
bodyText = userLocale("api.post.send_notifications_and_forget.mention_body")
|
||||||
|
subjectText = userLocale("api.post.send_notifications_and_forget.mention_subject")
|
||||||
|
channelName = channel.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
month := userLocale(tm.Month().String())
|
||||||
|
day := fmt.Sprintf("%d", tm.Day())
|
||||||
|
year := fmt.Sprintf("%d", tm.Year())
|
||||||
|
zone, _ := tm.Zone()
|
||||||
|
|
||||||
|
subjectPage := utils.NewHTMLTemplate("post_subject", user.Locale)
|
||||||
|
subjectPage.Props["Subject"] = userLocale("api.templates.post_subject",
|
||||||
|
map[string]interface{}{"SubjectText": subjectText, "TeamDisplayName": team.DisplayName,
|
||||||
|
"Month": month[:3], "Day": day, "Year": year})
|
||||||
|
subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
|
||||||
|
|
||||||
|
bodyPage := utils.NewHTMLTemplate("post_body", user.Locale)
|
||||||
|
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
||||||
|
bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message)
|
||||||
|
bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name
|
||||||
|
bodyPage.Props["BodyText"] = bodyText
|
||||||
|
bodyPage.Props["Button"] = userLocale("api.templates.post_body.button")
|
||||||
|
bodyPage.Html["Info"] = template.HTML(userLocale("api.templates.post_body.info",
|
||||||
|
map[string]interface{}{"ChannelName": channelName, "SenderName": senderName,
|
||||||
|
"Hour": fmt.Sprintf("%02d", tm.Hour()), "Minute": fmt.Sprintf("%02d", tm.Minute()),
|
||||||
|
"TimeZone": zone, "Month": month, "Day": day}))
|
||||||
|
|
||||||
|
// attempt to fill in a message body if the post doesn't have any text
|
||||||
|
if len(strings.TrimSpace(bodyPage.Props["PostMessage"])) == 0 && len(post.Filenames) > 0 {
|
||||||
|
// extract the filenames from their paths and determine what type of files are attached
|
||||||
|
filenames := make([]string, len(post.Filenames))
|
||||||
|
onlyImages := true
|
||||||
|
for i, filename := range post.Filenames {
|
||||||
|
var err error
|
||||||
|
if filenames[i], err = url.QueryUnescape(filepath.Base(filename)); err != nil {
|
||||||
|
// this should never error since filepath was escaped using url.QueryEscape
|
||||||
|
filenames[i] = filepath.Base(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := filepath.Ext(filename)
|
||||||
|
onlyImages = onlyImages && model.IsFileExtImage(ext)
|
||||||
|
}
|
||||||
|
filenamesString := strings.Join(filenames, ", ")
|
||||||
|
|
||||||
|
var attachmentPrefix string
|
||||||
|
if onlyImages {
|
||||||
|
attachmentPrefix = "Image"
|
||||||
|
} else {
|
||||||
|
attachmentPrefix = "File"
|
||||||
|
}
|
||||||
|
if len(post.Filenames) > 1 {
|
||||||
|
attachmentPrefix += "s"
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyPage.Props["PostMessage"] = userLocale("api.post.send_notifications_and_forget.sent",
|
||||||
|
map[string]interface{}{"Prefix": attachmentPrefix, "Filenames": filenamesString})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.SendMail(user.Email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.send.error"), user.Email, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, senderName string, wasMentioned bool) {
|
||||||
|
var sessions []*model.Session
|
||||||
|
if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.sessions.error"), user.Id, result.Err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
sessions = result.Data.([]*model.Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelName string
|
||||||
|
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
channelName = senderName
|
||||||
|
} else {
|
||||||
|
channelName = channel.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
userLocale := utils.GetUserTranslations(user.Locale)
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
if len(session.DeviceId) > 0 &&
|
||||||
|
(strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":")) {
|
||||||
|
|
||||||
|
msg := model.PushNotification{}
|
||||||
|
if badge := <-Srv.Store.User().GetUnreadCount(user.Id); badge.Err != nil {
|
||||||
|
msg.Badge = 1
|
||||||
|
l4g.Error(utils.T("store.sql_user.get_unread_count.app_error"), user.Id, badge.Err)
|
||||||
|
} else {
|
||||||
|
msg.Badge = int(badge.Data.(int64))
|
||||||
|
}
|
||||||
|
msg.ServerId = utils.CfgDiagnosticId
|
||||||
|
msg.ChannelId = channel.Id
|
||||||
|
msg.ChannelName = channel.Name
|
||||||
|
|
||||||
|
if strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":") {
|
||||||
|
msg.Platform = model.PUSH_NOTIFY_APPLE
|
||||||
|
msg.DeviceId = strings.TrimPrefix(session.DeviceId, model.PUSH_NOTIFY_APPLE+":")
|
||||||
|
} else if strings.HasPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":") {
|
||||||
|
msg.Platform = model.PUSH_NOTIFY_ANDROID
|
||||||
|
msg.DeviceId = strings.TrimPrefix(session.DeviceId, model.PUSH_NOTIFY_ANDROID+":")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *utils.Cfg.EmailSettings.PushNotificationContents == model.FULL_NOTIFICATION {
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
msg.Category = model.CATEGORY_DM
|
||||||
|
msg.Message = "@" + senderName + ": " + model.ClearMentionTags(post.Message)
|
||||||
|
} else {
|
||||||
|
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_in") + channelName + ": " + model.ClearMentionTags(post.Message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
msg.Category = model.CATEGORY_DM
|
||||||
|
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_message")
|
||||||
|
} else if wasMentioned {
|
||||||
|
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_mention") + channelName
|
||||||
|
} else {
|
||||||
|
msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_non_mention") + channelName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{Transport: tr}
|
||||||
|
request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson()))
|
||||||
|
|
||||||
|
l4g.Debug(utils.T("api.post.send_notifications_and_forget.push_notification.debug"), msg.DeviceId, msg.Message)
|
||||||
|
if _, err := httpClient.Do(request); err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.push_notification.error"), user.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// notification sent, don't need to check other sessions
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateMentionCount(channelId, userId string) {
|
func updateMentionCount(channelId, userId string) {
|
||||||
if result := <-Srv.Store.Channel().IncrementMentionCount(channelId, userId); result.Err != nil {
|
if result := <-Srv.Store.Channel().IncrementMentionCount(channelId, userId); result.Err != nil {
|
||||||
l4g.Error(utils.T("api.post.update_mention_count_and_forget.update_error"), userId, channelId, result.Err)
|
l4g.Error(utils.T("api.post.update_mention_count_and_forget.update_error"), userId, channelId, result.Err)
|
||||||
|
|||||||
@@ -903,6 +903,14 @@
|
|||||||
"id": "api.post.send_notifications_and_forget.push_mention",
|
"id": "api.post.send_notifications_and_forget.push_mention",
|
||||||
"translation": " mentioned you in "
|
"translation": " mentioned you in "
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.send_notifications_and_forget.push_in",
|
||||||
|
"translation": " in "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.send_notifications_and_forget.push_non_mention",
|
||||||
|
"translation": " posted in "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.post.send_notifications_and_forget.push_message",
|
"id": "api.post.send_notifications_and_forget.push_message",
|
||||||
"translation": " sent you a direct message"
|
"translation": " sent you a direct message"
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ func (u *User) PreUpdate() {
|
|||||||
func (u *User) SetDefaultNotifications() {
|
func (u *User) SetDefaultNotifications() {
|
||||||
u.NotifyProps = make(map[string]string)
|
u.NotifyProps = make(map[string]string)
|
||||||
u.NotifyProps["email"] = "true"
|
u.NotifyProps["email"] = "true"
|
||||||
|
u.NotifyProps["push"] = USER_NOTIFY_MENTION
|
||||||
u.NotifyProps["desktop"] = USER_NOTIFY_ALL
|
u.NotifyProps["desktop"] = USER_NOTIFY_ALL
|
||||||
u.NotifyProps["desktop_sound"] = "true"
|
u.NotifyProps["desktop_sound"] = "true"
|
||||||
u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username
|
u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ func getClientConfig(c *model.Config) map[string]string {
|
|||||||
props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper)
|
props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper)
|
||||||
|
|
||||||
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
|
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
|
||||||
|
props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications)
|
||||||
props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail)
|
props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail)
|
||||||
props["EnableSignInWithEmail"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithEmail)
|
props["EnableSignInWithEmail"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithEmail)
|
||||||
props["EnableSignInWithUsername"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithUsername)
|
props["EnableSignInWithUsername"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithUsername)
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ function getNotificationsStateFromStores() {
|
|||||||
if (user.notify_props && user.notify_props.email) {
|
if (user.notify_props && user.notify_props.email) {
|
||||||
email = user.notify_props.email;
|
email = user.notify_props.email;
|
||||||
}
|
}
|
||||||
|
var push = 'mention';
|
||||||
|
if (user.notify_props && user.notify_props.push) {
|
||||||
|
push = user.notify_props.push;
|
||||||
|
}
|
||||||
|
|
||||||
var usernameKey = false;
|
var usernameKey = false;
|
||||||
var mentionKey = false;
|
var mentionKey = false;
|
||||||
@@ -72,9 +76,20 @@ function getNotificationsStateFromStores() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound,
|
return {
|
||||||
usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0,
|
notifyLevel: desktop,
|
||||||
firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
|
notifyPushLevel: push,
|
||||||
|
enableEmail: email,
|
||||||
|
soundNeeded,
|
||||||
|
enableSound: sound,
|
||||||
|
usernameKey,
|
||||||
|
mentionKey,
|
||||||
|
customKeys,
|
||||||
|
customKeysChecked: customKeys.length > 0,
|
||||||
|
firstNameKey,
|
||||||
|
allKey,
|
||||||
|
channelKey
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const holders = defineMessages({
|
const holders = defineMessages({
|
||||||
@@ -121,6 +136,7 @@ class NotificationsTab extends React.Component {
|
|||||||
this.updateChannelKey = this.updateChannelKey.bind(this);
|
this.updateChannelKey = this.updateChannelKey.bind(this);
|
||||||
this.updateCustomMentionKeys = this.updateCustomMentionKeys.bind(this);
|
this.updateCustomMentionKeys = this.updateCustomMentionKeys.bind(this);
|
||||||
this.onCustomChange = this.onCustomChange.bind(this);
|
this.onCustomChange = this.onCustomChange.bind(this);
|
||||||
|
this.createPushNotificationSection = this.createPushNotificationSection.bind(this);
|
||||||
|
|
||||||
this.state = getNotificationsStateFromStores();
|
this.state = getNotificationsStateFromStores();
|
||||||
}
|
}
|
||||||
@@ -130,6 +146,7 @@ class NotificationsTab extends React.Component {
|
|||||||
data.email = this.state.enableEmail;
|
data.email = this.state.enableEmail;
|
||||||
data.desktop_sound = this.state.enableSound;
|
data.desktop_sound = this.state.enableSound;
|
||||||
data.desktop = this.state.notifyLevel;
|
data.desktop = this.state.notifyLevel;
|
||||||
|
data.push = this.state.notifyPushLevel;
|
||||||
|
|
||||||
var mentionKeys = [];
|
var mentionKeys = [];
|
||||||
if (this.state.usernameKey) {
|
if (this.state.usernameKey) {
|
||||||
@@ -185,15 +202,21 @@ class NotificationsTab extends React.Component {
|
|||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
handleNotifyRadio(notifyLevel) {
|
handleNotifyRadio(notifyLevel) {
|
||||||
this.setState({notifyLevel: notifyLevel});
|
this.setState({notifyLevel});
|
||||||
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePushRadio(notifyPushLevel) {
|
||||||
|
this.setState({notifyPushLevel});
|
||||||
|
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
||||||
|
}
|
||||||
|
|
||||||
handleEmailRadio(enableEmail) {
|
handleEmailRadio(enableEmail) {
|
||||||
this.setState({enableEmail: enableEmail});
|
this.setState({enableEmail});
|
||||||
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
||||||
}
|
}
|
||||||
handleSoundRadio(enableSound) {
|
handleSoundRadio(enableSound) {
|
||||||
this.setState({enableSound: enableSound});
|
this.setState({enableSound});
|
||||||
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
ReactDOM.findDOMNode(this.refs.wrapper).focus();
|
||||||
}
|
}
|
||||||
updateUsernameKey(val) {
|
updateUsernameKey(val) {
|
||||||
@@ -227,12 +250,126 @@ class NotificationsTab extends React.Component {
|
|||||||
ReactDOM.findDOMNode(this.refs.customcheck).checked = true;
|
ReactDOM.findDOMNode(this.refs.customcheck).checked = true;
|
||||||
this.updateCustomMentionKeys();
|
this.updateCustomMentionKeys();
|
||||||
}
|
}
|
||||||
|
createPushNotificationSection() {
|
||||||
|
var handleUpdateDesktopSection;
|
||||||
|
if (this.props.activeSection === 'push') {
|
||||||
|
var notifyActive = [false, false, false];
|
||||||
|
if (this.state.notifyPushLevel === 'all') {
|
||||||
|
notifyActive[0] = true;
|
||||||
|
} else if (this.state.notifyPushLevel === 'none') {
|
||||||
|
notifyActive[2] = true;
|
||||||
|
} else {
|
||||||
|
notifyActive[1] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputs = [];
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
|
<div key='userNotificationLevelOption'>
|
||||||
|
<div className='radio'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={notifyActive[0]}
|
||||||
|
onChange={this.handlePushRadio.bind(this, 'all')}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notification.allActivity'
|
||||||
|
defaultMessage='For all activity'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div className='radio'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={notifyActive[1]}
|
||||||
|
onChange={this.handlePushRadio.bind(this, 'mention')}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notifications.onlyMentions'
|
||||||
|
defaultMessage='For mentions and direct messages'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div className='radio'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={notifyActive[2]}
|
||||||
|
onChange={this.handlePushRadio.bind(this, 'none')}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notifications.off'
|
||||||
|
defaultMessage='Off'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const extraInfo = (
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notifications.info'
|
||||||
|
defaultMessage='Notification alerts are pushed to your mobile device when there is activity in Mattermost.'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMax
|
||||||
|
title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
|
||||||
|
extraInfo={extraInfo}
|
||||||
|
inputs={inputs}
|
||||||
|
submit={this.handleSubmit}
|
||||||
|
server_error={this.state.serverError}
|
||||||
|
updateSection={this.handleCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let describe = '';
|
||||||
|
if (this.state.notifyPushLevel === 'all') {
|
||||||
|
describe = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notification.allActivity'
|
||||||
|
defaultMessage='For all activity'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (this.state.notifyPushLevel === 'none') {
|
||||||
|
describe = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notifications.off'
|
||||||
|
defaultMessage='Off'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
describe = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.push_notifications.onlyMentions'
|
||||||
|
defaultMessage='For mentions and direct messages'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateDesktopSection = function updateDesktopSection() {
|
||||||
|
this.props.updateSection('push');
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMin
|
||||||
|
title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
|
||||||
|
describe={describe}
|
||||||
|
updateSection={handleUpdateDesktopSection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
var serverError = null;
|
const serverError = this.state.serverError;
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = this.state.serverError;
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = this.props.user;
|
var user = this.props.user;
|
||||||
|
|
||||||
@@ -764,6 +901,11 @@ class NotificationsTab extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pushNotificationSection;
|
||||||
|
if (global.window.mm_config.SendPushNotifications === 'true') {
|
||||||
|
pushNotificationSection = this.createPushNotificationSection();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
@@ -809,6 +951,8 @@ class NotificationsTab extends React.Component {
|
|||||||
<div className='divider-light'/>
|
<div className='divider-light'/>
|
||||||
{emailSection}
|
{emailSection}
|
||||||
<div className='divider-light'/>
|
<div className='divider-light'/>
|
||||||
|
{pushNotificationSection}
|
||||||
|
<div className='divider-light'/>
|
||||||
{keysSection}
|
{keysSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1390,6 +1390,11 @@
|
|||||||
"user.settings.modal.notifications": "Notifications",
|
"user.settings.modal.notifications": "Notifications",
|
||||||
"user.settings.modal.security": "Security",
|
"user.settings.modal.security": "Security",
|
||||||
"user.settings.modal.title": "Account Settings",
|
"user.settings.modal.title": "Account Settings",
|
||||||
|
"user.settings.push_notification.off": "Off",
|
||||||
|
"user.settings.push_notification.onlyMentions": "For mentions and direct messages",
|
||||||
|
"user.settings.push_notification.allActivity": "For all activity",
|
||||||
|
"user.settings.push_notification.info": "Notification alerts are pushed to your mobile device when there is activity in Mattermost.",
|
||||||
|
"user.settings.notification.push": "Mobile push notifications",
|
||||||
"user.settings.notification.allActivity": "For all activity",
|
"user.settings.notification.allActivity": "For all activity",
|
||||||
"user.settings.notification.soundConfig": "Please configure notification sounds in your browser settings",
|
"user.settings.notification.soundConfig": "Please configure notification sounds in your browser settings",
|
||||||
"user.settings.notifications.channelWide": "Channel-wide mentions \"@channel\"",
|
"user.settings.notifications.channelWide": "Channel-wide mentions \"@channel\"",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user