github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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 &gt;
   586  		{
   587  			regexp.MustCompile(`(?sm)^&gt;`),
   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)^>&gt;&gt;(.+)$`),
   599  			func(src string) string {
   600  				// remove >>> prefix, might have leading \n
   601  				prefixRegexp := regexp.MustCompile(`^([\n])?>&gt;&gt;(.*)`)
   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  }