Handle common CTCP messages

This commit is contained in:
Ken-Håvard Lieng 2020-05-20 04:19:40 +02:00
parent 4816fbfbca
commit 973578bb49
7 changed files with 105 additions and 39 deletions

View File

@ -21,6 +21,11 @@ type Client struct {
Realname string Realname string
HandleNickInUse func(string) string HandleNickInUse func(string) string
// Version is the reply to VERSION and FINGER CTCP messages
Version string
// Source is the reply to SOURCE CTCP messages
Source string
DownloadFolder string DownloadFolder string
Autoget bool Autoget bool
@ -53,8 +58,8 @@ func NewClient(nick, username string) *Client {
Username: username, Username: username,
Realname: nick, Realname: nick,
Messages: make(chan *Message, 32), Messages: make(chan *Message, 32),
ConnectionChanged: make(chan ConnectionState, 16), ConnectionChanged: make(chan ConnectionState, 4),
Progress: make(chan DownloadProgress, 16), Progress: make(chan DownloadProgress, 4),
out: make(chan string, 32), out: make(chan string, 32),
quit: make(chan struct{}), quit: make(chan struct{}),
reconnect: make(chan struct{}), reconnect: make(chan struct{}),
@ -154,6 +159,13 @@ func (c *Client) Notice(target, msg string) {
c.Writef("NOTICE %s :%s", target, msg) c.Writef("NOTICE %s :%s", target, msg)
} }
func (c *Client) ReplyCTCP(target, command, params string) {
c.Notice(target, EncodeCTCP(&CTCP{
Command: command,
Params: params,
}))
}
func (c *Client) Whois(nick string) { func (c *Client) Whois(nick string) {
c.Write("WHOIS " + nick) c.Write("WHOIS " + nick)
} }

View File

@ -128,6 +128,12 @@ func TestNotice(t *testing.T) {
assert.Equal(t, "NOTICE user :the message\r\n", <-out) assert.Equal(t, "NOTICE user :the message\r\n", <-out)
} }
func TestReplyCTCP(t *testing.T) {
c, out := testClientSend()
c.ReplyCTCP("user", "PING", "PONG")
assert.Equal(t, "NOTICE user :\x01PING PONG\x01\r\n", <-out)
}
func TestWhois(t *testing.T) { func TestWhois(t *testing.T) {
c, out := testClientSend() c, out := testClientSend()
c.Whois("user") c.Whois("user")

View File

@ -225,7 +225,7 @@ func (c *Client) recv() {
case Privmsg: case Privmsg:
if ctcp := msg.ToCTCP(); ctcp != nil { if ctcp := msg.ToCTCP(); ctcp != nil {
c.handleCTCP(ctcp) c.handleCTCP(ctcp, msg)
} }
case ReplyWelcome: case ReplyWelcome:
@ -250,14 +250,3 @@ func (c *Client) recv() {
c.Messages <- msg c.Messages <- msg
} }
} }
func (c *Client) handleCTCP(ctcp *CTCP) {
switch ctcp.Command {
case "DCC":
if strings.HasPrefix(ctcp.Params, "SEND") {
if dccSend := ParseDCCSend(ctcp); dccSend != nil {
go c.Download(dccSend)
}
}
}
}

76
pkg/irc/ctcp.go Normal file
View File

@ -0,0 +1,76 @@
package irc
import (
"fmt"
"strings"
"time"
)
// ClientInfo is the CTCP messages this client implements
const ClientInfo = "ACTION CLIENTINFO DCC FINGER PING SOURCE TIME VERSION USERINFO"
type CTCP struct {
Command string
Params string
}
func DecodeCTCP(str string) *CTCP {
if len(str) > 1 && str[0] == 0x01 {
parts := strings.SplitN(strings.Trim(str, "\x01"), " ", 2)
ctcp := CTCP{}
if parts[0] != "" {
ctcp.Command = parts[0]
} else {
return nil
}
if len(parts) == 2 {
ctcp.Params = parts[1]
}
return &ctcp
}
return nil
}
func EncodeCTCP(ctcp *CTCP) string {
if ctcp == nil || ctcp.Command == "" {
return ""
}
return fmt.Sprintf("\x01%s %s\x01", ctcp.Command, ctcp.Params)
}
func (c *Client) handleCTCP(ctcp *CTCP, msg *Message) {
switch ctcp.Command {
case "CLIENTINFO":
c.ReplyCTCP(msg.Nick, ctcp.Command, ClientInfo)
case "DCC":
if strings.HasPrefix(ctcp.Params, "SEND") {
if dccSend := ParseDCCSend(ctcp); dccSend != nil {
go c.Download(dccSend)
}
}
case "FINGER", "VERSION":
if c.Version != "" {
c.ReplyCTCP(msg.Nick, ctcp.Command, c.Version)
}
case "PING":
c.ReplyCTCP(msg.Nick, ctcp.Command, ctcp.Params)
case "SOURCE":
if c.Source != "" {
c.ReplyCTCP(msg.Nick, ctcp.Command, c.Source)
}
case "TIME":
c.ReplyCTCP(msg.Nick, ctcp.Command, time.Now().UTC().Format(time.RFC3339))
case "USERINFO":
c.ReplyCTCP(msg.Nick, ctcp.Command, fmt.Sprintf("%s (%s)", c.GetNick(), c.Realname))
}
}

View File

@ -19,32 +19,8 @@ func (m *Message) LastParam() string {
return "" return ""
} }
type CTCP struct {
Command string
Params string
}
func (m *Message) ToCTCP() *CTCP { func (m *Message) ToCTCP() *CTCP {
lp := m.LastParam() return DecodeCTCP(m.LastParam())
if len(lp) > 1 && lp[0] == 0x01 {
parts := strings.SplitN(strings.Trim(lp, "\x01"), " ", 2)
ctcp := CTCP{}
if parts[0] != "" {
ctcp.Command = parts[0]
} else {
return nil
}
if len(parts) == 2 {
ctcp.Params = parts[1]
}
return &ctcp
}
return nil
} }
func ParseMessage(line string) *Message { func ParseMessage(line string) *Message {

View File

@ -8,6 +8,7 @@ import (
"github.com/khlieng/dispatch/pkg/irc" "github.com/khlieng/dispatch/pkg/irc"
"github.com/khlieng/dispatch/storage" "github.com/khlieng/dispatch/storage"
"github.com/khlieng/dispatch/version"
) )
func createNickInUseHandler(i *irc.Client, state *State) func(string) string { func createNickInUseHandler(i *irc.Client, state *State) func(string) string {
@ -33,6 +34,8 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
i := irc.NewClient(server.Nick, server.Username) i := irc.NewClient(server.Nick, server.Username)
i.TLS = server.TLS i.TLS = server.TLS
i.Realname = server.Realname i.Realname = server.Realname
i.Version = fmt.Sprintf("Dispatch %s (git: %s)", version.Tag, version.Commit)
i.Source = "https://github.com/khlieng/dispatch"
i.HandleNickInUse = createNickInUseHandler(i, state) i.HandleNickInUse = createNickInUseHandler(i, state)
address := server.Host address := server.Host

View File

@ -176,6 +176,10 @@ func (i *ircHandler) mode(msg *irc.Message) {
} }
func (i *ircHandler) message(msg *irc.Message) { func (i *ircHandler) message(msg *irc.Message) {
if ctcp := msg.ToCTCP(); ctcp != nil && ctcp.Command != "ACTION" {
return
}
message := Message{ message := Message{
ID: betterguid.New(), ID: betterguid.New(),
Server: i.client.Host, Server: i.client.Host,