diff --git a/event_handler.go b/event_handler.go index 1a4d67c..27bc3fe 100644 --- a/event_handler.go +++ b/event_handler.go @@ -36,7 +36,46 @@ func eventHandler(ctx *IrcContext, rtm *slack.RTM) { } } else if strings.HasPrefix(ev.Msg.Channel, "D") { // Direct message to me - channame = ctx.Nick + users, err := usersInConversation(ctx, ev.Msg.Channel) + if err != nil { + // ERR_UNKNOWNERROR + SendIrcNumeric(ctx, 400, ctx.Nick, fmt.Sprintf("Cannot get conversation info for %s", ev.Msg.Channel)) + return + } + // we expect only two members in a direct message. Raise an + // error if not. + if len(users) != 2 { + // ERR_UNKNOWNERROR + SendIrcNumeric(ctx, 400, ctx.Nick, fmt.Sprintf("Exactly two users expected in direct message, got %d (conversation ID: %s)", len(users), ev.Msg.Channel)) + return + + } + // of the two users, one is me. Otherwise fail + if ctx.UserID() == "" { + // ERR_UNKNOWNERROR + SendIrcNumeric(ctx, 400, ctx.UserID(), "Cannot get my own user ID") + return + } + if users[0] != ctx.UserID() && users[1] != ctx.UserID() { + // ERR_UNKNOWNERROR + SendIrcNumeric(ctx, 400, ctx.UserID(), fmt.Sprintf("Got a direct message where I am not part of the members list (members: %s)", strings.Join(users, ", "))) + return + } + var recipientID string + if users[0] == ctx.UserID() { + // then it's the other user + recipientID = users[1] + } else { + recipientID = users[0] + } + // now resolve the ID to the user's nickname + nickname := ctx.GetUserInfo(recipientID) + if nickname == nil { + // ERR_UNKNOWNERROR + SendIrcNumeric(ctx, 400, ctx.UserID(), fmt.Sprintf("Unknown destination user ID %s for direct message %s", recipientID, ev.Msg.Channel)) + return + } + channame = nickname.Name } else { log.Printf("Unknown recipient ID: %s", ev.Msg.Channel) return diff --git a/irc_context.go b/irc_context.go index 192f0d2..3666698 100644 --- a/irc_context.go +++ b/irc_context.go @@ -70,6 +70,15 @@ func (ic *IrcContext) GetUserInfoByName(username string) *slack.User { return nil } +// UserID returns the user's Slack ID +func (ic IrcContext) UserID() string { + user := ic.GetUserInfoByName(ic.Nick) + if user == nil { + return "" + } + return user.ID +} + // Mask returns the IRC mask for the current user func (ic IrcContext) Mask() string { var username string diff --git a/irc_server.go b/irc_server.go index 9d38eef..aa3af51 100644 --- a/irc_server.go +++ b/irc_server.go @@ -106,20 +106,16 @@ func IrcSendChanInfoAfterJoin(ctx *IrcContext, name, topic string, members []str ctx.ChanMutex.Unlock() } -// join will join the channel with the given ID, name and topic, and send back a -// response to the IRC client -func join(ctx *IrcContext, id, name, topic string) { +func usersInConversation(ctx *IrcContext, conversation string) ([]string, error) { var ( - info string members, m []string nextCursor string err error ) for { - m, nextCursor, err = ctx.SlackClient.GetUsersInConversation(&slack.GetUsersInConversationParameters{ChannelID: id, Cursor: nextCursor}) + m, nextCursor, err = ctx.SlackClient.GetUsersInConversation(&slack.GetUsersInConversationParameters{ChannelID: conversation, Cursor: nextCursor}) if err != nil { - log.Printf("Cannot get member list for channel %s: %v", name, err) - break + return nil, fmt.Errorf("Cannot get member list for conversation %s: %v", conversation, err) } members = append(members, m...) log.Printf(" nextCursor=%v", nextCursor) @@ -127,12 +123,23 @@ func join(ctx *IrcContext, id, name, topic string) { break } } - info = "(joined) " + return members, nil +} + +// join will join the channel with the given ID, name and topic, and send back a +// response to the IRC client +func join(ctx *IrcContext, id, name, topic string) error { + members, err := usersInConversation(ctx, id) + if err != nil { + return err + } + info := "(joined) " info += fmt.Sprintf(" topic=%s members=%d", topic, len(members)) log.Printf(info) // the channels are already joined, notify the IRC client of their // existence go IrcSendChanInfoAfterJoin(ctx, name, topic, members, false) + return nil } // joinChannels gets all the available Slack channels and sends an IRC JOIN message @@ -156,7 +163,9 @@ func joinChannels(ctx *IrcContext) error { } for _, ch := range channels { if ch.IsMember { - join(ctx, ch.ID, ch.Name, ch.Topic.Value) + if err := join(ctx, ch.ID, ch.Name, ch.Topic.Value); err != nil { + return err + } } } return nil @@ -184,119 +193,6 @@ func IrcAfterLoggingIn(ctx *IrcContext, rtm *slack.RTM) error { } go eventHandler(ctx, rtm) - /* - go func(rtm *slack.RTM) { - log.Print("Started Slack event listener") - for msg := range rtm.IncomingEvents { - switch ev := msg.Data.(type) { - case *slack.MessageEvent: - // get user - var name string - user := ctx.GetUserInfo(ev.Msg.User) - if user == nil { - log.Printf("Error getting user info for %v", ev.Msg.User) - name = ev.Msg.User - } else { - name = user.Name - } - // get channel or other recipient (e.g. recipient of a direct message) - var channame string - if strings.HasPrefix(ev.Msg.Channel, "C") { - // Channel message - // TODO cache channel info - channel, err := ctx.SlackClient.GetChannelInfo(ev.Msg.Channel) - if err != nil { - log.Printf("Error getting channel info for %v: %v", ev.Msg.Channel, err) - channame = "unknown" - } else { - channame = "#" + channel.Name - } - } else if strings.HasPrefix(ev.Msg.Channel, "D") { - // Direct message to me - channame = ctx.Nick - } else { - log.Printf("Unknown recipient ID: %s", ev.Msg.Channel) - return - } - - log.Printf("SLACK msg from %v (%v) on %v: %v", - ev.Msg.User, - name, - ev.Msg.Channel, - ev.Msg.Text, - ) - if ev.Msg.User == "" && ev.Msg.Text == "" { - log.Printf("WARNING: empty user and message: %+v", ev.Msg) - continue - } - // replace UIDs with user names - text := ev.Msg.Text - // replace UIDs with nicknames - text = rxSlackUser.ReplaceAllStringFunc(text, func(subs string) string { - uid := subs[2 : len(subs)-1] - user := ctx.GetUserInfo(uid) - if user == nil { - return subs - } - return fmt.Sprintf("@%s", user.Name) - }) - // replace some HTML entities - text = ExpandText(text) - - // FIXME if two instances are connected to the Slack API at the - // same time, this will hide the other instance's message - // believing it was sent from here. But since it's not, both - // local echo and remote message won't be shown - botID := msg.Data.(*slack.MessageEvent).BotID - if name == ctx.Nick && botID != user.Profile.BotID { - // don't print my own messages - continue - } - // handle multi-line messages - for _, line := range strings.Split(text, "\n") { - privmsg := fmt.Sprintf(":%v!%v@%v PRIVMSG %v :%v\r\n", - name, ev.Msg.User, ctx.ServerName, - channame, line, - ) - log.Print(privmsg) - ctx.Conn.Write([]byte(privmsg)) - } - msgEv := msg.Data.(*slack.MessageEvent) - // Check if the topic has changed - if msgEv.Topic != ctx.Channels[msgEv.Channel].Topic { - // Send out new topic - channel, err := ctx.SlackClient.GetChannelInfo(msgEv.Channel) - if err != nil { - log.Printf("Cannot get channel name for %v", msgEv.Channel) - } else { - newTopic := fmt.Sprintf(":%v TOPIC #%v :%v\r\n", ctx.Mask(), channel.Name, msgEv.Topic) - log.Printf("Got new topic: %v", newTopic) - ctx.Conn.Write([]byte(newTopic)) - } - } - // check if new people joined the channel - added, removed := ctx.Channels[msgEv.Channel].MembersDiff(msgEv.Members) - if len(added) > 0 || len(removed) > 0 { - log.Printf("[*] People who joined: %v", added) - log.Printf("[*] People who left: %v", removed) - } - case *slack.ConnectedEvent: - log.Print("Connected to Slack") - ctx.SlackConnected = true - case *slack.DisconnectedEvent: - log.Printf("Disconnected from Slack (intentional: %v)", msg.Data.(*slack.DisconnectedEvent).Intentional) - ctx.SlackConnected = false - ctx.Conn.Close() - case *slack.MemberJoinedChannelEvent, *slack.MemberLeftChannelEvent: - // refresh the users list - // FIXME also send a JOIN / PART message to the IRC client - ctx.GetUsers(true) - default: - log.Printf("SLACK event: %v: %v", msg.Type, msg.Data) - } - } - }(rtm) - */ return nil }