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 &gt;
   587  		{
   588  			regexp.MustCompile(`(?sm)^&gt;`),
   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)^>&gt;&gt;(.+)$`),
   600  			func(src string) string {
   601  				// remove >>> prefix, might have leading \n
   602  				prefixRegexp := regexp.MustCompile(`^([\n])?>&gt;&gt;(.*)`)
   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  }