rewrite capability ack functionality

This commit is contained in:
Liam Stanley 2017-01-19 06:58:08 -05:00
parent a95be3ad67
commit aeef5a8774
3 changed files with 89 additions and 42 deletions

122
cap.go
View File

@ -11,14 +11,14 @@ import (
"strings"
)
var possibleCap = []string{
"account-notify",
"account-tag",
"away-notify",
"batch",
"cap-notify",
"chghost",
"message-tags",
var possibleCap = map[string][]string{
"account-notify": nil,
"account-tag": nil,
"away-notify": nil,
"batch": nil,
"cap-notify": nil,
"chghost": nil,
"message-tags": nil,
}
func (c *Client) listCAP() error {
@ -31,13 +31,46 @@ func (c *Client) listCAP() error {
return nil
}
func possibleCapList(c *Client) map[string][]string {
out := make(map[string][]string)
for k := range c.Config.SupportedCaps {
out[k] = c.Config.SupportedCaps[k]
}
for k := range possibleCap {
out[k] = possibleCap[k]
}
return out
}
func parseCap(raw string) map[string][]string {
out := make(map[string][]string)
parts := strings.Split(raw, " ")
var val int
for i := 0; i < len(parts); i++ {
val = strings.IndexByte(parts[i], prefixTagValue) // =
// No value splitter, or has splitter but no trailing value.
if val < 1 || len(parts[i]) < val+1 {
// The capability doesn't contain a value.
out[parts[i]] = []string{}
continue
}
out[parts[i][:val]] = strings.Split(parts[i][val+1:], ",")
}
return out
}
// handleCAP attempts to find out what IRCv3 capabilities the server supports.
// This will lock further registration until we have acknowledged the
// capabilities.
func handleCAP(c *Client, e Event) {
possible := c.Config.SupportedCaps
possible = append(possible, possibleCap...)
if len(e.Params) >= 2 && (e.Params[1] == CAP_NEW || e.Params[1] == CAP_DEL) {
c.listCAP()
return
@ -46,32 +79,51 @@ func handleCAP(c *Client, e Event) {
// We can assume there was a failure attempting to enable a capability.
if len(e.Params) == 2 && e.Params[1] == CAP_NAK {
// Let the server know that we're done.
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
possible := possibleCapList(c)
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
c.state.mu.Lock()
// Loop through and check if it's one we support.
for _, cap := range strings.Split(e.Trailing, " ") {
for i := 0; i < len(possible); i++ {
// Check if it's one that we support.
if possibleCap[i] == cap {
// Ensure that there are no duplicates.
var isin bool
for j := 0; j < len(c.state.tmpCap); j++ {
if c.state.tmpCap[j] == cap {
isin = true
break
}
}
if !isin {
c.state.tmpCap = append(c.state.tmpCap, cap)
caps := parseCap(e.Trailing)
for k := range caps {
if _, ok := possible[k]; !ok {
continue
}
if len(possible[k]) == 0 || len(caps[k]) == 0 {
c.state.tmpCap = append(c.state.tmpCap, k)
continue
}
var contains bool
for i := 0; i < len(caps[k]); i++ {
for j := 0; j < len(possible[k]); j++ {
if caps[k][i] == possible[k][j] {
// Assume we have a matching split value.
contains = true
break
}
if contains {
break
}
}
if contains {
break
}
}
if !contains {
continue
}
c.state.tmpCap = append(c.state.tmpCap, k)
}
c.state.mu.Unlock()
@ -80,12 +132,12 @@ func handleCAP(c *Client, e Event) {
if len(e.Params) == 2 {
// If we support no caps, just ack the CAP message and END.
if len(c.state.tmpCap) == 0 {
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
// Let them know which ones we'd like to enable.
c.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " ")})
c.write(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " ")})
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
// due to cap-notify, we can re-evaluate what we can support.
@ -101,7 +153,7 @@ func handleCAP(c *Client, e Event) {
c.state.mu.Unlock()
// Let the server know that we're done.
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
}
@ -210,20 +262,14 @@ func ParseTags(raw string) (t Tags) {
for i := 0; i < len(parts); i++ {
hasValue = strings.IndexByte(parts[i], prefixTagValue)
if hasValue < 1 {
// The tag doesn't contain a value or has a splitter with no value.
if hasValue < 1 || len(parts[i]) < hasValue+1 {
// The tag doesn't contain a value.
t[parts[i]] = ""
continue
}
// May have equals sign and no value as well.
if len(parts[i]) < hasValue+1 {
t[parts[i]] = ""
continue
}
t[parts[i][:hasValue]] = parts[i][hasValue+1:]
continue
}
return t

View File

@ -78,7 +78,8 @@ type Config struct {
// SupportedCaps are the IRCv3 capabilities you would like the client to
// support. Only use this if DisableTracking and DisableCapTracking are
// not enabled, otherwise you will need to handle CAP negotiation yourself.
SupportedCaps []string
// The keys value gets passed to the server if supported.
SupportedCaps map[string][]string
// Version is the application version information that will be used in
// response to a CTCP VERSION, if default CTCP replies have not been
// overwritten or a VERSION handler was already supplied.
@ -292,7 +293,7 @@ func (c *Client) Reconnect() (err error) {
}
if c.Config.ReconnectDelay < (10 * time.Second) {
c.Config.ReconnectDelay = 10 * time.Second
c.Config.ReconnectDelay = 25 * time.Second
}
if c.IsConnected() {

View File

@ -45,8 +45,8 @@ type state struct {
// last capability list command from the server.
tmpCap []string
// serverOptions are the standard capabilities and configurations
// supported by the server at connection time. This also includes ISUPPORT
// entries.
// supported by the server at connection time. This also includes
// RPL_ISUPPORT entries.
serverOptions map[string]string
// motd is the servers message of the day.
motd string