2020-12-21 00:32:39 +00:00
package main
import (
2021-04-09 16:14:44 +00:00
"fmt"
"io"
2020-12-21 00:32:39 +00:00
"os"
2021-04-09 16:14:44 +00:00
"path"
2020-12-21 00:32:39 +00:00
"time"
"github.com/go-irc/irc"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
type configFile struct {
2021-05-25 14:26:03 +00:00
AutoMessages [ ] * autoMessage ` yaml:"auto_messages" `
Channels [ ] string ` yaml:"channels" `
PermitAllowModerator bool ` yaml:"permit_allow_moderator" `
PermitTimeout time . Duration ` yaml:"permit_timeout" `
RawLog string ` yaml:"raw_log" `
2021-06-11 11:52:42 +00:00
Rules [ ] * Rule ` yaml:"rules" `
2021-05-25 14:26:03 +00:00
Variables map [ string ] interface { } ` yaml:"variables" `
2021-04-09 16:14:44 +00:00
rawLogWriter io . WriteCloser
2020-12-21 00:32:39 +00:00
}
2021-05-10 22:24:36 +00:00
func newConfigFile ( ) * configFile {
return & configFile {
2020-12-21 00:32:39 +00:00
PermitTimeout : time . Minute ,
}
}
func loadConfig ( filename string ) error {
var (
2021-05-24 15:36:16 +00:00
err error
tmpConfig * configFile
2020-12-21 00:32:39 +00:00
)
2021-05-24 15:36:16 +00:00
switch path . Ext ( filename ) {
case ".yaml" , ".yml" :
tmpConfig , err = parseConfigFromYAML ( filename )
default :
return errors . Errorf ( "Unknown config format %q" , path . Ext ( filename ) )
}
if err != nil {
return errors . Wrap ( err , "parsing config" )
2020-12-21 00:32:39 +00:00
}
if len ( tmpConfig . Channels ) == 0 {
log . Warn ( "Loaded config with empty channel list" )
}
if len ( tmpConfig . Rules ) == 0 {
log . Warn ( "Loaded config with empty ruleset" )
}
configLock . Lock ( )
defer configLock . Unlock ( )
2021-04-21 18:03:25 +00:00
tmpConfig . updateAutoMessagesFromConfig ( config )
2021-05-24 15:36:16 +00:00
tmpConfig . fixDurations ( )
2021-04-21 18:03:25 +00:00
2021-04-09 16:14:44 +00:00
switch {
case config != nil && config . RawLog == tmpConfig . RawLog :
tmpConfig . rawLogWriter = config . rawLogWriter
case tmpConfig . RawLog == "" :
if err = config . CloseRawMessageWriter ( ) ; err != nil {
return errors . Wrap ( err , "closing old raw log writer" )
}
tmpConfig . rawLogWriter = writeNoOpCloser { io . Discard }
default :
if err = config . CloseRawMessageWriter ( ) ; err != nil {
return errors . Wrap ( err , "closing old raw log writer" )
}
2021-06-28 22:05:11 +00:00
if err = os . MkdirAll ( path . Dir ( tmpConfig . RawLog ) , 0 o755 ) ; err != nil { //nolint:gomnd // This is a common directory permission
2021-04-09 16:14:44 +00:00
return errors . Wrap ( err , "creating directories for raw log" )
}
2021-06-28 22:05:11 +00:00
if tmpConfig . rawLogWriter , err = os . OpenFile ( tmpConfig . RawLog , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o644 ) ; err != nil { //nolint:gomnd // This is a common file permission
2021-04-09 16:14:44 +00:00
return errors . Wrap ( err , "opening raw log for appending" )
}
}
2021-05-10 22:24:36 +00:00
config = tmpConfig
2021-05-10 22:45:27 +00:00
log . WithFields ( log . Fields {
"auto_messages" : len ( config . AutoMessages ) ,
"rules" : len ( config . Rules ) ,
"channels" : len ( config . Channels ) ,
} ) . Info ( "Config file (re)loaded" )
2020-12-21 00:32:39 +00:00
return nil
}
2021-05-24 15:36:16 +00:00
func parseConfigFromYAML ( filename string ) ( * configFile , error ) {
f , err := os . Open ( filename )
if err != nil {
return nil , errors . Wrap ( err , "open config file" )
}
defer f . Close ( )
var (
decoder = yaml . NewDecoder ( f )
tmpConfig = newConfigFile ( )
)
decoder . SetStrict ( true )
if err = decoder . Decode ( & tmpConfig ) ; err != nil {
return nil , errors . Wrap ( err , "decode config file" )
}
return tmpConfig , nil
}
2021-04-09 16:14:44 +00:00
func ( c * configFile ) CloseRawMessageWriter ( ) error {
if c == nil || c . rawLogWriter == nil {
return nil
}
return c . rawLogWriter . Close ( )
}
2021-06-11 11:52:42 +00:00
func ( c configFile ) GetMatchingRules ( m * irc . Message , event * string ) [ ] * Rule {
2020-12-21 00:32:39 +00:00
configLock . RLock ( )
defer configLock . RUnlock ( )
2021-06-11 11:52:42 +00:00
var out [ ] * Rule
2020-12-21 00:32:39 +00:00
for _ , r := range c . Rules {
2021-06-11 11:52:42 +00:00
if r . matches ( m , event ) {
2020-12-21 00:32:39 +00:00
out = append ( out , r )
}
}
return out
}
2021-04-09 16:14:44 +00:00
func ( c configFile ) LogRawMessage ( m * irc . Message ) error {
_ , err := fmt . Fprintln ( c . rawLogWriter , m . String ( ) )
return errors . Wrap ( err , "writing raw log message" )
}
2021-04-21 18:03:25 +00:00
2021-05-24 15:36:16 +00:00
func ( c * configFile ) fixDurations ( ) {
// General fields
c . PermitTimeout = c . fixedDuration ( c . PermitTimeout )
// Fix rules
for _ , r := range c . Rules {
r . Cooldown = c . fixedDurationPtr ( r . Cooldown )
}
// Fix auto-messages
for _ , a := range c . AutoMessages {
a . TimeInterval = c . fixedDuration ( a . TimeInterval )
}
}
func ( configFile ) fixedDuration ( d time . Duration ) time . Duration {
if d > time . Second {
return d
}
return d * time . Second
}
func ( configFile ) fixedDurationPtr ( d * time . Duration ) * time . Duration {
if d == nil || * d > time . Second {
return d
}
fd := * d * time . Second
return & fd
}
2021-04-21 18:03:25 +00:00
func ( c * configFile ) updateAutoMessagesFromConfig ( old * configFile ) {
for idx , nam := range c . AutoMessages {
// By default assume last message to be sent now
// in order not to spam messages at startup
nam . lastMessageSent = time . Now ( )
if ! nam . IsValid ( ) {
log . WithField ( "index" , idx ) . Warn ( "Auto-Message configuration is invalid and therefore disabled" )
}
if old == nil {
// Initial config load, do not update timers
continue
}
for _ , oam := range old . AutoMessages {
if nam . ID ( ) != oam . ID ( ) {
continue
}
// We disable the old message as executing it would
// mess up the constraints of the new message
oam . lock . Lock ( )
oam . disabled = true
nam . lastMessageSent = oam . lastMessageSent
nam . linesSinceLastMessage = oam . linesSinceLastMessage
2021-05-10 23:01:12 +00:00
oam . lock . Unlock ( )
2021-04-21 18:03:25 +00:00
}
}
}