1// package weechat implements the WeeChat relay protocol. 2package weechat 3 4import ( 5 "bufio" 6 "bytes" 7 "compress/zlib" 8 "encoding/binary" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "sync" 15) 16 17// Reference: http://www.weechat.org/files/doc/stable/weechat_relay_protocol.en.html 18 19type command int 20 21const ( 22 cmdInit command = iota 23 cmdHdata 24 cmdInfo 25 cmdInfolist 26 cmdNicklist 27 cmdInput 28 cmdSync 29 cmdDesync 30 cmdQuit 31 cmdCount 32) 33 34var cmdStrings = [cmdCount]string{ 35 cmdInit: "init", 36 cmdHdata: "hdata", 37 cmdInfo: "info", 38 cmdInfolist: "infolist", 39 cmdNicklist: "nicklist", 40 cmdInput: "input", 41 cmdSync: "sync", 42 cmdDesync: "desync", 43 cmdQuit: "quit", 44} 45 46type Conn struct { 47 c net.Conn 48 r *bufio.Reader 49 lock sync.Mutex 50} 51 52func Dial(addr string) (*Conn, error) { 53 conn, err := net.Dial("tcp", addr) 54 if err != nil { 55 return nil, err 56 } 57 wc := &Conn{c: conn, r: bufio.NewReader(conn)} 58 // say hello 59 err = wc.send(cmdInit, "compression=off") 60 return wc, err 61} 62 63func (conn *Conn) Close() error { 64 if conn != nil && conn.c != nil { 65 return conn.c.Close() 66 } 67 return nil 68} 69 70func (conn *Conn) send(cmd command, args ...string) error { 71 buf := make([]byte, 0, 80) 72 buf = append(buf, cmdStrings[cmd]...) 73 for _, a := range args { 74 buf = append(buf, ' ') 75 buf = append(buf, a...) 76 } 77 buf = append(buf, '\n') 78 _, err := conn.c.Write(buf) 79 return err 80} 81 82var errMsgTooLarge = errors.New("message too large") 83 84// recv gets a message from the connection. 85func (conn *Conn) recv() (s []byte, err error) { 86 // A message is: 87 // - a uint32 length 88 // - a byte boolean for compression 89 // - length-5 bytes of data (plain or zlib compressed) 90 var buf [5]byte 91 _, err = io.ReadFull(conn.r, buf[:]) 92 if err != nil { 93 return nil, err 94 } 95 length := binary.BigEndian.Uint32(buf[:4]) 96 isCompressed := buf[4] == 1 97 if length >= 32<<20 { 98 return nil, errMsgTooLarge 99 } 100 101 s = make([]byte, length-5) 102 _, err = io.ReadFull(conn.r, s) 103 if err != nil { 104 return 105 } 106 if isCompressed { 107 zr, err := zlib.NewReader(bytes.NewBuffer(s)) 108 if err != nil { 109 return s, err 110 } 111 return ioutil.ReadAll(zr) 112 } 113 return s, nil 114} 115 116func (conn *Conn) ListBuffers() ([]Buffer, error) { 117 conn.lock.Lock() 118 defer conn.lock.Unlock() 119 var s []byte 120 err := conn.send(cmdHdata, "buffer:gui_buffers(*)") 121 if err == nil { 122 s, err = conn.recv() 123 } 124 if err != nil { 125 return nil, err 126 } 127 msg := message(s) 128 id, typ := msg.Buffer(), msg.GetType() 129 //t.Logf("id=%s type=%v", id, typ) 130 _, _ = id, typ 131 var buflist []Buffer 132 msg.HData(&buflist) 133 return buflist, nil 134} 135 136func (conn *Conn) BufferData(ptr uint64, limit int, filter string) (lines []LineData, err error) { 137 conn.lock.Lock() 138 defer conn.lock.Unlock() 139 var s []byte 140 var path string 141 if limit == 0 { 142 path = fmt.Sprintf("buffer:0x%x/lines/first_line(*)/data", ptr) 143 } else if limit > 0 { 144 path = fmt.Sprintf("buffer:0x%x/lines/first_line(%d)/data", ptr, limit) 145 } else if limit < 0 { 146 path = fmt.Sprintf("buffer:0x%x/lines/last_line(%d)/data", ptr, limit) 147 } 148 err = conn.send(cmdHdata, path, filter) 149 if err == nil { 150 s, err = conn.recv() 151 } 152 if err != nil { 153 return nil, err 154 } 155 msg := message(s) 156 id, typ := msg.Buffer(), msg.GetType() 157 //t.Logf("id=%s type=%v", id, typ) 158 _, _ = id, typ 159 msg.HData(&lines) 160 return lines, nil 161} 162 163func (conn *Conn) BuffersData() (lines []LineData, err error) { 164 conn.lock.Lock() 165 defer conn.lock.Unlock() 166 var s []byte 167 err = conn.send(cmdHdata, "buffer:gui_buffers(*)/lines/first_line(*)/data") 168 if err == nil { 169 s, err = conn.recv() 170 } 171 if err != nil { 172 return nil, err 173 } 174 msg := message(s) 175 id, typ := msg.Buffer(), msg.GetType() 176 //t.Logf("id=%s type=%v", id, typ) 177 _, _ = id, typ 178 msg.HData(&lines) 179 return lines, nil 180} 181