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