Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package slackimport
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
)
|
|
|
|
func slackConvertTimeStamp(ts string) int64 {
|
|
timeString := strings.SplitN(ts, ".", 2)[0]
|
|
|
|
timeStamp, err := strconv.ParseInt(timeString, 10, 64)
|
|
if err != nil {
|
|
mlog.Warn("Slack Import: Bad timestamp detected.")
|
|
return 1
|
|
}
|
|
return timeStamp * 1000 // Convert to milliseconds
|
|
}
|
|
|
|
func slackConvertChannelName(channelName string, channelId string) string {
|
|
newName := strings.Trim(channelName, "_-")
|
|
if len(newName) == 1 {
|
|
return "slack-channel-" + newName
|
|
}
|
|
|
|
if isValidChannelNameCharacters(newName) {
|
|
return newName
|
|
}
|
|
return strings.ToLower(channelId)
|
|
}
|
|
|
|
func slackConvertUserMentions(users []slackUser, posts map[string][]slackPost) map[string][]slackPost {
|
|
var regexes = make(map[string]*regexp.Regexp, len(users))
|
|
for _, user := range users {
|
|
r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>")
|
|
if err != nil {
|
|
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))
|
|
continue
|
|
}
|
|
regexes["@"+user.Username] = r
|
|
}
|
|
|
|
// Special cases.
|
|
regexes["@here"], _ = regexp.Compile(`<!here\|@here>`)
|
|
regexes["@channel"], _ = regexp.Compile("<!channel>")
|
|
regexes["@all"], _ = regexp.Compile("<!everyone>")
|
|
|
|
for channelName, channelPosts := range posts {
|
|
for postIdx, post := range channelPosts {
|
|
for mention, r := range regexes {
|
|
post.Text = r.ReplaceAllString(post.Text, mention)
|
|
posts[channelName][postIdx] = post
|
|
}
|
|
}
|
|
}
|
|
|
|
return posts
|
|
}
|
|
|
|
func slackConvertChannelMentions(channels []slackChannel, posts map[string][]slackPost) map[string][]slackPost {
|
|
var regexes = make(map[string]*regexp.Regexp, len(channels))
|
|
for _, channel := range channels {
|
|
r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>")
|
|
if err != nil {
|
|
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))
|
|
continue
|
|
}
|
|
regexes["~"+channel.Name] = r
|
|
}
|
|
|
|
for channelName, channelPosts := range posts {
|
|
for postIdx, post := range channelPosts {
|
|
for channelReplace, r := range regexes {
|
|
post.Text = r.ReplaceAllString(post.Text, channelReplace)
|
|
posts[channelName][postIdx] = post
|
|
}
|
|
}
|
|
}
|
|
|
|
return posts
|
|
}
|
|
|
|
func slackConvertPostsMarkup(posts map[string][]slackPost) map[string][]slackPost {
|
|
regexReplaceAllString := []struct {
|
|
regex *regexp.Regexp
|
|
rpl string
|
|
}{
|
|
// URL
|
|
{
|
|
regexp.MustCompile(`<([^|<>]+)\|([^|<>]+)>`),
|
|
"[$2]($1)",
|
|
},
|
|
// bold
|
|
{
|
|
regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`),
|
|
"$1**$2**",
|
|
},
|
|
// strikethrough
|
|
{
|
|
regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`),
|
|
"$1~~$2~~",
|
|
},
|
|
// single paragraph blockquote
|
|
// Slack converts > character to >
|
|
{
|
|
regexp.MustCompile(`(?sm)^>`),
|
|
">",
|
|
},
|
|
}
|
|
|
|
regexReplaceAllStringFunc := []struct {
|
|
regex *regexp.Regexp
|
|
fn func(string) string
|
|
}{
|
|
// multiple paragraphs blockquotes
|
|
{
|
|
regexp.MustCompile(`(?sm)^>>>(.+)$`),
|
|
func(src string) string {
|
|
// remove >>> prefix, might have leading \n
|
|
prefixRegexp := regexp.MustCompile(`^([\n])?>>>(.*)`)
|
|
src = prefixRegexp.ReplaceAllString(src, "$1$2")
|
|
// append > to start of line
|
|
appendRegexp := regexp.MustCompile(`(?m)^`)
|
|
return appendRegexp.ReplaceAllString(src, ">$0")
|
|
},
|
|
},
|
|
}
|
|
|
|
for channelName, channelPosts := range posts {
|
|
for postIdx, post := range channelPosts {
|
|
result := post.Text
|
|
|
|
for _, rule := range regexReplaceAllString {
|
|
result = rule.regex.ReplaceAllString(result, rule.rpl)
|
|
}
|
|
|
|
for _, rule := range regexReplaceAllStringFunc {
|
|
result = rule.regex.ReplaceAllStringFunc(result, rule.fn)
|
|
}
|
|
posts[channelName][postIdx].Text = result
|
|
}
|
|
}
|
|
|
|
return posts
|
|
}
|