diff --git a/bridge/bridge.go b/bridge/bridge.go index a0a93978..10e52c16 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -23,6 +23,9 @@ type Bridger interface { MsgChannel(channelID, text string) (string, error) MsgChannelThread(channelID, parentID, text string) (string, error) + AddReaction(msgID, emoji string) error + RemoveReaction(msgID, emoji string) error + StatusUser(userID string) (string, error) StatusUsers() (map[string]string, error) SetStatus(status string) error diff --git a/bridge/mattermost/mattermost.go b/bridge/mattermost/mattermost.go index 401c3449..df7a15ea 100644 --- a/bridge/mattermost/mattermost.go +++ b/bridge/mattermost/mattermost.go @@ -341,6 +341,42 @@ func (m *Mattermost) ModifyPost(msgID, text string) error { return nil } +func (m *Mattermost) AddReaction(msgID, emoji string) error { + logger.Debugf("adding reaction %#v, %#v", msgID, emoji) + reaction := &model.Reaction{ + UserId: m.mc.User.Id, + PostId: msgID, + EmojiName: emoji, + CreateAt: 0, + } + + _, resp := m.mc.Client.SaveReaction(reaction) + + if resp.Error != nil { + return resp.Error + } + + return nil +} + +func (m *Mattermost) RemoveReaction(msgID, emoji string) error { + logger.Debugf("removing reaction %#v, %#v", msgID, emoji) + reaction := &model.Reaction{ + UserId: m.mc.User.Id, + PostId: msgID, + EmojiName: emoji, + CreateAt: 0, + } + + _, resp := m.mc.Client.DeleteReaction(reaction) + + if resp.Error != nil { + return resp.Error + } + + return nil +} + func (m *Mattermost) Topic(channelID string) string { return m.mc.GetChannelHeader(channelID) } diff --git a/bridge/slack/slack.go b/bridge/slack/slack.go index e4804d5c..f72f204d 100644 --- a/bridge/slack/slack.go +++ b/bridge/slack/slack.go @@ -932,3 +932,11 @@ func (s *Slack) MsgChannelThread(username, parentID, text string) (string, error func (s *Slack) ModifyPost(channelID, text string) error { return nil } + +func (s *Slack) AddReaction(msgID, emoji string) error { + return nil +} + +func (s *Slack) RemoveReaction(msgID, emoji string) error { + return nil +} diff --git a/mm-go-irckit/server.go b/mm-go-irckit/server.go index d06f5094..5ccba936 100644 --- a/mm-go-irckit/server.go +++ b/mm-go-irckit/server.go @@ -398,6 +398,7 @@ func (s *server) add(u *User) (ok bool) { return true } +//nolint:goconst func (s *server) handshake(u *User) error { // Assign host u.Host = u.ResolveHost() @@ -406,71 +407,78 @@ func (s *server) handshake(u *User) error { // Consume N messages then give up. i := handshakeMsgTolerance // Read messages until we filled in USER details. - for msg := range u.DecodeCh { - // fmt.Printf("in handshake %#v\n", msg) - i-- - // Consume N messages then give up. - if i == 0 { - break - } - if msg == nil { - // Empty message, ignore. - continue - } - - // apparently NICK message can have a : prefix on connection - // https://github.com/42wim/matterircd/issues/32 - if (msg.Command == irc.NICK || msg.Command == irc.PASS) && msg.Trailing != "" { - msg.Params = append(msg.Params, msg.Trailing) - } - if len(msg.Params) < 1 { - continue - } +outerloop: + for { + select { + case msg := <-u.DecodeCh: + // fmt.Printf("in handshake %#v\n", msg) + i-- + // Consume N messages then give up. + if i == 0 { + break outerloop + } + if msg == nil { + // Empty message, ignore. + continue + } - switch msg.Command { - case irc.NICK: - u.Nick = msg.Params[0] - case irc.USER: - u.User = msg.Params[0] - u.Real = msg.Trailing - case irc.PASS: - u.Pass = msg.Params - } + // apparently NICK message can have a : prefix on connection + // https://github.com/42wim/matterircd/issues/32 + if (msg.Command == irc.NICK || msg.Command == irc.PASS) && msg.Trailing != "" { + msg.Params = append(msg.Params, msg.Trailing) + } + if len(msg.Params) < 1 { + continue + } - if u.Nick == "" || u.User == "" { - // Wait for both to be set before proceeding - continue - } - if len(u.Nick) > s.config.MaxNickLen { - u.Nick = u.Nick[:s.config.MaxNickLen] - } + switch msg.Command { + case irc.NICK: + u.Nick = msg.Params[0] + case irc.USER: + u.User = msg.Params[0] + u.Real = msg.Trailing + case irc.PASS: + u.Pass = msg.Params + } - ok := s.add(u) - if !ok { - s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{u.Nick}, "Nickname is already in use") - continue - } - s.u = u + if u.Nick == "" || u.User == "" { + // Wait for both to be set before proceeding + continue + } + if len(u.Nick) > s.config.MaxNickLen { + u.Nick = u.Nick[:s.config.MaxNickLen] + } - err := s.welcome(u) - if err == nil && u.Pass != nil { - service := "mattermost" - if len(u.Pass) == 1 { - service = "slack" + ok := s.add(u) + if !ok { + s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{u.Nick}, "Nickname is already in use") + continue } - login(u, &User{ - UserInfo: &bridge.UserInfo{ - Nick: service, - User: service, - Real: service, - Host: "service", + s.u = u + + err := s.welcome(u) + if err == nil && u.Pass != nil { + service := "mattermost" + if len(u.Pass) == 1 { + service = "slack" + } + login(u, &User{ + UserInfo: &bridge.UserInfo{ + Nick: service, + User: service, + Real: service, + Host: "service", + }, + channels: map[Channel]struct{}{}, }, - channels: map[Channel]struct{}{}, - }, - u.Pass, - service) + u.Pass, + service) + } + + return err + case <-time.After(10 * time.Second): + return ErrHandshakeFailed } - return err } return ErrHandshakeFailed } diff --git a/mm-go-irckit/server_commands.go b/mm-go-irckit/server_commands.go index 48909850..96bec06f 100644 --- a/mm-go-irckit/server_commands.go +++ b/mm-go-irckit/server_commands.go @@ -373,6 +373,10 @@ func CmdPrivMsg(s Server, u *User, msg *irc.Message) error { return nil } + if parseReactionToMsg(u, msg, ch.ID()) { + return nil + } + if threadMsgChannel(u, msg, ch.ID()) { return nil } @@ -383,7 +387,7 @@ func CmdPrivMsg(s Server, u *User, msg *irc.Message) error { msgID, err2 := u.br.MsgChannel(ch.ID(), msg.Trailing) if err2 != nil { - u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+msg.Trailing+" could not be send: "+err2.Error()) + u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+msg.Trailing+" could not be send"+err2.Error()) return err2 } @@ -411,6 +415,11 @@ func CmdPrivMsg(s Server, u *User, msg *irc.Message) error { if u.br == nil { return nil } + + if parseReactionToMsg(u, msg, toUser.User) { + return nil + } + if threadMsgUser(u, msg, toUser.User) { return nil } @@ -442,6 +451,54 @@ func CmdPrivMsg(s Server, u *User, msg *irc.Message) error { return s.EncodeMessage(u, irc.ERR_NOSUCHNICK, msg.Params, "No such nick/channel") } +func parseReactionToMsg(u *User, msg *irc.Message, channelID string) bool { + re := regexp.MustCompile(`^\@\@([0-9a-f]{3}|[0-9a-z]{26})\s+([\-\+]):(\S+):\s*$`) + matches := re.FindStringSubmatch(msg.Trailing) + if len(matches) != 4 { + return false + } + + msgID := matches[1] + action := matches[2] + emoji := matches[3] + + // matterircd style prefix/suffix contexts (e.g. 001 and fa2). + if len(msgID) == 3 { + id, err := strconv.ParseInt(msgID, 16, 0) + if err != nil { + logger.Errorf("couldn't parseint %s: %s", msgID, err) + } + + u.msgMapMutex.RLock() + defer u.msgMapMutex.RUnlock() + + m := u.msgMap[channelID] + + for k, v := range m { + if v == int(id) { + msgID = k + break + } + } + } + + if action == "-" { + err := u.br.RemoveReaction(msgID, emoji) + if err != nil { + u.MsgSpoofUser(u, u.br.Protocol(), "reaction: "+emoji+" could not be removed"+err.Error()) + } + + return true + } + + err := u.br.AddReaction(msgID, emoji) + if err != nil { + u.MsgSpoofUser(u, u.br.Protocol(), "reaction: "+emoji+" could not be added"+err.Error()) + } + + return true +} + func parseModifyMsg(u *User, msg *irc.Message, channelID string) bool { re := regexp.MustCompile(`^s(\/(?:[0-9a-f]{3}|[0-9a-z]{26}|!!)?\/)(.*)`) matches := re.FindStringSubmatch(msg.Trailing) @@ -512,7 +569,7 @@ func parseModifyMsg(u *User, msg *irc.Message, channelID string) bool { if strings.Contains(err.Error(), "permissions") { return false } - u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+text+" could not be modified: "+err.Error()) + u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+text+" could not be modified"+err.Error()) } else { u.saveLastViewedAt(channelID) } @@ -581,15 +638,21 @@ func parseThreadID(u *User, msg *irc.Message, channelID string) (string, string) return "", "" } -func threadMsgChannel(u *User, msg *irc.Message, channelID string) bool { +func threadMsgChannelUser(u *User, msg *irc.Message, channelID string, toUser bool) bool { threadID, text := parseThreadID(u, msg, channelID) if threadID == "" { return false } - msgID, err := u.br.MsgChannelThread(channelID, threadID, text) + var msgID string + var err error + if toUser { + msgID, err = u.br.MsgUserThread(channelID, threadID, text) + } else { + msgID, err = u.br.MsgChannelThread(channelID, threadID, text) + } if err != nil { - u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+text+" could not be send: "+err.Error()) + u.MsgSpoofUser(u, u.br.Protocol(), "msg: "+text+" could not be send"+err.Error()) return false } @@ -605,8 +668,12 @@ func threadMsgChannel(u *User, msg *irc.Message, channelID string) bool { return true } +func threadMsgChannel(u *User, msg *irc.Message, channelID string) bool { + return threadMsgChannelUser(u, msg, channelID, false) +} + func threadMsgUser(u *User, msg *irc.Message, toUser string) bool { - return threadMsgChannel(u, msg, toUser) + return threadMsgChannelUser(u, msg, toUser, true) } // CmdQuit is a handler for the /QUIT command. diff --git a/mm-go-irckit/user.go b/mm-go-irckit/user.go index 2302871f..b8f2b213 100644 --- a/mm-go-irckit/user.go +++ b/mm-go-irckit/user.go @@ -3,6 +3,7 @@ package irckit import ( "fmt" "net" + "regexp" "strings" "sync" "time" @@ -180,9 +181,10 @@ func (u *User) Decode() { // start timer now t.Reset(time.Duration(bufferTimeout) * time.Millisecond) } else { - if strings.HasPrefix(msg.Trailing, "\x01ACTION") { + re := regexp.MustCompile(`^(?:\@\@|s/)(?:[0-9a-f]{3}|[0-9a-z]{26}|!!)|/`) + if strings.HasPrefix(msg.Trailing, "\x01ACTION") || re.MatchString(msg.Trailing) { // flush buffer - logger.Debug("flushing buffer because of /me") + logger.Debug("flushing buffer because of /me, replies to threads, and message modifications") u.BufferedMsg.Trailing = strings.TrimSpace(u.BufferedMsg.Trailing) u.DecodeCh <- u.BufferedMsg u.BufferedMsg = nil