catbase/irc/irc.go

313 lines
6.1 KiB
Go
Raw Normal View History

2016-03-19 19:32:51 +00:00
// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors.
2016-03-10 18:37:07 +00:00
package irc
import (
"fmt"
2016-03-10 18:37:07 +00:00
"io"
"log"
"os"
"strings"
"time"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
2016-03-10 18:37:07 +00:00
"github.com/velour/catbase/config"
"github.com/velour/velour/irc"
)
const (
// DefaultPort is the port used to connect to
// the server if one is not specified.
defaultPort = "6667"
// InitialTimeout is the initial amount of time
// to delay before reconnecting. Each failed
// reconnection doubles the timout until
// a connection is made successfully.
initialTimeout = 2 * time.Second
// PingTime is the amount of inactive time
// to wait before sending a ping to the server.
pingTime = 120 * time.Second
actionPrefix = "\x01ACTION"
)
var throttle <-chan time.Time
type Irc struct {
Client *irc.Client
config *config.Config
quit chan bool
2018-05-02 11:02:04 +00:00
eventReceived func(msg.Message)
messageReceived func(msg.Message)
replyMessageReceived func(msg.Message, string)
2016-03-10 18:37:07 +00:00
}
func New(c *config.Config) *Irc {
i := Irc{}
i.config = c
return &i
}
func (i *Irc) RegisterEventReceived(f func(msg.Message)) {
2016-03-11 02:11:52 +00:00
i.eventReceived = f
2016-03-10 18:37:07 +00:00
}
func (i *Irc) RegisterMessageReceived(f func(msg.Message)) {
2016-03-11 02:11:52 +00:00
i.messageReceived = f
2016-03-10 18:37:07 +00:00
}
func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) {
i.replyMessageReceived = f
}
2016-03-10 18:37:07 +00:00
func (i *Irc) JoinChannel(channel string) {
log.Printf("Joining channel: %s", channel)
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
}
2017-10-31 13:40:03 +00:00
func (i *Irc) SendMessage(channel, message string) string {
2016-03-10 18:37:07 +00:00
for len(message) > 0 {
m := irc.Msg{
Cmd: "PRIVMSG",
Args: []string{channel, message},
}
_, err := m.RawString()
if err != nil {
mtl := err.(irc.MsgTooLong)
m.Args[1] = message[:mtl.NTrunc]
message = message[mtl.NTrunc:]
} else {
message = ""
}
if throttle == nil {
ratePerSec := i.config.GetInt("RatePerSec")
2016-03-10 18:37:07 +00:00
throttle = time.Tick(time.Second / time.Duration(ratePerSec))
}
<-throttle
i.Client.Out <- m
}
2017-10-31 13:40:03 +00:00
return "NO_IRC_IDENTIFIERS"
2016-03-10 18:37:07 +00:00
}
// Sends action to channel
2017-10-31 13:40:03 +00:00
func (i *Irc) SendAction(channel, message string) string {
2016-03-10 18:37:07 +00:00
message = actionPrefix + " " + message + "\x01"
i.SendMessage(channel, message)
2017-10-31 13:40:03 +00:00
return "NO_IRC_IDENTIFIERS"
2016-03-10 18:37:07 +00:00
}
func (i *Irc) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
return "NO_IRC_IDENTIFIERS", false
}
func (i *Irc) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
2017-10-31 14:07:20 +00:00
return "NO_IRC_IDENTIFIERS", false
}
2017-10-31 13:40:03 +00:00
func (i *Irc) React(channel, reaction string, message msg.Message) bool {
//we're not goign to do anything because it's IRC
2017-10-31 13:40:03 +00:00
return false
}
2017-10-31 13:40:03 +00:00
func (i *Irc) Edit(channel, newMessage, identifier string) bool {
//we're not goign to do anything because it's IRC
2017-10-31 13:40:03 +00:00
return false
}
func (i *Irc) GetEmojiList() map[string]string {
//we're not goign to do anything because it's IRC
return make(map[string]string)
}
func (i *Irc) Serve() error {
2016-03-11 02:11:52 +00:00
if i.eventReceived == nil || i.messageReceived == nil {
return fmt.Errorf("Missing an event handler")
2016-03-10 18:37:07 +00:00
}
var err error
i.Client, err = irc.DialSSL(
i.config.Get("Irc.Server"),
i.config.Get("Nick"),
i.config.Get("FullName"),
i.config.Get("Irc.Pass"),
2016-03-10 18:37:07 +00:00
true,
)
if err != nil {
return fmt.Errorf("%s", err)
2016-03-10 18:37:07 +00:00
}
for _, c := range i.config.GetArray("channels") {
2016-03-10 18:37:07 +00:00
i.JoinChannel(c)
}
i.quit = make(chan bool)
go i.handleConnection()
<-i.quit
return nil
2016-03-10 18:37:07 +00:00
}
func (i *Irc) handleConnection() {
t := time.NewTimer(pingTime)
defer func() {
t.Stop()
close(i.Client.Out)
for err := range i.Client.Errors {
if err != io.EOF {
log.Println(err)
}
}
}()
for {
select {
case msg, ok := <-i.Client.In:
if !ok { // disconnect
i.quit <- true
return
}
t.Stop()
t = time.NewTimer(pingTime)
i.handleMsg(msg)
case <-t.C:
i.Client.Out <- irc.Msg{Cmd: irc.PING, Args: []string{i.Client.Server}}
t = time.NewTimer(pingTime)
case err, ok := <-i.Client.Errors:
if ok && err != io.EOF {
log.Println(err)
i.quit <- true
return
}
}
}
}
// HandleMsg handles IRC messages from the server.
func (i *Irc) handleMsg(msg irc.Msg) {
botMsg := i.buildMessage(msg)
switch msg.Cmd {
case irc.ERROR:
log.Println(1, "Received error: "+msg.Raw)
case irc.PING:
i.Client.Out <- irc.Msg{Cmd: irc.PONG}
case irc.PONG:
// OK, ignore
case irc.ERR_NOSUCHNICK:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.ERR_NOSUCHCHANNEL:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.RPL_MOTD:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.RPL_NAMREPLY:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.RPL_TOPIC:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.KICK:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.TOPIC:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.MODE:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.JOIN:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.PART:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.QUIT:
os.Exit(1)
case irc.NOTICE:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.PRIVMSG:
2016-03-11 02:11:52 +00:00
i.messageReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.NICK:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.RPL_WHOREPLY:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
case irc.RPL_ENDOFWHO:
2016-03-11 02:11:52 +00:00
i.eventReceived(botMsg)
2016-03-10 18:37:07 +00:00
default:
cmd := irc.CmdNames[msg.Cmd]
log.Println("(" + cmd + ") " + msg.Raw)
}
}
// Builds our internal message type out of a Conn & Line from irc
func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message {
2016-03-10 18:37:07 +00:00
// Check for the user
u := user.User{
2016-03-10 18:37:07 +00:00
Name: inMsg.Origin,
}
channel := inMsg.Args[0]
if channel == i.config.Get("Nick") {
2016-03-10 18:37:07 +00:00
channel = inMsg.Args[0]
}
isAction := false
var message string
if len(inMsg.Args) > 1 {
message = inMsg.Args[1]
isAction = strings.HasPrefix(message, actionPrefix)
if isAction {
message = strings.TrimRight(message[len(actionPrefix):], "\x01")
message = strings.TrimSpace(message)
}
}
iscmd := false
filteredMessage := message
if !isAction {
2016-03-11 02:11:52 +00:00
iscmd, filteredMessage = bot.IsCmd(i.config, message)
2016-03-10 18:37:07 +00:00
}
msg := msg.Message{
User: &u,
2016-03-10 18:37:07 +00:00
Channel: channel,
Body: filteredMessage,
Raw: message,
Command: iscmd,
Action: isAction,
Time: time.Now(),
Host: inMsg.Host,
}
return msg
}
2016-04-21 15:19:38 +00:00
func (i Irc) Who(channel string) []string {
return []string{}
}