github.com/status-im/status-go@v1.1.0/protocol/messenger_communities_import_discord.go (about) 1 package protocol 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/golang/protobuf/proto" 13 "github.com/meirf/gopart" 14 "go.uber.org/zap" 15 16 "github.com/status-im/status-go/eth-node/crypto" 17 "github.com/status-im/status-go/eth-node/types" 18 "github.com/status-im/status-go/images" 19 "github.com/status-im/status-go/protocol/common" 20 "github.com/status-im/status-go/protocol/communities" 21 "github.com/status-im/status-go/protocol/discord" 22 "github.com/status-im/status-go/protocol/protobuf" 23 "github.com/status-im/status-go/protocol/requests" 24 "github.com/status-im/status-go/protocol/transport" 25 v1protocol "github.com/status-im/status-go/protocol/v1" 26 ) 27 28 func (m *Messenger) ExtractDiscordDataFromImportFiles(filesToImport []string) (*discord.ExtractedData, map[string]*discord.ImportError) { 29 30 extractedData := &discord.ExtractedData{ 31 Categories: map[string]*discord.Category{}, 32 ExportedData: make([]*discord.ExportedData, 0), 33 OldestMessageTimestamp: 0, 34 MessageCount: 0, 35 } 36 37 errors := map[string]*discord.ImportError{} 38 39 for _, fileToImport := range filesToImport { 40 filePath := strings.Replace(fileToImport, "file://", "", -1) 41 42 fileInfo, err := os.Stat(filePath) 43 if err != nil { 44 errors[fileToImport] = discord.Error(err.Error()) 45 continue 46 } 47 48 fileSize := fileInfo.Size() 49 if fileSize > discord.MaxImportFileSizeBytes { 50 errors[fileToImport] = discord.Error(discord.ErrImportFileTooBig.Error()) 51 continue 52 } 53 54 bytes, err := os.ReadFile(filePath) 55 if err != nil { 56 errors[fileToImport] = discord.Error(err.Error()) 57 continue 58 } 59 60 var discordExportedData discord.ExportedData 61 62 err = json.Unmarshal(bytes, &discordExportedData) 63 if err != nil { 64 errors[fileToImport] = discord.Error(err.Error()) 65 continue 66 } 67 68 if len(discordExportedData.Messages) == 0 { 69 errors[fileToImport] = discord.Error(discord.ErrNoMessageData.Error()) 70 continue 71 } 72 73 discordExportedData.Channel.FilePath = filePath 74 categoryID := discordExportedData.Channel.CategoryID 75 76 discordCategory := discord.Category{ 77 ID: categoryID, 78 Name: discordExportedData.Channel.CategoryName, 79 } 80 81 _, ok := extractedData.Categories[categoryID] 82 if !ok { 83 extractedData.Categories[categoryID] = &discordCategory 84 } 85 86 extractedData.MessageCount = extractedData.MessageCount + discordExportedData.MessageCount 87 extractedData.ExportedData = append(extractedData.ExportedData, &discordExportedData) 88 89 if len(discordExportedData.Messages) > 0 { 90 msgTime, err := time.Parse(discordTimestampLayout, discordExportedData.Messages[0].Timestamp) 91 if err != nil { 92 m.logger.Error("failed to parse discord message timestamp", zap.Error(err)) 93 continue 94 } 95 96 if extractedData.OldestMessageTimestamp == 0 || int(msgTime.Unix()) <= extractedData.OldestMessageTimestamp { 97 // Exported discord channel data already comes with `messages` being 98 // sorted, starting with the oldest, so we can safely rely on the first 99 // message 100 extractedData.OldestMessageTimestamp = int(msgTime.Unix()) 101 } 102 } 103 } 104 return extractedData, errors 105 } 106 107 func (m *Messenger) ExtractDiscordChannelsAndCategories(filesToImport []string) (*MessengerResponse, map[string]*discord.ImportError) { 108 109 response := &MessengerResponse{} 110 111 extractedData, errs := m.ExtractDiscordDataFromImportFiles(filesToImport) 112 113 for _, category := range extractedData.Categories { 114 response.AddDiscordCategory(category) 115 } 116 for _, export := range extractedData.ExportedData { 117 response.AddDiscordChannel(&export.Channel) 118 } 119 if extractedData.OldestMessageTimestamp != 0 { 120 response.DiscordOldestMessageTimestamp = extractedData.OldestMessageTimestamp 121 } 122 123 return response, errs 124 } 125 126 func (m *Messenger) RequestExtractDiscordChannelsAndCategories(filesToImport []string) { 127 go func() { 128 response, errors := m.ExtractDiscordChannelsAndCategories(filesToImport) 129 m.config.messengerSignalsHandler.DiscordCategoriesAndChannelsExtracted( 130 response.DiscordCategories, 131 response.DiscordChannels, 132 int64(response.DiscordOldestMessageTimestamp), 133 errors) 134 }() 135 } 136 func (m *Messenger) saveDiscordAuthorIfNotExists(discordAuthor *protobuf.DiscordMessageAuthor) *discord.ImportError { 137 exists, err := m.persistence.HasDiscordMessageAuthor(discordAuthor.GetId()) 138 if err != nil { 139 m.logger.Error("failed to check if message author exists in database", zap.Error(err)) 140 return discord.Error(err.Error()) 141 } 142 143 if !exists { 144 err := m.persistence.SaveDiscordMessageAuthor(discordAuthor) 145 if err != nil { 146 return discord.Error(err.Error()) 147 } 148 } 149 150 return nil 151 } 152 153 func (m *Messenger) convertDiscordMessageTimeStamp(discordMessage *protobuf.DiscordMessage, timestamp time.Time) *discord.ImportError { 154 discordMessage.Timestamp = fmt.Sprintf("%d", timestamp.Unix()) 155 156 if discordMessage.TimestampEdited != "" { 157 timestampEdited, err := time.Parse(discordTimestampLayout, discordMessage.TimestampEdited) 158 if err != nil { 159 m.logger.Error("failed to parse discord message timestamp", zap.Error(err)) 160 return discord.Warning(err.Error()) 161 } 162 // Convert timestamp to unix timestamp 163 discordMessage.TimestampEdited = fmt.Sprintf("%d", timestampEdited.Unix()) 164 } 165 166 return nil 167 } 168 169 func (m *Messenger) createPinMessageFromDiscordMessage(message *common.Message, pinnedMessageID string, channelID string, community *communities.Community) (*common.PinMessage, *discord.ImportError) { 170 pinMessage := protobuf.PinMessage{ 171 Clock: message.WhisperTimestamp, 172 MessageId: pinnedMessageID, 173 ChatId: message.LocalChatID, 174 MessageType: protobuf.MessageType_COMMUNITY_CHAT, 175 Pinned: true, 176 } 177 178 encodedPayload, err := proto.Marshal(&pinMessage) 179 if err != nil { 180 m.logger.Error("failed to parse marshal pin message", zap.Error(err)) 181 return nil, discord.Warning(err.Error()) 182 } 183 184 wrappedPayload, err := v1protocol.WrapMessageV1(encodedPayload, protobuf.ApplicationMetadataMessage_PIN_MESSAGE, community.PrivateKey()) 185 if err != nil { 186 m.logger.Error("failed to wrap pin message", zap.Error(err)) 187 return nil, discord.Warning(err.Error()) 188 } 189 190 pinMessageToSave := &common.PinMessage{ 191 ID: types.EncodeHex(v1protocol.MessageID(&community.PrivateKey().PublicKey, wrappedPayload)), 192 PinMessage: &pinMessage, 193 LocalChatID: channelID, 194 From: message.From, 195 SigPubKey: message.SigPubKey, 196 WhisperTimestamp: message.WhisperTimestamp, 197 } 198 199 return pinMessageToSave, nil 200 } 201 202 func (m *Messenger) processDiscordMessages(discordChannel *discord.ExportedData, 203 channel *Chat, 204 importProgress *discord.ImportProgress, 205 progressUpdates chan *discord.ImportProgress, 206 fromDate int64, 207 community *communities.Community) ( 208 map[string]*common.Message, 209 []*common.PinMessage, 210 map[string]*protobuf.DiscordMessageAuthor, 211 []*protobuf.DiscordMessageAttachment) { 212 213 messagesToSave := make(map[string]*common.Message, 0) 214 pinMessagesToSave := make([]*common.PinMessage, 0) 215 authorProfilesToSave := make(map[string]*protobuf.DiscordMessageAuthor, 0) 216 messageAttachmentsToDownload := make([]*protobuf.DiscordMessageAttachment, 0) 217 218 for _, discordMessage := range discordChannel.Messages { 219 220 timestamp, err := time.Parse(discordTimestampLayout, discordMessage.Timestamp) 221 if err != nil { 222 m.logger.Error("failed to parse discord message timestamp", zap.Error(err)) 223 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 224 progressUpdates <- importProgress 225 continue 226 } 227 228 if timestamp.Unix() < fromDate { 229 progressUpdates <- importProgress 230 continue 231 } 232 233 importErr := m.saveDiscordAuthorIfNotExists(discordMessage.Author) 234 if importErr != nil { 235 importProgress.AddTaskError(discord.ImportMessagesTask, importErr) 236 progressUpdates <- importProgress 237 continue 238 } 239 240 hasPayload, err := m.persistence.HasDiscordMessageAuthorImagePayload(discordMessage.Author.GetId()) 241 if err != nil { 242 m.logger.Error("failed to check if message avatar payload exists in database", zap.Error(err)) 243 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 244 progressUpdates <- importProgress 245 continue 246 } 247 248 if !hasPayload { 249 authorProfilesToSave[discordMessage.Author.Id] = discordMessage.Author 250 } 251 252 // Convert timestamp to unix timestamp 253 importErr = m.convertDiscordMessageTimeStamp(discordMessage, timestamp) 254 if importErr != nil { 255 importProgress.AddTaskError(discord.ImportMessagesTask, importErr) 256 progressUpdates <- importProgress 257 continue 258 } 259 260 for i := range discordMessage.Attachments { 261 discordMessage.Attachments[i].MessageId = discordMessage.Id 262 } 263 messageAttachmentsToDownload = append(messageAttachmentsToDownload, discordMessage.Attachments...) 264 265 clockAndTimestamp := uint64(timestamp.Unix()) * 1000 266 communityPubKey := community.PrivateKey().PublicKey 267 268 chatMessage := protobuf.ChatMessage{ 269 Timestamp: clockAndTimestamp, 270 MessageType: protobuf.MessageType_COMMUNITY_CHAT, 271 ContentType: protobuf.ChatMessage_DISCORD_MESSAGE, 272 Clock: clockAndTimestamp, 273 ChatId: channel.ID, 274 Payload: &protobuf.ChatMessage_DiscordMessage{ 275 DiscordMessage: discordMessage, 276 }, 277 } 278 279 // Handle message replies 280 if discordMessage.Type == string(discord.MessageTypeReply) && discordMessage.Reference != nil { 281 repliedMessageID := community.IDString() + discordMessage.Reference.MessageId 282 if _, exists := messagesToSave[repliedMessageID]; exists { 283 chatMessage.ResponseTo = repliedMessageID 284 } 285 } 286 287 messageToSave := &common.Message{ 288 ID: community.IDString() + discordMessage.Id, 289 WhisperTimestamp: clockAndTimestamp, 290 From: types.EncodeHex(crypto.FromECDSAPub(&communityPubKey)), 291 Seen: true, 292 LocalChatID: channel.ID, 293 SigPubKey: &communityPubKey, 294 CommunityID: community.IDString(), 295 ChatMessage: &chatMessage, 296 } 297 298 err = messageToSave.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)) 299 if err != nil { 300 m.logger.Error("failed to prepare message content", zap.Error(err)) 301 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 302 progressUpdates <- importProgress 303 continue 304 } 305 306 // Handle pin messages 307 if discordMessage.Type == string(discord.MessageTypeChannelPinned) && discordMessage.Reference != nil { 308 309 pinnedMessageID := community.IDString() + discordMessage.Reference.MessageId 310 _, exists := messagesToSave[pinnedMessageID] 311 if exists { 312 pinMessageToSave, importErr := m.createPinMessageFromDiscordMessage(messageToSave, pinnedMessageID, channel.ID, community) 313 if importErr != nil { 314 importProgress.AddTaskError(discord.ImportMessagesTask, importErr) 315 progressUpdates <- importProgress 316 continue 317 } 318 319 pinMessagesToSave = append(pinMessagesToSave, pinMessageToSave) 320 321 // Generate SystemMessagePinnedMessage 322 systemMessage, importErr := m.generateSystemPinnedMessage(pinMessageToSave, channel, clockAndTimestamp, pinnedMessageID) 323 if importErr != nil { 324 importProgress.AddTaskError(discord.ImportMessagesTask, importErr) 325 progressUpdates <- importProgress 326 continue 327 } 328 329 messagesToSave[systemMessage.ID] = systemMessage 330 } 331 } else { 332 messagesToSave[messageToSave.ID] = messageToSave 333 } 334 } 335 336 return messagesToSave, pinMessagesToSave, authorProfilesToSave, messageAttachmentsToDownload 337 } 338 339 func calculateProgress(i int, t int, currentProgress float32) float32 { 340 current := float32(1) / float32(t) * currentProgress 341 if i > 1 { 342 return float32(i-1)/float32(t) + current 343 } 344 return current 345 } 346 347 func (m *Messenger) MarkDiscordCommunityImportAsCancelled(communityID string) { 348 m.importingCommunities[communityID] = true 349 } 350 351 func (m *Messenger) MarkDiscordChannelImportAsCancelled(channelID string) { 352 m.importingChannels[channelID] = true 353 } 354 355 func (m *Messenger) DiscordImportMarkedAsCancelled(communityID string) bool { 356 cancelled, exists := m.importingCommunities[communityID] 357 return exists && cancelled 358 } 359 360 func (m *Messenger) DiscordImportChannelMarkedAsCancelled(channelID string) bool { 361 cancelled, exists := m.importingChannels[channelID] 362 return exists && cancelled 363 } 364 365 func (m *Messenger) cleanUpImports() { 366 for id := range m.importingCommunities { 367 m.cleanUpImport(id) 368 } 369 } 370 371 func (m *Messenger) cleanUpImport(communityID string) { 372 community, err := m.communitiesManager.GetByIDString(communityID) 373 if err != nil { 374 m.logger.Error("clean up failed, couldn't delete community", zap.Error(err)) 375 return 376 } 377 deleteErr := m.communitiesManager.DeleteCommunity(community.ID()) 378 if deleteErr != nil { 379 m.logger.Error("clean up failed, couldn't delete community", zap.Error(deleteErr)) 380 } 381 deleteErr = m.persistence.DeleteMessagesByCommunityID(community.IDString()) 382 if deleteErr != nil { 383 m.logger.Error("clean up failed, couldn't delete community messages", zap.Error(deleteErr)) 384 } 385 m.config.messengerSignalsHandler.DiscordCommunityImportCleanedUp(communityID) 386 } 387 388 func (m *Messenger) cleanUpImportChannel(communityID string, channelID string) { 389 _, err := m.DeleteCommunityChat(types.HexBytes(communityID), channelID) 390 if err != nil { 391 m.logger.Error("clean up failed, couldn't delete community chat", zap.Error(err)) 392 return 393 } 394 395 err = m.persistence.DeleteMessagesByChatID(channelID) 396 if err != nil { 397 m.logger.Error("clean up failed, couldn't delete community chat messages", zap.Error(err)) 398 return 399 } 400 } 401 402 func (m *Messenger) publishImportProgress(progress *discord.ImportProgress) { 403 m.config.messengerSignalsHandler.DiscordCommunityImportProgress(progress) 404 } 405 406 func (m *Messenger) publishChannelImportProgress(progress *discord.ImportProgress) { 407 m.config.messengerSignalsHandler.DiscordChannelImportProgress(progress) 408 } 409 410 func (m *Messenger) startPublishImportProgressInterval(c chan *discord.ImportProgress, cancel chan string, done chan struct{}) { 411 412 var currentProgress *discord.ImportProgress 413 414 go func() { 415 ticker := time.NewTicker(2 * time.Second) 416 defer ticker.Stop() 417 418 for { 419 select { 420 case <-ticker.C: 421 if currentProgress != nil { 422 m.publishImportProgress(currentProgress) 423 if currentProgress.Stopped { 424 return 425 } 426 } 427 case progressUpdate := <-c: 428 currentProgress = progressUpdate 429 case <-done: 430 if currentProgress != nil { 431 m.publishImportProgress(currentProgress) 432 } 433 return 434 case communityID := <-cancel: 435 if currentProgress != nil { 436 m.publishImportProgress(currentProgress) 437 } 438 m.cleanUpImport(communityID) 439 m.config.messengerSignalsHandler.DiscordCommunityImportCancelled(communityID) 440 return 441 case <-m.quit: 442 m.cleanUpImports() 443 return 444 } 445 } 446 }() 447 } 448 449 func (m *Messenger) startPublishImportChannelProgressInterval(c chan *discord.ImportProgress, cancel chan []string, done chan struct{}) { 450 451 var currentProgress *discord.ImportProgress 452 453 go func() { 454 ticker := time.NewTicker(2 * time.Second) 455 defer ticker.Stop() 456 457 for { 458 select { 459 case <-ticker.C: 460 if currentProgress != nil { 461 m.publishChannelImportProgress(currentProgress) 462 if currentProgress.Stopped { 463 return 464 } 465 } 466 case progressUpdate := <-c: 467 currentProgress = progressUpdate 468 case <-done: 469 if currentProgress != nil { 470 m.publishChannelImportProgress(currentProgress) 471 } 472 return 473 case ids := <-cancel: 474 if currentProgress != nil { 475 m.publishImportProgress(currentProgress) 476 } 477 if len(ids) > 0 { 478 communityID := ids[0] 479 channelID := ids[1] 480 discordChannelID := ids[2] 481 m.cleanUpImportChannel(communityID, channelID) 482 m.config.messengerSignalsHandler.DiscordChannelImportCancelled(discordChannelID) 483 } 484 return 485 case <-m.quit: 486 m.cleanUpImports() 487 return 488 } 489 } 490 }() 491 } 492 func createCommunityChannelForImport(request *requests.ImportDiscordChannel) *protobuf.CommunityChat { 493 return &protobuf.CommunityChat{ 494 Permissions: &protobuf.CommunityPermissions{ 495 Access: protobuf.CommunityPermissions_AUTO_ACCEPT, 496 }, 497 Identity: &protobuf.ChatIdentity{ 498 DisplayName: request.Name, 499 Emoji: request.Emoji, 500 Description: request.Description, 501 Color: request.Color, 502 }, 503 CategoryId: "", 504 HideIfPermissionsNotMet: false, 505 } 506 } 507 508 func (m *Messenger) RequestImportDiscordChannel(request *requests.ImportDiscordChannel) { 509 go func() { 510 totalImportChunkCount := len(request.FilesToImport) 511 512 progressUpdates := make(chan *discord.ImportProgress) 513 514 done := make(chan struct{}) 515 cancel := make(chan []string) 516 517 var newChat *Chat 518 519 m.startPublishImportChannelProgressInterval(progressUpdates, cancel, done) 520 521 importProgress := &discord.ImportProgress{} 522 importProgress.Init(totalImportChunkCount, []discord.ImportTask{ 523 discord.ChannelsCreationTask, 524 discord.ImportMessagesTask, 525 discord.DownloadAssetsTask, 526 discord.InitCommunityTask, 527 }) 528 529 importProgress.ChannelID = request.DiscordChannelID 530 importProgress.ChannelName = request.Name 531 // initial progress immediately 532 533 if err := request.Validate(); err != nil { 534 errmsg := fmt.Sprintf("Request validation failed: '%s'", err.Error()) 535 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg)) 536 importProgress.StopTask(discord.ChannelsCreationTask) 537 progressUpdates <- importProgress 538 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 539 return 540 } 541 542 // Here's 3 steps: Find the corrent channel in files, get the community and create the channel 543 progressValue := float32(0.3) 544 545 m.publishChannelImportProgress(importProgress) 546 547 community, err := m.GetCommunityByID(request.CommunityID) 548 549 if err != nil { 550 errmsg := fmt.Sprintf("Couldn't get the community '%s': '%s'", request.CommunityID, err.Error()) 551 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg)) 552 importProgress.StopTask(discord.ChannelsCreationTask) 553 progressUpdates <- importProgress 554 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 555 return 556 } 557 558 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, progressValue) 559 progressUpdates <- importProgress 560 561 for i, importFile := range request.FilesToImport { 562 m.importingChannels[request.DiscordChannelID] = false 563 564 exportData, errs := m.ExtractDiscordDataFromImportFiles([]string{importFile}) 565 if len(errs) > 0 { 566 for _, err := range errs { 567 importProgress.AddTaskError(discord.ChannelsCreationTask, err) 568 } 569 importProgress.StopTask(discord.ChannelsCreationTask) 570 progressUpdates <- importProgress 571 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 572 return 573 } 574 575 var channel *discord.ExportedData 576 577 for _, ch := range exportData.ExportedData { 578 if ch.Channel.ID == request.DiscordChannelID { 579 channel = ch 580 } 581 } 582 583 if channel == nil { 584 if i < len(request.FilesToImport)-1 { 585 // skip this file 586 continue 587 } else if i == len(request.FilesToImport)-1 { 588 errmsg := fmt.Sprintf("Couldn't find the target channel id in files: '%s'", request.DiscordChannelID) 589 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg)) 590 importProgress.StopTask(discord.ChannelsCreationTask) 591 progressUpdates <- importProgress 592 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 593 return 594 } 595 } 596 progressValue := float32(0.6) 597 598 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, progressValue) 599 progressUpdates <- importProgress 600 601 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 602 importProgress.StopTask(discord.ChannelsCreationTask) 603 progressUpdates <- importProgress 604 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 605 return 606 } 607 608 if len(channel.Channel.ID) == 0 { 609 // skip this file and try to find in the next file 610 continue 611 } 612 exists := false 613 614 for _, chatID := range community.ChatIDs() { 615 if strings.HasSuffix(chatID, request.DiscordChannelID) { 616 exists = true 617 break 618 } 619 } 620 621 if !exists { 622 communityChat := createCommunityChannelForImport(request) 623 624 changes, err := m.communitiesManager.CreateChat(request.CommunityID, communityChat, false, channel.Channel.ID) 625 if err != nil { 626 errmsg := err.Error() 627 if errors.Is(err, communities.ErrInvalidCommunityDescriptionDuplicatedName) { 628 errmsg = fmt.Sprintf("Couldn't create channel '%s': %s", communityChat.Identity.DisplayName, err.Error()) 629 fmt.Println(errmsg) 630 } 631 632 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg)) 633 importProgress.StopTask(discord.ChannelsCreationTask) 634 progressUpdates <- importProgress 635 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 636 return 637 } 638 639 community = changes.Community 640 for chatID, chat := range changes.ChatsAdded { 641 newChat = CreateCommunityChat(request.CommunityID.String(), chatID, chat, m.getTimesource()) 642 } 643 644 progressValue = float32(1.0) 645 646 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, progressValue) 647 progressUpdates <- importProgress 648 } else { 649 // When channel with current discord id already exist we should skip import 650 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error("Channel already imported to this community")) 651 importProgress.StopTask(discord.ChannelsCreationTask) 652 progressUpdates <- importProgress 653 cancel <- []string{request.CommunityID.String(), "", request.DiscordChannelID} 654 return 655 } 656 657 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 658 importProgress.StopTask(discord.ImportMessagesTask) 659 progressUpdates <- importProgress 660 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 661 return 662 } 663 664 messagesToSave, pinMessagesToSave, authorProfilesToSave, messageAttachmentsToDownload := 665 m.processDiscordMessages(channel, newChat, importProgress, progressUpdates, request.From, community) 666 667 // If either there were no messages in the channel or something happened and all the messages errored, we 668 // we still up the percent to 100% 669 if len(messagesToSave) == 0 { 670 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, 1.0) 671 progressUpdates <- importProgress 672 } 673 674 var discordMessages []*protobuf.DiscordMessage 675 for _, msg := range messagesToSave { 676 if msg.ChatMessage.ContentType == protobuf.ChatMessage_DISCORD_MESSAGE { 677 discordMessages = append(discordMessages, msg.GetDiscordMessage()) 678 } 679 } 680 681 // We save these messages in chunks, so we don't block the database 682 // for a longer period of time 683 discordMessageChunks := chunkSlice(discordMessages, maxChunkSizeMessages) 684 chunksCount := len(discordMessageChunks) 685 686 for ii, msgs := range discordMessageChunks { 687 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d discord messages", ii+1, chunksCount, len(msgs))) 688 err := m.persistence.SaveDiscordMessages(msgs) 689 if err != nil { 690 m.cleanUpImportChannel(request.CommunityID.String(), newChat.ID) 691 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 692 importProgress.StopTask(discord.ImportMessagesTask) 693 progressUpdates <- importProgress 694 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 695 return 696 } 697 698 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 699 importProgress.StopTask(discord.ImportMessagesTask) 700 progressUpdates <- importProgress 701 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 702 return 703 } 704 705 // We're multiplying `chunksCount` by `0.25` so we leave 25% for additional save operations 706 // 0.5 are the previous 50% of progress 707 currentCount := ii + 1 708 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.5+(float32(currentCount)/float32(chunksCount))*0.25) 709 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, progressValue) 710 progressUpdates <- importProgress 711 712 // We slow down the saving of message chunks to keep the database responsive 713 if currentCount < chunksCount { 714 time.Sleep(2 * time.Second) 715 } 716 } 717 718 // Get slice of all values in `messagesToSave` map 719 var messages = make([]*common.Message, 0, len(messagesToSave)) 720 for _, msg := range messagesToSave { 721 messages = append(messages, msg) 722 } 723 724 // Same as above, we save these messages in chunks so we don't block 725 // the database for a longer period of time 726 messageChunks := chunkSlice(messages, maxChunkSizeMessages) 727 chunksCount = len(messageChunks) 728 729 for ii, msgs := range messageChunks { 730 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d app messages", ii+1, chunksCount, len(msgs))) 731 err := m.persistence.SaveMessages(msgs) 732 if err != nil { 733 m.cleanUpImportChannel(request.CommunityID.String(), request.DiscordChannelID) 734 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 735 importProgress.StopTask(discord.ImportMessagesTask) 736 progressUpdates <- importProgress 737 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 738 739 return 740 } 741 742 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 743 importProgress.StopTask(discord.ImportMessagesTask) 744 progressUpdates <- importProgress 745 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 746 return 747 } 748 749 // 0.75 are the previous 75% of progress, hence we multiply our chunk progress 750 // by 0.25 751 currentCount := ii + 1 752 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.75+(float32(currentCount)/float32(chunksCount))*0.25) 753 // progressValue := 0.75 + ((float32(currentCount) / float32(chunksCount)) * 0.25) 754 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, progressValue) 755 progressUpdates <- importProgress 756 757 // We slow down the saving of message chunks to keep the database responsive 758 if currentCount < chunksCount { 759 time.Sleep(2 * time.Second) 760 } 761 } 762 763 pinMessageChunks := chunkSlice(pinMessagesToSave, maxChunkSizeMessages) 764 for _, pinMsgs := range pinMessageChunks { 765 err := m.persistence.SavePinMessages(pinMsgs) 766 if err != nil { 767 m.cleanUpImportChannel(request.CommunityID.String(), request.DiscordChannelID) 768 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 769 importProgress.StopTask(discord.ImportMessagesTask) 770 progressUpdates <- importProgress 771 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 772 773 return 774 } 775 776 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 777 importProgress.StopTask(discord.ImportMessagesTask) 778 progressUpdates <- importProgress 779 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 780 return 781 } 782 } 783 784 totalAssetsCount := len(messageAttachmentsToDownload) + len(authorProfilesToSave) 785 var assetCounter discord.AssetCounter 786 787 var wg sync.WaitGroup 788 789 for id, author := range authorProfilesToSave { 790 wg.Add(1) 791 go func(id string, author *protobuf.DiscordMessageAuthor) { 792 defer wg.Done() 793 794 m.logger.Debug(fmt.Sprintf("downloading asset %d/%d", assetCounter.Value()+1, totalAssetsCount)) 795 imagePayload, err := discord.DownloadAvatarAsset(author.AvatarUrl) 796 if err != nil { 797 errmsg := fmt.Sprintf("Couldn't download profile avatar '%s': %s", author.AvatarUrl, err.Error()) 798 importProgress.AddTaskError( 799 discord.DownloadAssetsTask, 800 discord.Warning(errmsg), 801 ) 802 progressUpdates <- importProgress 803 804 return 805 } 806 807 err = m.persistence.UpdateDiscordMessageAuthorImage(author.Id, imagePayload) 808 if err != nil { 809 importProgress.AddTaskError(discord.DownloadAssetsTask, discord.Warning(err.Error())) 810 progressUpdates <- importProgress 811 812 return 813 } 814 815 author.AvatarImagePayload = imagePayload 816 authorProfilesToSave[id] = author 817 818 if m.DiscordImportMarkedAsCancelled(request.DiscordChannelID) { 819 importProgress.StopTask(discord.DownloadAssetsTask) 820 progressUpdates <- importProgress 821 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 822 return 823 } 824 825 assetCounter.Increase() 826 progressValue := calculateProgress(i+1, totalImportChunkCount, (float32(assetCounter.Value())/float32(totalAssetsCount))*0.5) 827 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 828 progressUpdates <- importProgress 829 830 }(id, author) 831 } 832 wg.Wait() 833 834 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 835 importProgress.StopTask(discord.DownloadAssetsTask) 836 progressUpdates <- importProgress 837 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 838 return 839 } 840 841 for idxRange := range gopart.Partition(len(messageAttachmentsToDownload), 100) { 842 attachments := messageAttachmentsToDownload[idxRange.Low:idxRange.High] 843 wg.Add(1) 844 go func(attachments []*protobuf.DiscordMessageAttachment) { 845 defer wg.Done() 846 for ii, attachment := range attachments { 847 848 m.logger.Debug(fmt.Sprintf("downloading asset %d/%d", assetCounter.Value()+1, totalAssetsCount)) 849 850 assetPayload, contentType, err := discord.DownloadAsset(attachment.Url) 851 if err != nil { 852 errmsg := fmt.Sprintf("Couldn't download message attachment '%s': %s", attachment.Url, err.Error()) 853 importProgress.AddTaskError( 854 discord.DownloadAssetsTask, 855 discord.Warning(errmsg), 856 ) 857 progressUpdates <- importProgress 858 continue 859 } 860 861 attachment.Payload = assetPayload 862 attachment.ContentType = contentType 863 messageAttachmentsToDownload[ii] = attachment 864 865 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 866 importProgress.StopTask(discord.DownloadAssetsTask) 867 progressUpdates <- importProgress 868 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 869 return 870 } 871 872 assetCounter.Increase() 873 progressValue := calculateProgress(i+1, totalImportChunkCount, (float32(assetCounter.Value())/float32(totalAssetsCount))*0.5) 874 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 875 progressUpdates <- importProgress 876 } 877 }(attachments) 878 } 879 wg.Wait() 880 881 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 882 importProgress.StopTask(discord.DownloadAssetsTask) 883 progressUpdates <- importProgress 884 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 885 return 886 } 887 888 attachmentChunks := chunkAttachmentsByByteSize(messageAttachmentsToDownload, maxChunkSizeBytes) 889 chunksCount = len(attachmentChunks) 890 891 for ii, attachments := range attachmentChunks { 892 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d discord message attachments", ii+1, chunksCount, len(attachments))) 893 err := m.persistence.SaveDiscordMessageAttachments(attachments) 894 if err != nil { 895 importProgress.AddTaskError(discord.DownloadAssetsTask, discord.Warning(err.Error())) 896 importProgress.Stop() 897 progressUpdates <- importProgress 898 899 continue 900 } 901 902 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 903 importProgress.StopTask(discord.DownloadAssetsTask) 904 progressUpdates <- importProgress 905 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 906 return 907 } 908 909 // 0.5 are the previous 50% of progress, hence we multiply our chunk progress 910 // by 0.5 911 currentCount := ii + 1 912 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.5+(float32(currentCount)/float32(chunksCount))*0.5) 913 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 914 progressUpdates <- importProgress 915 916 // We slow down the saving of attachment chunks to keep the database responsive 917 if currentCount < chunksCount { 918 time.Sleep(2 * time.Second) 919 } 920 } 921 922 if len(attachmentChunks) == 0 { 923 progressValue := calculateProgress(i+1, totalImportChunkCount, 1.0) 924 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 925 } 926 927 _, err := m.transport.JoinPublic(newChat.ID) 928 if err != nil { 929 m.logger.Error("failed to load filter for chat", zap.Error(err)) 930 continue 931 } 932 933 wakuChatMessages, err := m.chatMessagesToWakuMessages(messages, community) 934 if err != nil { 935 m.logger.Error("failed to convert chat messages into waku messages", zap.Error(err)) 936 continue 937 } 938 939 wakuPinMessages, err := m.pinMessagesToWakuMessages(pinMessagesToSave, community) 940 if err != nil { 941 m.logger.Error("failed to convert pin messages into waku messages", zap.Error(err)) 942 continue 943 } 944 945 wakuMessages := append(wakuChatMessages, wakuPinMessages...) 946 947 topics, err := m.archiveManager.GetCommunityChatsTopics(request.CommunityID) 948 if err != nil { 949 m.logger.Error("failed to get community chat topics", zap.Error(err)) 950 continue 951 } 952 953 startDate := time.Unix(int64(exportData.OldestMessageTimestamp), 0) 954 endDate := time.Now() 955 956 _, err = m.archiveManager.CreateHistoryArchiveTorrentFromMessages( 957 request.CommunityID, 958 wakuMessages, 959 topics, 960 startDate, 961 endDate, 962 messageArchiveInterval, 963 community.Encrypted(), 964 ) 965 if err != nil { 966 m.logger.Error("failed to create history archive torrent", zap.Error(err)) 967 continue 968 } 969 communitySettings, err := m.communitiesManager.GetCommunitySettingsByID(request.CommunityID) 970 if err != nil { 971 m.logger.Error("Failed to get community settings", zap.Error(err)) 972 continue 973 } 974 if m.archiveManager.IsReady() && communitySettings.HistoryArchiveSupportEnabled { 975 976 err = m.archiveManager.SeedHistoryArchiveTorrent(request.CommunityID) 977 if err != nil { 978 m.logger.Error("failed to seed history archive", zap.Error(err)) 979 } 980 go m.archiveManager.StartHistoryArchiveTasksInterval(community, messageArchiveInterval) 981 } 982 } 983 984 importProgress.UpdateTaskProgress(discord.InitCommunityTask, float32(0.0)) 985 986 if m.DiscordImportChannelMarkedAsCancelled(request.DiscordChannelID) { 987 importProgress.StopTask(discord.InitCommunityTask) 988 progressUpdates <- importProgress 989 cancel <- []string{request.CommunityID.String(), newChat.ID, request.DiscordChannelID} 990 return 991 } 992 993 // Chats need to be saved after the community has been published, 994 // hence we make this part of the `InitCommunityTask` 995 err = m.saveChat(newChat) 996 997 if err != nil { 998 m.cleanUpImportChannel(request.CommunityID.String(), request.DiscordChannelID) 999 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1000 importProgress.Stop() 1001 progressUpdates <- importProgress 1002 cancel <- []string{request.CommunityID.String(), request.DiscordChannelID} 1003 return 1004 } 1005 1006 // Make sure all progress tasks are at 100%, in case one of the steps had errors 1007 // The front-end doesn't understand that the import is done until all tasks are at 100% 1008 importProgress.UpdateTaskProgress(discord.CommunityCreationTask, float32(1.0)) 1009 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, float32(1.0)) 1010 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, float32(1.0)) 1011 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, float32(1.0)) 1012 importProgress.UpdateTaskProgress(discord.InitCommunityTask, float32(1.0)) 1013 1014 m.config.messengerSignalsHandler.DiscordChannelImportFinished(request.CommunityID.String(), newChat.ID) 1015 close(done) 1016 }() 1017 } 1018 1019 func (m *Messenger) RequestImportDiscordCommunity(request *requests.ImportDiscordCommunity) { 1020 go func() { 1021 1022 totalImportChunkCount := len(request.FilesToImport) 1023 1024 progressUpdates := make(chan *discord.ImportProgress) 1025 done := make(chan struct{}) 1026 cancel := make(chan string) 1027 m.startPublishImportProgressInterval(progressUpdates, cancel, done) 1028 1029 importProgress := &discord.ImportProgress{} 1030 importProgress.Init(totalImportChunkCount, []discord.ImportTask{ 1031 discord.CommunityCreationTask, 1032 discord.ChannelsCreationTask, 1033 discord.ImportMessagesTask, 1034 discord.DownloadAssetsTask, 1035 discord.InitCommunityTask, 1036 }) 1037 importProgress.CommunityName = request.Name 1038 1039 // initial progress immediately 1040 m.publishImportProgress(importProgress) 1041 1042 createCommunityRequest := request.ToCreateCommunityRequest() 1043 1044 // We're calling `CreateCommunity` on `communitiesManager` directly, instead of 1045 // using the `Messenger` API, so we get more control over when we set up filters, 1046 // the community is published and data is being synced (we don't want the community 1047 // to show up in clients while the import is in progress) 1048 discordCommunity, err := m.communitiesManager.CreateCommunity(createCommunityRequest, false) 1049 if err != nil { 1050 importProgress.AddTaskError(discord.CommunityCreationTask, discord.Error(err.Error())) 1051 importProgress.StopTask(discord.CommunityCreationTask) 1052 progressUpdates <- importProgress 1053 return 1054 } 1055 1056 communitySettings := communities.CommunitySettings{ 1057 CommunityID: discordCommunity.IDString(), 1058 HistoryArchiveSupportEnabled: true, 1059 } 1060 err = m.communitiesManager.SaveCommunitySettings(communitySettings) 1061 if err != nil { 1062 m.cleanUpImport(discordCommunity.IDString()) 1063 importProgress.AddTaskError(discord.CommunityCreationTask, discord.Error(err.Error())) 1064 importProgress.StopTask(discord.CommunityCreationTask) 1065 progressUpdates <- importProgress 1066 return 1067 } 1068 1069 communityID := discordCommunity.IDString() 1070 1071 // marking import as not cancelled 1072 m.importingCommunities[communityID] = false 1073 importProgress.CommunityID = communityID 1074 importProgress.CommunityImages = make(map[string]images.IdentityImage) 1075 1076 imgs := discordCommunity.Images() 1077 for t, i := range imgs { 1078 importProgress.CommunityImages[t] = images.IdentityImage{Name: t, Payload: i.Payload} 1079 } 1080 1081 importProgress.UpdateTaskProgress(discord.CommunityCreationTask, 1) 1082 progressUpdates <- importProgress 1083 1084 if m.DiscordImportMarkedAsCancelled(communityID) { 1085 importProgress.StopTask(discord.CommunityCreationTask) 1086 progressUpdates <- importProgress 1087 cancel <- communityID 1088 return 1089 } 1090 1091 var chatsToSave []*Chat 1092 createdChats := make(map[string]*Chat, 0) 1093 processedChannelIds := make(map[string]string, 0) 1094 processedCategoriesIds := make(map[string]string, 0) 1095 1096 // The map with counts of duplicated channel names 1097 uniqueChatNames := make(map[string]int, 0) 1098 1099 for i, importFile := range request.FilesToImport { 1100 1101 exportData, errs := m.ExtractDiscordDataFromImportFiles([]string{importFile}) 1102 if len(errs) > 0 { 1103 for _, err := range errs { 1104 importProgress.AddTaskError(discord.CommunityCreationTask, err) 1105 } 1106 progressUpdates <- importProgress 1107 return 1108 } 1109 totalChannelsCount := len(exportData.ExportedData) 1110 totalMessageCount := exportData.MessageCount 1111 1112 if totalChannelsCount == 0 || totalMessageCount == 0 { 1113 importError := discord.Error(fmt.Errorf("No channel to import messages from in file: %s", importFile).Error()) 1114 if totalMessageCount == 0 { 1115 importError.Message = fmt.Errorf("No messages to import in file: %s", importFile).Error() 1116 } 1117 importProgress.AddTaskError(discord.ChannelsCreationTask, importError) 1118 progressUpdates <- importProgress 1119 continue 1120 } 1121 1122 importProgress.CurrentChunk = i + 1 1123 1124 // We actually only ever receive a single category 1125 // from `exportData` but since it's a map, we still have to 1126 // iterate over it to access its values 1127 for _, category := range exportData.Categories { 1128 1129 categories := discordCommunity.Categories() 1130 exists := false 1131 for catID := range categories { 1132 if strings.HasSuffix(catID, category.ID) { 1133 exists = true 1134 break 1135 } 1136 } 1137 1138 if !exists { 1139 createCommunityCategoryRequest := &requests.CreateCommunityCategory{ 1140 CommunityID: discordCommunity.ID(), 1141 CategoryName: category.Name, 1142 ThirdPartyID: category.ID, 1143 ChatIDs: make([]string, 0), 1144 } 1145 // We call `CreateCategory` on `communitiesManager` directly so we can control 1146 // whether or not the community update should be published (it should not until the 1147 // import has finished) 1148 communityWithCategories, changes, err := m.communitiesManager.CreateCategory(createCommunityCategoryRequest, false) 1149 if err != nil { 1150 m.cleanUpImport(communityID) 1151 importProgress.AddTaskError(discord.CommunityCreationTask, discord.Error(err.Error())) 1152 importProgress.StopTask(discord.CommunityCreationTask) 1153 progressUpdates <- importProgress 1154 return 1155 } 1156 discordCommunity = communityWithCategories 1157 // This looks like we keep overriding the same field but there's 1158 // only one `CategoriesAdded` change at this point. 1159 for _, addedCategory := range changes.CategoriesAdded { 1160 processedCategoriesIds[category.ID] = addedCategory.CategoryId 1161 } 1162 } 1163 } 1164 1165 progressValue := calculateProgress(i+1, totalImportChunkCount, (float32(1) / 2)) 1166 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, progressValue) 1167 1168 progressUpdates <- importProgress 1169 1170 if m.DiscordImportMarkedAsCancelled(communityID) { 1171 importProgress.StopTask(discord.CommunityCreationTask) 1172 progressUpdates <- importProgress 1173 cancel <- communityID 1174 return 1175 } 1176 1177 messagesToSave := make(map[string]*common.Message, 0) 1178 pinMessagesToSave := make([]*common.PinMessage, 0) 1179 authorProfilesToSave := make(map[string]*protobuf.DiscordMessageAuthor, 0) 1180 messageAttachmentsToDownload := make([]*protobuf.DiscordMessageAttachment, 0) 1181 1182 // Save to access the first item here as we process 1183 // exported data by files which only holds a single channel 1184 channel := exportData.ExportedData[0] 1185 chatIDs := discordCommunity.ChatIDs() 1186 1187 exists := false 1188 for _, chatID := range chatIDs { 1189 if strings.HasSuffix(chatID, channel.Channel.ID) { 1190 exists = true 1191 break 1192 } 1193 } 1194 1195 if !exists { 1196 channelUniqueName := channel.Channel.Name 1197 if count, ok := uniqueChatNames[channelUniqueName]; ok { 1198 uniqueChatNames[channelUniqueName] = count + 1 1199 channelUniqueName = fmt.Sprintf("%s_%d", channelUniqueName, uniqueChatNames[channelUniqueName]) 1200 } else { 1201 uniqueChatNames[channelUniqueName] = 1 1202 } 1203 1204 communityChat := &protobuf.CommunityChat{ 1205 Permissions: &protobuf.CommunityPermissions{ 1206 Access: protobuf.CommunityPermissions_AUTO_ACCEPT, 1207 }, 1208 Identity: &protobuf.ChatIdentity{ 1209 DisplayName: channelUniqueName, 1210 Emoji: "", 1211 Description: channel.Channel.Description, 1212 Color: discordCommunity.Color(), 1213 }, 1214 CategoryId: processedCategoriesIds[channel.Channel.CategoryID], 1215 HideIfPermissionsNotMet: false, 1216 } 1217 1218 // We call `CreateChat` on `communitiesManager` directly to get more control 1219 // over whether we want to publish the updated community description. 1220 changes, err := m.communitiesManager.CreateChat(discordCommunity.ID(), communityChat, false, channel.Channel.ID) 1221 if err != nil { 1222 m.cleanUpImport(communityID) 1223 errmsg := err.Error() 1224 if errors.Is(err, communities.ErrInvalidCommunityDescriptionDuplicatedName) { 1225 errmsg = fmt.Sprintf("Couldn't create channel '%s': %s", communityChat.Identity.DisplayName, err.Error()) 1226 } 1227 importProgress.AddTaskError(discord.ChannelsCreationTask, discord.Error(errmsg)) 1228 importProgress.StopTask(discord.ChannelsCreationTask) 1229 progressUpdates <- importProgress 1230 return 1231 } 1232 discordCommunity = changes.Community 1233 1234 // This looks like we keep overriding the chat id value 1235 // as we iterate over `ChatsAdded`, however at this point we 1236 // know there was only a single such change (and it's a map) 1237 for chatID, chat := range changes.ChatsAdded { 1238 c := CreateCommunityChat(communityID, chatID, chat, m.getTimesource()) 1239 createdChats[c.ID] = c 1240 chatsToSave = append(chatsToSave, c) 1241 processedChannelIds[channel.Channel.ID] = c.ID 1242 } 1243 } 1244 1245 progressValue = calculateProgress(i+1, totalImportChunkCount, 1) 1246 importProgress.UpdateTaskProgress(discord.ChannelsCreationTask, progressValue) 1247 progressUpdates <- importProgress 1248 1249 for ii, discordMessage := range channel.Messages { 1250 1251 timestamp, err := time.Parse(discordTimestampLayout, discordMessage.Timestamp) 1252 if err != nil { 1253 m.logger.Error("failed to parse discord message timestamp", zap.Error(err)) 1254 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1255 progressUpdates <- importProgress 1256 continue 1257 } 1258 1259 if timestamp.Unix() < request.From { 1260 progressUpdates <- importProgress 1261 continue 1262 } 1263 1264 exists, err := m.persistence.HasDiscordMessageAuthor(discordMessage.Author.GetId()) 1265 if err != nil { 1266 m.logger.Error("failed to check if message author exists in database", zap.Error(err)) 1267 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1268 progressUpdates <- importProgress 1269 continue 1270 } 1271 1272 if !exists { 1273 err := m.persistence.SaveDiscordMessageAuthor(discordMessage.Author) 1274 if err != nil { 1275 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1276 progressUpdates <- importProgress 1277 continue 1278 } 1279 } 1280 1281 hasPayload, err := m.persistence.HasDiscordMessageAuthorImagePayload(discordMessage.Author.GetId()) 1282 if err != nil { 1283 m.logger.Error("failed to check if message avatar payload exists in database", zap.Error(err)) 1284 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1285 progressUpdates <- importProgress 1286 continue 1287 } 1288 1289 if !hasPayload { 1290 authorProfilesToSave[discordMessage.Author.Id] = discordMessage.Author 1291 } 1292 1293 // Convert timestamp to unix timestamp 1294 discordMessage.Timestamp = fmt.Sprintf("%d", timestamp.Unix()) 1295 1296 if discordMessage.TimestampEdited != "" { 1297 timestampEdited, err := time.Parse(discordTimestampLayout, discordMessage.TimestampEdited) 1298 if err != nil { 1299 m.logger.Error("failed to parse discord message timestamp", zap.Error(err)) 1300 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1301 progressUpdates <- importProgress 1302 continue 1303 } 1304 // Convert timestamp to unix timestamp 1305 discordMessage.TimestampEdited = fmt.Sprintf("%d", timestampEdited.Unix()) 1306 } 1307 1308 for i := range discordMessage.Attachments { 1309 discordMessage.Attachments[i].MessageId = discordMessage.Id 1310 } 1311 messageAttachmentsToDownload = append(messageAttachmentsToDownload, discordMessage.Attachments...) 1312 1313 clockAndTimestamp := uint64(timestamp.Unix()) * 1000 1314 communityPubKey := discordCommunity.PrivateKey().PublicKey 1315 1316 chatMessage := protobuf.ChatMessage{ 1317 Timestamp: clockAndTimestamp, 1318 MessageType: protobuf.MessageType_COMMUNITY_CHAT, 1319 ContentType: protobuf.ChatMessage_DISCORD_MESSAGE, 1320 Clock: clockAndTimestamp, 1321 ChatId: processedChannelIds[channel.Channel.ID], 1322 Payload: &protobuf.ChatMessage_DiscordMessage{ 1323 DiscordMessage: discordMessage, 1324 }, 1325 } 1326 1327 // Handle message replies 1328 if discordMessage.Type == string(discord.MessageTypeReply) && discordMessage.Reference != nil { 1329 repliedMessageID := communityID + discordMessage.Reference.MessageId 1330 if _, exists := messagesToSave[repliedMessageID]; exists { 1331 chatMessage.ResponseTo = repliedMessageID 1332 } 1333 } 1334 1335 messageToSave := &common.Message{ 1336 ID: communityID + discordMessage.Id, 1337 WhisperTimestamp: clockAndTimestamp, 1338 From: types.EncodeHex(crypto.FromECDSAPub(&communityPubKey)), 1339 Seen: true, 1340 LocalChatID: processedChannelIds[channel.Channel.ID], 1341 SigPubKey: &communityPubKey, 1342 CommunityID: communityID, 1343 ChatMessage: &chatMessage, 1344 } 1345 1346 err = messageToSave.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)) 1347 if err != nil { 1348 m.logger.Error("failed to prepare message content", zap.Error(err)) 1349 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1350 progressUpdates <- importProgress 1351 continue 1352 } 1353 1354 // Handle pin messages 1355 if discordMessage.Type == string(discord.MessageTypeChannelPinned) && discordMessage.Reference != nil { 1356 1357 pinnedMessageID := communityID + discordMessage.Reference.MessageId 1358 _, exists := messagesToSave[pinnedMessageID] 1359 if exists { 1360 pinMessage := protobuf.PinMessage{ 1361 Clock: messageToSave.WhisperTimestamp, 1362 MessageId: pinnedMessageID, 1363 ChatId: messageToSave.LocalChatID, 1364 MessageType: protobuf.MessageType_COMMUNITY_CHAT, 1365 Pinned: true, 1366 } 1367 1368 encodedPayload, err := proto.Marshal(&pinMessage) 1369 if err != nil { 1370 m.logger.Error("failed to parse marshal pin message", zap.Error(err)) 1371 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1372 progressUpdates <- importProgress 1373 continue 1374 } 1375 1376 wrappedPayload, err := v1protocol.WrapMessageV1(encodedPayload, protobuf.ApplicationMetadataMessage_PIN_MESSAGE, discordCommunity.PrivateKey()) 1377 if err != nil { 1378 m.logger.Error("failed to wrap pin message", zap.Error(err)) 1379 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1380 progressUpdates <- importProgress 1381 continue 1382 } 1383 1384 pinMessageToSave := common.PinMessage{ 1385 ID: types.EncodeHex(v1protocol.MessageID(&communityPubKey, wrappedPayload)), 1386 PinMessage: &pinMessage, 1387 LocalChatID: processedChannelIds[channel.Channel.ID], 1388 From: messageToSave.From, 1389 SigPubKey: messageToSave.SigPubKey, 1390 WhisperTimestamp: messageToSave.WhisperTimestamp, 1391 } 1392 1393 pinMessagesToSave = append(pinMessagesToSave, &pinMessageToSave) 1394 1395 // Generate SystemMessagePinnedMessage 1396 1397 chat, ok := createdChats[pinMessageToSave.LocalChatID] 1398 if !ok { 1399 err := errors.New("failed to get chat for pin message") 1400 m.logger.Warn(err.Error(), 1401 zap.String("PinMessageId", pinMessageToSave.ID), 1402 zap.String("ChatID", pinMessageToSave.LocalChatID)) 1403 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1404 progressUpdates <- importProgress 1405 continue 1406 } 1407 1408 id, err := generatePinMessageNotificationID(&m.identity.PublicKey, &pinMessageToSave, chat) 1409 if err != nil { 1410 m.logger.Warn("failed to generate pin message notification ID", 1411 zap.String("PinMessageId", pinMessageToSave.ID)) 1412 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Warning(err.Error())) 1413 progressUpdates <- importProgress 1414 continue 1415 } 1416 systemMessage := &common.Message{ 1417 ChatMessage: &protobuf.ChatMessage{ 1418 Clock: pinMessageToSave.Clock, 1419 Timestamp: clockAndTimestamp, 1420 ChatId: chat.ID, 1421 MessageType: pinMessageToSave.MessageType, 1422 ResponseTo: pinMessage.MessageId, 1423 ContentType: protobuf.ChatMessage_SYSTEM_MESSAGE_PINNED_MESSAGE, 1424 }, 1425 WhisperTimestamp: clockAndTimestamp, 1426 ID: id, 1427 LocalChatID: chat.ID, 1428 From: messageToSave.From, 1429 Seen: true, 1430 } 1431 1432 messagesToSave[systemMessage.ID] = systemMessage 1433 } 1434 } else { 1435 messagesToSave[messageToSave.ID] = messageToSave 1436 } 1437 1438 progressValue := calculateProgress(i+1, totalImportChunkCount, float32(ii+1)/float32(len(channel.Messages))*0.5) 1439 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, progressValue) 1440 progressUpdates <- importProgress 1441 } 1442 1443 if m.DiscordImportMarkedAsCancelled(communityID) { 1444 importProgress.StopTask(discord.ImportMessagesTask) 1445 progressUpdates <- importProgress 1446 cancel <- communityID 1447 return 1448 } 1449 1450 var discordMessages []*protobuf.DiscordMessage 1451 for _, msg := range messagesToSave { 1452 if msg.ChatMessage.ContentType == protobuf.ChatMessage_DISCORD_MESSAGE { 1453 discordMessages = append(discordMessages, msg.GetDiscordMessage()) 1454 } 1455 } 1456 1457 // We save these messages in chunks, so we don't block the database 1458 // for a longer period of time 1459 discordMessageChunks := chunkSlice(discordMessages, maxChunkSizeMessages) 1460 chunksCount := len(discordMessageChunks) 1461 1462 for ii, msgs := range discordMessageChunks { 1463 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d discord messages", ii+1, chunksCount, len(msgs))) 1464 err = m.persistence.SaveDiscordMessages(msgs) 1465 if err != nil { 1466 m.cleanUpImport(communityID) 1467 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1468 importProgress.StopTask(discord.ImportMessagesTask) 1469 progressUpdates <- importProgress 1470 return 1471 } 1472 1473 if m.DiscordImportMarkedAsCancelled(communityID) { 1474 importProgress.StopTask(discord.ImportMessagesTask) 1475 progressUpdates <- importProgress 1476 cancel <- communityID 1477 return 1478 } 1479 1480 // We're multiplying `chunksCount` by `0.25` so we leave 25% for additional save operations 1481 // 0.5 are the previous 50% of progress 1482 currentCount := ii + 1 1483 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.5+(float32(currentCount)/float32(chunksCount))*0.25) 1484 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, progressValue) 1485 progressUpdates <- importProgress 1486 1487 // We slow down the saving of message chunks to keep the database responsive 1488 if currentCount < chunksCount { 1489 time.Sleep(2 * time.Second) 1490 } 1491 } 1492 1493 // Get slice of all values in `messagesToSave` map 1494 1495 var messages = make([]*common.Message, 0, len(messagesToSave)) 1496 for _, msg := range messagesToSave { 1497 messages = append(messages, msg) 1498 } 1499 1500 // Same as above, we save these messages in chunks so we don't block 1501 // the database for a longer period of time 1502 messageChunks := chunkSlice(messages, maxChunkSizeMessages) 1503 chunksCount = len(messageChunks) 1504 1505 for ii, msgs := range messageChunks { 1506 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d app messages", ii+1, chunksCount, len(msgs))) 1507 err = m.persistence.SaveMessages(msgs) 1508 if err != nil { 1509 m.cleanUpImport(communityID) 1510 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1511 importProgress.StopTask(discord.ImportMessagesTask) 1512 progressUpdates <- importProgress 1513 return 1514 } 1515 1516 if m.DiscordImportMarkedAsCancelled(communityID) { 1517 importProgress.StopTask(discord.ImportMessagesTask) 1518 progressUpdates <- importProgress 1519 cancel <- communityID 1520 return 1521 } 1522 1523 // 0.75 are the previous 75% of progress, hence we multiply our chunk progress 1524 // by 0.25 1525 currentCount := ii + 1 1526 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.75+(float32(currentCount)/float32(chunksCount))*0.25) 1527 // progressValue := 0.75 + ((float32(currentCount) / float32(chunksCount)) * 0.25) 1528 importProgress.UpdateTaskProgress(discord.ImportMessagesTask, progressValue) 1529 progressUpdates <- importProgress 1530 1531 // We slow down the saving of message chunks to keep the database responsive 1532 if currentCount < chunksCount { 1533 time.Sleep(2 * time.Second) 1534 } 1535 } 1536 1537 pinMessageChunks := chunkSlice(pinMessagesToSave, maxChunkSizeMessages) 1538 for _, pinMsgs := range pinMessageChunks { 1539 err = m.persistence.SavePinMessages(pinMsgs) 1540 if err != nil { 1541 m.cleanUpImport(communityID) 1542 importProgress.AddTaskError(discord.ImportMessagesTask, discord.Error(err.Error())) 1543 importProgress.StopTask(discord.ImportMessagesTask) 1544 progressUpdates <- importProgress 1545 return 1546 } 1547 1548 if m.DiscordImportMarkedAsCancelled(communityID) { 1549 importProgress.StopTask(discord.ImportMessagesTask) 1550 progressUpdates <- importProgress 1551 cancel <- communityID 1552 return 1553 } 1554 } 1555 1556 totalAssetsCount := len(messageAttachmentsToDownload) + len(authorProfilesToSave) 1557 var assetCounter discord.AssetCounter 1558 1559 var wg sync.WaitGroup 1560 1561 for id, author := range authorProfilesToSave { 1562 wg.Add(1) 1563 go func(id string, author *protobuf.DiscordMessageAuthor) { 1564 defer wg.Done() 1565 1566 m.logger.Debug(fmt.Sprintf("downloading asset %d/%d", assetCounter.Value()+1, totalAssetsCount)) 1567 imagePayload, err := discord.DownloadAvatarAsset(author.AvatarUrl) 1568 if err != nil { 1569 errmsg := fmt.Sprintf("Couldn't download profile avatar '%s': %s", author.AvatarUrl, err.Error()) 1570 importProgress.AddTaskError( 1571 discord.DownloadAssetsTask, 1572 discord.Warning(errmsg), 1573 ) 1574 progressUpdates <- importProgress 1575 return 1576 } 1577 1578 err = m.persistence.UpdateDiscordMessageAuthorImage(author.Id, imagePayload) 1579 if err != nil { 1580 importProgress.AddTaskError(discord.DownloadAssetsTask, discord.Warning(err.Error())) 1581 progressUpdates <- importProgress 1582 return 1583 } 1584 1585 author.AvatarImagePayload = imagePayload 1586 authorProfilesToSave[id] = author 1587 1588 if m.DiscordImportMarkedAsCancelled(discordCommunity.IDString()) { 1589 importProgress.StopTask(discord.DownloadAssetsTask) 1590 progressUpdates <- importProgress 1591 cancel <- discordCommunity.IDString() 1592 return 1593 } 1594 1595 assetCounter.Increase() 1596 progressValue := calculateProgress(i+1, totalImportChunkCount, (float32(assetCounter.Value())/float32(totalAssetsCount))*0.5) 1597 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 1598 progressUpdates <- importProgress 1599 1600 }(id, author) 1601 } 1602 wg.Wait() 1603 1604 if m.DiscordImportMarkedAsCancelled(communityID) { 1605 importProgress.StopTask(discord.DownloadAssetsTask) 1606 progressUpdates <- importProgress 1607 cancel <- communityID 1608 return 1609 } 1610 1611 for idxRange := range gopart.Partition(len(messageAttachmentsToDownload), 100) { 1612 attachments := messageAttachmentsToDownload[idxRange.Low:idxRange.High] 1613 wg.Add(1) 1614 go func(attachments []*protobuf.DiscordMessageAttachment) { 1615 defer wg.Done() 1616 for ii, attachment := range attachments { 1617 1618 m.logger.Debug(fmt.Sprintf("downloading asset %d/%d", assetCounter.Value()+1, totalAssetsCount)) 1619 1620 assetPayload, contentType, err := discord.DownloadAsset(attachment.Url) 1621 if err != nil { 1622 errmsg := fmt.Sprintf("Couldn't download message attachment '%s': %s", attachment.Url, err.Error()) 1623 importProgress.AddTaskError( 1624 discord.DownloadAssetsTask, 1625 discord.Warning(errmsg), 1626 ) 1627 progressUpdates <- importProgress 1628 continue 1629 } 1630 1631 attachment.Payload = assetPayload 1632 attachment.ContentType = contentType 1633 messageAttachmentsToDownload[ii] = attachment 1634 1635 if m.DiscordImportMarkedAsCancelled(communityID) { 1636 importProgress.StopTask(discord.DownloadAssetsTask) 1637 progressUpdates <- importProgress 1638 cancel <- communityID 1639 return 1640 } 1641 1642 assetCounter.Increase() 1643 progressValue := calculateProgress(i+1, totalImportChunkCount, (float32(assetCounter.Value())/float32(totalAssetsCount))*0.5) 1644 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 1645 progressUpdates <- importProgress 1646 } 1647 }(attachments) 1648 } 1649 wg.Wait() 1650 1651 if m.DiscordImportMarkedAsCancelled(communityID) { 1652 importProgress.StopTask(discord.DownloadAssetsTask) 1653 progressUpdates <- importProgress 1654 cancel <- communityID 1655 return 1656 } 1657 1658 attachmentChunks := chunkAttachmentsByByteSize(messageAttachmentsToDownload, maxChunkSizeBytes) 1659 chunksCount = len(attachmentChunks) 1660 1661 for ii, attachments := range attachmentChunks { 1662 m.logger.Debug(fmt.Sprintf("saving %d/%d chunk with %d discord message attachments", ii+1, chunksCount, len(attachments))) 1663 err = m.persistence.SaveDiscordMessageAttachments(attachments) 1664 if err != nil { 1665 m.cleanUpImport(communityID) 1666 importProgress.AddTaskError(discord.DownloadAssetsTask, discord.Error(err.Error())) 1667 importProgress.Stop() 1668 progressUpdates <- importProgress 1669 return 1670 } 1671 1672 if m.DiscordImportMarkedAsCancelled(communityID) { 1673 importProgress.StopTask(discord.DownloadAssetsTask) 1674 progressUpdates <- importProgress 1675 cancel <- communityID 1676 return 1677 } 1678 1679 // 0.5 are the previous 50% of progress, hence we multiply our chunk progress 1680 // by 0.5 1681 currentCount := ii + 1 1682 progressValue := calculateProgress(i+1, totalImportChunkCount, 0.5+(float32(currentCount)/float32(chunksCount))*0.5) 1683 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 1684 progressUpdates <- importProgress 1685 1686 // We slow down the saving of attachment chunks to keep the database responsive 1687 if currentCount < chunksCount { 1688 time.Sleep(2 * time.Second) 1689 } 1690 } 1691 1692 if len(attachmentChunks) == 0 { 1693 progressValue := calculateProgress(i+1, totalImportChunkCount, 1.0) 1694 importProgress.UpdateTaskProgress(discord.DownloadAssetsTask, progressValue) 1695 } 1696 1697 _, err := m.transport.JoinPublic(processedChannelIds[channel.Channel.ID]) 1698 if err != nil { 1699 m.logger.Error("failed to load filter for chat", zap.Error(err)) 1700 continue 1701 } 1702 1703 wakuChatMessages, err := m.chatMessagesToWakuMessages(messages, discordCommunity) 1704 if err != nil { 1705 m.logger.Error("failed to convert chat messages into waku messages", zap.Error(err)) 1706 continue 1707 } 1708 1709 wakuPinMessages, err := m.pinMessagesToWakuMessages(pinMessagesToSave, discordCommunity) 1710 if err != nil { 1711 m.logger.Error("failed to convert pin messages into waku messages", zap.Error(err)) 1712 continue 1713 } 1714 1715 wakuMessages := append(wakuChatMessages, wakuPinMessages...) 1716 1717 topics, err := m.archiveManager.GetCommunityChatsTopics(discordCommunity.ID()) 1718 if err != nil { 1719 m.logger.Error("failed to get community chat topics", zap.Error(err)) 1720 continue 1721 } 1722 1723 startDate := time.Unix(int64(exportData.OldestMessageTimestamp), 0) 1724 endDate := time.Now() 1725 1726 _, err = m.archiveManager.CreateHistoryArchiveTorrentFromMessages( 1727 discordCommunity.ID(), 1728 wakuMessages, 1729 topics, 1730 startDate, 1731 endDate, 1732 messageArchiveInterval, 1733 discordCommunity.Encrypted(), 1734 ) 1735 if err != nil { 1736 m.logger.Error("failed to create history archive torrent", zap.Error(err)) 1737 continue 1738 } 1739 1740 if m.archiveManager.IsReady() && communitySettings.HistoryArchiveSupportEnabled { 1741 1742 err = m.archiveManager.SeedHistoryArchiveTorrent(discordCommunity.ID()) 1743 if err != nil { 1744 m.logger.Error("failed to seed history archive", zap.Error(err)) 1745 } 1746 go m.archiveManager.StartHistoryArchiveTasksInterval(discordCommunity, messageArchiveInterval) 1747 } 1748 } 1749 1750 err = m.publishOrg(discordCommunity, false) 1751 if err != nil { 1752 m.cleanUpImport(communityID) 1753 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1754 importProgress.Stop() 1755 progressUpdates <- importProgress 1756 return 1757 } 1758 1759 if m.DiscordImportMarkedAsCancelled(communityID) { 1760 importProgress.StopTask(discord.InitCommunityTask) 1761 progressUpdates <- importProgress 1762 cancel <- communityID 1763 return 1764 } 1765 1766 // Chats need to be saved after the community has been published, 1767 // hence we make this part of the `InitCommunityTask` 1768 err = m.saveChats(chatsToSave) 1769 if err != nil { 1770 m.cleanUpImport(communityID) 1771 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1772 importProgress.Stop() 1773 progressUpdates <- importProgress 1774 return 1775 } 1776 1777 importProgress.UpdateTaskProgress(discord.InitCommunityTask, 0.15) 1778 progressUpdates <- importProgress 1779 1780 if m.DiscordImportMarkedAsCancelled(communityID) { 1781 importProgress.StopTask(discord.InitCommunityTask) 1782 progressUpdates <- importProgress 1783 cancel <- communityID 1784 return 1785 } 1786 1787 // Init the community filter so we can receive messages on the community 1788 _, err = m.InitCommunityFilters([]transport.CommunityFilterToInitialize{{ 1789 Shard: discordCommunity.Shard(), 1790 PrivKey: discordCommunity.PrivateKey(), 1791 }}) 1792 if err != nil { 1793 m.cleanUpImport(communityID) 1794 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1795 importProgress.StopTask(discord.InitCommunityTask) 1796 progressUpdates <- importProgress 1797 return 1798 } 1799 importProgress.UpdateTaskProgress(discord.InitCommunityTask, 0.25) 1800 progressUpdates <- importProgress 1801 1802 if m.DiscordImportMarkedAsCancelled(communityID) { 1803 importProgress.StopTask(discord.InitCommunityTask) 1804 progressUpdates <- importProgress 1805 cancel <- communityID 1806 return 1807 } 1808 1809 _, err = m.transport.InitPublicFilters(m.DefaultFilters(discordCommunity)) 1810 if err != nil { 1811 m.cleanUpImport(communityID) 1812 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1813 importProgress.StopTask(discord.InitCommunityTask) 1814 progressUpdates <- importProgress 1815 return 1816 } 1817 1818 importProgress.UpdateTaskProgress(discord.InitCommunityTask, 0.5) 1819 progressUpdates <- importProgress 1820 1821 if m.DiscordImportMarkedAsCancelled(communityID) { 1822 importProgress.StopTask(discord.InitCommunityTask) 1823 progressUpdates <- importProgress 1824 cancel <- communityID 1825 return 1826 } 1827 1828 filters := m.transport.Filters() 1829 _, err = m.scheduleSyncFilters(filters) 1830 if err != nil { 1831 m.cleanUpImport(communityID) 1832 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1833 importProgress.StopTask(discord.InitCommunityTask) 1834 progressUpdates <- importProgress 1835 return 1836 } 1837 importProgress.UpdateTaskProgress(discord.InitCommunityTask, 0.75) 1838 progressUpdates <- importProgress 1839 1840 if m.DiscordImportMarkedAsCancelled(communityID) { 1841 importProgress.StopTask(discord.InitCommunityTask) 1842 progressUpdates <- importProgress 1843 cancel <- communityID 1844 return 1845 } 1846 1847 err = m.reregisterForPushNotifications() 1848 if err != nil { 1849 m.cleanUpImport(communityID) 1850 importProgress.AddTaskError(discord.InitCommunityTask, discord.Error(err.Error())) 1851 importProgress.StopTask(discord.InitCommunityTask) 1852 progressUpdates <- importProgress 1853 return 1854 } 1855 importProgress.UpdateTaskProgress(discord.InitCommunityTask, 1) 1856 progressUpdates <- importProgress 1857 1858 if m.DiscordImportMarkedAsCancelled(communityID) { 1859 importProgress.StopTask(discord.InitCommunityTask) 1860 progressUpdates <- importProgress 1861 cancel <- communityID 1862 return 1863 } 1864 1865 m.config.messengerSignalsHandler.DiscordCommunityImportFinished(communityID) 1866 close(done) 1867 }() 1868 }