Send irc features to the client
This commit is contained in:
parent
9267c661dc
commit
613d9fca6e
20 changed files with 690 additions and 304 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
134
pkg/irc/feature.go
Normal 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
49
pkg/irc/feature_test.go
Normal 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"))
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue