diff --git a/config.example.yaml b/config.example.yaml new file mode 100644 index 0000000..5d3fbac --- /dev/null +++ b/config.example.yaml @@ -0,0 +1,20 @@ +watbot: + server: + host: irc.casa # mandatory, no default + port: 6697 + tls_verify: true + name: watest + nick: watest # nick is name by default + user: watest # user is nick by default + admins: # optional, no default + hosts: + - admin.example.com + ignores: # optional, no default + hosts: + - annoying.example.com + channels: # optional, no default + join: + - crantest # channels without a prefix character will be prefixed with "#" + permitted: + - '#lucy' + diff --git a/go.mod b/go.mod index d3cef45..84d6919 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,11 @@ module git.circuitco.de/self/watbot go 1.15 require ( + github.com/creasty/defaults v1.8.0 github.com/go-irc/irc v2.1.0+incompatible - github.com/namsral/flag v1.7.4-pre + github.com/stretchr/testify v1.9.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/sqlite v1.1.4 gorm.io/gorm v1.20.11 ) diff --git a/go.sum b/go.sum index 32fef9d..0d79cef 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,8 @@ -github.com/go-irc/irc v1.3.0 h1:IMD+d/+EzY51ecMLOz73r/NXTZrEp8khrePxRCvX71M= +github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= +github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-irc/irc v2.1.0+incompatible h1:pg7pMVq5OYQbqTxceByD/EN8VIsba7DtKn49rsCnG8Y= github.com/go-irc/irc v2.1.0+incompatible/go.mod h1:jJILTRy8s/qOvusiKifAEfhQMVwft1ZwQaVJnnzmyX4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -7,8 +11,24 @@ github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= -github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs= -github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= diff --git a/main.go b/main.go index e817bcc..c242571 100644 --- a/main.go +++ b/main.go @@ -1,40 +1,113 @@ package main -import "fmt" -import "crypto/tls" +import ( + "crypto/tls" + "errors" + "fmt" + "log" + "os" -import "github.com/go-irc/irc" -import "github.com/namsral/flag" + "flag" + "github.com/go-irc/irc" -import "git.circuitco.de/self/watbot/wat" + "git.circuitco.de/self/watbot/wat" + "github.com/creasty/defaults" + "gopkg.in/yaml.v3" +) + +type Config struct { + Watbot watConfig `yaml:"watbot"` +} + +type watConfig struct { + Nick string `yaml:"nick"` + Pass string `yaml:"pass"` + User string `yaml:"user"` + Name string `yaml:"name"` + Admins struct { + Hosts []string `yaml:"hosts"` + } `yaml:"admins"` + Channels struct { + Join []string `yaml:"join"` + Permitted []string `yaml:"permitted"` + } `yaml:"channels"` + Ignores struct { + Hosts []string `yaml:"hosts"` + } `yaml:"ignores"` + Server struct { + Host string `yaml:"host"` + Port int `default:"6697" yaml:"port"` + TlsVerify bool `default:"true" yaml:"tls_verify"` + } `yaml:"server"` +} + +func readConfig(configPath string) (*watConfig, error) { + allConfig := Config{} + + buffer, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("Could not read configuration file: %s", err) + } + + err = yaml.Unmarshal(buffer, &allConfig) + if err != nil { + return nil, fmt.Errorf("Could not parse configuration file: %s", err) + } + + config := &allConfig.Watbot + defaults.Set(config) + + if config.Server.Host == "" { + return nil, errors.New("Shall I play wattery to know where to connect to?") + } + + if config.Name != "" && config.Nick == "" { + config.Nick = config.Name + } + + if config.Nick != "" && config.User == "" { + config.User = config.Nick + } + + if config.Name == "" || config.Nick == "" || config.User == "" { + return nil, errors.New("Don't know who I am.") + } + + return config, nil +} func main() { - pass := flag.String("pass", "", "password") + var configPathArg string + flag.StringVar(&configPathArg, "config", "config.yaml", "Path to configuration file") flag.Parse() - fmt.Printf("PASS len %d\n", len(*pass)) - config := irc.ClientConfig{ - Nick: "watt", - Pass: *pass, - User: "wat", - Name: "wat", + log.Println("Starting with configuration:", configPathArg) + + config, err := readConfig(configPathArg) + if err != nil { + log.Fatalln(err) + os.Exit(1) + } + + ircConfig := irc.ClientConfig{ + Nick: config.Nick, + Pass: config.Pass, + User: config.User, + Name: config.Name, } watConfig := wat.WatConfig{ - PermittedChannels: []string{ - "#lucy", - "#sweden", - }, - IgnoredHosts: []string{ - "tripsit/user/creatonez", - }, + AutoJoinChannels: config.Channels.Join, + PermittedChannels: config.Channels.Permitted, + IgnoredHosts: config.Ignores.Hosts, + AdminHosts: config.Admins.Hosts, } tcpConf := &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: !config.Server.TlsVerify, } - conn, err := tls.Dial("tcp", "127.0.0.1:6697", tcpConf) + conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port), tcpConf) if err != nil { fmt.Println("err " + err.Error()) return } - wwat := wat.NewWatBot(&config, &watConfig, conn) + wwat := wat.NewWatBot(&ircConfig, &watConfig, conn) wwat.Run() } diff --git a/wat/bot.go b/wat/bot.go index 48559d4..a555de4 100644 --- a/wat/bot.go +++ b/wat/bot.go @@ -18,8 +18,10 @@ type WatBot struct { } type WatConfig struct { - PermittedChannels []string + AdminHosts []string IgnoredHosts []string + AutoJoinChannels []string + PermittedChannels []string } func NewWatBot(config *irc.ClientConfig, watConfig *WatConfig, serverConn *tls.Conn) *WatBot { @@ -35,23 +37,29 @@ func CleanNick(nick string) string { return string(nick[0]) + "\u200c" + nick[1:] } +func PrefixChannel(channel string) string { + // there could theoretically be other channel prefixes .. + if channel[0] != '#' && channel[0] != '!' { + channel = "#" + channel + } + return channel +} + func (w *WatBot) HandleIrcMsg(c *irc.Client, m *irc.Message) { switch cmd := m.Command; cmd { case "PING": w.write("PONG", m.Params[0]) case "PRIVMSG": w.Msg(m) + case "001": + for _, channel := range w.c.AutoJoinChannels { + w.write("JOIN", PrefixChannel(channel)) + } } } func (w *WatBot) Admin(m *irc.Message) bool { - admins := [2]string{"mph.monster", "cranberry.juice"} - for _, admin := range admins { - if m.Prefix.Host == admin { - return true - } - } - return false + return w.Allowed(m.Prefix.Host, w.c.AdminHosts) } func (w *WatBot) Allowed(c string, r []string) bool { @@ -73,7 +81,7 @@ func (w *WatBot) CanRespond(m *irc.Message) bool { // if !strings.Contains(m.Prefix.Host, "") { // return false // } - if !w.Allowed(m.Params[0], w.c.PermittedChannels) { + if !w.Allowed(PrefixChannel(m.Params[0]), w.c.PermittedChannels) { return false } return true