github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/post.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "encoding/json" 8 "errors" 9 "fmt" 10 "net/http" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/mattermost/mattermost-server/v5/mlog" 16 "github.com/mattermost/mattermost-server/v5/model" 17 "github.com/mattermost/mattermost-server/v5/plugin" 18 "github.com/mattermost/mattermost-server/v5/services/cache" 19 "github.com/mattermost/mattermost-server/v5/store" 20 "github.com/mattermost/mattermost-server/v5/utils" 21 ) 22 23 const ( 24 PENDING_POST_IDS_CACHE_SIZE = 25000 25 PENDING_POST_IDS_CACHE_TTL = 30 * time.Second 26 PAGE_DEFAULT = 0 27 ) 28 29 func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { 30 // Check that channel has not been deleted 31 channel, errCh := a.Srv().Store.Channel().Get(post.ChannelId, true) 32 if errCh != nil { 33 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, errCh.Error(), http.StatusBadRequest) 34 return nil, err 35 } 36 37 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 38 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 39 return nil, err 40 } 41 42 if channel.DeleteAt != 0 { 43 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 44 return nil, err 45 } 46 47 rp, err := a.CreatePost(post, channel, true, setOnline) 48 if err != nil { 49 if err.Id == "api.post.create_post.root_id.app_error" || 50 err.Id == "api.post.create_post.channel_root_id.app_error" || 51 err.Id == "api.post.create_post.parent_id.app_error" { 52 err.StatusCode = http.StatusBadRequest 53 } 54 55 if err.Id == "api.post.create_post.town_square_read_only" { 56 user, userErr := a.Srv().Store.User().Get(post.UserId) 57 if userErr != nil { 58 return nil, userErr 59 } 60 61 T := utils.GetUserTranslations(user.Locale) 62 a.SendEphemeralPost( 63 post.UserId, 64 &model.Post{ 65 ChannelId: channel.Id, 66 ParentId: post.ParentId, 67 RootId: post.RootId, 68 UserId: post.UserId, 69 Message: T("api.post.create_post.town_square_read_only"), 70 CreateAt: model.GetMillis() + 1, 71 }, 72 ) 73 } 74 return nil, err 75 } 76 77 // Update the LastViewAt only if the post does not have from_webhook prop set (e.g. Zapier app), 78 // or if it does not have from_bot set (e.g. from discovering the user is a bot within CreatePost). 79 _, fromWebhook := post.GetProps()["from_webhook"] 80 _, fromBot := post.GetProps()["from_bot"] 81 if !fromWebhook && !fromBot { 82 if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil { 83 mlog.Error( 84 "Encountered error updating last viewed", 85 mlog.String("channel_id", post.ChannelId), 86 mlog.String("user_id", post.UserId), 87 mlog.Err(err), 88 ) 89 } 90 } 91 92 return rp, nil 93 } 94 95 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 96 channel, err := a.Srv().Store.Channel().Get(post.ChannelId, true) 97 if err != nil { 98 var nfErr *store.ErrNotFound 99 switch { 100 case errors.As(err, &nfErr): 101 return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound) 102 default: 103 return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.find.app_error", nil, err.Error(), http.StatusInternalServerError) 104 } 105 } 106 107 return a.CreatePost(post, channel, triggerWebhooks, true) 108 } 109 110 // deduplicateCreatePost attempts to make posting idempotent within a caching window. 111 func (a *App) deduplicateCreatePost(post *model.Post) (foundPost *model.Post, err *model.AppError) { 112 // We rely on the client sending the pending post id across "duplicate" requests. If there 113 // isn't one, we can't deduplicate, so allow creation normally. 114 if post.PendingPostId == "" { 115 return nil, nil 116 } 117 118 const unknownPostId = "" 119 120 // Query the cache atomically for the given pending post id, saving a record if 121 // it hasn't previously been seen. 122 var postId string 123 nErr := a.Srv().seenPendingPostIdsCache.Get(post.PendingPostId, &postId) 124 if nErr == cache.ErrKeyNotFound { 125 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, unknownPostId, PENDING_POST_IDS_CACHE_TTL) 126 return nil, nil 127 } 128 129 if nErr != nil { 130 return nil, model.NewAppError("errorGetPostId", "api.post.error_get_post_id.pending", nil, "", http.StatusInternalServerError) 131 } 132 133 // If another thread saved the cache record, but hasn't yet updated it with the actual post 134 // id (because it's still saving), notify the client with an error. Ideally, we'd wait 135 // for the other thread, but coordinating that adds complexity to the happy path. 136 if postId == unknownPostId { 137 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.pending", nil, "", http.StatusInternalServerError) 138 } 139 140 // If the other thread finished creating the post, return the created post back to the 141 // client, making the API call feel idempotent. 142 actualPost, err := a.GetSinglePost(postId) 143 if err != nil { 144 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.failed_to_get", nil, err.Error(), http.StatusInternalServerError) 145 } 146 147 mlog.Debug("Deduplicated create post", mlog.String("post_id", actualPost.Id), mlog.String("pending_post_id", post.PendingPostId)) 148 149 return actualPost, nil 150 } 151 152 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError) { 153 foundPost, err := a.deduplicateCreatePost(post) 154 if err != nil { 155 return nil, err 156 } 157 if foundPost != nil { 158 return foundPost, nil 159 } 160 161 // If we get this far, we've recorded the client-provided pending post id to the cache. 162 // Remove it if we fail below, allowing a proper retry by the client. 163 defer func() { 164 if post.PendingPostId == "" { 165 return 166 } 167 168 if err != nil { 169 a.Srv().seenPendingPostIdsCache.Remove(post.PendingPostId) 170 return 171 } 172 173 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, savedPost.Id, PENDING_POST_IDS_CACHE_TTL) 174 }() 175 176 post.SanitizeProps() 177 178 var pchan chan store.StoreResult 179 if len(post.RootId) > 0 { 180 pchan = make(chan store.StoreResult, 1) 181 go func() { 182 r, pErr := a.Srv().Store.Post().Get(post.RootId, false) 183 pchan <- store.StoreResult{Data: r, Err: pErr} 184 close(pchan) 185 }() 186 } 187 188 user, err := a.Srv().Store.User().Get(post.UserId) 189 if err != nil { 190 return nil, err 191 } 192 193 if user.IsBot { 194 post.AddProp("from_bot", "true") 195 } 196 197 if a.Srv().License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 198 !post.IsSystemMessage() && 199 channel.Name == model.DEFAULT_CHANNEL && 200 !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 201 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 202 } 203 204 var ephemeralPost *model.Post 205 if post.Type == "" && !a.HasPermissionToChannel(user.Id, channel.Id, model.PERMISSION_USE_CHANNEL_MENTIONS) { 206 mention := post.DisableMentionHighlights() 207 if mention != "" { 208 T := utils.GetUserTranslations(user.Locale) 209 ephemeralPost = &model.Post{ 210 UserId: user.Id, 211 RootId: post.RootId, 212 ParentId: post.ParentId, 213 ChannelId: channel.Id, 214 Message: T("model.post.channel_notifications_disabled_in_channel.message", model.StringInterface{"ChannelName": channel.Name, "Mention": mention}), 215 Props: model.StringInterface{model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 216 } 217 } 218 } 219 220 // Verify the parent/child relationships are correct 221 var parentPostList *model.PostList 222 if pchan != nil { 223 result := <-pchan 224 if result.Err != nil { 225 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 226 } 227 parentPostList = result.Data.(*model.PostList) 228 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 229 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 230 } 231 232 rootPost := parentPostList.Posts[post.RootId] 233 if len(rootPost.RootId) > 0 { 234 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 235 } 236 237 if post.ParentId == "" { 238 post.ParentId = post.RootId 239 } 240 241 if post.RootId != post.ParentId { 242 parent := parentPostList.Posts[post.ParentId] 243 if parent == nil { 244 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 245 } 246 } 247 } 248 249 post.Hashtags, _ = model.ParseHashtags(post.Message) 250 251 if err = a.FillInPostProps(post, channel); err != nil { 252 return nil, err 253 } 254 255 // Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088 256 if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok { 257 jsonAttachments, err := json.Marshal(attachments) 258 if err == nil { 259 attachmentsInterface := []interface{}{} 260 err = json.Unmarshal(jsonAttachments, &attachmentsInterface) 261 post.AddProp("attachments", attachmentsInterface) 262 } 263 if err != nil { 264 mlog.Error("Could not convert post attachments to map interface.", mlog.Err(err)) 265 } 266 } 267 268 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 269 var rejectionError *model.AppError 270 pluginContext := a.PluginContext() 271 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 272 replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post) 273 if rejectionReason != "" { 274 id := "Post rejected by plugin. " + rejectionReason 275 if rejectionReason == plugin.DismissPostError { 276 id = plugin.DismissPostError 277 } 278 rejectionError = model.NewAppError("createPost", id, nil, "", http.StatusBadRequest) 279 return false 280 } 281 if replacementPost != nil { 282 post = replacementPost 283 } 284 285 return true 286 }, plugin.MessageWillBePostedId) 287 288 if rejectionError != nil { 289 return nil, rejectionError 290 } 291 } 292 293 rpost, err := a.Srv().Store.Post().Save(post) 294 if err != nil { 295 return nil, err 296 } 297 298 // Update the mapping from pending post id to the actual post id, for any clients that 299 // might be duplicating requests. 300 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, rpost.Id, PENDING_POST_IDS_CACHE_TTL) 301 302 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 303 a.Srv().Go(func() { 304 pluginContext := a.PluginContext() 305 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 306 hooks.MessageHasBeenPosted(pluginContext, rpost) 307 return true 308 }, plugin.MessageHasBeenPostedId) 309 }) 310 } 311 312 if a.Metrics() != nil { 313 a.Metrics().IncrementPostCreate() 314 } 315 316 if len(post.FileIds) > 0 { 317 if err = a.attachFilesToPost(post); err != nil { 318 mlog.Error("Encountered error attaching files to post", mlog.String("post_id", post.Id), mlog.Any("file_ids", post.FileIds), mlog.Err(err)) 319 } 320 321 if a.Metrics() != nil { 322 a.Metrics().IncrementPostFileAttachment(len(post.FileIds)) 323 } 324 } 325 326 // Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs 327 // to be done when we send the post over the websocket in handlePostEvents 328 rpost = a.PreparePostForClient(rpost, true, false) 329 330 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList, setOnline); err != nil { 331 mlog.Error("Failed to handle post events", mlog.Err(err)) 332 } 333 334 // Send any ephemeral posts after the post is created to ensure it shows up after the latest post created 335 if ephemeralPost != nil { 336 a.SendEphemeralPost(post.UserId, ephemeralPost) 337 } 338 339 return rpost, nil 340 } 341 342 func (a *App) attachFilesToPost(post *model.Post) *model.AppError { 343 var attachedIds []string 344 for _, fileId := range post.FileIds { 345 err := a.Srv().Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId) 346 if err != nil { 347 mlog.Warn("Failed to attach file to post", mlog.String("file_id", fileId), mlog.String("post_id", post.Id), mlog.Err(err)) 348 continue 349 } 350 351 attachedIds = append(attachedIds, fileId) 352 } 353 354 if len(post.FileIds) != len(attachedIds) { 355 // We couldn't attach all files to the post, so ensure that post.FileIds reflects what was actually attached 356 post.FileIds = attachedIds 357 358 if _, err := a.Srv().Store.Post().Overwrite(post); err != nil { 359 return err 360 } 361 } 362 363 return nil 364 } 365 366 // FillInPostProps should be invoked before saving posts to fill in properties such as 367 // channel_mentions. 368 // 369 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 370 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 371 channelMentions := post.ChannelMentions() 372 channelMentionsProp := make(map[string]interface{}) 373 374 if len(channelMentions) > 0 { 375 if channel == nil { 376 postChannel, err := a.Srv().Store.Channel().GetForPost(post.Id) 377 if err != nil { 378 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, err.Error(), http.StatusBadRequest) 379 } 380 channel = postChannel 381 } 382 383 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 384 if err != nil { 385 return err 386 } 387 388 for _, mentioned := range mentionedChannels { 389 if mentioned.Type == model.CHANNEL_OPEN { 390 team, err := a.Srv().Store.Team().Get(mentioned.TeamId) 391 if err != nil { 392 mlog.Error("Failed to get team of the channel mention", mlog.String("team_id", channel.TeamId), mlog.String("channel_id", channel.Id), mlog.Err(err)) 393 } 394 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 395 "display_name": mentioned.DisplayName, 396 "team_name": team.Name, 397 } 398 } 399 } 400 } 401 402 if len(channelMentionsProp) > 0 { 403 post.AddProp("channel_mentions", channelMentionsProp) 404 } else if post.GetProps() != nil { 405 post.DelProp("channel_mentions") 406 } 407 408 matched := model.AT_MENTION_PATTEN.MatchString(post.Message) 409 if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched && !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_GROUP_MENTIONS) { 410 post.AddProp(model.POST_PROPS_GROUP_HIGHLIGHT_DISABLED, true) 411 } 412 413 return nil 414 } 415 416 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList, setOnline bool) error { 417 var team *model.Team 418 if len(channel.TeamId) > 0 { 419 t, err := a.Srv().Store.Team().Get(channel.TeamId) 420 if err != nil { 421 return err 422 } 423 team = t 424 } else { 425 // Blank team for DMs 426 team = &model.Team{} 427 } 428 429 a.invalidateCacheForChannel(channel) 430 a.invalidateCacheForChannelPosts(channel.Id) 431 432 if _, err := a.SendNotifications(post, team, channel, user, parentPostList, setOnline); err != nil { 433 return err 434 } 435 436 if post.Type != model.POST_AUTO_RESPONDER { // don't respond to an auto-responder 437 a.Srv().Go(func() { 438 _, err := a.SendAutoResponseIfNecessary(channel, user) 439 if err != nil { 440 mlog.Error("Failed to send auto response", mlog.String("user_id", user.Id), mlog.String("post_id", post.Id), mlog.Err(err)) 441 } 442 }) 443 } 444 445 if triggerWebhooks { 446 a.Srv().Go(func() { 447 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 448 mlog.Error(err.Error()) 449 } 450 }) 451 } 452 453 return nil 454 } 455 456 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 457 post.Type = model.POST_EPHEMERAL 458 459 // fill in fields which haven't been specified which have sensible defaults 460 if post.Id == "" { 461 post.Id = model.NewId() 462 } 463 if post.CreateAt == 0 { 464 post.CreateAt = model.GetMillis() 465 } 466 if post.GetProps() == nil { 467 post.SetProps(make(model.StringInterface)) 468 } 469 470 post.GenerateActionIds() 471 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) 472 post = a.PreparePostForClient(post, true, false) 473 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 474 message.Add("post", post.ToJson()) 475 a.Publish(message) 476 477 return post 478 } 479 480 func (a *App) UpdateEphemeralPost(userId string, post *model.Post) *model.Post { 481 post.Type = model.POST_EPHEMERAL 482 483 post.UpdateAt = model.GetMillis() 484 if post.GetProps() == nil { 485 post.SetProps(make(model.StringInterface)) 486 } 487 488 post.GenerateActionIds() 489 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, userId, nil) 490 post = a.PreparePostForClient(post, true, false) 491 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 492 message.Add("post", post.ToJson()) 493 a.Publish(message) 494 495 return post 496 } 497 498 func (a *App) DeleteEphemeralPost(userId, postId string) { 499 post := &model.Post{ 500 Id: postId, 501 UserId: userId, 502 Type: model.POST_EPHEMERAL, 503 DeleteAt: model.GetMillis(), 504 UpdateAt: model.GetMillis(), 505 } 506 507 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", "", userId, nil) 508 message.Add("post", post.ToJson()) 509 a.Publish(message) 510 } 511 512 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 513 post.SanitizeProps() 514 515 postLists, err := a.Srv().Store.Post().Get(post.Id, false) 516 if err != nil { 517 return nil, err 518 } 519 oldPost := postLists.Posts[post.Id] 520 521 if oldPost == nil { 522 err = model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 523 return nil, err 524 } 525 526 if oldPost.DeleteAt != 0 { 527 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 528 return nil, err 529 } 530 531 if oldPost.IsSystemMessage() { 532 err = model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 533 return nil, err 534 } 535 536 if a.Srv().License() != nil { 537 if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 538 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 539 return nil, err 540 } 541 } 542 543 channel, err := a.GetChannel(oldPost.ChannelId) 544 if err != nil { 545 return nil, err 546 } 547 548 if channel.DeleteAt != 0 { 549 return nil, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 550 } 551 552 newPost := &model.Post{} 553 newPost = oldPost.Clone() 554 555 if newPost.Message != post.Message { 556 newPost.Message = post.Message 557 newPost.EditAt = model.GetMillis() 558 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 559 } 560 561 if !safeUpdate { 562 newPost.IsPinned = post.IsPinned 563 newPost.HasReactions = post.HasReactions 564 newPost.FileIds = post.FileIds 565 newPost.SetProps(post.GetProps()) 566 } 567 568 // Avoid deep-equal checks if EditAt was already modified through message change 569 if newPost.EditAt == oldPost.EditAt && (!oldPost.FileIds.Equals(newPost.FileIds) || !oldPost.AttachmentsEqual(newPost)) { 570 newPost.EditAt = model.GetMillis() 571 } 572 573 if err = a.FillInPostProps(post, nil); err != nil { 574 return nil, err 575 } 576 577 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 578 var rejectionReason string 579 pluginContext := a.PluginContext() 580 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 581 newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost) 582 return post != nil 583 }, plugin.MessageWillBeUpdatedId) 584 if newPost == nil { 585 return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 586 } 587 } 588 589 rpost, err := a.Srv().Store.Post().Update(newPost, oldPost) 590 if err != nil { 591 return nil, err 592 } 593 594 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 595 a.Srv().Go(func() { 596 pluginContext := a.PluginContext() 597 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 598 hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost) 599 return true 600 }, plugin.MessageHasBeenUpdatedId) 601 }) 602 } 603 604 rpost = a.PreparePostForClient(rpost, false, true) 605 606 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil) 607 message.Add("post", rpost.ToJson()) 608 a.Publish(message) 609 610 a.invalidateCacheForChannelPosts(rpost.ChannelId) 611 612 return rpost, nil 613 } 614 615 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 616 post, err := a.GetSinglePost(postId) 617 if err != nil { 618 return nil, err 619 } 620 621 channel, err := a.GetChannel(post.ChannelId) 622 if err != nil { 623 return nil, err 624 } 625 626 if channel.DeleteAt != 0 { 627 err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 628 return nil, err 629 } 630 631 if !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_CHANNEL_MENTIONS) { 632 patch.DisableMentionHighlights() 633 } 634 635 post.Patch(patch) 636 637 updatedPost, err := a.UpdatePost(post, false) 638 if err != nil { 639 return nil, err 640 } 641 642 return updatedPost, nil 643 } 644 645 func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 646 return a.Srv().Store.Post().GetPosts(options, false) 647 } 648 649 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 650 return a.Srv().Store.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: offset, PerPage: limit}, true) 651 } 652 653 func (a *App) GetPostsEtag(channelId string) string { 654 return a.Srv().Store.Post().GetEtag(channelId, true) 655 } 656 657 func (a *App) GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError) { 658 return a.Srv().Store.Post().GetPostsSince(options, true) 659 } 660 661 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 662 return a.Srv().Store.Post().GetSingle(postId) 663 } 664 665 func (a *App) GetPostThread(postId string, skipFetchThreads bool) (*model.PostList, *model.AppError) { 666 return a.Srv().Store.Post().Get(postId, skipFetchThreads) 667 } 668 669 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 670 return a.Srv().Store.Post().GetFlaggedPosts(userId, offset, limit) 671 } 672 673 func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) { 674 return a.Srv().Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit) 675 } 676 677 func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 678 return a.Srv().Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit) 679 } 680 681 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 682 list, err := a.Srv().Store.Post().Get(postId, false) 683 if err != nil { 684 return nil, err 685 } 686 687 if len(list.Order) != 1 { 688 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 689 } 690 post := list.Posts[list.Order[0]] 691 692 channel, err := a.GetChannel(post.ChannelId) 693 if err != nil { 694 return nil, err 695 } 696 697 if err = a.JoinChannel(channel, userId); err != nil { 698 return nil, err 699 } 700 701 return list, nil 702 } 703 704 func (a *App) GetPostsBeforePost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 705 return a.Srv().Store.Post().GetPostsBefore(options) 706 } 707 708 func (a *App) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 709 return a.Srv().Store.Post().GetPostsAfter(options) 710 } 711 712 func (a *App) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) { 713 if before { 714 return a.Srv().Store.Post().GetPostsBefore(options) 715 } 716 return a.Srv().Store.Post().GetPostsAfter(options) 717 } 718 719 func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) { 720 return a.Srv().Store.Post().GetPostAfterTime(channelId, time) 721 } 722 723 func (a *App) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) { 724 return a.Srv().Store.Post().GetPostIdAfterTime(channelId, time) 725 } 726 727 func (a *App) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) { 728 return a.Srv().Store.Post().GetPostIdBeforeTime(channelId, time) 729 } 730 731 func (a *App) GetNextPostIdFromPostList(postList *model.PostList) string { 732 if len(postList.Order) > 0 { 733 firstPostId := postList.Order[0] 734 firstPost := postList.Posts[firstPostId] 735 nextPostId, err := a.GetPostIdAfterTime(firstPost.ChannelId, firstPost.CreateAt) 736 if err != nil { 737 mlog.Warn("GetNextPostIdFromPostList: failed in getting next post", mlog.Err(err)) 738 } 739 740 return nextPostId 741 } 742 743 return "" 744 } 745 746 func (a *App) GetPrevPostIdFromPostList(postList *model.PostList) string { 747 if len(postList.Order) > 0 { 748 lastPostId := postList.Order[len(postList.Order)-1] 749 lastPost := postList.Posts[lastPostId] 750 previousPostId, err := a.GetPostIdBeforeTime(lastPost.ChannelId, lastPost.CreateAt) 751 if err != nil { 752 mlog.Warn("GetPrevPostIdFromPostList: failed in getting previous post", mlog.Err(err)) 753 } 754 755 return previousPostId 756 } 757 758 return "" 759 } 760 761 // AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList. 762 // The conditional blocks ensure that it sets those cursor IDs immediately as afterPost, beforePost or empty, 763 // and only query to database whenever necessary. 764 func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, beforePost string, since int64, page, perPage int) { 765 prevPostIdSet := false 766 prevPostId := "" 767 nextPostIdSet := false 768 nextPostId := "" 769 770 if since > 0 { // "since" query to return empty NextPostId and PrevPostId 771 nextPostIdSet = true 772 prevPostIdSet = true 773 } else if afterPost != "" { 774 if page == 0 { 775 prevPostId = afterPost 776 prevPostIdSet = true 777 } 778 779 if len(originalList.Order) < perPage { 780 nextPostIdSet = true 781 } 782 } else if beforePost != "" { 783 if page == 0 { 784 nextPostId = beforePost 785 nextPostIdSet = true 786 } 787 788 if len(originalList.Order) < perPage { 789 prevPostIdSet = true 790 } 791 } 792 793 if !nextPostIdSet { 794 nextPostId = a.GetNextPostIdFromPostList(originalList) 795 } 796 797 if !prevPostIdSet { 798 prevPostId = a.GetPrevPostIdFromPostList(originalList) 799 } 800 801 originalList.NextPostId = nextPostId 802 originalList.PrevPostId = prevPostId 803 } 804 func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limitBefore, limitAfter int, skipFetchThreads bool) (*model.PostList, *model.AppError) { 805 var member *model.ChannelMember 806 var err *model.AppError 807 if member, err = a.GetChannelMember(channelId, userId); err != nil { 808 return nil, err 809 } else if member.LastViewedAt == 0 { 810 return model.NewPostList(), nil 811 } 812 813 lastUnreadPostId, err := a.GetPostIdAfterTime(channelId, member.LastViewedAt) 814 if err != nil { 815 return nil, err 816 } else if lastUnreadPostId == "" { 817 return model.NewPostList(), nil 818 } 819 820 postList, err := a.GetPostThread(lastUnreadPostId, skipFetchThreads) 821 if err != nil { 822 return nil, err 823 } 824 // Reset order to only include the last unread post: if the thread appears in the centre 825 // channel organically, those replies will be added below. 826 postList.Order = []string{lastUnreadPostId} 827 828 if postListBefore, err := a.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitBefore, SkipFetchThreads: skipFetchThreads}); err != nil { 829 return nil, err 830 } else if postListBefore != nil { 831 postList.Extend(postListBefore) 832 } 833 834 if postListAfter, err := a.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitAfter - 1, SkipFetchThreads: skipFetchThreads}); err != nil { 835 return nil, err 836 } else if postListAfter != nil { 837 postList.Extend(postListAfter) 838 } 839 840 postList.SortByCreateAt() 841 return postList, nil 842 } 843 844 func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) { 845 post, err := a.Srv().Store.Post().GetSingle(postId) 846 if err != nil { 847 err.StatusCode = http.StatusBadRequest 848 return nil, err 849 } 850 851 channel, err := a.GetChannel(post.ChannelId) 852 if err != nil { 853 return nil, err 854 } 855 856 if channel.DeleteAt != 0 { 857 err := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest) 858 return nil, err 859 } 860 861 if err := a.Srv().Store.Post().Delete(postId, model.GetMillis(), deleteByID); err != nil { 862 return nil, err 863 } 864 865 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 866 message.Add("post", a.PreparePostForClient(post, false, false).ToJson()) 867 a.Publish(message) 868 869 a.Srv().Go(func() { 870 a.DeletePostFiles(post) 871 }) 872 a.Srv().Go(func() { 873 a.DeleteFlaggedPosts(post.Id) 874 }) 875 876 a.invalidateCacheForChannelPosts(post.ChannelId) 877 878 return post, nil 879 } 880 881 func (a *App) DeleteFlaggedPosts(postId string) { 882 if err := a.Srv().Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil { 883 mlog.Warn("Unable to delete flagged post preference when deleting post.", mlog.Err(err)) 884 return 885 } 886 } 887 888 func (a *App) DeletePostFiles(post *model.Post) { 889 if len(post.FileIds) == 0 { 890 return 891 } 892 893 if _, err := a.Srv().Store.FileInfo().DeleteForPost(post.Id); err != nil { 894 mlog.Warn("Encountered error when deleting files for post", mlog.String("post_id", post.Id), mlog.Err(err)) 895 } 896 } 897 898 func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId string, includeDeleted bool) (*model.Channel, error) { 899 if strings.HasPrefix(channelName, "@") && strings.Contains(channelName, ",") { 900 var userIds []string 901 users, err := a.GetUsersByUsernames(strings.Split(channelName[1:], ","), false, nil) 902 if err != nil { 903 return nil, err 904 } 905 for _, user := range users { 906 userIds = append(userIds, user.Id) 907 } 908 909 channel, err := a.GetGroupChannel(userIds) 910 if err != nil { 911 return nil, err 912 } 913 return channel, nil 914 } 915 916 if strings.HasPrefix(channelName, "@") && !strings.Contains(channelName, ",") { 917 user, err := a.GetUserByUsername(channelName[1:]) 918 if err != nil { 919 return nil, err 920 } 921 channel, err := a.GetOrCreateDirectChannel(userId, user.Id) 922 if err != nil { 923 return nil, err 924 } 925 return channel, nil 926 } 927 928 channel, err := a.GetChannelByName(channelName, teamId, includeDeleted) 929 if err != nil { 930 return nil, err 931 } 932 return channel, nil 933 } 934 935 func (a *App) searchPostsInTeam(teamId string, userId string, paramsList []*model.SearchParams, modifierFun func(*model.SearchParams)) (*model.PostList, *model.AppError) { 936 var wg sync.WaitGroup 937 938 pchan := make(chan store.StoreResult, len(paramsList)) 939 940 for _, params := range paramsList { 941 // Don't allow users to search for everything. 942 if params.Terms == "*" { 943 continue 944 } 945 modifierFun(params) 946 wg.Add(1) 947 948 go func(params *model.SearchParams) { 949 defer wg.Done() 950 postList, err := a.Srv().Store.Post().Search(teamId, userId, params) 951 pchan <- store.StoreResult{Data: postList, Err: err} 952 }(params) 953 } 954 955 wg.Wait() 956 close(pchan) 957 958 posts := model.NewPostList() 959 960 for result := range pchan { 961 if result.Err != nil { 962 return nil, result.Err 963 } 964 data := result.Data.(*model.PostList) 965 posts.Extend(data) 966 } 967 968 posts.SortByCreateAt() 969 return posts, nil 970 } 971 972 func (a *App) convertChannelNamesToChannelIds(channels []string, userId string, teamId string, includeDeletedChannels bool) []string { 973 for idx, channelName := range channels { 974 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) 975 if err != nil { 976 mlog.Error("error getting channel id by name from in filter", mlog.Err(err)) 977 continue 978 } 979 channels[idx] = channel.Id 980 } 981 return channels 982 } 983 984 func (a *App) convertUserNameToUserIds(usernames []string) []string { 985 for idx, username := range usernames { 986 if user, err := a.GetUserByUsername(username); err != nil { 987 mlog.Error("error getting user by username", mlog.String("user_name", username), mlog.Err(err)) 988 } else { 989 usernames[idx] = user.Id 990 } 991 } 992 return usernames 993 } 994 995 func (a *App) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) { 996 if !*a.Config().ServiceSettings.EnablePostSearch { 997 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v", teamId), http.StatusNotImplemented) 998 } 999 return a.searchPostsInTeam(teamId, "", paramsList, func(params *model.SearchParams) { 1000 params.SearchWithoutUserId = true 1001 }) 1002 } 1003 1004 func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) { 1005 var postSearchResults *model.PostSearchResults 1006 var err *model.AppError 1007 paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset) 1008 includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels 1009 1010 if !*a.Config().ServiceSettings.EnablePostSearch { 1011 return nil, model.NewAppError("SearchPostsInTeamForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) 1012 } 1013 1014 finalParamsList := []*model.SearchParams{} 1015 1016 for _, params := range paramsList { 1017 params.OrTerms = isOrSearch 1018 // Don't allow users to search for "*" 1019 if params.Terms != "*" { 1020 // Convert channel names to channel IDs 1021 params.InChannels = a.convertChannelNamesToChannelIds(params.InChannels, userId, teamId, includeDeletedChannels) 1022 params.ExcludedChannels = a.convertChannelNamesToChannelIds(params.ExcludedChannels, userId, teamId, includeDeletedChannels) 1023 1024 // Convert usernames to user IDs 1025 params.FromUsers = a.convertUserNameToUserIds(params.FromUsers) 1026 params.ExcludedUsers = a.convertUserNameToUserIds(params.ExcludedUsers) 1027 1028 finalParamsList = append(finalParamsList, params) 1029 } 1030 } 1031 1032 // If the processed search params are empty, return empty search results. 1033 if len(finalParamsList) == 0 { 1034 return model.MakePostSearchResults(model.NewPostList(), nil), nil 1035 } 1036 1037 postSearchResults, err = a.Srv().Store.Post().SearchPostsInTeamForUser(finalParamsList, userId, teamId, isOrSearch, includeDeleted, page, perPage) 1038 if err != nil { 1039 return nil, err 1040 } 1041 1042 return postSearchResults, nil 1043 } 1044 1045 func (a *App) GetFileInfosForPostWithMigration(postId string) ([]*model.FileInfo, *model.AppError) { 1046 1047 pchan := make(chan store.StoreResult, 1) 1048 go func() { 1049 post, err := a.Srv().Store.Post().GetSingle(postId) 1050 pchan <- store.StoreResult{Data: post, Err: err} 1051 close(pchan) 1052 }() 1053 1054 infos, err := a.GetFileInfosForPost(postId, false) 1055 if err != nil { 1056 return nil, err 1057 } 1058 1059 if len(infos) == 0 { 1060 // No FileInfos were returned so check if they need to be created for this post 1061 result := <-pchan 1062 if result.Err != nil { 1063 return nil, result.Err 1064 } 1065 post := result.Data.(*model.Post) 1066 1067 if len(post.Filenames) > 0 { 1068 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, false) 1069 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, true) 1070 // The post has Filenames that need to be replaced with FileInfos 1071 infos = a.MigrateFilenamesToFileInfos(post) 1072 } 1073 } 1074 1075 return infos, nil 1076 } 1077 1078 func (a *App) GetFileInfosForPost(postId string, fromMaster bool) ([]*model.FileInfo, *model.AppError) { 1079 return a.Srv().Store.FileInfo().GetForPost(postId, fromMaster, false, true) 1080 } 1081 1082 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 1083 if f := a.ImageProxyAdder(); f != nil { 1084 return post.WithRewrittenImageURLs(f) 1085 } 1086 return post 1087 } 1088 1089 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 1090 if f := a.ImageProxyRemover(); f != nil { 1091 return post.WithRewrittenImageURLs(f) 1092 } 1093 return post 1094 } 1095 1096 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 1097 if f := a.ImageProxyRemover(); f != nil { 1098 return patch.WithRewrittenImageURLs(f) 1099 } 1100 return patch 1101 } 1102 1103 func (a *App) ImageProxyAdder() func(string) string { 1104 if !*a.Config().ImageProxySettings.Enable { 1105 return nil 1106 } 1107 1108 return func(url string) string { 1109 return a.Srv().ImageProxy.GetProxiedImageURL(url) 1110 } 1111 } 1112 1113 func (a *App) ImageProxyRemover() (f func(string) string) { 1114 if !*a.Config().ImageProxySettings.Enable { 1115 return nil 1116 } 1117 1118 return func(url string) string { 1119 return a.Srv().ImageProxy.GetUnproxiedImageURL(url) 1120 } 1121 } 1122 1123 func (s *Server) MaxPostSize() int { 1124 maxPostSize := s.Store.Post().GetMaxPostSize() 1125 if maxPostSize == 0 { 1126 return model.POST_MESSAGE_MAX_RUNES_V1 1127 } 1128 1129 return maxPostSize 1130 } 1131 1132 func (a *App) MaxPostSize() int { 1133 return a.Srv().MaxPostSize() 1134 } 1135 1136 // countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the 1137 // given post. 1138 func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *model.AppError) { 1139 channel, err := a.GetChannel(post.ChannelId) 1140 if err != nil { 1141 return 0, err 1142 } 1143 1144 if channel.Type == model.CHANNEL_DIRECT { 1145 // In a DM channel, every post made by the other user is a mention 1146 count, countErr := a.Srv().Store.Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id)) 1147 if countErr != nil { 1148 return 0, countErr 1149 } 1150 1151 return count, countErr 1152 } 1153 1154 channelMember, err := a.GetChannelMember(channel.Id, user.Id) 1155 if err != nil { 1156 return 0, err 1157 } 1158 1159 keywords := addMentionKeywordsForUser( 1160 map[string][]string{}, 1161 user, 1162 channelMember.NotifyProps, 1163 &model.Status{Status: model.STATUS_ONLINE}, // Assume the user is online since they would've triggered this 1164 true, // Assume channel mentions are always allowed for simplicity 1165 ) 1166 commentMentions := user.NotifyProps[model.COMMENTS_NOTIFY_PROP] 1167 checkForCommentMentions := commentMentions == model.COMMENTS_NOTIFY_ROOT || commentMentions == model.COMMENTS_NOTIFY_ANY 1168 1169 // A mapping of thread root IDs to whether or not a post in that thread mentions the user 1170 mentionedByThread := make(map[string]bool) 1171 1172 thread, err := a.GetPostThread(post.Id, false) 1173 if err != nil { 1174 return 0, err 1175 } 1176 1177 count := 0 1178 1179 if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) { 1180 count += 1 1181 } 1182 1183 page := 0 1184 perPage := 200 1185 for { 1186 postList, err := a.GetPostsAfterPost(model.GetPostsOptions{ 1187 ChannelId: post.ChannelId, 1188 PostId: post.Id, 1189 Page: page, 1190 PerPage: perPage, 1191 }) 1192 if err != nil { 1193 return 0, err 1194 } 1195 1196 for _, postId := range postList.Order { 1197 if isPostMention(user, postList.Posts[postId], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) { 1198 count += 1 1199 } 1200 } 1201 1202 if len(postList.Order) < perPage { 1203 break 1204 } 1205 1206 page += 1 1207 } 1208 1209 return count, nil 1210 } 1211 1212 func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool { 1213 if post.RootId == "" { 1214 // Not a comment 1215 return false 1216 } 1217 1218 if mentioned, ok := mentionedByThread[post.RootId]; ok { 1219 // We've already figured out if the user was mentioned by this thread 1220 return mentioned 1221 } 1222 1223 // Whether or not the user was mentioned because they started the thread 1224 mentioned := otherPosts[post.RootId].UserId == user.Id 1225 1226 // Or because they commented on it before this post 1227 if !mentioned && user.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ANY { 1228 for _, otherPost := range otherPosts { 1229 if otherPost.Id == post.Id { 1230 continue 1231 } 1232 1233 if otherPost.RootId != post.RootId { 1234 continue 1235 } 1236 1237 if otherPost.UserId == user.Id && otherPost.CreateAt < post.CreateAt { 1238 // Found a comment made by the user from before this post 1239 mentioned = true 1240 break 1241 } 1242 } 1243 } 1244 1245 mentionedByThread[post.RootId] = mentioned 1246 return mentioned 1247 } 1248 1249 func isPostMention(user *model.User, post *model.Post, keywords map[string][]string, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool { 1250 // Prevent the user from mentioning themselves 1251 if post.UserId == user.Id && post.GetProp("from_webhook") != "true" { 1252 return false 1253 } 1254 1255 // Check for keyword mentions 1256 mentions := getExplicitMentions(post, keywords, make(map[string]*model.Group)) 1257 if _, ok := mentions.Mentions[user.Id]; ok { 1258 return true 1259 } 1260 1261 // Check for mentions caused by being added to the channel 1262 if post.Type == model.POST_ADD_TO_CHANNEL { 1263 if addedUserId, ok := post.GetProp(model.POST_PROPS_ADDED_USER_ID).(string); ok && addedUserId == user.Id { 1264 return true 1265 } 1266 } 1267 1268 // Check for comment mentions 1269 if checkForCommentMentions && isCommentMention(user, post, otherPosts, mentionedByThread) { 1270 return true 1271 } 1272 1273 return false 1274 }