package twitch import ( "github.com/rs/zerolog/log" "github.com/velour/catbase/config" "github.com/velour/velour/irc" "io" "time" ) var throttle <-chan time.Time type eventFunc func(channel, who, body string) type IRC struct { t *Twitch c *config.Config client *irc.Client event eventFunc quit chan bool } func (t *Twitch) ConnectIRC(server, user, pass string, handler eventFunc, disconnect func()) (*IRC, error) { log.Debug().Msgf("Connecting to %s, %s:%s", server, user, pass) i := &IRC{ t: t, c: t.c, event: handler} wait := make(chan bool) go i.serve(server, user, pass, wait, disconnect) <-wait return i, nil } func (i *IRC) Join(channel string) error { i.client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} return nil } func (i *IRC) Say(channel, body string) error { if _, err := i.sendMessage(channel, body); err != nil { return err } return nil } func (i *IRC) serve(server, user, pass string, wait chan bool, disconnect func()) { if i.event == nil { log.Error().Msgf("Missing event handler") wait <- true return } var err error i.client, err = irc.DialSSL(server, user, user, pass, true) if err != nil { log.Error(). Err(err). Strs("args", []string{server, user, pass}). Msgf("Could not connect") wait <- true return } i.client.Out <- irc.Msg{Cmd: "CAP REQ", Args: []string{":twitch.tv/membership"}} i.quit = make(chan bool) go i.handleConnection() wait <- true <-i.quit disconnect() } func (i *IRC) handleMsg(msg irc.Msg) { switch msg.Cmd { case irc.ERROR: log.Info().Msgf("Received error: " + msg.Raw) case irc.PING: i.client.Out <- irc.Msg{Cmd: irc.PONG} case irc.PONG: // OK, ignore case irc.KICK: fallthrough case irc.TOPIC: fallthrough case irc.NOTICE: fallthrough case irc.PRIVMSG: if len(msg.Args) < 2 { break } i.event(msg.Args[0], msg.Origin, msg.Args[1]) case irc.QUIT: log.Debug(). Interface("msg", msg). Msgf("QUIT") i.quit <- true default: log.Debug(). Interface("msg", msg). Msgf("IRC EVENT") } } func (i *IRC) sendMessage(channel, message string, args ...any) (string, error) { 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.c.GetInt("RatePerSec", 5) throttle = time.Tick(time.Second / time.Duration(ratePerSec)) } <-throttle i.client.Out <- m } return "NO_IRC_IDENTIFIERS", nil } func (i *IRC) handleConnection() { pingTime := time.Duration(i.c.GetInt("twitch.pingtime", 60)) * time.Second t := time.NewTimer(pingTime) defer func() { t.Stop() close(i.client.Out) for err := range i.client.Errors { if err != io.EOF { log.Error().Err(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.Error().Err(err) i.quit <- true return } } } }