start working on ctcp handling

This commit is contained in:
Liam Stanley 2016-12-24 00:15:41 -05:00
parent 3364fbdbe6
commit 3abefe033c
4 changed files with 180 additions and 2 deletions

@ -524,6 +524,21 @@ func (c *Client) PartMessage(channel, message string) error {
return c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message})
}
// SendCTCP sends a CTCP request to target.
func (c *Client) SendCTCP(target, ctcpType, message string) error {
out := encodeCTCPRaw(ctcpType, message)
if out == "" {
return errors.New("invalid CTCP")
}
return c.Message(target, out)
}
// SendCTCPf sends a CTCP request to target using a specific format.
func (c *Client) SendCTCPf(target, ctcpType, format string, a ...interface{}) error {
return c.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
}
// Message sends a PRIVMSG to target (either channel, service, or user).
func (c *Client) Message(target, message string) error {
if !IsValidNick(target) && !IsValidChannel(target) {

@ -4,6 +4,18 @@
package girc
// Standard CTCP based constants
const (
CTCP_PING = "PING"
CTCP_PONG = "PONG"
CTCP_VERSION = "VERSION"
CTCP_USERINFO = "USERINFO"
CTCP_CLIENTINFO = "CLIENTINFO"
CTCP_FINGER = "FINGER"
CTCP_SOURCE = "SOURCE"
CTCP_TIME = "TIME"
)
// Misc constants for use with the client.
const (
ALLEVENTS = "*" // trigger on all events

151
ctcp.go Normal file

@ -0,0 +1,151 @@
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package girc
import (
"strings"
"sync"
)
const ctcpDelim byte = 0x01 // Prefix and suffix for CTCP messages.
type CTCPEvent struct {
Source *Source
Command string
Text string
}
func decodeCTCP(e *Event) *CTCPEvent {
if len(e.Params) != 1 {
return nil
}
if e.Command != "PRIVMSG" || !IsValidNick(e.Params[0]) {
return nil
}
if e.Trailing[0] != ctcpDelim || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
return nil
}
text := e.Trailing[1 : len(e.Trailing)-1]
s := strings.IndexByte(text, space)
// Check to see if it only contains a tag.
if s < 0 {
for i := 0; i < len(text); i++ {
// Check for A-Z, 0-9.
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
return nil
}
}
return &CTCPEvent{Source: e.Source, Command: text}
}
// Loop through checking the tag first.
for i := 0; i < s; i++ {
// Check for A-Z, 0-9.
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
return nil
}
}
return &CTCPEvent{Source: e.Source, Command: text[1:s], Text: text[s+1 : len(text)-1]}
}
func encodeCTCP(ctcp *CTCPEvent) (out string) {
if ctcp == nil {
return ""
}
return encodeCTCPRaw(ctcp.Command, ctcp.Text)
}
func encodeCTCPRaw(cmd, text string) (out string) {
if len(cmd) <= 0 {
return ""
}
out = string(ctcpDelim) + cmd
if len(text) > 0 {
out += string(space) + text
}
return out + string(ctcpDelim)
}
type CTCP struct {
// mu is the mutex that should be used when accessing callbacks.
mu sync.RWMutex
// handlers is a map of CTCP message -> functions.
handlers map[string]CTCPHandler
}
func newCTCP() *CTCP {
return &CTCP{handlers: map[string]CTCPHandler{}}
}
func (c *CTCP) Call(event *CTCPEvent, client *Client) {
c.mu.RLock()
if _, ok := c.handlers[event.Command]; !ok {
c.mu.RUnlock()
return
}
go c.handlers[event.Command](client, event)
c.mu.RUnlock()
}
func (c *CTCP) parseCMD(cmd string) string {
cmd = strings.ToUpper(cmd)
for i := 0; i < len(cmd); i++ {
// Check for A-Z, 0-9.
if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) {
return ""
}
}
return cmd
}
func (c *CTCP) Set(cmd string, handler func(client *Client, ctcp *CTCPEvent)) {
if cmd = c.parseCMD(cmd); cmd == "" {
return
}
c.mu.Lock()
c.handlers[cmd] = CTCPHandler(handler)
c.mu.Unlock()
}
func (c *CTCP) Clear(cmd string) {
if cmd = c.parseCMD(cmd); cmd == "" {
return
}
c.mu.Lock()
delete(c.handlers, cmd)
c.mu.Unlock()
}
func (c *CTCP) ClearAll() {
c.mu.Lock()
c.handlers = map[string]CTCPHandler{}
c.mu.Unlock()
}
// CTCPHandler is a type that represents the function necessary to
// implement a CTCP handler.
type CTCPHandler func(client *Client, ctcp *CTCPEvent)
func (c *CTCP) addDefaultHandlers() {}
func handleCTCPPing(client *Client, ctcp *CTCPEvent) {
client.SendCTCP(ctcp.Source.Name, CTCP_PING, "")
}

@ -34,7 +34,7 @@ func cutCRFunc(r rune) bool {
// CR or LF>
// <crlf> :: CR LF
type Event struct {
*Source // The source of the event.
Source *Source // The source of the event.
Command string // the IRC command, e.g. JOIN, PRIVMSG, KILL.
Params []string // parameters to the command. Commonly nickname, channel, etc.
Trailing string // any trailing data. e.g. with a PRIVMSG, this is the message text.
@ -240,7 +240,7 @@ func (e *Event) IsAction() bool {
return false
}
if !strings.HasPrefix(e.Trailing, "\001ACTION") || !strings.HasSuffix(e.Trailing, "\001") {
if !strings.HasPrefix(e.Trailing, "\001ACTION") || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
return false
}