RITlug/teleirc

View on GitHub
internal/handlers/irc/handlers.go

Summary

Maintainability
A
3 hrs
Test Coverage
B
83%
package irc

import (
    "fmt"
    "regexp"
    "strings"

    "github.com/lrstanley/girc"
)

const (
    joinFmt         = "* %s joins"
    partFmt         = "* %s parts"
    quitFmt         = "* %s quit (%s)"
    kickFmt         = "* %s kicked %s from %s: %s"
    topicChangeFmt  = "* %s changed topic to: %s"
    topicClearedFmt = "* %s removed topic"
    nickFmt         = "* %s is now known as: %s"
)

/*
Handler specifies a function that handles an IRC event
In this case, we take an IRC client and return a function that
handles an IRC event
*/
type Handler = func(c ClientInterface) func(*girc.Client, girc.Event)

/*
checkBlacklist checks the IRC blacklist for a name, and returns whether
or not the name is in the blacklist
*/
func checkBlacklist(c ClientInterface, toCheck string) bool {
    for _, name := range c.IRCSettings().IRCBlacklist {
        if strings.EqualFold(toCheck, name) {
            return true
        }
    }
    return false
}

func shouldSendJoin(c ClientInterface, toCheck string) bool {
    var settings = c.TgSettings()
    if settings.ShowJoinMessage {
        return true
    } else if settings.JoinMessageAllowList == nil {
        return false
    }

    for _, name := range settings.JoinMessageAllowList {
        if strings.EqualFold(toCheck, name) {
            return true
        }
    }
    return false
}

func shouldSendLeave(c ClientInterface, toCheck string) bool {
    var settings = c.TgSettings()
    if settings.ShowLeaveMessage {
        return true
    } else if settings.LeaveMessageAllowList == nil {
        return false
    }

    for _, name := range settings.LeaveMessageAllowList {
        if strings.EqualFold(toCheck, name) {
            return true
        }
    }
    return false
}

func hasNoForwardPrefix(c ClientInterface, toCheck string) bool {
    noForwardPrefix := c.IRCSettings().NoForwardPrefix

    if noForwardPrefix == "" {
        return false
    }

    if strings.HasPrefix(toCheck, c.IRCSettings().NoForwardPrefix) {
        return true
    }
    return false
}

/*
connectHandler returns a function to use as the connect handler for girc,
so that the specified channel is joined after the server connection is established
*/
func connectHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("connectHandler triggered")
        if c.IRCSettings().ChannelKey != "" {
            c.JoinKey(c.IRCSettings().Channel, c.IRCSettings().ChannelKey)
        } else {
            c.Join(c.IRCSettings().Channel)
        }
    }
}

func disconnectHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("disconnectHandler triggered")
        if c.TgSettings().ShowDisconnectMessage {
            c.SendToTg("Lost connection to '" + c.IRCSettings().Channel + "' on '" + c.IRCSettings().Server + "'")
        }
    }
}

/*
messageHandler handles the PRIVMSG IRC event, which entails both private
and channel messages. However, it only cares about channel messages
*/
func messageHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    var colorStripper = regexp.MustCompile(`[\x02\x1F\x0F\x16]|\x03(\d\d?(,\d\d?)?)?`)
    var ircChannel = c.IRCSettings().Channel

    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("messageHandler triggered")

        // Only send if user is not in blacklist ...
        if !(checkBlacklist(c, e.Source.Name)) {
            // ... and if the channel matches. Array index is safe because IsFromChannel
            // itself does it this way.
            if e.IsFromChannel() && e.Params[0] == ircChannel {
                formatted := ""
                if e.IsAction() {
                    msg := e.Last()
                    // Strips out ACTION word from text
                    formatted = "* " + e.Source.Name + " " + msg[8:len(msg)-1]
                } else {
                    formatted = c.IRCSettings().Prefix + e.Source.Name + c.IRCSettings().Suffix + " " + e.Params[1]
                }

                if hasNoForwardPrefix(c, e.Params[1]) {
                    return // sender didn't want this forwarded
                }

                // Strip of mIRC formatting
                formatted = colorStripper.ReplaceAllString(formatted, "")

                c.SendToTg(formatted)
            }
        }
    }
}

func joinHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("joinHandler triggered")
        if (e.Source != nil) && shouldSendJoin(c, e.Source.Name) {
            c.SendToTg(fmt.Sprintf(joinFmt, e.Source.Name))
        }
    }
}

func partHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("partHandler triggered")
        if (e.Source != nil) && shouldSendLeave(c, e.Source.Name) {
            c.SendToTg(fmt.Sprintf(partFmt, e.Source.Name))
        }
    }
}

func topicHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("topicHandler triggered")
        if c.TgSettings().ShowTopicMessage {
            // e.Source.Name is the user who changed the topic.
            // e.Params[0] is the channel where the topic changed.
            // e.Params[1] is the new topic.  We should assume that
            // this may or may not appear as its possible to clear a topic.
            if len(e.Params) <= 1 {
                c.SendToTg(fmt.Sprintf(topicClearedFmt, e.Source.Name))
            } else {
                c.SendToTg(fmt.Sprintf(topicChangeFmt, e.Source.Name, e.Params[1]))
            }
        }
    }
}

func quitHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("quitHandler triggered")
        if (e.Source != nil) && shouldSendLeave(c, e.Source.Name) {
            c.SendToTg(fmt.Sprintf(quitFmt, e.Source.Name, e.Params[0]))
        }
    }
}

/*
kickHandler handles the event when a user is kicked from the IRC channel.
*/
func kickHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("kickHandler triggered")
        if c.TgSettings().ShowKickMessage {
            var reason string
            // Params are obtained from the kick command: /kick #channel nickname [reason]
            if len(e.Params) == 2 {
                reason = "Reason Undefined"
            } else {
                reason = e.Last()
            }
            c.SendToTg(fmt.Sprintf(kickFmt, e.Source.Name, e.Params[1], e.Params[0], reason))
        }
    }
}

func nickHandler(c ClientInterface) func(*girc.Client, girc.Event) {
    return func(gc *girc.Client, e girc.Event) {
        c.Logger().LogDebug("nickHandler triggered")
        if c.TgSettings().ShowNickMessage {
            // e.Source.Name is the original name.
            // e.Params[0] is the new nick name.
            // However, let's assume it is possible (though unlikely)
            // e.Params can be empty.
            var newName string
            if len(e.Params) == 0 {
                newName = "Unspecified Name"
            } else {
                newName = e.Params[0]
            }
            c.SendToTg(fmt.Sprintf(nickFmt, e.Source.Name, newName))
        }
    }
}

/*
getHandlerMapping returns a mapping of girc event types to handlers
*/
func getHandlerMapping() map[string]Handler {
    return map[string]Handler{
        girc.CONNECTED:    connectHandler,
        girc.DISCONNECTED: disconnectHandler,
        girc.JOIN:         joinHandler,
        girc.KICK:         kickHandler,
        girc.NICK:         nickHandler,
        girc.PRIVMSG:      messageHandler,
        girc.PART:         partHandler,
        girc.TOPIC:        topicHandler,
        girc.QUIT:         quitHandler,
    }
}