diff --git a/irc.go b/irc.go index d83f16d..f02c848 100644 --- a/irc.go +++ b/irc.go @@ -57,11 +57,6 @@ func loggerHandler(_ *irc.Conn, line *irc.Line) { log.Printf("Received: '%s'", line.Raw) } -type ChannelState struct { - Channel IRCChannel - BackoffCounter Delayer -} - type IRCNotifier struct { // Nick stores the nickname specified in the config, because irc.Client // might change its copy. @@ -82,8 +77,7 @@ type IRCNotifier struct { sessionUpSignal chan bool sessionDownSignal chan bool - PreJoinChannels []IRCChannel - JoinedChannels map[string]ChannelState + channelReconciler *ChannelReconciler UsePrivmsg bool @@ -108,21 +102,24 @@ func NewIRCNotifier(stopCtx context.Context, stopWg *sync.WaitGroup, config *Con ircConfig.Timeout = connectionTimeoutSecs * time.Second ircConfig.NewNick = func(n string) string { return n + "^" } + client := irc.Client(ircConfig) + backoffCounter := delayerMaker.NewDelayer( ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs, time.Second) + channelReconciler := NewChannelReconciler(config, client, delayerMaker) + notifier := &IRCNotifier{ Nick: config.IRCNick, NickPassword: config.IRCNickPass, - Client: irc.Client(ircConfig), + Client: client, AlertMsgs: alertMsgs, stopCtx: stopCtx, stopWg: stopWg, sessionUpSignal: make(chan bool), sessionDownSignal: make(chan bool), - PreJoinChannels: config.IRCChannels, - JoinedChannels: make(map[string]ChannelState), + channelReconciler: channelReconciler, UsePrivmsg: config.UsePrivmsg, NickservDelayWait: nickservWaitSecs * time.Second, BackoffCounter: backoffCounter, @@ -146,63 +143,11 @@ func (n *IRCNotifier) registerHandlers() { n.sessionDownSignal <- false }) - n.Client.HandleFunc(irc.KICK, - func(_ *irc.Conn, line *irc.Line) { - n.HandleKick(line.Args[1], line.Args[0]) - }) - for _, event := range []string{irc.NOTICE, "433"} { n.Client.HandleFunc(event, loggerHandler) } } -func (n *IRCNotifier) HandleKick(nick string, channel string) { - if nick != n.Client.Me().Nick { - // received kick info for somebody else - return - } - state, ok := n.JoinedChannels[channel] - if !ok { - log.Printf("Being kicked out of non-joined channel (%s), ignoring", channel) - return - } - log.Printf("Being kicked out of %s, re-joining", channel) - go func() { - if ok := state.BackoffCounter.DelayContext(n.stopCtx); !ok { - return - } - n.Client.Join(state.Channel.Name, state.Channel.Password) - }() - -} - -func (n *IRCNotifier) CleanupChannels() { - log.Printf("Deregistering all channels.") - n.JoinedChannels = make(map[string]ChannelState) -} - -func (n *IRCNotifier) JoinChannel(channel *IRCChannel) { - if _, joined := n.JoinedChannels[channel.Name]; joined { - return - } - log.Printf("Joining %s", channel.Name) - n.Client.Join(channel.Name, channel.Password) - bm := BackoffMaker{} - state := ChannelState{ - Channel: *channel, - BackoffCounter: bm.NewDelayer( - ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs, - time.Second), - } - n.JoinedChannels[channel.Name] = state -} - -func (n *IRCNotifier) JoinChannels() { - for _, channel := range n.PreJoinChannels { - n.JoinChannel(&channel) - } -} - func (n *IRCNotifier) MaybeIdentifyNick() { if n.NickPassword == "" { return @@ -233,7 +178,7 @@ func (n *IRCNotifier) MaybeSendAlertMsg(alertMsg *AlertMsg) { ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_connected").Inc() return } - n.JoinChannel(&IRCChannel{Name: alertMsg.Channel}) + n.channelReconciler.JoinChannel(&IRCChannel{Name: alertMsg.Channel}) if n.UsePrivmsg { n.Client.Privmsg(alertMsg.Channel, alertMsg.Alert) @@ -265,7 +210,7 @@ func (n *IRCNotifier) ConnectedPhase() { n.MaybeSendAlertMsg(&alertMsg) case <-n.sessionDownSignal: n.sessionUp = false - n.CleanupChannels() + n.channelReconciler.CleanupChannels() n.Client.Quit("see ya") ircConnectedGauge.Set(0) case <-n.stopCtx.Done(): @@ -289,7 +234,7 @@ func (n *IRCNotifier) SetupPhase() { case <-n.sessionUpSignal: n.sessionUp = true n.MaybeIdentifyNick() - n.JoinChannels() + n.channelReconciler.JoinChannels() ircConnectedGauge.Set(1) case <-n.sessionDownSignal: log.Printf("Receiving a session down before the session is up, this is odd") diff --git a/reconciler.go b/reconciler.go new file mode 100644 index 0000000..4caf136 --- /dev/null +++ b/reconciler.go @@ -0,0 +1,109 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "log" + "sync" + "time" + + irc "github.com/fluffle/goirc/client" +) + +type channelState struct { + Channel IRCChannel + BackoffCounter Delayer +} + +type ChannelReconciler struct { + preJoinChannels []IRCChannel + client *irc.Conn + + delayerMaker DelayerMaker + + channels map[string]*channelState + + stopCtx context.Context + stopCtxCancel context.CancelFunc + stopWg sync.WaitGroup + + mu sync.Mutex +} + +func NewChannelReconciler(config *Config, client *irc.Conn, delayerMaker DelayerMaker) *ChannelReconciler { + reconciler := &ChannelReconciler{ + preJoinChannels: config.IRCChannels, + client: client, + delayerMaker: delayerMaker, + channels: make(map[string]*channelState), + } + + reconciler.registerHandlers() + + return reconciler +} + +func (r *ChannelReconciler) registerHandlers() { + r.client.HandleFunc(irc.KICK, + func(_ *irc.Conn, line *irc.Line) { + r.HandleKick(line.Args[1], line.Args[0]) + }) +} + +func (r *ChannelReconciler) HandleKick(nick string, channel string) { + if nick != r.client.Me().Nick { + // received kick info for somebody else + return + } + state, ok := r.channels[channel] + if !ok { + log.Printf("Being kicked out of non-joined channel (%s), ignoring", channel) + return + } + log.Printf("Being kicked out of %s, re-joining", channel) + go func() { + if ok := state.BackoffCounter.DelayContext(r.stopCtx); !ok { + return + } + r.client.Join(state.Channel.Name, state.Channel.Password) + }() +} + +func (r *ChannelReconciler) CleanupChannels() { + log.Printf("Deregistering all channels.") + r.channels = make(map[string]*channelState) +} + +func (r *ChannelReconciler) JoinChannel(channel *IRCChannel) { + if _, joined := r.channels[channel.Name]; joined { + return + } + log.Printf("Joining %s", channel.Name) + r.client.Join(channel.Name, channel.Password) + state := &channelState{ + Channel: *channel, + BackoffCounter: r.delayerMaker.NewDelayer( + ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs, + time.Second), + } + r.channels[channel.Name] = state +} + +func (r *ChannelReconciler) JoinChannels() { + for _, channel := range r.preJoinChannels { + r.JoinChannel(&channel) + } +}