start working on ctcp handling
This commit is contained in:
parent
3364fbdbe6
commit
3abefe033c
15
client.go
15
client.go
@ -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) {
|
||||
|
12
contants.go
12
contants.go
@ -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
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, "")
|
||||
}
|
4
event.go
4
event.go
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user