github.com/lologarithm/mattermost-server@v5.3.2-0.20181002060438-c82a84ed765b+incompatible/app/slackimport.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 "archive/zip" 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "io" 12 "mime/multipart" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 "time" 18 "unicode/utf8" 19 20 "net/http" 21 22 "github.com/mattermost/mattermost-server/mlog" 23 "github.com/mattermost/mattermost-server/model" 24 "github.com/mattermost/mattermost-server/utils" 25 ) 26 27 type SlackChannel struct { 28 Id string `json:"id"` 29 Name string `json:"name"` 30 Members []string `json:"members"` 31 Topic map[string]string `json:"topic"` 32 Purpose map[string]string `json:"purpose"` 33 } 34 35 type SlackProfile struct { 36 FirstName string `json:"first_name"` 37 LastName string `json:"last_name"` 38 Email string `json:"email"` 39 } 40 41 type SlackUser struct { 42 Id string `json:"id"` 43 Username string `json:"name"` 44 Profile SlackProfile `json:"profile"` 45 } 46 47 type SlackFile struct { 48 Id string `json:"id"` 49 Title string `json:"title"` 50 } 51 52 type SlackPost struct { 53 User string `json:"user"` 54 BotId string `json:"bot_id"` 55 BotUsername string `json:"username"` 56 Text string `json:"text"` 57 TimeStamp string `json:"ts"` 58 Type string `json:"type"` 59 SubType string `json:"subtype"` 60 Comment *SlackComment `json:"comment"` 61 Upload bool `json:"upload"` 62 File *SlackFile `json:"file"` 63 Attachments []*model.SlackAttachment `json:"attachments"` 64 } 65 66 var isValidChannelNameCharacters = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString 67 68 type SlackComment struct { 69 User string `json:"user"` 70 Comment string `json:"comment"` 71 } 72 73 func truncateRunes(s string, i int) string { 74 runes := []rune(s) 75 if len(runes) > i { 76 return string(runes[:i]) 77 } 78 return s 79 } 80 81 func SlackConvertTimeStamp(ts string) int64 { 82 timeString := strings.SplitN(ts, ".", 2)[0] 83 84 timeStamp, err := strconv.ParseInt(timeString, 10, 64) 85 if err != nil { 86 mlog.Warn("Slack Import: Bad timestamp detected.") 87 return 1 88 } 89 return timeStamp * 1000 // Convert to milliseconds 90 } 91 92 func SlackConvertChannelName(channelName string, channelId string) string { 93 newName := strings.Trim(channelName, "_-") 94 if len(newName) == 1 { 95 return "slack-channel-" + newName 96 } 97 98 if isValidChannelNameCharacters(newName) { 99 return newName 100 } else { 101 return strings.ToLower(channelId) 102 } 103 } 104 105 func SlackParseChannels(data io.Reader) ([]SlackChannel, error) { 106 decoder := json.NewDecoder(data) 107 108 var channels []SlackChannel 109 if err := decoder.Decode(&channels); err != nil { 110 mlog.Warn("Slack Import: Error occurred when parsing some Slack channels. Import may work anyway.") 111 return channels, err 112 } 113 return channels, nil 114 } 115 116 func SlackParseUsers(data io.Reader) ([]SlackUser, error) { 117 decoder := json.NewDecoder(data) 118 119 var users []SlackUser 120 err := decoder.Decode(&users) 121 // This actually returns errors that are ignored. 122 // In this case it is erroring because of a null that Slack 123 // introduced. So we just return the users here. 124 return users, err 125 } 126 127 func SlackParsePosts(data io.Reader) ([]SlackPost, error) { 128 decoder := json.NewDecoder(data) 129 130 var posts []SlackPost 131 if err := decoder.Decode(&posts); err != nil { 132 mlog.Warn("Slack Import: Error occurred when parsing some Slack posts. Import may work anyway.") 133 return posts, err 134 } 135 return posts, nil 136 } 137 138 func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, importerLog *bytes.Buffer) map[string]*model.User { 139 // Log header 140 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.created")) 141 importerLog.WriteString("===============\r\n\r\n") 142 143 addedUsers := make(map[string]*model.User) 144 145 // Need the team 146 var team *model.Team 147 if result := <-a.Srv.Store.Team().Get(teamId); result.Err != nil { 148 importerLog.WriteString(utils.T("api.slackimport.slack_import.team_fail")) 149 return addedUsers 150 } else { 151 team = result.Data.(*model.Team) 152 } 153 154 for _, sUser := range slackusers { 155 firstName := sUser.Profile.FirstName 156 lastName := sUser.Profile.LastName 157 email := sUser.Profile.Email 158 if email == "" { 159 email = sUser.Username + "@example.com" 160 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username})) 161 mlog.Warn(fmt.Sprintf("Slack Import: User %v does not have an email address in the Slack export. Used %v as a placeholder. The user should update their email address once logged in to the system.", email, sUser.Username)) 162 } 163 164 password := model.NewId() 165 166 // Check for email conflict and use existing user if found 167 if result := <-a.Srv.Store.User().GetByEmail(email); result.Err == nil { 168 existingUser := result.Data.(*model.User) 169 addedUsers[sUser.Id] = existingUser 170 if err := a.JoinUserToTeam(team, addedUsers[sUser.Id], ""); err != nil { 171 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing_failed", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) 172 } else { 173 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) 174 } 175 continue 176 } 177 178 newUser := model.User{ 179 Username: sUser.Username, 180 FirstName: firstName, 181 LastName: lastName, 182 Email: email, 183 Password: password, 184 } 185 186 if mUser := a.OldImportUser(team, &newUser); mUser != nil { 187 addedUsers[sUser.Id] = mUser 188 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.email_pwd", map[string]interface{}{"Email": newUser.Email, "Password": password})) 189 } else { 190 importerLog.WriteString(utils.T("api.slackimport.slack_add_users.unable_import", map[string]interface{}{"Username": sUser.Username})) 191 } 192 } 193 194 return addedUsers 195 } 196 197 func (a *App) SlackAddBotUser(teamId string, log *bytes.Buffer) *model.User { 198 var team *model.Team 199 if result := <-a.Srv.Store.Team().Get(teamId); result.Err != nil { 200 log.WriteString(utils.T("api.slackimport.slack_import.team_fail")) 201 return nil 202 } else { 203 team = result.Data.(*model.Team) 204 } 205 206 password := model.NewId() 207 username := "slackimportuser_" + model.NewId() 208 email := username + "@localhost" 209 210 botUser := model.User{ 211 Username: username, 212 FirstName: "", 213 LastName: "", 214 Email: email, 215 Password: password, 216 } 217 218 if mUser := a.OldImportUser(team, &botUser); mUser != nil { 219 log.WriteString(utils.T("api.slackimport.slack_add_bot_user.email_pwd", map[string]interface{}{"Email": botUser.Email, "Password": password})) 220 return mUser 221 } else { 222 log.WriteString(utils.T("api.slackimport.slack_add_bot_user.unable_import", map[string]interface{}{"Username": username})) 223 return nil 224 } 225 } 226 227 func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User) { 228 for _, sPost := range posts { 229 switch { 230 case sPost.Type == "message" && (sPost.SubType == "" || sPost.SubType == "file_share"): 231 if sPost.User == "" { 232 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 233 continue 234 } else if users[sPost.User] == nil { 235 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 236 continue 237 } 238 newPost := model.Post{ 239 UserId: users[sPost.User].Id, 240 ChannelId: channel.Id, 241 Message: sPost.Text, 242 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 243 } 244 if sPost.Upload { 245 if fileInfo, ok := a.SlackUploadFile(sPost, uploads, teamId, newPost.ChannelId, newPost.UserId); ok { 246 newPost.FileIds = append(newPost.FileIds, fileInfo.Id) 247 newPost.Message = sPost.File.Title 248 } 249 } 250 a.OldImportPost(&newPost) 251 for _, fileId := range newPost.FileIds { 252 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, newPost.Id); result.Err != nil { 253 mlog.Error(fmt.Sprintf("Slack Import: An error occurred when attaching files to a message, post_id=%s, file_ids=%v, err=%v.", newPost.Id, newPost.FileIds, result.Err)) 254 } 255 } 256 257 case sPost.Type == "message" && sPost.SubType == "file_comment": 258 if sPost.Comment == nil { 259 mlog.Debug("Slack Import: Unable to import the message as it has no comments.") 260 continue 261 } else if sPost.Comment.User == "" { 262 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 263 continue 264 } else if users[sPost.Comment.User] == nil { 265 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 266 continue 267 } 268 newPost := model.Post{ 269 UserId: users[sPost.Comment.User].Id, 270 ChannelId: channel.Id, 271 Message: sPost.Comment.Comment, 272 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 273 } 274 a.OldImportPost(&newPost) 275 case sPost.Type == "message" && sPost.SubType == "bot_message": 276 if botUser == nil { 277 mlog.Warn("Slack Import: Unable to import the bot message as the bot user does not exist.") 278 continue 279 } else if sPost.BotId == "" { 280 mlog.Warn("Slack Import: Unable to import bot message as the BotId field is missing.") 281 continue 282 } 283 284 props := make(model.StringInterface) 285 props["override_username"] = sPost.BotUsername 286 if len(sPost.Attachments) > 0 { 287 props["attachments"] = sPost.Attachments 288 } 289 290 post := &model.Post{ 291 UserId: botUser.Id, 292 ChannelId: channel.Id, 293 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 294 Message: sPost.Text, 295 Type: model.POST_SLACK_ATTACHMENT, 296 } 297 298 a.OldImportIncomingWebhookPost(post, props) 299 case sPost.Type == "message" && (sPost.SubType == "channel_join" || sPost.SubType == "channel_leave"): 300 if sPost.User == "" { 301 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 302 continue 303 } else if users[sPost.User] == nil { 304 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 305 continue 306 } 307 308 var postType string 309 if sPost.SubType == "channel_join" { 310 postType = model.POST_JOIN_CHANNEL 311 } else { 312 postType = model.POST_LEAVE_CHANNEL 313 } 314 315 newPost := model.Post{ 316 UserId: users[sPost.User].Id, 317 ChannelId: channel.Id, 318 Message: sPost.Text, 319 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 320 Type: postType, 321 Props: model.StringInterface{ 322 "username": users[sPost.User].Username, 323 }, 324 } 325 a.OldImportPost(&newPost) 326 case sPost.Type == "message" && sPost.SubType == "me_message": 327 if sPost.User == "" { 328 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 329 continue 330 } else if users[sPost.User] == nil { 331 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 332 continue 333 } 334 newPost := model.Post{ 335 UserId: users[sPost.User].Id, 336 ChannelId: channel.Id, 337 Message: "*" + sPost.Text + "*", 338 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 339 } 340 a.OldImportPost(&newPost) 341 case sPost.Type == "message" && sPost.SubType == "channel_topic": 342 if sPost.User == "" { 343 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 344 continue 345 } else if users[sPost.User] == nil { 346 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 347 continue 348 } 349 newPost := model.Post{ 350 UserId: users[sPost.User].Id, 351 ChannelId: channel.Id, 352 Message: sPost.Text, 353 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 354 Type: model.POST_HEADER_CHANGE, 355 } 356 a.OldImportPost(&newPost) 357 case sPost.Type == "message" && sPost.SubType == "channel_purpose": 358 if sPost.User == "" { 359 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 360 continue 361 } else if users[sPost.User] == nil { 362 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 363 continue 364 } 365 newPost := model.Post{ 366 UserId: users[sPost.User].Id, 367 ChannelId: channel.Id, 368 Message: sPost.Text, 369 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 370 Type: model.POST_PURPOSE_CHANGE, 371 } 372 a.OldImportPost(&newPost) 373 case sPost.Type == "message" && sPost.SubType == "channel_name": 374 if sPost.User == "" { 375 mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") 376 continue 377 } else if users[sPost.User] == nil { 378 mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) 379 continue 380 } 381 newPost := model.Post{ 382 UserId: users[sPost.User].Id, 383 ChannelId: channel.Id, 384 Message: sPost.Text, 385 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 386 Type: model.POST_DISPLAYNAME_CHANGE, 387 } 388 a.OldImportPost(&newPost) 389 default: 390 mlog.Warn(fmt.Sprintf("Slack Import: Unable to import the message as its type is not supported: post_type=%v, post_subtype=%v.", sPost.Type, sPost.SubType)) 391 } 392 } 393 } 394 395 func (a *App) SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, teamId string, channelId string, userId string) (*model.FileInfo, bool) { 396 if sPost.File != nil { 397 if file, ok := uploads[sPost.File.Id]; ok { 398 openFile, err := file.Open() 399 if err != nil { 400 mlog.Warn(fmt.Sprintf("Slack Import: Unable to open the file %v from the Slack export: %v.", sPost.File.Id, err.Error())) 401 return nil, false 402 } 403 defer openFile.Close() 404 405 timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(sPost.TimeStamp)) 406 uploadedFile, err := a.OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name)) 407 if err != nil { 408 mlog.Warn(fmt.Sprintf("Slack Import: An error occurred when uploading file %v: %v.", sPost.File.Id, err.Error())) 409 return nil, false 410 } 411 412 return uploadedFile, true 413 } else { 414 mlog.Warn(fmt.Sprintf("Slack Import: Unable to import file %v as the file is missing from the Slack export zip file.", sPost.File.Id)) 415 return nil, false 416 } 417 } else { 418 mlog.Warn("Slack Import: Unable to attach the file to the post as the latter has no file section present in Slack export.") 419 return nil, false 420 } 421 } 422 423 func (a *App) deactivateSlackBotUser(user *model.User) { 424 _, err := a.UpdateActive(user, false) 425 if err != nil { 426 mlog.Warn("Slack Import: Unable to deactivate the user account used for the bot.") 427 } 428 } 429 430 func (a *App) addSlackUsersToChannel(members []string, users map[string]*model.User, channel *model.Channel, log *bytes.Buffer) { 431 for _, member := range members { 432 if user, ok := users[member]; !ok { 433 log.WriteString(utils.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]interface{}{"Username": "?"})) 434 } else { 435 if _, err := a.AddUserToChannel(user, channel); err != nil { 436 log.WriteString(utils.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]interface{}{"Username": user.Username})) 437 } 438 } 439 } 440 } 441 442 func SlackSanitiseChannelProperties(channel model.Channel) model.Channel { 443 if utf8.RuneCountInString(channel.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { 444 mlog.Warn(fmt.Sprintf("Slack Import: Channel %v display name exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) 445 channel.DisplayName = truncateRunes(channel.DisplayName, model.CHANNEL_DISPLAY_NAME_MAX_RUNES) 446 } 447 448 if len(channel.Name) > model.CHANNEL_NAME_MAX_LENGTH { 449 mlog.Warn(fmt.Sprintf("Slack Import: Channel %v handle exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) 450 channel.Name = channel.Name[0:model.CHANNEL_NAME_MAX_LENGTH] 451 } 452 453 if utf8.RuneCountInString(channel.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { 454 mlog.Warn(fmt.Sprintf("Slack Import: Channel %v purpose exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) 455 channel.Purpose = truncateRunes(channel.Purpose, model.CHANNEL_PURPOSE_MAX_RUNES) 456 } 457 458 if utf8.RuneCountInString(channel.Header) > model.CHANNEL_HEADER_MAX_RUNES { 459 mlog.Warn(fmt.Sprintf("Slack Import: Channel %v header exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) 460 channel.Header = truncateRunes(channel.Header, model.CHANNEL_HEADER_MAX_RUNES) 461 } 462 463 return channel 464 } 465 466 func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[string][]SlackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User, importerLog *bytes.Buffer) map[string]*model.Channel { 467 // Write Header 468 importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.added")) 469 importerLog.WriteString("=================\r\n\r\n") 470 471 addedChannels := make(map[string]*model.Channel) 472 for _, sChannel := range slackchannels { 473 newChannel := model.Channel{ 474 TeamId: teamId, 475 Type: model.CHANNEL_OPEN, 476 DisplayName: sChannel.Name, 477 Name: SlackConvertChannelName(sChannel.Name, sChannel.Id), 478 Purpose: sChannel.Purpose["value"], 479 Header: sChannel.Topic["value"], 480 } 481 newChannel = SlackSanitiseChannelProperties(newChannel) 482 483 var mChannel *model.Channel 484 if result := <-a.Srv.Store.Channel().GetByName(teamId, sChannel.Name, true); result.Err == nil { 485 // The channel already exists as an active channel. Merge with the existing one. 486 mChannel = result.Data.(*model.Channel) 487 importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.merge", map[string]interface{}{"DisplayName": newChannel.DisplayName})) 488 } else if result := <-a.Srv.Store.Channel().GetDeletedByName(teamId, sChannel.Name); result.Err == nil { 489 // The channel already exists but has been deleted. Generate a random string for the handle instead. 490 newChannel.Name = model.NewId() 491 newChannel = SlackSanitiseChannelProperties(newChannel) 492 } 493 494 if mChannel == nil { 495 // Haven't found an existing channel to merge with. Try importing it as a new one. 496 mChannel = a.OldImportChannel(&newChannel) 497 if mChannel == nil { 498 mlog.Warn(fmt.Sprintf("Slack Import: Unable to import Slack channel: %s.", newChannel.DisplayName)) 499 importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName})) 500 continue 501 } 502 } 503 504 a.addSlackUsersToChannel(sChannel.Members, users, mChannel, importerLog) 505 importerLog.WriteString(newChannel.DisplayName + "\r\n") 506 addedChannels[sChannel.Id] = mChannel 507 a.SlackAddPosts(teamId, mChannel, posts[sChannel.Name], users, uploads, botUser) 508 } 509 510 return addedChannels 511 } 512 513 func SlackConvertUserMentions(users []SlackUser, posts map[string][]SlackPost) map[string][]SlackPost { 514 var regexes = make(map[string]*regexp.Regexp, len(users)) 515 for _, user := range users { 516 r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>") 517 if err != nil { 518 mlog.Warn(fmt.Sprintf("Slack Import: Unable to compile the @mention, matching regular expression for the Slack user %v (id=%v).", user.Id, user.Username), mlog.String("user_id", user.Id)) 519 continue 520 } 521 regexes["@"+user.Username] = r 522 } 523 524 // Special cases. 525 regexes["@here"], _ = regexp.Compile(`<!here\|@here>`) 526 regexes["@channel"], _ = regexp.Compile("<!channel>") 527 regexes["@all"], _ = regexp.Compile("<!everyone>") 528 529 for channelName, channelPosts := range posts { 530 for postIdx, post := range channelPosts { 531 for mention, r := range regexes { 532 post.Text = r.ReplaceAllString(post.Text, mention) 533 posts[channelName][postIdx] = post 534 } 535 } 536 } 537 538 return posts 539 } 540 541 func SlackConvertChannelMentions(channels []SlackChannel, posts map[string][]SlackPost) map[string][]SlackPost { 542 var regexes = make(map[string]*regexp.Regexp, len(channels)) 543 for _, channel := range channels { 544 r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>") 545 if err != nil { 546 mlog.Warn(fmt.Sprintf("Slack Import: Unable to compile the !channel, matching regular expression for the Slack channel %v (id=%v).", channel.Id, channel.Name)) 547 continue 548 } 549 regexes["~"+channel.Name] = r 550 } 551 552 for channelName, channelPosts := range posts { 553 for postIdx, post := range channelPosts { 554 for channelReplace, r := range regexes { 555 post.Text = r.ReplaceAllString(post.Text, channelReplace) 556 posts[channelName][postIdx] = post 557 } 558 } 559 } 560 561 return posts 562 } 563 564 func SlackConvertPostsMarkup(posts map[string][]SlackPost) map[string][]SlackPost { 565 regexReplaceAllString := []struct { 566 regex *regexp.Regexp 567 rpl string 568 }{ 569 // URL 570 { 571 regexp.MustCompile(`<([^|<>]+)\|([^|<>]+)>`), 572 "[$2]($1)", 573 }, 574 // bold 575 { 576 regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`), 577 "$1**$2**", 578 }, 579 // strikethrough 580 { 581 regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`), 582 "$1~~$2~~", 583 }, 584 // single paragraph blockquote 585 // Slack converts > character to > 586 { 587 regexp.MustCompile(`(?sm)^>`), 588 ">", 589 }, 590 } 591 592 regexReplaceAllStringFunc := []struct { 593 regex *regexp.Regexp 594 fn func(string) string 595 }{ 596 // multiple paragraphs blockquotes 597 { 598 regexp.MustCompile(`(?sm)^>>>(.+)$`), 599 func(src string) string { 600 // remove >>> prefix, might have leading \n 601 prefixRegexp := regexp.MustCompile(`^([\n])?>>>(.*)`) 602 src = prefixRegexp.ReplaceAllString(src, "$1$2") 603 // append > to start of line 604 appendRegexp := regexp.MustCompile(`(?m)^`) 605 return appendRegexp.ReplaceAllString(src, ">$0") 606 }, 607 }, 608 } 609 610 for channelName, channelPosts := range posts { 611 for postIdx, post := range channelPosts { 612 result := post.Text 613 614 for _, rule := range regexReplaceAllString { 615 result = rule.regex.ReplaceAllString(result, rule.rpl) 616 } 617 618 for _, rule := range regexReplaceAllStringFunc { 619 result = rule.regex.ReplaceAllStringFunc(result, rule.fn) 620 } 621 posts[channelName][postIdx].Text = result 622 } 623 } 624 625 return posts 626 } 627 628 func (a *App) SlackImport(fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) { 629 // Create log file 630 log := bytes.NewBufferString(utils.T("api.slackimport.slack_import.log")) 631 632 zipreader, err := zip.NewReader(fileData, fileSize) 633 if err != nil || zipreader.File == nil { 634 log.WriteString(utils.T("api.slackimport.slack_import.zip.app_error")) 635 return model.NewAppError("SlackImport", "api.slackimport.slack_import.zip.app_error", nil, err.Error(), http.StatusBadRequest), log 636 } 637 638 var channels []SlackChannel 639 var users []SlackUser 640 posts := make(map[string][]SlackPost) 641 uploads := make(map[string]*zip.File) 642 for _, file := range zipreader.File { 643 reader, err := file.Open() 644 if err != nil { 645 log.WriteString(utils.T("api.slackimport.slack_import.open.app_error", map[string]interface{}{"Filename": file.Name})) 646 return model.NewAppError("SlackImport", "api.slackimport.slack_import.open.app_error", map[string]interface{}{"Filename": file.Name}, err.Error(), http.StatusInternalServerError), log 647 } 648 if file.Name == "channels.json" { 649 channels, _ = SlackParseChannels(reader) 650 } else if file.Name == "users.json" { 651 users, _ = SlackParseUsers(reader) 652 } else { 653 spl := strings.Split(file.Name, "/") 654 if len(spl) == 2 && strings.HasSuffix(spl[1], ".json") { 655 newposts, _ := SlackParsePosts(reader) 656 channel := spl[0] 657 if _, ok := posts[channel]; !ok { 658 posts[channel] = newposts 659 } else { 660 posts[channel] = append(posts[channel], newposts...) 661 } 662 } else if len(spl) == 3 && spl[0] == "__uploads" { 663 uploads[spl[1]] = file 664 } 665 } 666 } 667 668 posts = SlackConvertUserMentions(users, posts) 669 posts = SlackConvertChannelMentions(channels, posts) 670 posts = SlackConvertPostsMarkup(posts) 671 672 addedUsers := a.SlackAddUsers(teamID, users, log) 673 botUser := a.SlackAddBotUser(teamID, log) 674 675 a.SlackAddChannels(teamID, channels, posts, addedUsers, uploads, botUser, log) 676 677 if botUser != nil { 678 a.deactivateSlackBotUser(botUser) 679 } 680 681 a.InvalidateAllCaches() 682 683 log.WriteString(utils.T("api.slackimport.slack_import.notes")) 684 log.WriteString("=======\r\n\r\n") 685 686 log.WriteString(utils.T("api.slackimport.slack_import.note1")) 687 log.WriteString(utils.T("api.slackimport.slack_import.note2")) 688 log.WriteString(utils.T("api.slackimport.slack_import.note3")) 689 690 return nil, log 691 } 692 693 // 694 // -- Old SlackImport Functions -- 695 // Import functions are sutible for entering posts and users into the database without 696 // some of the usual checks. (IsValid is still run) 697 // 698 699 func (a *App) OldImportPost(post *model.Post) { 700 // Workaround for empty messages, which may be the case if they are webhook posts. 701 firstIteration := true 702 maxPostSize := a.MaxPostSize() 703 for messageRuneCount := utf8.RuneCountInString(post.Message); messageRuneCount > 0 || firstIteration; messageRuneCount = utf8.RuneCountInString(post.Message) { 704 firstIteration = false 705 var remainder string 706 if messageRuneCount > maxPostSize { 707 remainder = string(([]rune(post.Message))[maxPostSize:]) 708 post.Message = truncateRunes(post.Message, maxPostSize) 709 } else { 710 remainder = "" 711 } 712 713 post.Hashtags, _ = model.ParseHashtags(post.Message) 714 715 if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { 716 mlog.Debug(fmt.Sprintf("Error saving post. user=%v, message=%v", post.UserId, post.Message)) 717 } 718 719 for _, fileId := range post.FileIds { 720 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { 721 mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) 722 } 723 } 724 725 post.Id = "" 726 post.CreateAt++ 727 post.Message = remainder 728 } 729 } 730 731 func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User { 732 user.MakeNonNil() 733 734 user.Roles = model.SYSTEM_USER_ROLE_ID 735 736 if result := <-a.Srv.Store.User().Save(user); result.Err != nil { 737 mlog.Error(fmt.Sprintf("Error saving user. err=%v", result.Err)) 738 return nil 739 } else { 740 ruser := result.Data.(*model.User) 741 742 if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { 743 mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", cresult.Err)) 744 } 745 746 if err := a.JoinUserToTeam(team, user, ""); err != nil { 747 mlog.Error(fmt.Sprintf("Failed to join team when importing err=%v", err)) 748 } 749 750 return ruser 751 } 752 } 753 754 func (a *App) OldImportChannel(channel *model.Channel) *model.Channel { 755 if result := <-a.Srv.Store.Channel().Save(channel, *a.Config().TeamSettings.MaxChannelsPerTeam); result.Err != nil { 756 return nil 757 } else { 758 sc := result.Data.(*model.Channel) 759 760 return sc 761 } 762 } 763 764 func (a *App) OldImportFile(timestamp time.Time, file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) { 765 buf := bytes.NewBuffer(nil) 766 io.Copy(buf, file) 767 data := buf.Bytes() 768 769 fileInfo, err := a.DoUploadFile(timestamp, teamId, channelId, userId, fileName, data) 770 if err != nil { 771 return nil, err 772 } 773 774 if fileInfo.IsImage() && fileInfo.MimeType != "image/svg+xml" { 775 img, width, height := prepareImage(data) 776 if img != nil { 777 a.generateThumbnailImage(*img, fileInfo.ThumbnailPath, width, height) 778 a.generatePreviewImage(*img, fileInfo.PreviewPath, width) 779 } 780 } 781 782 return fileInfo, nil 783 } 784 785 func (a *App) OldImportIncomingWebhookPost(post *model.Post, props model.StringInterface) { 786 linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) 787 post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})") 788 789 post.AddProp("from_webhook", "true") 790 791 if _, ok := props["override_username"]; !ok { 792 post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME) 793 } 794 795 if len(props) > 0 { 796 for key, val := range props { 797 if key == "attachments" { 798 if attachments, success := val.([]*model.SlackAttachment); success { 799 model.ParseSlackAttachment(post, attachments) 800 } 801 } else if key != "from_webhook" { 802 post.AddProp(key, val) 803 } 804 } 805 } 806 807 a.OldImportPost(post) 808 }