diff --git a/bridge/discord.go b/bridge/discord.go index 0b45702..c11ecaa 100644 --- a/bridge/discord.go +++ b/bridge/discord.go @@ -27,6 +27,7 @@ func prepareDiscord(dib *Bridge, botToken, guildID string) (*discordBot, error) // These events are all fired in separate goroutines discord.AddHandler(discord.onMessageCreate) discord.AddHandler(discord.onMemberListChunk) + discord.AddHandler(discord.onMemberUpdate) return discord, nil } @@ -61,8 +62,26 @@ func (d *discordBot) onMessageCreate(s *discordgo.Session, m *discordgo.MessageC func (d *discordBot) onMemberListChunk(s *discordgo.Session, m *discordgo.GuildMembersChunk) { fmt.Println("Chunk received.") + for _, m := range m.Members { - fmt.Println(m.Nick, m.User.Discriminator, m.User.ID, m.User.Mention(), m.User.String()) + d.handleMemberUpdate(m) + } +} + +func (d *discordBot) onMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { + fmt.Println("Member updated", m.User.Username, m.Nick) + d.handleMemberUpdate(m.Member) +} + +func (d *discordBot) handleMemberUpdate(m *discordgo.Member) { + nickname := m.Nick + if nickname == "" { + nickname = m.User.Username + } + d.h.updateUserChan <- DiscordUser{ + Nick: nickname, + Discriminator: m.User.Discriminator, + ID: m.User.ID, } } diff --git a/bridge/home.go b/bridge/home.go index 909c514..a3ebec1 100644 --- a/bridge/home.go +++ b/bridge/home.go @@ -18,6 +18,7 @@ type home struct { discordMessagesChan chan DiscordNewMessage discordMessageEventsChan chan DiscordMessageEvent + updateUserChan chan DiscordUser } func prepareHome(dib *Bridge, discord *discordBot, ircListener *ircListener, ircManager *ircManager) { @@ -31,6 +32,7 @@ func prepareHome(dib *Bridge, discord *discordBot, ircListener *ircListener, irc discordMessagesChan: make(chan DiscordNewMessage), discordMessageEventsChan: make(chan DiscordMessageEvent), + updateUserChan: make(chan DiscordUser), } go dib.h.loop() @@ -40,20 +42,6 @@ func (h *home) GetIRCChannels() []string { return h.dib.chanIRC } -func (h *home) GetDiscordUserInfo(userID string) (discriminator, username string, err error) { - // TODO: Catch username changes, and cache UserID:Username mappings somewhere - u, err := h.discord.User(userID) - if err != nil { - fmt.Println("Could not find user", err) - return "", "", err - } - - discriminator = u.Discriminator - username = u.Username - - return -} - func (h *home) loop() { for { select { @@ -69,18 +57,26 @@ func (h *home) loop() { case msg := <-h.discordMessageEventsChan: ircChan := h.dib.chanMapToIRC[msg.channelID] if ircChan == "" { + fmt.Println("Ignoring message sent from an unhandled channel.") continue } - h.ircManager.PulseID(msg.userID) h.ircManager.SendMessage(msg.userID, ircChan, msg.message) + // Notification to potentially update, or create, a user + case user := <-h.updateUserChan: + if user.ID != "83386293446246400" { + continue + } + + h.ircManager.CreateConnection(user.ID, user.Discriminator, user.Nick) // Done! case <-h.done: fmt.Println("Closing all connections!") h.discord.Close() h.ircListener.Disconnect() h.ircManager.DisconnectAll() + default: } diff --git a/bridge/irc_connection.go b/bridge/irc_connection.go index 884a165..c2893d3 100644 --- a/bridge/irc_connection.go +++ b/bridge/irc_connection.go @@ -11,8 +11,9 @@ import ( type ircConnection struct { *irc.Connection - userID string - username string + userID string + discriminator string + username string messages chan DiscordNewMessage @@ -39,18 +40,13 @@ func (i *ircConnection) JoinChannels() { i.SendRaw("JOIN " + strings.Join(channels, ",")) } -func (i *ircConnection) RefreshUsername() (err error) { - username, err := i.manager.generateUsername(i.userID) - - if err != nil { - return - } +func (i *ircConnection) UpdateDetails(discriminator string, nickname string) { + username := i.manager.generateUsername(discriminator, nickname) + i.discriminator = discriminator i.username = username if i.Connected() { i.SendRaw("NICK " + username) } - - return } diff --git a/bridge/irc_manager.go b/bridge/irc_manager.go index 2ae706d..23601db 100644 --- a/bridge/irc_manager.go +++ b/bridge/irc_manager.go @@ -2,6 +2,7 @@ package bridge import ( "fmt" + "time" irc "github.com/thoj/go-ircevent" ) @@ -30,17 +31,15 @@ func (m *ircManager) DisconnectAll() { } } -func (m *ircManager) CreateConnection(userID string) (*ircConnection, error) { +func (m *ircManager) CreateConnection(userID string, discriminator string, nick string) (*ircConnection, error) { if con, ok := m.ircConnections[userID]; ok { - fmt.Println("Returning cached IRC connection") - + con.UpdateDetails(discriminator, nick) return con, nil + } else if (discriminator == "") && (nick == "") { + panic("Expected nickname and discriminator") } - username, err := m.generateUsername(userID) - if err != nil { - return nil, err - } + username := m.generateUsername(discriminator, nick) innerCon := irc.IRC(username, "BetterDiscordBot") setupIRCConnection(innerCon) @@ -57,7 +56,7 @@ func (m *ircManager) CreateConnection(userID string) (*ircConnection, error) { m.ircConnections[userID] = con - err = con.Connect(m.ircServerAddress) + err := con.Connect(m.ircServerAddress) if err != nil { fmt.Println("error opening irc connection,", err) return nil, err @@ -69,34 +68,31 @@ func (m *ircManager) CreateConnection(userID string) (*ircConnection, error) { } // TODO: Catch username changes, and cache UserID:Username mappings somewhere -func (m *ircManager) generateUsername(userID string) (string, error) { - _, username, err := m.h.GetDiscordUserInfo(userID) - if err != nil { - return "", err - } - - return username + "^d", nil +func (m *ircManager) generateUsername(_ string, nick string) string { + return nick + "^d" // return fmt.Sprintf("[%s-%s]", username, discriminator), nil } -func (m *ircManager) PulseID(userID string) { - _, err := m.CreateConnection(userID) - - if err != nil { - panic(err) - } -} - func (m *ircManager) SendMessage(userID, channel, message string) { - con, err := m.CreateConnection(userID) - if err != nil { - panic(err) + con, ok := m.ircConnections[userID] + if !ok { + panic("Could not find connection") } - con.messages <- DiscordNewMessage{ + msg := DiscordNewMessage{ ircChannel: channel, str: message, } + + select { + // Try to send the message immediately + case con.messages <- msg: + // If it can't after 5ms, do it in a separate goroutine + case <-time.After(time.Millisecond * 5): + go func() { + con.messages <- msg + }() + } } // TODO diff --git a/bridge/structs.go b/bridge/structs.go index 5c66cc8..539f058 100644 --- a/bridge/structs.go +++ b/bridge/structs.go @@ -12,3 +12,10 @@ type DiscordNewMessage struct { ircChannel string str string } + +// DiscordUser is information that IRC needs to know about a user +type DiscordUser struct { + Nick string // non-unique nickname + Discriminator string // locally unique ID + ID string // globally unique id +} diff --git a/bridge/utils.go b/bridge/utils.go index 97f2cfb..ac527f0 100644 --- a/bridge/utils.go +++ b/bridge/utils.go @@ -2,6 +2,12 @@ package bridge import ( "crypto/tls" + "strconv" + + "strings" + "unicode/utf8" + + "github.com/pkg/errors" "github.com/thoj/go-ircevent" ) @@ -11,3 +17,33 @@ func setupIRCConnection(con *irc.Connection) { con.UseTLS = true con.TLSConfig = &tls.Config{InsecureSkipVerify: true} // TODO: REALLY, THIS IS NOT A VERIFIED CONNECTION! } + +// Leftpad is from github.com/douglarek/leftpad +func Leftpad(s string, length int, ch ...rune) string { + c := ' ' + if len(ch) > 0 { + c = ch[0] + } + l := length - utf8.RuneCountInString(s) + if l > 0 { + s = strings.Repeat(string(c), l) + s + } + return s +} + +// Takes a snowflake and the first half of an IP to make an IP suitable for WEBIRC +func SnowflakeToIP(base string, snowflake string) string { + num, err := strconv.ParseUint(snowflake, 10, 64) + if err != nil { + panic(errors.Wrap(err, "could not convert snowflake to uint")) + } + + for i, c := range Leftpad(strconv.FormatUint(num, 16), 16, '0') { + if (i % 4) == 0 { + base += ":" + } + base += string(c) + } + + return base +}