github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+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 "io" 11 "mime/multipart" 12 "path/filepath" 13 "regexp" 14 "strconv" 15 "strings" 16 "unicode/utf8" 17 18 "net/http" 19 20 l4g "github.com/alecthomas/log4go" 21 "github.com/mattermost/mattermost-server/model" 22 "github.com/mattermost/mattermost-server/utils" 23 ) 24 25 type SlackChannel struct { 26 Id string `json:"id"` 27 Name string `json:"name"` 28 Members []string `json:"members"` 29 Topic map[string]string `json:"topic"` 30 Purpose map[string]string `json:"purpose"` 31 } 32 33 type SlackUser struct { 34 Id string `json:"id"` 35 Username string `json:"name"` 36 Profile map[string]string `json:"profile"` 37 } 38 39 type SlackFile struct { 40 Id string `json:"id"` 41 Title string `json:"title"` 42 } 43 44 type SlackPost struct { 45 User string `json:"user"` 46 BotId string `json:"bot_id"` 47 BotUsername string `json:"username"` 48 Text string `json:"text"` 49 TimeStamp string `json:"ts"` 50 Type string `json:"type"` 51 SubType string `json:"subtype"` 52 Comment *SlackComment `json:"comment"` 53 Upload bool `json:"upload"` 54 File *SlackFile `json:"file"` 55 Attachments []*model.SlackAttachment `json:"attachments"` 56 } 57 58 var isValidChannelNameCharacters = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString 59 60 type SlackComment struct { 61 User string `json:"user"` 62 Comment string `json:"comment"` 63 } 64 65 func truncateRunes(s string, i int) string { 66 runes := []rune(s) 67 if len(runes) > i { 68 return string(runes[:i]) 69 } 70 return s 71 } 72 73 func SlackConvertTimeStamp(ts string) int64 { 74 timeString := strings.SplitN(ts, ".", 2)[0] 75 76 timeStamp, err := strconv.ParseInt(timeString, 10, 64) 77 if err != nil { 78 l4g.Warn(utils.T("api.slackimport.slack_convert_timestamp.bad.warn")) 79 return 1 80 } 81 return timeStamp * 1000 // Convert to milliseconds 82 } 83 84 func SlackConvertChannelName(channelName string, channelId string) string { 85 newName := strings.Trim(channelName, "_-") 86 if len(newName) == 1 { 87 return "slack-channel-" + newName 88 } 89 90 if isValidChannelNameCharacters(newName) { 91 return newName 92 } else { 93 return strings.ToLower(channelId) 94 } 95 } 96 97 func SlackParseChannels(data io.Reader) ([]SlackChannel, error) { 98 decoder := json.NewDecoder(data) 99 100 var channels []SlackChannel 101 if err := decoder.Decode(&channels); err != nil { 102 l4g.Warn(utils.T("api.slackimport.slack_parse_channels.error")) 103 return channels, err 104 } 105 return channels, nil 106 } 107 108 func SlackParseUsers(data io.Reader) ([]SlackUser, error) { 109 decoder := json.NewDecoder(data) 110 111 var users []SlackUser 112 if err := decoder.Decode(&users); err != nil { 113 // This actually returns errors that are ignored. 114 // In this case it is erroring because of a null that Slack 115 // introduced. So we just return the users here. 116 return users, err 117 } 118 return users, nil 119 } 120 121 func SlackParsePosts(data io.Reader) ([]SlackPost, error) { 122 decoder := json.NewDecoder(data) 123 124 var posts []SlackPost 125 if err := decoder.Decode(&posts); err != nil { 126 l4g.Warn(utils.T("api.slackimport.slack_parse_posts.error")) 127 return posts, err 128 } 129 return posts, nil 130 } 131 132 func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map[string]*model.User { 133 // Log header 134 log.WriteString(utils.T("api.slackimport.slack_add_users.created")) 135 log.WriteString("===============\r\n\r\n") 136 137 addedUsers := make(map[string]*model.User) 138 139 // Need the team 140 var team *model.Team 141 if result := <-a.Srv.Store.Team().Get(teamId); result.Err != nil { 142 log.WriteString(utils.T("api.slackimport.slack_import.team_fail")) 143 return addedUsers 144 } else { 145 team = result.Data.(*model.Team) 146 } 147 148 for _, sUser := range slackusers { 149 firstName := "" 150 lastName := "" 151 if name, ok := sUser.Profile["first_name"]; ok { 152 firstName = name 153 } 154 if name, ok := sUser.Profile["last_name"]; ok { 155 lastName = name 156 } 157 158 email := sUser.Profile["email"] 159 if email == "" { 160 email = sUser.Username + "@example.com" 161 log.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username})) 162 l4g.Warn(utils.T("api.slackimport.slack_add_users.missing_email_address.warn", map[string]interface{}{"Email": email, "Username": sUser.Username})) 163 } 164 165 password := model.NewId() 166 167 // Check for email conflict and use existing user if found 168 if result := <-a.Srv.Store.User().GetByEmail(email); result.Err == nil { 169 existingUser := result.Data.(*model.User) 170 addedUsers[sUser.Id] = existingUser 171 if err := a.JoinUserToTeam(team, addedUsers[sUser.Id], ""); err != nil { 172 log.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing_failed", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) 173 } else { 174 log.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) 175 } 176 continue 177 } 178 179 newUser := model.User{ 180 Username: sUser.Username, 181 FirstName: firstName, 182 LastName: lastName, 183 Email: email, 184 Password: password, 185 } 186 187 if mUser := a.OldImportUser(team, &newUser); mUser != nil { 188 addedUsers[sUser.Id] = mUser 189 log.WriteString(utils.T("api.slackimport.slack_add_users.email_pwd", map[string]interface{}{"Email": newUser.Email, "Password": password})) 190 } else { 191 log.WriteString(utils.T("api.slackimport.slack_add_users.unable_import", map[string]interface{}{"Username": sUser.Username})) 192 } 193 } 194 195 return addedUsers 196 } 197 198 func (a *App) SlackAddBotUser(teamId string, log *bytes.Buffer) *model.User { 199 var team *model.Team 200 if result := <-a.Srv.Store.Team().Get(teamId); result.Err != nil { 201 log.WriteString(utils.T("api.slackimport.slack_import.team_fail")) 202 return nil 203 } else { 204 team = result.Data.(*model.Team) 205 } 206 207 password := model.NewId() 208 username := "slackimportuser_" + model.NewId() 209 email := username + "@localhost" 210 211 botUser := model.User{ 212 Username: username, 213 FirstName: "", 214 LastName: "", 215 Email: email, 216 Password: password, 217 } 218 219 if mUser := a.OldImportUser(team, &botUser); mUser != nil { 220 log.WriteString(utils.T("api.slackimport.slack_add_bot_user.email_pwd", map[string]interface{}{"Email": botUser.Email, "Password": password})) 221 return mUser 222 } else { 223 log.WriteString(utils.T("api.slackimport.slack_add_bot_user.unable_import", map[string]interface{}{"Username": username})) 224 return nil 225 } 226 } 227 228 func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User) { 229 for _, sPost := range posts { 230 switch { 231 case sPost.Type == "message" && (sPost.SubType == "" || sPost.SubType == "file_share"): 232 if sPost.User == "" { 233 l4g.Debug(utils.T("api.slackimport.slack_add_posts.without_user.debug")) 234 continue 235 } else if users[sPost.User] == nil { 236 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 237 continue 238 } 239 newPost := model.Post{ 240 UserId: users[sPost.User].Id, 241 ChannelId: channel.Id, 242 Message: sPost.Text, 243 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 244 } 245 if sPost.Upload { 246 if fileInfo, ok := a.SlackUploadFile(sPost, uploads, teamId, newPost.ChannelId, newPost.UserId); ok { 247 newPost.FileIds = append(newPost.FileIds, fileInfo.Id) 248 newPost.Message = sPost.File.Title 249 } 250 } 251 a.OldImportPost(&newPost) 252 for _, fileId := range newPost.FileIds { 253 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, newPost.Id); result.Err != nil { 254 l4g.Error(utils.T("api.slackimport.slack_add_posts.attach_files.error"), newPost.Id, newPost.FileIds, result.Err) 255 } 256 } 257 258 case sPost.Type == "message" && sPost.SubType == "file_comment": 259 if sPost.Comment == nil { 260 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_comment.debug")) 261 continue 262 } else if sPost.Comment.User == "" { 263 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) 264 continue 265 } else if users[sPost.Comment.User] == nil { 266 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 267 continue 268 } 269 newPost := model.Post{ 270 UserId: users[sPost.Comment.User].Id, 271 ChannelId: channel.Id, 272 Message: sPost.Comment.Comment, 273 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 274 } 275 a.OldImportPost(&newPost) 276 case sPost.Type == "message" && sPost.SubType == "bot_message": 277 if botUser == nil { 278 l4g.Warn(utils.T("api.slackimport.slack_add_posts.bot_user_no_exists.warn")) 279 continue 280 } else if sPost.BotId == "" { 281 l4g.Warn(utils.T("api.slackimport.slack_add_posts.no_bot_id.warn")) 282 continue 283 } 284 285 props := make(model.StringInterface) 286 props["override_username"] = sPost.BotUsername 287 if len(sPost.Attachments) > 0 { 288 props["attachments"] = sPost.Attachments 289 } 290 291 post := &model.Post{ 292 UserId: botUser.Id, 293 ChannelId: channel.Id, 294 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 295 Message: sPost.Text, 296 Type: model.POST_SLACK_ATTACHMENT, 297 } 298 299 a.OldImportIncomingWebhookPost(post, props) 300 case sPost.Type == "message" && (sPost.SubType == "channel_join" || sPost.SubType == "channel_leave"): 301 if sPost.User == "" { 302 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) 303 continue 304 } else if users[sPost.User] == nil { 305 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 306 continue 307 } 308 309 var postType string 310 if sPost.SubType == "channel_join" { 311 postType = model.POST_JOIN_CHANNEL 312 } else { 313 postType = model.POST_LEAVE_CHANNEL 314 } 315 316 newPost := model.Post{ 317 UserId: users[sPost.User].Id, 318 ChannelId: channel.Id, 319 Message: sPost.Text, 320 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 321 Type: postType, 322 Props: model.StringInterface{ 323 "username": users[sPost.User].Username, 324 }, 325 } 326 a.OldImportPost(&newPost) 327 case sPost.Type == "message" && sPost.SubType == "me_message": 328 if sPost.User == "" { 329 l4g.Debug(utils.T("api.slackimport.slack_add_posts.without_user.debug")) 330 continue 331 } else if users[sPost.User] == nil { 332 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 333 continue 334 } 335 newPost := model.Post{ 336 UserId: users[sPost.User].Id, 337 ChannelId: channel.Id, 338 Message: "*" + sPost.Text + "*", 339 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 340 } 341 a.OldImportPost(&newPost) 342 case sPost.Type == "message" && sPost.SubType == "channel_topic": 343 if sPost.User == "" { 344 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) 345 continue 346 } else if users[sPost.User] == nil { 347 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 348 continue 349 } 350 newPost := model.Post{ 351 UserId: users[sPost.User].Id, 352 ChannelId: channel.Id, 353 Message: sPost.Text, 354 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 355 Type: model.POST_HEADER_CHANGE, 356 } 357 a.OldImportPost(&newPost) 358 case sPost.Type == "message" && sPost.SubType == "channel_purpose": 359 if sPost.User == "" { 360 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) 361 continue 362 } else if users[sPost.User] == nil { 363 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 364 continue 365 } 366 newPost := model.Post{ 367 UserId: users[sPost.User].Id, 368 ChannelId: channel.Id, 369 Message: sPost.Text, 370 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 371 Type: model.POST_PURPOSE_CHANGE, 372 } 373 a.OldImportPost(&newPost) 374 case sPost.Type == "message" && sPost.SubType == "channel_name": 375 if sPost.User == "" { 376 l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) 377 continue 378 } else if users[sPost.User] == nil { 379 l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) 380 continue 381 } 382 newPost := model.Post{ 383 UserId: users[sPost.User].Id, 384 ChannelId: channel.Id, 385 Message: sPost.Text, 386 CreateAt: SlackConvertTimeStamp(sPost.TimeStamp), 387 Type: model.POST_DISPLAYNAME_CHANGE, 388 } 389 a.OldImportPost(&newPost) 390 default: 391 l4g.Warn(utils.T("api.slackimport.slack_add_posts.unsupported.warn"), sPost.Type, sPost.SubType) 392 } 393 } 394 } 395 396 func (a *App) SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, teamId string, channelId string, userId string) (*model.FileInfo, bool) { 397 if sPost.File != nil { 398 if file, ok := uploads[sPost.File.Id]; ok { 399 openFile, err := file.Open() 400 if err != nil { 401 l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_open_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()})) 402 return nil, false 403 } 404 defer openFile.Close() 405 406 timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(sPost.TimeStamp)) 407 uploadedFile, err := a.OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name)) 408 if err != nil { 409 l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_upload_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()})) 410 return nil, false 411 } 412 413 return uploadedFile, true 414 } else { 415 l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_not_found.warn", map[string]interface{}{"FileId": sPost.File.Id})) 416 return nil, false 417 } 418 } else { 419 l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_not_in_json.warn")) 420 return nil, false 421 } 422 } 423 424 func (a *App) deactivateSlackBotUser(user *model.User) { 425 _, err := a.UpdateActive(user, false) 426 if err != nil { 427 l4g.Warn(utils.T("api.slackimport.slack_deactivate_bot_user.failed_to_deactivate", err)) 428 } 429 } 430 431 func (a *App) addSlackUsersToChannel(members []string, users map[string]*model.User, channel *model.Channel, log *bytes.Buffer) { 432 for _, member := range members { 433 if user, ok := users[member]; !ok { 434 log.WriteString(utils.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]interface{}{"Username": "?"})) 435 } else { 436 if _, err := a.AddUserToChannel(user, channel); err != nil { 437 log.WriteString(utils.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]interface{}{"Username": user.Username})) 438 } 439 } 440 } 441 } 442 443 func SlackSanitiseChannelProperties(channel model.Channel) model.Channel { 444 if utf8.RuneCountInString(channel.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { 445 l4g.Warn("api.slackimport.slack_sanitise_channel_properties.display_name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) 446 channel.DisplayName = truncateRunes(channel.DisplayName, model.CHANNEL_DISPLAY_NAME_MAX_RUNES) 447 } 448 449 if len(channel.Name) > model.CHANNEL_NAME_MAX_LENGTH { 450 l4g.Warn("api.slackimport.slack_sanitise_channel_properties.name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) 451 channel.Name = channel.Name[0:model.CHANNEL_NAME_MAX_LENGTH] 452 } 453 454 if utf8.RuneCountInString(channel.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { 455 l4g.Warn("api.slackimport.slack_sanitise_channel_properties.purpose_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) 456 channel.Purpose = truncateRunes(channel.Purpose, model.CHANNEL_PURPOSE_MAX_RUNES) 457 } 458 459 if utf8.RuneCountInString(channel.Header) > model.CHANNEL_HEADER_MAX_RUNES { 460 l4g.Warn("api.slackimport.slack_sanitise_channel_properties.header_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) 461 channel.Header = truncateRunes(channel.Header, model.CHANNEL_HEADER_MAX_RUNES) 462 } 463 464 return channel 465 } 466 467 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, log *bytes.Buffer) map[string]*model.Channel { 468 // Write Header 469 log.WriteString(utils.T("api.slackimport.slack_add_channels.added")) 470 log.WriteString("=================\r\n\r\n") 471 472 addedChannels := make(map[string]*model.Channel) 473 for _, sChannel := range slackchannels { 474 newChannel := model.Channel{ 475 TeamId: teamId, 476 Type: model.CHANNEL_OPEN, 477 DisplayName: sChannel.Name, 478 Name: SlackConvertChannelName(sChannel.Name, sChannel.Id), 479 Purpose: sChannel.Purpose["value"], 480 Header: sChannel.Topic["value"], 481 } 482 newChannel = SlackSanitiseChannelProperties(newChannel) 483 484 var mChannel *model.Channel 485 if result := <-a.Srv.Store.Channel().GetByName(teamId, sChannel.Name, true); result.Err == nil { 486 // The channel already exists as an active channel. Merge with the existing one. 487 mChannel = result.Data.(*model.Channel) 488 log.WriteString(utils.T("api.slackimport.slack_add_channels.merge", map[string]interface{}{"DisplayName": newChannel.DisplayName})) 489 } else if result := <-a.Srv.Store.Channel().GetDeletedByName(teamId, sChannel.Name); result.Err == nil { 490 // The channel already exists but has been deleted. Generate a random string for the handle instead. 491 newChannel.Name = model.NewId() 492 newChannel = SlackSanitiseChannelProperties(newChannel) 493 } 494 495 if mChannel == nil { 496 // Haven't found an existing channel to merge with. Try importing it as a new one. 497 mChannel = a.OldImportChannel(&newChannel) 498 if mChannel == nil { 499 l4g.Warn(utils.T("api.slackimport.slack_add_channels.import_failed.warn"), newChannel.DisplayName) 500 log.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName})) 501 continue 502 } 503 } 504 505 a.addSlackUsersToChannel(sChannel.Members, users, mChannel, log) 506 log.WriteString(newChannel.DisplayName + "\r\n") 507 addedChannels[sChannel.Id] = mChannel 508 a.SlackAddPosts(teamId, mChannel, posts[sChannel.Name], users, uploads, botUser) 509 } 510 511 return addedChannels 512 } 513 514 func SlackConvertUserMentions(users []SlackUser, posts map[string][]SlackPost) map[string][]SlackPost { 515 var regexes = make(map[string]*regexp.Regexp, len(users)) 516 for _, user := range users { 517 r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>") 518 if err != nil { 519 l4g.Warn(utils.T("api.slackimport.slack_convert_user_mentions.compile_regexp_failed.warn"), user.Id, user.Username) 520 continue 521 } 522 regexes["@"+user.Username] = r 523 } 524 525 // Special cases. 526 regexes["@here"], _ = regexp.Compile(`<!here\|@here>`) 527 regexes["@channel"], _ = regexp.Compile("<!channel>") 528 regexes["@all"], _ = regexp.Compile("<!everyone>") 529 530 for channelName, channelPosts := range posts { 531 for postIdx, post := range channelPosts { 532 for mention, r := range regexes { 533 post.Text = r.ReplaceAllString(post.Text, mention) 534 posts[channelName][postIdx] = post 535 } 536 } 537 } 538 539 return posts 540 } 541 542 func SlackConvertChannelMentions(channels []SlackChannel, posts map[string][]SlackPost) map[string][]SlackPost { 543 var regexes = make(map[string]*regexp.Regexp, len(channels)) 544 for _, channel := range channels { 545 r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>") 546 if err != nil { 547 l4g.Warn(utils.T("api.slackimport.slack_convert_channel_mentions.compile_regexp_failed.warn"), channel.Id, channel.Name) 548 continue 549 } 550 regexes["~"+channel.Name] = r 551 } 552 553 for channelName, channelPosts := range posts { 554 for postIdx, post := range channelPosts { 555 for channelReplace, r := range regexes { 556 post.Text = r.ReplaceAllString(post.Text, channelReplace) 557 posts[channelName][postIdx] = post 558 } 559 } 560 } 561 562 return posts 563 } 564 565 func SlackConvertPostsMarkup(posts map[string][]SlackPost) map[string][]SlackPost { 566 regexReplaceAllString := []struct { 567 regex *regexp.Regexp 568 rpl string 569 }{ 570 // URL 571 { 572 regexp.MustCompile(`<([^|<>]+)\|([^|<>]+)>`), 573 "[$2]($1)", 574 }, 575 // bold 576 { 577 regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`), 578 "$1**$2**", 579 }, 580 // strikethrough 581 { 582 regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`), 583 "$1~~$2~~", 584 }, 585 // single paragraph blockquote 586 // Slack converts > character to > 587 { 588 regexp.MustCompile(`(?sm)^>`), 589 ">", 590 }, 591 } 592 593 regexReplaceAllStringFunc := []struct { 594 regex *regexp.Regexp 595 fn func(string) string 596 }{ 597 // multiple paragraphs blockquotes 598 { 599 regexp.MustCompile(`(?sm)^>>>(.+)$`), 600 func(src string) string { 601 // remove >>> prefix, might have leading \n 602 prefixRegexp := regexp.MustCompile(`^([\n])?>>>(.*)`) 603 src = prefixRegexp.ReplaceAllString(src, "$1$2") 604 // append > to start of line 605 appendRegexp := regexp.MustCompile(`(?m)^`) 606 return appendRegexp.ReplaceAllString(src, ">$0") 607 }, 608 }, 609 } 610 611 for channelName, channelPosts := range posts { 612 for postIdx, post := range channelPosts { 613 result := post.Text 614 615 for _, rule := range regexReplaceAllString { 616 result = rule.regex.ReplaceAllString(result, rule.rpl) 617 } 618 619 for _, rule := range regexReplaceAllStringFunc { 620 result = rule.regex.ReplaceAllStringFunc(result, rule.fn) 621 } 622 posts[channelName][postIdx].Text = result 623 } 624 } 625 626 return posts 627 } 628 629 func (a *App) SlackImport(fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) { 630 // Create log file 631 log := bytes.NewBufferString(utils.T("api.slackimport.slack_import.log")) 632 633 zipreader, err := zip.NewReader(fileData, fileSize) 634 if err != nil || zipreader.File == nil { 635 log.WriteString(utils.T("api.slackimport.slack_import.zip.app_error")) 636 return model.NewAppError("SlackImport", "api.slackimport.slack_import.zip.app_error", nil, err.Error(), http.StatusBadRequest), log 637 } 638 639 var channels []SlackChannel 640 var users []SlackUser 641 posts := make(map[string][]SlackPost) 642 uploads := make(map[string]*zip.File) 643 for _, file := range zipreader.File { 644 reader, err := file.Open() 645 if err != nil { 646 log.WriteString(utils.T("api.slackimport.slack_import.open.app_error", map[string]interface{}{"Filename": file.Name})) 647 return model.NewAppError("SlackImport", "api.slackimport.slack_import.open.app_error", map[string]interface{}{"Filename": file.Name}, err.Error(), http.StatusInternalServerError), log 648 } 649 if file.Name == "channels.json" { 650 channels, _ = SlackParseChannels(reader) 651 } else if file.Name == "users.json" { 652 users, _ = SlackParseUsers(reader) 653 } else { 654 spl := strings.Split(file.Name, "/") 655 if len(spl) == 2 && strings.HasSuffix(spl[1], ".json") { 656 newposts, _ := SlackParsePosts(reader) 657 channel := spl[0] 658 if _, ok := posts[channel]; !ok { 659 posts[channel] = newposts 660 } else { 661 posts[channel] = append(posts[channel], newposts...) 662 } 663 } else if len(spl) == 3 && spl[0] == "__uploads" { 664 uploads[spl[1]] = file 665 } 666 } 667 } 668 669 posts = SlackConvertUserMentions(users, posts) 670 posts = SlackConvertChannelMentions(channels, posts) 671 posts = SlackConvertPostsMarkup(posts) 672 673 addedUsers := a.SlackAddUsers(teamID, users, log) 674 botUser := a.SlackAddBotUser(teamID, log) 675 676 a.SlackAddChannels(teamID, channels, posts, addedUsers, uploads, botUser, log) 677 678 if botUser != nil { 679 a.deactivateSlackBotUser(botUser) 680 } 681 682 a.InvalidateAllCaches() 683 684 log.WriteString(utils.T("api.slackimport.slack_import.notes")) 685 log.WriteString("=======\r\n\r\n") 686 687 log.WriteString(utils.T("api.slackimport.slack_import.note1")) 688 log.WriteString(utils.T("api.slackimport.slack_import.note2")) 689 log.WriteString(utils.T("api.slackimport.slack_import.note3")) 690 691 return nil, log 692 }