* Move Channels into App In this PR, we make Channels as part of App instead of Server. This is part of the transition period of moving fields from Server to Channels. For now, Channels contains Server. So the hierarchy is App -> Channels -> Server. And as a first step, we also move httpService to Channels. ```release-note NONE ``` * Fixing another test ```release-note NONE ``` * new method ```release-note NONE ```
651 строка
20 KiB
Go
651 строка
20 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
|
"github.com/mattermost/mattermost-server/v6/utils"
|
|
)
|
|
|
|
type notificationType string
|
|
|
|
const (
|
|
notificationTypeClear notificationType = "clear"
|
|
notificationTypeMessage notificationType = "message"
|
|
notificationTypeUpdateBadge notificationType = "update_badge"
|
|
notificationTypeDummy notificationType = "dummy"
|
|
)
|
|
|
|
type PushNotificationsHub struct {
|
|
notificationsChan chan PushNotification
|
|
app *App // XXX: This will go away once push notifications move to their own package.
|
|
sema chan struct{}
|
|
stopChan chan struct{}
|
|
wg *sync.WaitGroup
|
|
semaWg *sync.WaitGroup
|
|
buffer int
|
|
}
|
|
|
|
type PushNotification struct {
|
|
notificationType notificationType
|
|
currentSessionId string
|
|
userID string
|
|
channelID string
|
|
post *model.Post
|
|
user *model.User
|
|
channel *model.Channel
|
|
senderName string
|
|
channelName string
|
|
explicitMention bool
|
|
channelWideMention bool
|
|
replyToThreadType string
|
|
}
|
|
|
|
func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
|
|
explicitMention bool, channelWideMention bool, replyToThreadType string) *model.AppError {
|
|
cfg := a.Config()
|
|
msg, appErr := a.BuildPushNotificationMessage(
|
|
*cfg.EmailSettings.PushNotificationContents,
|
|
post,
|
|
user,
|
|
channel,
|
|
channelName,
|
|
senderName,
|
|
explicitMention,
|
|
channelWideMention,
|
|
replyToThreadType,
|
|
)
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
return a.sendPushNotificationToAllSessions(msg, user.Id, "")
|
|
}
|
|
|
|
func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, userID string, skipSessionId string) *model.AppError {
|
|
sessions, err := a.getMobileAppSessions(userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if msg == nil {
|
|
return model.NewAppError(
|
|
"pushNotification",
|
|
"api.push_notifications.message.parse.app_error",
|
|
nil,
|
|
"",
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
|
|
for _, session := range sessions {
|
|
// Don't send notifications to this session if it's expired or we want to skip it
|
|
if session.IsExpired() || (skipSessionId != "" && skipSessionId == session.Id) {
|
|
continue
|
|
}
|
|
|
|
// We made a copy to avoid decoding and parsing all the time
|
|
tmpMessage := msg.DeepCopy()
|
|
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
|
|
tmpMessage.AckId = model.NewId()
|
|
|
|
err := a.sendToPushProxy(tmpMessage, session)
|
|
if err != nil {
|
|
a.NotificationsLog().Error("Notification error",
|
|
mlog.String("ackId", tmpMessage.AckId),
|
|
mlog.String("type", tmpMessage.Type),
|
|
mlog.String("userId", session.UserId),
|
|
mlog.String("postId", tmpMessage.PostId),
|
|
mlog.String("channelId", tmpMessage.ChannelId),
|
|
mlog.String("deviceId", tmpMessage.DeviceId),
|
|
mlog.String("status", err.Error()),
|
|
)
|
|
continue
|
|
}
|
|
|
|
a.NotificationsLog().Info("Notification sent",
|
|
mlog.String("ackId", tmpMessage.AckId),
|
|
mlog.String("type", tmpMessage.Type),
|
|
mlog.String("userId", session.UserId),
|
|
mlog.String("postId", tmpMessage.PostId),
|
|
mlog.String("channelId", tmpMessage.ChannelId),
|
|
mlog.String("deviceId", tmpMessage.DeviceId),
|
|
mlog.String("status", model.PushSendSuccess),
|
|
)
|
|
|
|
if a.Metrics() != nil {
|
|
a.Metrics().IncrementPostSentPush()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) sendPushNotification(notification *PostNotification, user *model.User, explicitMention, channelWideMention bool, replyToThreadType string) {
|
|
cfg := a.Config()
|
|
channel := notification.Channel
|
|
post := notification.Post
|
|
|
|
nameFormat := a.GetNotificationNameFormat(user)
|
|
|
|
channelName := notification.GetChannelName(nameFormat, user.Id)
|
|
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
|
|
|
|
select {
|
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
|
notificationType: notificationTypeMessage,
|
|
post: post,
|
|
user: user,
|
|
channel: channel,
|
|
senderName: senderName,
|
|
channelName: channelName,
|
|
explicitMention: explicitMention,
|
|
channelWideMention: channelWideMention,
|
|
replyToThreadType: replyToThreadType,
|
|
}:
|
|
case <-a.Srv().PushNotificationsHub.stopChan:
|
|
return
|
|
}
|
|
}
|
|
|
|
func (a *App) getPushNotificationMessage(contentsConfig, postMessage string, explicitMention, channelWideMention,
|
|
hasFiles bool, senderName string, channelType model.ChannelType, replyToThreadType string, userLocale i18n.TranslateFunc) string {
|
|
|
|
// If the post only has images then push an appropriate message
|
|
if postMessage == "" && hasFiles {
|
|
if channelType == model.ChannelTypeDirect {
|
|
return strings.Trim(userLocale("api.post.send_notifications_and_forget.push_image_only"), " ")
|
|
}
|
|
return senderName + userLocale("api.post.send_notifications_and_forget.push_image_only")
|
|
}
|
|
|
|
if contentsConfig == model.FullNotification {
|
|
if channelType == model.ChannelTypeDirect {
|
|
return model.ClearMentionTags(postMessage)
|
|
}
|
|
return senderName + ": " + model.ClearMentionTags(postMessage)
|
|
}
|
|
|
|
if channelType == model.ChannelTypeDirect {
|
|
if replyToThreadType == model.CommentsNotifyCRT {
|
|
if contentsConfig == model.GenericNoChannelNotification {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_crt_thread")
|
|
}
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_crt_thread_dm")
|
|
}
|
|
return userLocale("api.post.send_notifications_and_forget.push_message")
|
|
}
|
|
|
|
if replyToThreadType == model.CommentsNotifyCRT {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_crt_thread")
|
|
}
|
|
|
|
if channelWideMention {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_channel_mention")
|
|
}
|
|
|
|
if explicitMention {
|
|
return senderName + userLocale("api.post.send_notifications_and_forget.push_explicit_mention")
|
|
}
|
|
|
|
if replyToThreadType == model.CommentsNotifyRoot {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_post")
|
|
}
|
|
|
|
if replyToThreadType == model.CommentsNotifyAny {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_thread")
|
|
}
|
|
|
|
if replyToThreadType == model.UserNotifyAll {
|
|
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_crt_thread")
|
|
}
|
|
|
|
return senderName + userLocale("api.post.send_notifications_and_forget.push_general_message")
|
|
}
|
|
|
|
func (a *App) clearPushNotificationSync(currentSessionId, userID, channelID string) *model.AppError {
|
|
msg := &model.PushNotification{
|
|
Type: model.PushTypeClear,
|
|
Version: model.PushMessageV2,
|
|
ChannelId: channelID,
|
|
ContentAvailable: 1,
|
|
}
|
|
|
|
unreadCount, err := a.Srv().Store.User().GetUnreadCount(userID)
|
|
if err != nil {
|
|
return model.NewAppError("clearPushNotificationSync", "app.user.get_unread_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
msg.Badge = int(unreadCount)
|
|
|
|
return a.sendPushNotificationToAllSessions(msg, userID, currentSessionId)
|
|
}
|
|
|
|
func (a *App) clearPushNotification(currentSessionId, userID, channelID string) {
|
|
select {
|
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
|
notificationType: notificationTypeClear,
|
|
currentSessionId: currentSessionId,
|
|
userID: userID,
|
|
channelID: channelID,
|
|
}:
|
|
case <-a.Srv().PushNotificationsHub.stopChan:
|
|
return
|
|
}
|
|
}
|
|
|
|
func (a *App) updateMobileAppBadgeSync(userID string) *model.AppError {
|
|
msg := &model.PushNotification{
|
|
Type: model.PushTypeUpdateBadge,
|
|
Version: model.PushMessageV2,
|
|
Sound: "none",
|
|
ContentAvailable: 1,
|
|
}
|
|
|
|
unreadCount, err := a.Srv().Store.User().GetUnreadCount(userID)
|
|
if err != nil {
|
|
return model.NewAppError("updateMobileAppBadgeSync", "app.user.get_unread_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
msg.Badge = int(unreadCount)
|
|
|
|
return a.sendPushNotificationToAllSessions(msg, userID, "")
|
|
}
|
|
|
|
func (a *App) UpdateMobileAppBadge(userID string) {
|
|
select {
|
|
case a.Srv().PushNotificationsHub.notificationsChan <- PushNotification{
|
|
notificationType: notificationTypeUpdateBadge,
|
|
userID: userID,
|
|
}:
|
|
case <-a.Srv().PushNotificationsHub.stopChan:
|
|
return
|
|
}
|
|
}
|
|
|
|
func (s *Server) createPushNotificationsHub() {
|
|
buffer := *s.Config().EmailSettings.PushNotificationBuffer
|
|
hub := PushNotificationsHub{
|
|
notificationsChan: make(chan PushNotification, buffer),
|
|
app: New(ServerConnector(s.Channels())),
|
|
wg: new(sync.WaitGroup),
|
|
semaWg: new(sync.WaitGroup),
|
|
sema: make(chan struct{}, runtime.NumCPU()*8), // numCPU * 8 is a good amount of concurrency.
|
|
stopChan: make(chan struct{}),
|
|
buffer: buffer,
|
|
}
|
|
go hub.start()
|
|
s.PushNotificationsHub = hub
|
|
}
|
|
|
|
func (hub *PushNotificationsHub) start() {
|
|
hub.wg.Add(1)
|
|
defer hub.wg.Done()
|
|
for {
|
|
select {
|
|
case notification := <-hub.notificationsChan:
|
|
// We just ignore dummy notifications.
|
|
// These are used to pump out any remaining notifications
|
|
// before we stop the hub.
|
|
if notification.notificationType == notificationTypeDummy {
|
|
continue
|
|
}
|
|
// Adding to the waitgroup first.
|
|
hub.semaWg.Add(1)
|
|
// Get token.
|
|
hub.sema <- struct{}{}
|
|
go func(notification PushNotification) {
|
|
defer func() {
|
|
// Release token.
|
|
<-hub.sema
|
|
// Now marking waitgroup as done.
|
|
hub.semaWg.Done()
|
|
}()
|
|
|
|
var err *model.AppError
|
|
switch notification.notificationType {
|
|
case notificationTypeClear:
|
|
err = hub.app.clearPushNotificationSync(notification.currentSessionId, notification.userID, notification.channelID)
|
|
case notificationTypeMessage:
|
|
err = hub.app.sendPushNotificationSync(
|
|
notification.post,
|
|
notification.user,
|
|
notification.channel,
|
|
notification.channelName,
|
|
notification.senderName,
|
|
notification.explicitMention,
|
|
notification.channelWideMention,
|
|
notification.replyToThreadType,
|
|
)
|
|
case notificationTypeUpdateBadge:
|
|
err = hub.app.updateMobileAppBadgeSync(notification.userID)
|
|
default:
|
|
mlog.Debug("Invalid notification type", mlog.String("notification_type", string(notification.notificationType)))
|
|
}
|
|
|
|
if err != nil {
|
|
mlog.Error("Unable to send push notification", mlog.String("notification_type", string(notification.notificationType)), mlog.Err(err))
|
|
}
|
|
}(notification)
|
|
case <-hub.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (hub *PushNotificationsHub) stop() {
|
|
// Drain the channel.
|
|
for i := 0; i < hub.buffer+1; i++ {
|
|
hub.notificationsChan <- PushNotification{
|
|
notificationType: notificationTypeDummy,
|
|
}
|
|
}
|
|
close(hub.stopChan)
|
|
// We need to wait for the outer for loop to exit first.
|
|
// We cannot just send struct{}{} to stopChan because there are
|
|
// other listeners to the channel. And sending just once
|
|
// will cause a race.
|
|
hub.wg.Wait()
|
|
// And then we wait for the semaphore to finish.
|
|
hub.semaWg.Wait()
|
|
}
|
|
|
|
func (s *Server) StopPushNotificationsHubWorkers() {
|
|
s.PushNotificationsHub.stop()
|
|
}
|
|
|
|
func (a *App) sendToPushProxy(msg *model.PushNotification, session *model.Session) error {
|
|
msg.ServerId = a.TelemetryId()
|
|
|
|
a.NotificationsLog().Info("Notification will be sent",
|
|
mlog.String("ackId", msg.AckId),
|
|
mlog.String("type", msg.Type),
|
|
mlog.String("userId", session.UserId),
|
|
mlog.String("postId", msg.PostId),
|
|
mlog.String("status", model.PushSendPrepare),
|
|
)
|
|
|
|
msgJSON, jsonErr := json.Marshal(msg)
|
|
if jsonErr != nil {
|
|
return errors.Wrap(jsonErr, "failed to encode to JSON")
|
|
}
|
|
|
|
url := strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/") + model.APIURLSuffixV1 + "/send_push"
|
|
request, err := http.NewRequest("POST", url, bytes.NewReader(msgJSON))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := a.Srv().pushNotificationClient.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var pushResponse model.PushResponse
|
|
if jsonErr := json.NewDecoder(resp.Body).Decode(&pushResponse); jsonErr != nil {
|
|
return errors.Wrap(jsonErr, "failed to decode from JSON")
|
|
}
|
|
|
|
switch pushResponse[model.PushStatus] {
|
|
case model.PushStatusRemove:
|
|
a.AttachDeviceId(session.Id, "", session.ExpiresAt)
|
|
a.ClearSessionCacheForUser(session.UserId)
|
|
return errors.New("Device was reported as removed")
|
|
case model.PushStatusFail:
|
|
return errors.New(pushResponse[model.PushStatusErrorMsg])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) SendAckToPushProxy(ack *model.PushNotificationAck) error {
|
|
if ack == nil {
|
|
return nil
|
|
}
|
|
|
|
a.NotificationsLog().Info("Notification received",
|
|
mlog.String("ackId", ack.Id),
|
|
mlog.String("type", ack.NotificationType),
|
|
mlog.String("deviceType", ack.ClientPlatform),
|
|
mlog.Int64("receivedAt", ack.ClientReceivedAt),
|
|
mlog.String("status", model.PushReceived),
|
|
)
|
|
|
|
ackJSON, jsonErr := json.Marshal(ack)
|
|
if jsonErr != nil {
|
|
return errors.Wrap(jsonErr, "failed to encode to JSON")
|
|
}
|
|
|
|
request, err := http.NewRequest(
|
|
"POST",
|
|
strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.APIURLSuffixV1+"/ack",
|
|
bytes.NewReader(ackJSON),
|
|
)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := a.Srv().pushNotificationClient.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
// Reading the body to completion.
|
|
_, err = io.Copy(ioutil.Discard, resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) getMobileAppSessions(userID string) ([]*model.Session, *model.AppError) {
|
|
sessions, err := a.Srv().Store.Session().GetSessionsWithActiveDeviceIds(userID)
|
|
if err != nil {
|
|
return nil, model.NewAppError("getMobileAppSessions", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
return sessions, nil
|
|
}
|
|
|
|
func ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post) bool {
|
|
return DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned) &&
|
|
DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId)
|
|
}
|
|
|
|
func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned bool) bool {
|
|
userNotifyProps := user.NotifyProps
|
|
userNotify := userNotifyProps[model.PushNotifyProp]
|
|
channelNotify, ok := channelNotifyProps[model.PushNotifyProp]
|
|
if !ok || channelNotify == "" {
|
|
channelNotify = model.ChannelNotifyDefault
|
|
}
|
|
|
|
// If the channel is muted do not send push notifications
|
|
if channelNotifyProps[model.MarkUnreadNotifyProp] == model.ChannelMarkUnreadMention {
|
|
return false
|
|
}
|
|
|
|
if post.IsSystemMessage() {
|
|
return false
|
|
}
|
|
|
|
if channelNotify == model.UserNotifyNone {
|
|
return false
|
|
}
|
|
|
|
if channelNotify == model.ChannelNotifyMention && !wasMentioned {
|
|
return false
|
|
}
|
|
|
|
if userNotify == model.UserNotifyMention && channelNotify == model.ChannelNotifyDefault && !wasMentioned {
|
|
return false
|
|
}
|
|
|
|
if (userNotify == model.UserNotifyAll || channelNotify == model.ChannelNotifyAll) &&
|
|
(post.UserId != user.Id || post.GetProp("from_webhook") == "true") {
|
|
return true
|
|
}
|
|
|
|
if userNotify == model.UserNotifyNone &&
|
|
channelNotify == model.ChannelNotifyDefault {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *model.Status, channelID string) bool {
|
|
// If User status is DND or OOO return false right away
|
|
if status.Status == model.StatusDnd || status.Status == model.StatusOutOfOffice {
|
|
return false
|
|
}
|
|
|
|
pushStatus, ok := userNotifyProps[model.PushStatusNotifyProp]
|
|
if (pushStatus == model.StatusOnline || !ok) && (status.ActiveChannel != channelID || model.GetMillis()-status.LastActivityAt > model.StatusChannelTimeout) {
|
|
return true
|
|
}
|
|
|
|
if pushStatus == model.StatusAway && (status.Status == model.StatusAway || status.Status == model.StatusOffline) {
|
|
return true
|
|
}
|
|
|
|
if pushStatus == model.StatusOffline && status.Status == model.StatusOffline {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (a *App) BuildPushNotificationMessage(contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
|
|
explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError) {
|
|
|
|
var msg *model.PushNotification
|
|
|
|
notificationInterface := a.Srv().Notification
|
|
if (notificationInterface == nil || notificationInterface.CheckLicense() != nil) && contentsConfig == model.IdLoadedNotification {
|
|
contentsConfig = model.GenericNotification
|
|
}
|
|
|
|
if contentsConfig == model.IdLoadedNotification {
|
|
msg = a.buildIdLoadedPushNotificationMessage(post, user)
|
|
} else {
|
|
msg = a.buildFullPushNotificationMessage(contentsConfig, post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType)
|
|
}
|
|
|
|
unreadCount, err := a.Srv().Store.User().GetUnreadCount(user.Id)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BuildPushNotificationMessage", "app.user.get_unread_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
msg.Badge = int(unreadCount)
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
func (a *App) buildIdLoadedPushNotificationMessage(post *model.Post, user *model.User) *model.PushNotification {
|
|
userLocale := i18n.GetUserTranslations(user.Locale)
|
|
msg := &model.PushNotification{
|
|
PostId: post.Id,
|
|
ChannelId: post.ChannelId,
|
|
Category: model.CategoryCanReply,
|
|
Version: model.PushMessageV2,
|
|
Type: model.PushTypeMessage,
|
|
IsIdLoaded: true,
|
|
SenderId: user.Id,
|
|
Message: userLocale("api.push_notification.id_loaded.default_message"),
|
|
}
|
|
|
|
return msg
|
|
}
|
|
|
|
func (a *App) buildFullPushNotificationMessage(contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
|
|
explicitMention bool, channelWideMention bool, replyToThreadType string) *model.PushNotification {
|
|
|
|
msg := &model.PushNotification{
|
|
Category: model.CategoryCanReply,
|
|
Version: model.PushMessageV2,
|
|
Type: model.PushTypeMessage,
|
|
TeamId: channel.TeamId,
|
|
ChannelId: channel.Id,
|
|
PostId: post.Id,
|
|
RootId: post.RootId,
|
|
SenderId: post.UserId,
|
|
IsIdLoaded: false,
|
|
}
|
|
|
|
userLocale := i18n.GetUserTranslations(user.Locale)
|
|
cfg := a.Config()
|
|
if contentsConfig != model.GenericNoChannelNotification || channel.Type == model.ChannelTypeDirect {
|
|
msg.ChannelName = channelName
|
|
}
|
|
|
|
if a.isCRTEnabledForUser(user.Id) && post.RootId != "" {
|
|
if contentsConfig != model.GenericNoChannelNotification {
|
|
props := map[string]interface{}{"channelName": channelName}
|
|
msg.ChannelName = userLocale("api.push_notification.title.collapsed_threads", props)
|
|
|
|
if channel.Type == model.ChannelTypeDirect {
|
|
msg.ChannelName = userLocale("api.push_notification.title.collapsed_threads_dm")
|
|
}
|
|
}
|
|
}
|
|
|
|
msg.SenderName = senderName
|
|
if ou, ok := post.GetProp("override_username").(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
|
|
msg.OverrideUsername = ou
|
|
msg.SenderName = ou
|
|
}
|
|
|
|
if oi, ok := post.GetProp("override_icon_url").(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
|
|
msg.OverrideIconURL = oi
|
|
}
|
|
|
|
if fw, ok := post.GetProp("from_webhook").(string); ok {
|
|
msg.FromWebhook = fw
|
|
}
|
|
|
|
postMessage := post.Message
|
|
stripped, err := utils.StripMarkdown(postMessage)
|
|
if err != nil {
|
|
mlog.Warn("Failed parse to markdown", mlog.String("post_id", post.Id), mlog.Err(err))
|
|
} else {
|
|
postMessage = stripped
|
|
}
|
|
for _, attachment := range post.Attachments() {
|
|
if attachment.Fallback != "" {
|
|
postMessage += "\n" + attachment.Fallback
|
|
}
|
|
}
|
|
|
|
hasFiles := post.FileIds != nil && len(post.FileIds) > 0
|
|
|
|
msg.Message = a.getPushNotificationMessage(
|
|
contentsConfig,
|
|
postMessage,
|
|
explicitMention,
|
|
channelWideMention,
|
|
hasFiles,
|
|
msg.SenderName,
|
|
channel.Type,
|
|
replyToThreadType,
|
|
userLocale,
|
|
)
|
|
|
|
return msg
|
|
}
|