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 irc 15 16import ( 17 "fmt" 18 "net" 19 "strings" 20 "unicode" 21) 22 23type Hook func(*Client, Message) error 24 25type Client struct { 26 conn net.Conn 27 hooks map[string][]Hook 28 Nickname string 29 Realname string 30 Channels []string 31} 32 33func NewClient(conn net.Conn) *Client { 34 c := &Client{ 35 conn: conn, 36 hooks: make(map[string][]Hook), 37 } 38 39 c.CmdHook("join", joinCmd) 40 c.CmdHook("part", partCmd) 41 c.CmdHook("kick", kickCmd) 42 43 c.CmdHook("ping", pingCmd) 44 return c 45} 46 47func (c *Client) Setup(nick, name, host string) { 48 c.Nickname = nick 49 c.Realname = name 50 51 c.Write("USER %s %s * :%s", c.Nickname, host, c.Realname) 52 c.Write("NICK %s", c.Nickname) 53} 54 55func (c *Client) Connected(channel string) bool { 56 for _, c := range c.Channels { 57 if c == channel { 58 return true 59 } 60 } 61 62 return false 63} 64 65func (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 err 69 } 70 71 return nil 72} 73 74func (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 <- err 82 } 83 }(hook) 84 } 85 } 86} 87 88func (c *Client) CmdHook(cmd string, hook Hook) { 89 c.hooks[cmd] = append(c.hooks[cmd], hook) 90} 91 92func joinCmd(client *Client, msg Message) error { 93 if msg.Sender.Name == client.Nickname { 94 client.Channels = append(client.Channels, msg.Data) 95 } 96 97 return nil 98} 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}