1// This program is free software: you can redistribute it and/or modify2// it under the terms of the GNU Affero General Public License as3// published by the Free Software Foundation, either version 3 of the4// License, or (at your option) any later version.5//6// This program is distributed in the hope that it will be useful, but7// WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU9// Affero General Public License for more details.10//11// You should have received a copy of the GNU Affero General Public12// License along with this program. If not, see <http://www.gnu.org/licenses/>.1314package irc1516import (17 "fmt"18 "net"19 "strings"20 "unicode"21)2223type Hook func(*Client, Message) error2425type Client struct {26 conn net.Conn27 hooks map[string][]Hook28 Nickname string29 Realname string30 Channels []string31}3233func NewClient(conn net.Conn) *Client {34 c := &Client{35 conn: conn,36 hooks: make(map[string][]Hook),37 }3839 c.CmdHook("join", joinCmd)40 c.CmdHook("part", partCmd)41 c.CmdHook("kick", kickCmd)4243 c.CmdHook("ping", pingCmd)44 return c45}4647func (c *Client) Setup(nick, name, host string) {48 c.Nickname = nick49 c.Realname = name5051 c.Write("USER %s %s * :%s", c.Nickname, host, c.Realname)52 c.Write("NICK %s", c.Nickname)53}5455func (c *Client) Connected(channel string) bool {56 for _, c := range c.Channels {57 if c == channel {58 return true59 }60 }6162 return false63}6465func (c *Client) Write(format string, argv ...interface{}) error {66 _, err := fmt.Fprintf(c.conn, "%s\r\n", sanitize(fmt.Sprintf(format, argv...)))67 if err != nil {68 return err69 }7071 return nil72}7374func (c *Client) Handle(data string, ch chan error) {75 msg := parseMessage(data)76 hooks, ok := c.hooks[msg.Command]77 if ok {78 for _, hook := range hooks {79 go func(h Hook) {80 if err := h(c, msg); err != nil {81 ch <- err82 }83 }(hook)84 }85 }86}8788func (c *Client) CmdHook(cmd string, hook Hook) {89 c.hooks[cmd] = append(c.hooks[cmd], hook)90}9192func joinCmd(client *Client, msg Message) error {93 if msg.Sender.Name == client.Nickname {94 client.Channels = append(client.Channels, msg.Data)95 }9697 return nil98}99100func partCmd(client *Client, msg Message) error {101 if msg.Sender.Name == client.Nickname {102 client.Channels = remove(msg.Data, client.Channels)103 }104105 return nil106}107108func kickCmd(client *Client, msg Message) error {109 if msg.Data == client.Nickname {110 target := strings.Fields(msg.Receiver)[0]111 client.Channels = remove(target, client.Channels)112 }113114 return nil115}116117func pingCmd(client *Client, msg Message) error {118 return client.Write("PONG %s", msg.Data)119}120121// sanitize removes all non-printable characters from122// the given string by returning a new string without them.123func sanitize(text string) string {124 mfunc := func(r rune) rune {125 switch {126 case !unicode.IsPrint(r):127 return ' '128 case unicode.IsSpace(r):129 return ' '130 default:131 return r132 }133 }134135 escaped := strings.Map(mfunc, text)136 return strings.Join(strings.Fields(escaped), " ")137}138139// remove deletes a given element from a given slice. A new slice140// which does not contain the given element is returned.141func remove(element string, slice []string) []string {142 var newSlice []string143 for _, e := range slice {144 if e != element {145 newSlice = append(newSlice, e)146 }147 }148149 return newSlice150}