Send irc features to the client

This commit is contained in:
Ken-Håvard Lieng 2019-01-27 08:53:07 +01:00
parent 9267c661dc
commit 613d9fca6e
20 changed files with 690 additions and 304 deletions

View file

@ -14,7 +14,7 @@ const (
)
func (c *Client) Casefold(s string) string {
mapping := c.Support.Get("CASEMAPPING")
mapping := c.Features.String("CASEMAPPING")
if mapping == "" {
mapping = RFC1459
}
@ -22,7 +22,7 @@ func (c *Client) Casefold(s string) string {
}
func (c *Client) EqualFold(s1, s2 string) bool {
mapping := c.Support.Get("CASEMAPPING")
mapping := c.Features.String("CASEMAPPING")
if mapping == "" {
mapping = RFC1459
}

View file

@ -11,20 +11,20 @@ import (
)
type Client struct {
Server string
Host string
TLS bool
TLSConfig *tls.Config
Password string
Username string
Realname string
Server string
Host string
TLS bool
TLSConfig *tls.Config
Password string
Username string
Realname string
HandleNickInUse func(string) string
Messages chan *Message
ConnectionChanged chan ConnectionState
HandleNickInUse func(string) string
nick string
channels []string
Support *iSupport
Features *Features
nick string
channels []string
conn net.Conn
connected bool
@ -44,7 +44,7 @@ type Client struct {
func NewClient(nick, username string) *Client {
return &Client{
nick: nick,
Support: newISupport(),
Features: NewFeatures(),
Username: username,
Realname: nick,
Messages: make(chan *Message, 32),

View file

@ -232,7 +232,7 @@ func (c *Client) recv() {
go c.send()
case ReplyISupport:
c.Support.parse(msg.Params)
c.Features.Parse(msg.Params)
case ErrNicknameInUse:
if c.HandleNickInUse != nil {

134
pkg/irc/feature.go Normal file
View file

@ -0,0 +1,134 @@
package irc
import (
"strconv"
"strings"
"sync"
)
type Features struct {
m map[string]interface{}
lock sync.Mutex
}
func NewFeatures() *Features {
return &Features{
m: map[string]interface{}{},
}
}
func (f *Features) Map() map[string]interface{} {
m := map[string]interface{}{}
f.lock.Lock()
for k, v := range f.m {
m[k] = v
}
f.lock.Unlock()
return m
}
func (f *Features) Parse(params []string) {
f.lock.Lock()
for _, param := range params[1 : len(params)-1] {
key, val := splitParam(param)
if key == "" {
continue
}
if key[0] == '-' {
delete(f.m, key[1:])
} else {
if t, ok := featureTransforms[key]; ok {
f.m[key] = t(val)
} else {
f.m[key] = val
}
}
}
f.lock.Unlock()
}
func (f *Features) Has(key string) bool {
f.lock.Lock()
_, has := f.m[key]
f.lock.Unlock()
return has
}
func (f *Features) Get(key string) interface{} {
f.lock.Lock()
v := f.m[key]
f.lock.Unlock()
return v
}
func (f *Features) String(key string) string {
if v, ok := f.Get(key).(string); ok {
return v
}
return ""
}
func (f *Features) Int(key string) int {
if v, ok := f.Get(key).(int); ok {
return v
}
return 0
}
type featureTransform func(interface{}) interface{}
func toInt(v interface{}) interface{} {
s := v.(string)
if s == "" {
return 0
}
i, _ := strconv.Atoi(s)
return i
}
func toCharList(v interface{}) interface{} {
s := v.(string)
list := make([]string, len(s))
for i := range s {
list[i] = s[i : i+1]
}
return list
}
func parseChanlimit(v interface{}) interface{} {
limits := map[string]int{}
pairs := strings.Split(v.(string), ",")
for _, p := range pairs {
pair := strings.Split(p, ":")
if len(pair) == 2 {
prefixes := pair[0]
limit, _ := strconv.Atoi(pair[1])
for i := range prefixes {
limits[prefixes[i:i+1]] = limit
}
}
}
return limits
}
var featureTransforms = map[string]featureTransform{
"AWAYLEN": toInt,
"CHANLIMIT": parseChanlimit,
"CHANNELLEN": toInt,
"CHANTYPES": toCharList,
"HOSTLEN": toInt,
"KICKLEN": toInt,
"MAXCHANNELS": toInt,
"MAXTARGETS": toInt,
"MODES": toInt,
"NICKLEN": toInt,
"TOPICLEN": toInt,
"USERLEN": toInt,
}

49
pkg/irc/feature_test.go Normal file
View file

@ -0,0 +1,49 @@
package irc
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseFeatures(t *testing.T) {
s := NewFeatures()
featureTransforms["CAKE"] = toInt
s.Parse([]string{"bob", "CAKE=31", "PIE", ":durr"})
assert.Equal(t, 31, s.Int("CAKE"))
assert.Equal(t, "", s.String("CAKE"))
assert.True(t, s.Has("CAKE"))
assert.True(t, s.Has("PIE"))
assert.False(t, s.Has("APPLES"))
assert.Equal(t, "", s.String("APPLES"))
assert.Equal(t, 0, s.Int("APPLES"))
s.Parse([]string{"bob", "-PIE", ":hurr"})
assert.False(t, s.Has("PIE"))
s.Parse([]string{"bob", "CAKE=1337", ":durr"})
assert.Equal(t, 1337, s.Int("CAKE"))
s.Parse([]string{"bob", "CAKE=", ":durr"})
assert.Equal(t, "", s.String("CAKE"))
assert.True(t, s.Has("CAKE"))
delete(featureTransforms, "CAKE")
s.Parse([]string{"bob", "CAKE===", ":durr"})
assert.Equal(t, "==", s.String("CAKE"))
s.Parse([]string{"bob", "-CAKE=31", ":durr"})
assert.False(t, s.Has("CAKE"))
s.Parse([]string{"bob", "CHANLIMIT=#&:50", ":durr"})
assert.Equal(t, map[string]int{"#": 50, "&": 50}, s.Get("CHANLIMIT"))
s.Parse([]string{"bob", "CHANLIMIT=#:50,&:25", ":durr"})
assert.Equal(t, map[string]int{"#": 50, "&": 25}, s.Get("CHANLIMIT"))
s.Parse([]string{"bob", "CHANLIMIT=&:50,#:", ":durr"})
assert.Equal(t, map[string]int{"#": 0, "&": 50}, s.Get("CHANLIMIT"))
s.Parse([]string{"bob", "CHANTYPES=#&", ":durr"})
assert.Equal(t, []string{"#", "&"}, s.Get("CHANTYPES"))
}

View file

@ -2,9 +2,6 @@ package irc
import (
"strings"
"sync"
"github.com/spf13/cast"
)
type Message struct {
@ -96,55 +93,6 @@ func ParseMessage(line string) *Message {
return &msg
}
type iSupport struct {
support map[string]string
lock sync.Mutex
}
func newISupport() *iSupport {
return &iSupport{
support: map[string]string{},
}
}
func (i *iSupport) parse(params []string) {
i.lock.Lock()
for _, param := range params[1 : len(params)-1] {
key, val := splitParam(param)
if key == "" {
continue
}
if key[0] == '-' {
delete(i.support, key[1:])
} else {
i.support[key] = val
}
}
i.lock.Unlock()
}
func (i *iSupport) Has(key string) bool {
i.lock.Lock()
_, has := i.support[key]
i.lock.Unlock()
return has
}
func (i *iSupport) Get(key string) string {
i.lock.Lock()
v := i.support[key]
i.lock.Unlock()
return v
}
func (i *iSupport) GetInt(key string) int {
i.lock.Lock()
v := i.support[key]
i.lock.Unlock()
return cast.ToInt(v)
}
func splitParam(param string) (string, string) {
parts := strings.SplitN(param, "=", 2)
if len(parts) == 2 {

View file

@ -187,31 +187,3 @@ func TestBadMessage(t *testing.T) {
assert.Nil(t, ParseMessage(":"))
assert.Nil(t, ParseMessage(""))
}
func TestParseISupport(t *testing.T) {
s := newISupport()
s.parse([]string{"bob", "CAKE=31", "PIE", ":durr"})
assert.Equal(t, 31, s.GetInt("CAKE"))
assert.Equal(t, "31", s.Get("CAKE"))
assert.True(t, s.Has("CAKE"))
assert.True(t, s.Has("PIE"))
assert.False(t, s.Has("APPLES"))
assert.Equal(t, "", s.Get("APPLES"))
assert.Equal(t, 0, s.GetInt("APPLES"))
s.parse([]string{"bob", "-PIE", ":hurr"})
assert.False(t, s.Has("PIE"))
s.parse([]string{"bob", "CAKE=1337", ":durr"})
assert.Equal(t, 1337, s.GetInt("CAKE"))
s.parse([]string{"bob", "CAKE=", ":durr"})
assert.Equal(t, "", s.Get("CAKE"))
assert.True(t, s.Has("CAKE"))
s.parse([]string{"bob", "CAKE===", ":durr"})
assert.Equal(t, "==", s.Get("CAKE"))
s.parse([]string{"bob", "-CAKE=31", ":durr"})
assert.False(t, s.Has("CAKE"))
}