From 08e54ed8244e2ec9a278d16f80d5ed2a8e2964f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Sat, 28 Jul 2018 19:20:44 +0200 Subject: Split notifications file into different files (#9164) --- app/notification.go | 596 ---------------------------------------------------- 1 file changed, 596 deletions(-) (limited to 'app/notification.go') diff --git a/app/notification.go b/app/notification.go index 477f73a27..3a6d4696d 100644 --- a/app/notification.go +++ b/app/notification.go @@ -5,13 +5,8 @@ package app import ( "fmt" - "html" - "net/http" - "net/url" - "path/filepath" "sort" "strings" - "time" "unicode" "github.com/mattermost/mattermost-server/mlog" @@ -19,7 +14,6 @@ import ( "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils/markdown" - "github.com/nicksnyder/go-i18n/i18n" ) const ( @@ -387,533 +381,6 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod return mentionedUsersList, nil } -func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel *model.Channel, team *model.Team, channelName string, senderName string, sender *model.User) *model.AppError { - if channel.IsGroupOrDirect() { - if result := <-a.Srv.Store.Team().GetTeamsByUserId(user.Id); result.Err != nil { - return result.Err - } else { - // if the recipient isn't in the current user's team, just pick one - teams := result.Data.([]*model.Team) - found := false - - for i := range teams { - if teams[i].Id == team.Id { - found = true - break - } - } - - if !found && len(teams) > 0 { - team = teams[0] - } else { - // in case the user hasn't joined any teams we send them to the select_team page - team = &model.Team{Name: "select_team", DisplayName: a.Config().TeamSettings.SiteName} - } - } - } - if *a.Config().EmailSettings.EnableEmailBatching { - var sendBatched bool - if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_NOTIFICATIONS, model.PREFERENCE_NAME_EMAIL_INTERVAL); result.Err != nil { - // if the call fails, assume that the interval has not been explicitly set and batch the notifications - sendBatched = true - } else { - // if the user has chosen to receive notifications immediately, don't batch them - sendBatched = result.Data.(model.Preference).Value != model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS - } - - if sendBatched { - if err := a.AddNotificationEmailToBatch(user, post, team); err == nil { - return nil - } - } - - // fall back to sending a single email if we can't batch it for some reason - } - - var useMilitaryTime bool - translateFunc := utils.GetUserTranslations(user.Locale) - if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time"); result.Err != nil { - useMilitaryTime = true - } else { - useMilitaryTime = result.Data.(model.Preference).Value == "true" - } - - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - if license := a.License(); license != nil && *license.Features.EmailNotificationContents { - emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType - } - - var subjectText string - if channel.Type == model.CHANNEL_DIRECT { - subjectText = getDirectMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, senderName, useMilitaryTime) - } else if channel.Type == model.CHANNEL_GROUP { - subjectText = getGroupMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, channelName, emailNotificationContentsType, useMilitaryTime) - } else if *a.Config().EmailSettings.UseChannelInEmailNotifications { - subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName+" ("+channel.DisplayName+")", useMilitaryTime) - } else { - subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName, useMilitaryTime) - } - - teamURL := a.GetSiteURL() + "/" + team.Name - var bodyText = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, teamURL, emailNotificationContentsType, useMilitaryTime, translateFunc) - - a.Go(func() { - if err := a.SendMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil { - mlog.Error(fmt.Sprint("api.post.send_notifications_and_forget.send.error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err)) - } - }) - - if a.Metrics != nil { - a.Metrics.IncrementPostSentEmail() - } - - return nil -} - -/** - * Computes the subject line for direct notification email messages - */ -func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "SenderDisplayName": senderName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - return translateFunc("app.notification.subject.direct.full", subjectParameters) -} - -/** - * Computes the subject line for group, public, and private email messages - */ -func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, teamName string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "TeamName": teamName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - return translateFunc("app.notification.subject.notification.full", subjectParameters) -} - -/** - * Computes the subject line for group email messages - */ -func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, channelName string, emailNotificationContentsType string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectText string - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "ChannelName": channelName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - subjectText = translateFunc("app.notification.subject.group_message.full", subjectParameters) - } else { - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - subjectText = translateFunc("app.notification.subject.group_message.generic", subjectParameters) - } - return subjectText -} - -/** - * Computes the email body for notification messages - */ -func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, teamURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) string { - // only include message contents in notification email if email notification contents type is set to full - var bodyPage *utils.HTMLTemplate - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage = a.NewEmailTemplate("post_body_full", recipient.Locale) - bodyPage.Props["PostMessage"] = a.GetMessageForNotification(post, translateFunc) - } else { - bodyPage = a.NewEmailTemplate("post_body_generic", recipient.Locale) - } - - bodyPage.Props["SiteURL"] = a.GetSiteURL() - if teamName != "select_team" { - bodyPage.Props["TeamLink"] = teamURL + "/pl/" + post.Id - } else { - bodyPage.Props["TeamLink"] = teamURL - } - - t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) - - if channel.Type == model.CHANNEL_DIRECT { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full") - bodyPage.Props["Info1"] = "" - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } else if channel.Type == model.CHANNEL_GROUP { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full") - bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full", - map[string]interface{}{ - "ChannelName": channelName, - }) - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } else { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full") - bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full", - map[string]interface{}{ - "ChannelName": channelName, - }) - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } - - bodyPage.Props["Button"] = translateFunc("api.templates.post_body.button") - - return bodyPage.Render() -} - -type formattedPostTime struct { - Time time.Time - Year string - Month string - Day string - Hour string - Minute string - TimeZone string -} - -func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) formattedPostTime { - preferredTimezone := user.GetPreferredTimezone() - postTime := time.Unix(post.CreateAt/1000, 0) - zone, _ := postTime.Zone() - - localTime := postTime - if preferredTimezone != "" { - loc, _ := time.LoadLocation(preferredTimezone) - if loc != nil { - localTime = postTime.In(loc) - zone, _ = localTime.Zone() - } - } - - hour := localTime.Format("15") - period := "" - if !useMilitaryTime { - hour = localTime.Format("3") - period = " " + localTime.Format("PM") - } - - return formattedPostTime{ - Time: localTime, - Year: fmt.Sprintf("%d", localTime.Year()), - Month: translateFunc(localTime.Month().String()), - Day: fmt.Sprintf("%d", localTime.Day()), - Hour: fmt.Sprintf("%s", hour), - Minute: fmt.Sprintf("%02d"+period, localTime.Minute()), - TimeZone: zone, - } -} - -func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string { - if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 { - return post.Message - } - - // extract the filenames from their paths and determine what type of files are attached - var infos []*model.FileInfo - if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, true); result.Err != nil { - mlog.Warn(fmt.Sprintf("Encountered error when getting files for notification message, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) - } else { - infos = result.Data.([]*model.FileInfo) - } - - filenames := make([]string, len(infos)) - onlyImages := true - for i, info := range infos { - if escaped, err := url.QueryUnescape(filepath.Base(info.Name)); err != nil { - // this should never error since filepath was escaped using url.QueryEscape - filenames[i] = escaped - } else { - filenames[i] = info.Name - } - - onlyImages = onlyImages && info.IsImage() - } - - props := map[string]interface{}{"Filenames": strings.Join(filenames, ", ")} - - if onlyImages { - return translateFunc("api.post.get_message_for_notification.images_sent", len(filenames), props) - } else { - return translateFunc("api.post.get_message_for_notification.files_sent", len(filenames), props) - } -} - -func (a *App) sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, channelName string, sender *model.User, senderName string, - explicitMention, channelWideMention bool, replyToThreadType string) *model.AppError { - cfg := a.Config() - contentsConfig := *cfg.EmailSettings.PushNotificationContents - teammateNameConfig := *cfg.TeamSettings.TeammateNameDisplay - sessions, err := a.getMobileAppSessions(user.Id) - sentBySystem := senderName == utils.T("system.message.name") - if err != nil { - return err - } - - msg := model.PushNotification{} - if badge := <-a.Srv.Store.User().GetUnreadCount(user.Id); badge.Err != nil { - msg.Badge = 1 - mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, badge.Err), mlog.String("user_id", user.Id)) - } else { - msg.Badge = int(badge.Data.(int64)) - } - - msg.Category = model.CATEGORY_CAN_REPLY - msg.Version = model.PUSH_MESSAGE_V2 - msg.Type = model.PUSH_TYPE_MESSAGE - msg.TeamId = channel.TeamId - msg.ChannelId = channel.Id - msg.PostId = post.Id - msg.RootId = post.RootId - msg.SenderId = post.UserId - - if !sentBySystem { - senderName = sender.GetDisplayName(teammateNameConfig) - preference, prefError := a.GetPreferenceByCategoryAndNameForUser(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format") - if prefError == nil && preference.Value != teammateNameConfig { - senderName = sender.GetDisplayName(preference.Value) - } - } - - if channel.Type == model.CHANNEL_DIRECT { - channelName = fmt.Sprintf("@%v", senderName) - } - - if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { - msg.ChannelName = channelName - } - - if ou, ok := post.Props["override_username"].(string); ok && cfg.ServiceSettings.EnablePostUsernameOverride { - msg.OverrideUsername = ou - senderName = ou - } - - if oi, ok := post.Props["override_icon_url"].(string); ok && cfg.ServiceSettings.EnablePostIconOverride { - msg.OverrideIconUrl = oi - } - - if fw, ok := post.Props["from_webhook"].(string); ok { - msg.FromWebhook = fw - } - - userLocale := utils.GetUserTranslations(user.Locale) - hasFiles := post.FileIds != nil && len(post.FileIds) > 0 - - msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, senderName, channelName, channel.Type, replyToThreadType, userLocale) - - for _, session := range sessions { - - if session.IsExpired() { - continue - } - - tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) - tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) - - mlog.Debug(fmt.Sprintf("Sending push notification to device %v for user %v with msg of '%v'", tmpMessage.DeviceId, user.Id, msg.Message), mlog.String("user_id", user.Id)) - - a.Go(func(session *model.Session) func() { - return func() { - a.sendToPushProxy(tmpMessage, session) - } - }(session)) - - if a.Metrics != nil { - a.Metrics.IncrementPostSentPush() - } - } - - return nil -} - -func (a *App) getPushNotificationMessage(postMessage string, explicitMention, channelWideMention, hasFiles bool, - senderName, channelName, channelType, replyToThreadType string, userLocale i18n.TranslateFunc) string { - message := "" - - contentsConfig := *a.Config().EmailSettings.PushNotificationContents - - if contentsConfig == model.FULL_NOTIFICATION { - if channelType == model.CHANNEL_DIRECT { - message = model.ClearMentionTags(postMessage) - } else { - message = "@" + senderName + ": " + model.ClearMentionTags(postMessage) - } - } else { - if channelType == model.CHANNEL_DIRECT { - message = userLocale("api.post.send_notifications_and_forget.push_message") - } else if channelWideMention { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_channel_mention") - } else if explicitMention { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_explicit_mention") - } else if replyToThreadType == THREAD_ROOT { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_post") - } else if replyToThreadType == THREAD_ANY { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_thread") - } else { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_general_message") - } - } - - // If the post only has images then push an appropriate message - if len(postMessage) == 0 && hasFiles { - if channelType == model.CHANNEL_DIRECT { - message = strings.Trim(userLocale("api.post.send_notifications_and_forget.push_image_only"), " ") - } else { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_image_only") - } - } - - return message -} - -func (a *App) ClearPushNotification(userId string, channelId string) { - a.Go(func() { - // Sleep is to allow the read replicas a chance to fully sync - // the unread count for sending an accurate count. - // Delaying a little doesn't hurt anything and is cheaper than - // attempting to read from master. - time.Sleep(time.Second * 5) - - sessions, err := a.getMobileAppSessions(userId) - if err != nil { - mlog.Error(err.Error()) - return - } - - msg := model.PushNotification{} - msg.Type = model.PUSH_TYPE_CLEAR - msg.ChannelId = channelId - msg.ContentAvailable = 0 - if badge := <-a.Srv.Store.User().GetUnreadCount(userId); badge.Err != nil { - msg.Badge = 0 - mlog.Error(fmt.Sprint("We could not get the unread message count for the user", userId, badge.Err), mlog.String("user_id", userId)) - } else { - msg.Badge = int(badge.Data.(int64)) - } - - mlog.Debug(fmt.Sprintf("Clearing push notification to %v with channel_id %v", msg.DeviceId, msg.ChannelId)) - - for _, session := range sessions { - tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) - tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) - a.Go(func() { - a.sendToPushProxy(tmpMessage, session) - }) - } - }) -} - -func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session) { - msg.ServerId = a.DiagnosticId() - - request, _ := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson())) - - if resp, err := a.HTTPClient(true).Do(request); err != nil { - mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, err.Error()), mlog.String("user_id", session.UserId)) - } else { - pushResponse := model.PushResponseFromJson(resp.Body) - if resp.Body != nil { - consumeAndClose(resp) - } - - if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_REMOVE { - mlog.Info(fmt.Sprintf("Device was reported as removed for UserId=%v SessionId=%v removing push for this session", session.UserId, session.Id), mlog.String("user_id", session.UserId)) - a.AttachDeviceId(session.Id, "", session.ExpiresAt) - a.ClearSessionCacheForUser(session.UserId) - } - - if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_FAIL { - mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, pushResponse[model.PUSH_STATUS_ERROR_MSG]), mlog.String("user_id", session.UserId)) - } - } -} - -func (a *App) getMobileAppSessions(userId string) ([]*model.Session, *model.AppError) { - if result := <-a.Srv.Store.Session().GetSessionsWithActiveDeviceIds(userId); result.Err != nil { - return nil, result.Err - } else { - return result.Data.([]*model.Session), nil - } -} - func (a *App) sendOutOfChannelMentions(sender *model.User, post *model.Post, users []*model.User) *model.AppError { if len(users) == 0 { return nil @@ -1156,66 +623,3 @@ func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookF return keywords } - -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.PUSH_NOTIFY_PROP] - channelNotify, ok := channelNotifyProps[model.PUSH_NOTIFY_PROP] - - // If the channel is muted do not send push notifications - if channelMuted, ok := channelNotifyProps[model.MARK_UNREAD_NOTIFY_PROP]; ok { - if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { - return false - } - } - - if post.IsSystemMessage() { - return false - } - - if channelNotify == model.USER_NOTIFY_NONE { - return false - } - - if channelNotify == model.CHANNEL_NOTIFY_MENTION && !wasMentioned { - return false - } - - if userNotify == model.USER_NOTIFY_MENTION && (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) && !wasMentioned { - return false - } - - if (userNotify == model.USER_NOTIFY_ALL || channelNotify == model.CHANNEL_NOTIFY_ALL) && - (post.UserId != user.Id || post.Props["from_webhook"] == "true") { - return true - } - - if userNotify == model.USER_NOTIFY_NONE && - (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) { - 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.STATUS_DND || status.Status == model.STATUS_OUT_OF_OFFICE { - return false - } - - if pushStatus, ok := userNotifyProps["push_status"]; (pushStatus == model.STATUS_ONLINE || !ok) && (status.ActiveChannel != channelId || model.GetMillis()-status.LastActivityAt > model.STATUS_CHANNEL_TIMEOUT) { - return true - } else if pushStatus == model.STATUS_AWAY && (status.Status == model.STATUS_AWAY || status.Status == model.STATUS_OFFLINE) { - return true - } else if pushStatus == model.STATUS_OFFLINE && status.Status == model.STATUS_OFFLINE { - return true - } - - return false -} -- cgit v1.2.3-1-g7c22