marvin

A simple and modular IRC bot

git clone https://git.8pit.net/marvin.git

  1// This program is free software: you can redistribute it and/or modify
  2// it under the terms of the GNU Affero General Public License as
  3// published by the Free Software Foundation, either version 3 of the
  4// License, or (at your option) any later version.
  5//
  6// This program is distributed in the hope that it will be useful, but
  7// WITHOUT ANY WARRANTY; without even the implied warranty of
  8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  9// Affero General Public License for more details.
 10//
 11// You should have received a copy of the GNU Affero General Public
 12// License along with this program. If not, see <http://www.gnu.org/licenses/>.
 13
 14package main
 15
 16import (
 17	"bufio"
 18	"crypto/tls"
 19	"crypto/x509"
 20	"flag"
 21	"fmt"
 22	"github.com/nmeum/marvin/irc"
 23	"github.com/nmeum/marvin/modules"
 24	"io/ioutil"
 25	"log"
 26	"net"
 27	"os"
 28	"strings"
 29	"time"
 30)
 31
 32const (
 33	appName = "marvin"
 34)
 35
 36var (
 37	conf = flag.String("c", "marvin.json", "configuration file")
 38	verb = flag.Bool("v", false, "verbose output")
 39)
 40
 41func main() {
 42	flag.Parse()
 43	logger := log.New(os.Stderr, "ERROR: ", 0)
 44
 45	config, err := readConfig(*conf)
 46	if err != nil && !os.IsNotExist(err) {
 47		logger.Fatal(err)
 48	}
 49
 50	conn, err := connect(config)
 51	if err != nil {
 52		logger.Fatal(err)
 53	}
 54	defer conn.Close()
 55
 56	errChan := make(chan error)
 57	go func() {
 58		for err := range errChan {
 59			logger.Println(err)
 60		}
 61	}()
 62
 63	ircBot, err := setup(conn, config)
 64	if err != nil {
 65		logger.Fatal(err)
 66	}
 67
 68	reader := bufio.NewReader(conn)
 69	for {
 70		line, err := reader.ReadString('\n')
 71		if err != nil {
 72			logger.Println(err)
 73			break
 74		}
 75
 76		line = strings.Trim(line, "\n")
 77		line = strings.Trim(line, "\r")
 78
 79		if *verb {
 80			fmt.Println(line)
 81		}
 82
 83		ircBot.Handle(line, errChan)
 84	}
 85}
 86
 87func setup(conn net.Conn, config config) (client *irc.Client, err error) {
 88	client = irc.NewClient(conn)
 89	client.CmdHook("001", func(c *irc.Client, m irc.Message) error {
 90		time.Sleep(3 * time.Second) // Wait for NickServ etc
 91		return c.Write("JOIN %s", strings.Join(config.Chan, ","))
 92	})
 93
 94	moduleSet := modules.NewModuleSet(client, config.Conf)
 95	for _, fn := range moduleInits {
 96		fn(moduleSet)
 97	}
 98
 99	client.Setup(config.Nick, config.Name, config.Host)
100	return client, moduleSet.LoadAll()
101}
102
103func connect(config config) (conn net.Conn, err error) {
104	netw := "tcp"
105	addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
106
107	if len(config.Cert) >= 1 {
108		certFile, err := ioutil.ReadFile(config.Cert)
109		if err != nil {
110			return nil, err
111		}
112
113		caCertPool := x509.NewCertPool()
114		caCertPool.AppendCertsFromPEM(certFile)
115
116		tlsConfig := &tls.Config{RootCAs: caCertPool}
117		if len(config.ClientCert) >= 1 && len(config.ClientKey) >= 1 {
118			clientCert, err := tls.LoadX509KeyPair(config.ClientCert, config.ClientKey)
119			if err != nil {
120				return nil, err
121			}
122
123			tlsConfig.Certificates = []tls.Certificate{ clientCert }
124		}
125		return tls.Dial(netw, addr, tlsConfig)
126	}
127
128	return net.Dial(netw, addr)
129}