Skip to content

Instantly share code, notes, and snippets.

@Code-Hex
Last active August 1, 2022 11:58
Show Gist options
  • Save Code-Hex/e1f78e63ebe71ffc1d7b20453c088fab to your computer and use it in GitHub Desktop.
Save Code-Hex/e1f78e63ebe71ffc1d7b20453c088fab to your computer and use it in GitHub Desktop.
Slack to discord message migration tool
module github.com/Code-Hex/slack2discord
go 1.18
require (
github.com/Code-Hex/dd v1.1.0 // indirect
github.com/bwmarrin/discordgo v0.25.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/slack-go/slack v0.11.2 // indirect
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
)
package main
import (
"context"
"fmt"
"html"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/bwmarrin/discordgo"
"github.com/slack-go/slack"
)
func main() {
if err := run(context.Background()); err != nil {
fmt.Fprintf(os.Stderr, "failed to run: %q", err)
os.Exit(1)
}
}
const slackChannel = "C..." // slack channel ID
const discordChannel = "..." // discord channel ID
// < # opening angle bracket
// ([^>\|]+) # link
// (?:\|([^>]+))? # label
// > # closing angle bracket
var re = regexp.MustCompile(`<([^>\|]+)(?:\|([^>]+))?>`)
func run(ctx context.Context) error {
slackClient := slack.New("xoxp-...")
session, err := discordgo.New("...")
if err != nil {
return err
}
messages, err := history(ctx, slackClient)
if err != nil {
return err
}
for _, m := range messages {
fmt.Println("-------", m.Timestamp)
fmt.Println(m.Text)
_, err = session.ChannelMessageSend(discordChannel, m.Text)
if err != nil {
return fmt.Errorf("failed to send message: %w", err)
}
time.Sleep(time.Millisecond * 500)
}
// fmt.Println(dd.Dump(messages))
return nil
}
type Message struct {
Text string
Timestamp string // to use save point
}
func history(ctx context.Context, slackClient *slack.Client) ([]*Message, error) {
var (
cursor string
messages []*Message
)
for i := 0; ; i++ {
resp, err := slackClient.GetConversationHistoryContext(ctx, &slack.GetConversationHistoryParameters{
ChannelID: slackChannel,
Cursor: cursor,
})
if err != nil {
return nil, err
}
for _, message := range resp.Messages {
// If failed, you can continue to specify the timestamp here!
// if message.Timestamp <= "1648392833.619319" {
// continue
// }
text := strings.TrimSpace(message.Text)
if text == "" {
continue
}
if strings.Contains(text, "さんがチャンネルに参加しました") {
continue
}
if strings.Contains(text, "がこのチャンネルの説明を") {
continue
}
replacedText := re.ReplaceAllStringFunc(text, func(s string) string {
parts := re.FindStringSubmatch(s)
link := parts[1]
return link
})
t, err := ts2time(message.Timestamp)
if err != nil {
return nil, err
}
content := fmt.Sprintf("(%s)\n\n%s", t.Format("2006-01-02 15:04:05"), html.UnescapeString(replacedText))
messages = append(messages, &Message{
Text: content,
Timestamp: message.Timestamp,
})
}
if !resp.HasMore {
break
}
cursor = resp.ResponseMetaData.NextCursor
time.Sleep(time.Millisecond * 500)
fmt.Println(i, len(messages), cursor)
}
return reverse(messages), nil
}
var jst *time.Location
var once sync.Once
func getJST() *time.Location {
once.Do(func() {
jst, _ = time.LoadLocation("Asia/Tokyo")
})
return jst
}
func ts2time(ts string) (time.Time, error) {
sep := strings.Split(ts, ".")
if len(sep) != 2 {
return time.Time{}, fmt.Errorf("unexpected ts: %s", ts)
}
sec, ms := sep[0], sep[1]
seci, err := strconv.Atoi(sec)
if err != nil {
return time.Time{}, fmt.Errorf("seci: %w", err)
}
msi, err := strconv.Atoi(ms)
if err != nil {
return time.Time{}, fmt.Errorf("msi: %w", err)
}
return time.Unix(int64(seci), int64(msi)).In(getJST()), nil
}
func reverse(s []*Message) []*Message {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
@Code-Hex
Copy link
Author

Code-Hex commented Aug 1, 2022

Get Discord API Token for yours!

Discord の自分のアカウントの Token を取得しよう!

Get Slack API for yours!

You need to get user (xoxp-) token.

スクリーンショット 2022-08-01 20 57 17

User Token Scopes

Scopes that access user data and act on behalf of users that authorize them.

OAuth Scope Description
channels:history View messages and other content in a user’s public channels
im:history View messages and other content in a user’s direct messages

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment