kunt

golang IRC bot
git clone git://git.2f30.org/kunt
Log | Files | Refs | LICENSE

irc.go (7077B)


      1 // Copyright 2013 TLH and dsp. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package irc
      6 
      7 import (
      8 	"crypto/tls"
      9 	"encoding/json"
     10 	"fmt"
     11 	"io/ioutil"
     12 	"log"
     13 	"net"
     14 	"strings"
     15 	"sync"
     16 )
     17 
     18 type Context struct {
     19 	conn        net.Conn        // actual connection
     20 	outgoingMsg chan Message    // TX messaging channel
     21 	incomingMsg chan Message    // RX messaging channel
     22 	ev          []Event         // slice of callbacks
     23 	evnum       int             // number of callbacks
     24 	network     network         // irc network state
     25 	intercepts  []*chan Message // intercept chans
     26 	sync.Mutex
     27 }
     28 
     29 type Config struct {
     30 	NetworkName string    // network name
     31 	Nick        string    // nickname
     32 	User        string    // username
     33 	RealName    string    // real name
     34 	Pass        string    // password
     35 	Serv        string    // server address
     36 	Port        string    // server port
     37 	Ssl         bool      // enable/disable ssl
     38 	Ipv6        bool      // enable/disable ipv6 (not implemented)
     39 	Channels    []string  // array of channels to join
     40 }
     41 
     42 type Channel struct {
     43 	name   string // name of irc channel
     44 	key    string // password for irc channel
     45 	joined bool   // whether we have joined this channel or not
     46 }
     47 
     48 type network struct {
     49 	name     string    // network name
     50 	nick     string    // nickname
     51 	user     string    // username
     52 	realName string    // real name
     53 	pass     string    // password
     54 	serv     string    // server address
     55 	port     string    // server port
     56 	tls      bool      // enable/disable ssl
     57 	ipv6     bool      // enable/disable ipv6 (not implemented)
     58 	channels []Channel // slice of channels
     59 }
     60 
     61 func LoadConfig(fname string) (con *Config, err error) {
     62 	bytes, err := ioutil.ReadFile(fname)
     63 	if err != nil {
     64 		log.Printf(" [LoadConfig] error reading file %s\n", fname)
     65 		return nil, err
     66 	}
     67 	con = new(Config)
     68 	err = json.Unmarshal(bytes, &con)
     69 	if err != nil {
     70 		log.Printf(" [LoadConfig] error reading file %s\n", fname)
     71 		return nil, err
     72 	}
     73 	return con, nil
     74 }
     75 
     76 // Create a new IrcContext
     77 func NewIrcContext(ircConfig Config) *Context {
     78 	chans := make([]Channel, len(ircConfig.Channels))
     79 
     80 	for i, _ := range chans {
     81 		chans[i] = Channel{ircConfig.Channels[i],"",false}
     82 	}
     83 
     84 	network := network{
     85 		name:     ircConfig.NetworkName,
     86 		nick:     ircConfig.Nick,
     87 		user:     ircConfig.User,
     88 		realName: ircConfig.RealName,
     89 		pass:     ircConfig.Pass,
     90 		serv:     ircConfig.Serv,
     91 		port:     ircConfig.Port,
     92 		tls:      ircConfig.Ssl,
     93 		ipv6:     ircConfig.Ipv6,
     94 		channels: chans,
     95 	}
     96 
     97 	return &Context{
     98 		outgoingMsg: make(chan Message),
     99 		incomingMsg: make(chan Message),
    100 		ev:          make([]Event, 0),
    101 		network:     network,
    102 		intercepts:  make([]*chan Message, 0),
    103 	}
    104 }
    105 
    106 func (i *Context) AddIntercept(a *chan Message) {
    107 	i.Lock()
    108 	defer i.Unlock()
    109 	i.intercepts = append(i.intercepts, a)
    110 }
    111 
    112 func (i *Context) DelIntercept(ap *chan Message) error {
    113 	i.Lock()
    114 	defer i.Unlock()
    115 	for j := 0; j < len(i.intercepts); j++ {
    116 		if ap == i.intercepts[j] {
    117 			if j == 0 {
    118 				i.intercepts = make([]*chan Message, 0)
    119 				return nil
    120 			}
    121 			i.intercepts = append(i.intercepts[:j], i.intercepts[j+1:]...)
    122 			return nil
    123 		}
    124 	}
    125 	return fmt.Errorf("Intercept not found")
    126 }
    127 
    128 type ChanIter func() (c *Channel, ok bool)
    129 
    130 func MakeChanIter(i *Context) ChanIter {
    131 	j := 0
    132 	return func() (c *Channel, ok bool) {
    133 		i.Lock()
    134 		defer i.Unlock()
    135 		if j < len(i.network.channels) {
    136 			j++
    137 			return &i.network.channels[j], true
    138 		}
    139 		return nil, false
    140 	}
    141 }
    142 
    143 func (i *Context) JoinChannel(c string, key string) {
    144 	i.Lock()
    145 	defer i.Unlock()
    146 	found := false
    147 	for _, v := range i.network.channels {
    148 		if v.name == c {
    149 			found = true
    150 			break
    151 		}
    152 	}
    153 	if !found {
    154 		i.network.channels = append(i.network.channels,
    155 			Channel{c, key, false})
    156 	}
    157 	for j, v := range i.network.channels {
    158 		if v.name != c {
    159 			continue
    160 		}
    161 		if !v.joined {
    162 			i.Join(v.name, v.key)
    163 			i.network.channels[j].joined = true
    164 			break
    165 		} else {
    166 			i.Join(v.name, v.key)
    167 			break
    168 		}
    169 	}
    170 	return
    171 }
    172 
    173 func (i *Context) JoinChannels() {
    174 	for _, v := range i.network.channels {
    175 		i.JoinChannel(v.name, v.key)
    176 	}
    177 }
    178 
    179 func (i *Context) PartChannel(s string, text string) error {
    180 	i.Lock()
    181 	defer i.Unlock()
    182 	for c, v := range i.network.channels {
    183 		if v.name == s {
    184 			if v.joined {
    185 				i.Part(s, text)
    186 				i.network.channels[c].joined = false
    187 				return nil
    188 			} else {
    189 				return fmt.Errorf("Channel %s is not joined", s)
    190 			}
    191 		}
    192 	}
    193 	return fmt.Errorf("Can't find channel %s", s)
    194 }
    195 
    196 // Connect to the server.  Do not join any channels by default.
    197 func (i *Context) Connect() error {
    198 	service := i.network.serv + ":" + i.network.port
    199 
    200 	if i.network.tls {
    201 		if conf, err := loadCerts(); err != nil {
    202 			return err
    203 		} else {
    204 			if conn, err := tls.Dial("tcp", service, conf); err != nil {
    205 				return err
    206 			} else {
    207 				i.conn = conn
    208 			}
    209 		}
    210 	} else {
    211 		if conn, err := net.Dial("tcp", service); err != nil {
    212 			return err
    213 		} else {
    214 			i.conn = conn
    215 		}
    216 	}
    217 
    218 	// Fire off the goroutines now!
    219 	go i.incomingMsgLoop()
    220 	go i.outgoingMsgLoop()
    221 	go i.rxRawMessages()
    222 
    223 	return nil
    224 }
    225 
    226 // Login to server
    227 func (i *Context) Login() {
    228 	if i.network.pass != "" {
    229 		i.Pass()
    230 	}
    231 	i.Nick()
    232 	i.User()
    233 }
    234 
    235 func (i *Context) read(buf []byte) (int, error) {
    236 	n, err := i.conn.Read(buf)
    237 	if err != nil {
    238 		return n, err
    239 	}
    240 	return n, nil
    241 }
    242 
    243 // This is the actual raw read loop.  Here we parse the incoming bytes
    244 // and form messages.
    245 func (i *Context) rxRawMessages() error {
    246 	for {
    247 		var buf [512]byte
    248 		n, err := i.read(buf[0:])
    249 		if err != nil {
    250 			return err
    251 		}
    252 		if n != 0 {
    253 			log.Println(string(buf[0 : n-1]))
    254 			s := string(buf[0:n])
    255 
    256 			msg, err := i.ParseRawMessage(s)
    257 			if err != nil {
    258 				log.Printf("%s", err)
    259 			} else {
    260 				i.incomingMsg <- *msg
    261 				for _, v := range i.intercepts {
    262 					*v <- *msg
    263 				}
    264 			}
    265 		}
    266 	}
    267 	return nil
    268 }
    269 
    270 // Transmit a raw message
    271 func (i *Context) TxRawMessage(msg []byte) error {
    272 	_, err := i.conn.Write(msg[0:])
    273 	if err != nil {
    274 		return err
    275 	}
    276 	return nil
    277 }
    278 
    279 func (i *Context) String() string {
    280 	s := fmt.Sprintf("IRC CONTEXT DUMP")
    281 	if len(i.ev) == 0 {
    282 		s += fmt.Sprintf("\n\tEvent handlers: [NONE]\n")
    283 	} else {
    284 		s += fmt.Sprintf("\n\tEvent handlers: [%d]\n", len(i.ev))
    285 		for _, v := range i.ev {
    286 			s += fmt.Sprintf("\t\tCommand: %s\n", v.Command)
    287 		}
    288 	}
    289 	s += fmt.Sprintf("\tNetwork:\n")
    290 	s += fmt.Sprintf("\t\tName: %s\n", i.network.name)
    291 	s += fmt.Sprintf("\t\tNick: %s\n", i.network.nick)
    292 	s += fmt.Sprintf("\t\tUser: %s\n", i.network.user)
    293 	if i.network.pass != "" {
    294 		s += fmt.Sprintf("\t\tPass: %s\n", i.network.pass)
    295 	} else {
    296 		s += fmt.Sprintf("\t\tPass: [NONE]\n")
    297 	}
    298 	s += fmt.Sprintf("\t\tServer: %s\n", i.network.serv)
    299 	if i.network.tls {
    300 		s += fmt.Sprintf("\t\tTLS: on")
    301 	} else {
    302 		s += fmt.Sprintf("\t\tTLS: off")
    303 	}
    304 	if len(i.network.channels) == 0 {
    305 		s += fmt.Sprintf("\n\t\tChannels: [NONE]")
    306 	} else {
    307 		s += fmt.Sprintf("\n\t\tChannels: [%d]\n", len(i.network.channels))
    308 		for _, v := range i.network.channels {
    309 			s += fmt.Sprintf("\t\t\tName: %s\n", v.name)
    310 		}
    311 	}
    312 	return strings.TrimSpace(s)
    313 }