mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-25 13:29:27 +01:00
Initial METADATA work. Subs are totally broken and SYNC returns wrong data
This commit is contained in:
parent
b77a378ddf
commit
2eb2467de2
@ -36,6 +36,8 @@ const (
|
|||||||
MaxLine Capability = "oragono.io/maxline"
|
MaxLine Capability = "oragono.io/maxline"
|
||||||
// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
|
// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
|
||||||
MessageTags Capability = "draft/message-tags-0.2"
|
MessageTags Capability = "draft/message-tags-0.2"
|
||||||
|
// Metadata is this draft IRCv3 capability: https://github.com/jwheare/ircv3-specifications/blob/metadata/core/metadata-3.2.md
|
||||||
|
Metadata Capability = "draft/metadata"
|
||||||
// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
|
// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
|
||||||
MultiPrefix Capability = "multi-prefix"
|
MultiPrefix Capability = "multi-prefix"
|
||||||
// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
|
// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
|
||||||
|
@ -25,6 +25,7 @@ type Channel struct {
|
|||||||
members MemberSet
|
members MemberSet
|
||||||
membersCache []*Client // allow iteration over channel members without holding the lock
|
membersCache []*Client // allow iteration over channel members without holding the lock
|
||||||
membersCacheMutex sync.Mutex // tier 2; see `regenerateMembersCache`
|
membersCacheMutex sync.Mutex // tier 2; see `regenerateMembersCache`
|
||||||
|
metadata *MetadataManager
|
||||||
name string
|
name string
|
||||||
nameCasefolded string
|
nameCasefolded string
|
||||||
server *Server
|
server *Server
|
||||||
@ -56,6 +57,7 @@ func NewChannel(s *Server, name string, addDefaultModes bool, regInfo *Registere
|
|||||||
modes.InviteMask: NewUserMaskSet(),
|
modes.InviteMask: NewUserMaskSet(),
|
||||||
},
|
},
|
||||||
members: make(MemberSet),
|
members: make(MemberSet),
|
||||||
|
metadata: NewMetadataManager(),
|
||||||
name: name,
|
name: name,
|
||||||
nameCasefolded: casefoldedName,
|
nameCasefolded: casefoldedName,
|
||||||
server: s,
|
server: s,
|
||||||
|
@ -60,6 +60,7 @@ type Client struct {
|
|||||||
languages []string
|
languages []string
|
||||||
maxlenTags uint32
|
maxlenTags uint32
|
||||||
maxlenRest uint32
|
maxlenRest uint32
|
||||||
|
metadata *MetadataManager
|
||||||
nick string
|
nick string
|
||||||
nickCasefolded string
|
nickCasefolded string
|
||||||
nickMaskCasefolded string
|
nickMaskCasefolded string
|
||||||
@ -100,6 +101,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
ctime: now,
|
ctime: now,
|
||||||
flags: make(map[modes.Mode]bool),
|
flags: make(map[modes.Mode]bool),
|
||||||
|
metadata: NewMetadataManager(),
|
||||||
server: server,
|
server: server,
|
||||||
socket: &socket,
|
socket: &socket,
|
||||||
nick: "*", // * is used until actual nick is given
|
nick: "*", // * is used until actual nick is given
|
||||||
|
@ -161,6 +161,10 @@ func init() {
|
|||||||
handler: lusersHandler,
|
handler: lusersHandler,
|
||||||
minParams: 0,
|
minParams: 0,
|
||||||
},
|
},
|
||||||
|
"METADATA": {
|
||||||
|
handler: metadataHandler,
|
||||||
|
minParams: 2,
|
||||||
|
},
|
||||||
"MODE": {
|
"MODE": {
|
||||||
handler: modeHandler,
|
handler: modeHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
|
@ -34,6 +34,7 @@ var (
|
|||||||
errNoSuchChannel = errors.New("No such channel")
|
errNoSuchChannel = errors.New("No such channel")
|
||||||
errRenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
errRenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
||||||
errSaslFail = errors.New("SASL failed")
|
errSaslFail = errors.New("SASL failed")
|
||||||
|
errTooManyKeys = errors.New("Too many metadata keys")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socket Errors
|
// Socket Errors
|
||||||
|
296
irc/handlers.go
296
irc/handlers.go
@ -1311,6 +1311,300 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// METADATA * SUB <key>{ <key>}
|
||||||
|
// METADATA * SUBS
|
||||||
|
// METADATA * UNSUB <key>{ <key>}
|
||||||
|
// METADATA <target> CLEAR
|
||||||
|
// METADATA <target> GET <key>{ <key>}
|
||||||
|
// METADATA <target> LIST
|
||||||
|
// METADATA <target> SET <key> [<value>]
|
||||||
|
// METADATA <target> SYNC
|
||||||
|
func metadataHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
handler, exists := metadataSubcommands[strings.ToLower(msg.Params[1])]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown subcommand"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(server, client, msg, rb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// METADATA <target> CLEAR
|
||||||
|
func metadataClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
var mm *MetadataManager
|
||||||
|
|
||||||
|
targetString := msg.Params[0]
|
||||||
|
target, err := CasefoldChannel(targetString)
|
||||||
|
if err == nil {
|
||||||
|
channel := server.channels.Get(target)
|
||||||
|
if channel == nil {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
|
rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.nick, targetString, client.t("You're not a channel operator"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = channel.metadata
|
||||||
|
} else {
|
||||||
|
target, err = CasefoldName(targetString)
|
||||||
|
user := server.clients.Get(target)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
if len(target) > 0 {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if user != client {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, "*", client.t("Permission denied"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = user.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range mm.Clear() {
|
||||||
|
rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*")
|
||||||
|
}
|
||||||
|
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// METADATA <target> GET <key>{ <key>}
|
||||||
|
func metadataGetHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
if len(msg.Params) < 3 {
|
||||||
|
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var mm *MetadataManager
|
||||||
|
|
||||||
|
targetString := msg.Params[0]
|
||||||
|
target, err := CasefoldChannel(targetString)
|
||||||
|
if err == nil {
|
||||||
|
channel := server.channels.Get(target)
|
||||||
|
if channel == nil {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !channel.hasClient(client) {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = channel.metadata
|
||||||
|
} else {
|
||||||
|
target, err = CasefoldName(targetString)
|
||||||
|
user := server.clients.Get(target)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
if len(target) > 0 {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = user.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, key := range msg.Params {
|
||||||
|
// only process actual keys, skip target and (sub)command name
|
||||||
|
if 1 < i {
|
||||||
|
key = strings.TrimSpace(strings.ToLower(key))
|
||||||
|
if !metadataKeyValid(key) {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, exists := mm.Get(key)
|
||||||
|
if !exists {
|
||||||
|
rb.Add(nil, server.name, ERR_NOMATCHINGKEY, client.nick, target, key, client.t("No matching key"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// METADATA <target> LIST
|
||||||
|
func metadataListHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
var mm *MetadataManager
|
||||||
|
|
||||||
|
targetString := msg.Params[0]
|
||||||
|
target, err := CasefoldChannel(targetString)
|
||||||
|
if err == nil {
|
||||||
|
channel := server.channels.Get(target)
|
||||||
|
if channel == nil {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !channel.hasClient(client) {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = channel.metadata
|
||||||
|
} else {
|
||||||
|
target, err = CasefoldName(targetString)
|
||||||
|
user := server.clients.Get(target)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
if len(target) > 0 {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = user.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range mm.List() {
|
||||||
|
rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
|
||||||
|
}
|
||||||
|
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// METADATA <target> SET <key> [<value>]
|
||||||
|
func metadataSetHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
if len(msg.Params) < 3 {
|
||||||
|
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve key/value
|
||||||
|
key := strings.TrimSpace(strings.ToLower(msg.Params[2]))
|
||||||
|
if !metadataKeyValid(key) {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var settingValue bool
|
||||||
|
var value string
|
||||||
|
if 3 < len(msg.Params) {
|
||||||
|
settingValue = true
|
||||||
|
value = msg.Params[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
// work on target
|
||||||
|
targetString := msg.Params[0]
|
||||||
|
target, err := CasefoldChannel(targetString)
|
||||||
|
if err == nil {
|
||||||
|
channel := server.channels.Get(target)
|
||||||
|
if channel == nil {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
|
rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.nick, targetString, client.t("You're not a channel operator"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if settingValue {
|
||||||
|
err := channel.metadata.Set(key, value, server.MetadataKeysLimit())
|
||||||
|
if err == errTooManyKeys {
|
||||||
|
rb.Add(nil, server.name, ERR_METADATALIMIT, client.nick, target, client.t("Metadata limit reached"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.metadata.Delete(key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target, err = CasefoldName(targetString)
|
||||||
|
user := server.clients.Get(target)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
if len(target) > 0 {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if user != client {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, "*", client.t("Permission denied"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if settingValue {
|
||||||
|
err := user.metadata.Set(key, value, server.MetadataKeysLimit())
|
||||||
|
if err == errTooManyKeys {
|
||||||
|
rb.Add(nil, server.name, ERR_METADATALIMIT, client.nick, target, client.t("Metadata limit reached"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.metadata.Delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if settingValue {
|
||||||
|
rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*")
|
||||||
|
}
|
||||||
|
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadataSubHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
client.Notice("METADATA SUB not yet implemented")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadataSubsHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
client.Notice("METADATA SUBS not yet implemented")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// METADATA <target> SYNC
|
||||||
|
//TODO(dan): SYNC also returns e.g. metadata keys of friends in the given channel, etc.
|
||||||
|
//Note:
|
||||||
|
// One difference with list is that you can’t get a whole channel full of members metadata with it.
|
||||||
|
// With sync you can, you get targets you didn’t explicitly request. It’s more of a trigger than a request.
|
||||||
|
// And it’s mainly only useful for the delayed synchronisation function.
|
||||||
|
func metadataSyncHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
var mm *MetadataManager
|
||||||
|
|
||||||
|
targetString := msg.Params[0]
|
||||||
|
target, err := CasefoldChannel(targetString)
|
||||||
|
if err == nil {
|
||||||
|
channel := server.channels.Get(target)
|
||||||
|
if channel == nil {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !channel.hasClient(client) {
|
||||||
|
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = channel.metadata
|
||||||
|
} else {
|
||||||
|
target, err = CasefoldName(targetString)
|
||||||
|
user := server.clients.Get(target)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
if len(target) > 0 {
|
||||||
|
rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mm = user.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range mm.List() {
|
||||||
|
rb.Add(nil, server.name, "METADATA", target, key, "*", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadataUnsubHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
client.Notice("METADATA UNSUB not yet implemented")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||||
func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
_, errChan := CasefoldChannel(msg.Params[0])
|
_, errChan := CasefoldChannel(msg.Params[0])
|
||||||
@ -1448,7 +1742,7 @@ func monitorHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
|||||||
handler, exists := monitorSubcommands[strings.ToLower(msg.Params[0])]
|
handler, exists := monitorSubcommands[strings.ToLower(msg.Params[0])]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", msg.Params[0], client.t("Unknown subcommand"))
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", client.t("Unknown subcommand"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +269,13 @@ channels). <elistcond>s modify how the channels are selected.`,
|
|||||||
Shows statistics about the size of the network. If <mask> is given, only
|
Shows statistics about the size of the network. If <mask> is given, only
|
||||||
returns stats for servers matching the given mask. If <server> is given, the
|
returns stats for servers matching the given mask. If <server> is given, the
|
||||||
command is processed by that server.`,
|
command is processed by that server.`,
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
text: `METADATA <target> <subcommand> [<param>...]
|
||||||
|
|
||||||
|
Sets, removes and displays metadata about clients and channels. For more
|
||||||
|
specific information, see the spec here:
|
||||||
|
https://github.com/jwheare/ircv3-specifications/blob/metadata/core/metadata-3.2.md`,
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
text: `MODE <target> [<modestring> [<mode arguments>...]]
|
text: `MODE <target> [<modestring> [<mode arguments>...]]
|
||||||
|
192
irc/metadata.go
Normal file
192
irc/metadata.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright (c) 2018 Daniel Oaks <daniel@danieloaks.net>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//TODO(dan): temporary hardcoded limits, make these configurable instead.
|
||||||
|
metadataKeysLimit = 20
|
||||||
|
metadataSubsLimit = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetadataKeysLimit returns how many metadata keys can be set on each client/channel.
|
||||||
|
//TODO(dan): have this be configurable in the config file instead.
|
||||||
|
func (server *Server) MetadataKeysLimit() int {
|
||||||
|
return metadataKeysLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataSubsLimit returns how many metadata keys can be subscribed to.
|
||||||
|
//TODO(dan): have this be configurable in the config file instead.
|
||||||
|
func (server *Server) MetadataSubsLimit() int {
|
||||||
|
return metadataSubsLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataManager manages metadata for a client or channel.
|
||||||
|
type MetadataManager struct {
|
||||||
|
sync.RWMutex
|
||||||
|
// keyvals holds our values internally.
|
||||||
|
keyvals map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetadataManager returns a new MetadataManager.
|
||||||
|
func NewMetadataManager() *MetadataManager {
|
||||||
|
var mm MetadataManager
|
||||||
|
mm.keyvals = make(map[string]string)
|
||||||
|
return &mm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear deletes all keys, returning a list of the deleted keys.
|
||||||
|
func (mm *MetadataManager) Clear() []string {
|
||||||
|
var keys []string
|
||||||
|
|
||||||
|
mm.Lock()
|
||||||
|
defer mm.Unlock()
|
||||||
|
|
||||||
|
for key := range mm.keyvals {
|
||||||
|
keys = append(keys, key)
|
||||||
|
delete(mm.keyvals, key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns all keys and values.
|
||||||
|
func (mm *MetadataManager) List() map[string]string {
|
||||||
|
data := make(map[string]string)
|
||||||
|
|
||||||
|
mm.RLock()
|
||||||
|
defer mm.RUnlock()
|
||||||
|
|
||||||
|
for key, value := range mm.keyvals {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value of a single key.
|
||||||
|
func (mm *MetadataManager) Get(key string) (string, bool) {
|
||||||
|
mm.RLock()
|
||||||
|
defer mm.RUnlock()
|
||||||
|
|
||||||
|
value, exists := mm.keyvals[key]
|
||||||
|
return value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the given key. A limit of -1 means ignore any limits.
|
||||||
|
func (mm *MetadataManager) Set(key, value string, limit int) error {
|
||||||
|
mm.Lock()
|
||||||
|
defer mm.Unlock()
|
||||||
|
|
||||||
|
_, currentlyExists := mm.keyvals[key]
|
||||||
|
if limit != -1 && !currentlyExists && limit < len(mm.keyvals)+1 {
|
||||||
|
return errTooManyKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
mm.keyvals[key] = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the given key.
|
||||||
|
func (mm *MetadataManager) Delete(key string) {
|
||||||
|
mm.Lock()
|
||||||
|
defer mm.Unlock()
|
||||||
|
|
||||||
|
delete(mm.keyvals, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataSubsManager manages metadata key subscriptions.
|
||||||
|
type MetadataSubsManager struct {
|
||||||
|
sync.RWMutex
|
||||||
|
// watchedKeys holds our list of watched (sub'd) keys.
|
||||||
|
watchedKeys map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetadataSubsManager returns a new MetadataSubsManager.
|
||||||
|
func NewMetadataSubsManager() *MetadataSubsManager {
|
||||||
|
var msm MetadataSubsManager
|
||||||
|
msm.watchedKeys = make(map[string]bool)
|
||||||
|
return &msm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub subscribes to the given keys.
|
||||||
|
func (msm *MetadataSubsManager) Sub(key ...string) {
|
||||||
|
msm.Lock()
|
||||||
|
defer msm.Unlock()
|
||||||
|
|
||||||
|
for _, k := range key {
|
||||||
|
msm.watchedKeys[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsub ubsubscribes from the given keys.
|
||||||
|
func (msm *MetadataSubsManager) Unsub(key ...string) {
|
||||||
|
msm.Lock()
|
||||||
|
defer msm.Unlock()
|
||||||
|
|
||||||
|
for _, k := range key {
|
||||||
|
delete(msm.watchedKeys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of the currently-subbed keys.
|
||||||
|
func (msm *MetadataSubsManager) List() []string {
|
||||||
|
var keys []string
|
||||||
|
|
||||||
|
msm.RLock()
|
||||||
|
defer msm.RUnlock()
|
||||||
|
|
||||||
|
for k := range msm.watchedKeys {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metadataValidChars = map[rune]bool{
|
||||||
|
'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
|
||||||
|
'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'o': true,
|
||||||
|
'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true,
|
||||||
|
'w': true, 'x': true, 'y': true, 'z': true, '0': true, '1': true, '2': true,
|
||||||
|
'3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true,
|
||||||
|
'_': true, '-': true, '.': true, ':': true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// metadataKeyValid returns true if the given key is valid.
|
||||||
|
func metadataKeyValid(key string) bool {
|
||||||
|
// key length
|
||||||
|
if len(key) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// invalid first character for a key
|
||||||
|
if key[0] == ':' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// name characters
|
||||||
|
for _, cha := range []rune(key) {
|
||||||
|
if metadataValidChars[rune(cha)] == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metadataSubcommands = map[string]func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool{
|
||||||
|
"clear": metadataClearHandler,
|
||||||
|
"get": metadataGetHandler,
|
||||||
|
"list": metadataListHandler,
|
||||||
|
"set": metadataSetHandler,
|
||||||
|
"sub": metadataSubHandler,
|
||||||
|
"subs": metadataSubsHandler,
|
||||||
|
"sync": metadataSyncHandler,
|
||||||
|
"unsub": metadataUnsubHandler,
|
||||||
|
}
|
||||||
|
)
|
@ -173,6 +173,21 @@ const (
|
|||||||
RPL_MONLIST = "732"
|
RPL_MONLIST = "732"
|
||||||
RPL_ENDOFMONLIST = "733"
|
RPL_ENDOFMONLIST = "733"
|
||||||
ERR_MONLISTFULL = "734"
|
ERR_MONLISTFULL = "734"
|
||||||
|
RPL_WHOISKEYVALUE = "760"
|
||||||
|
RPL_KEYVALUE = "761"
|
||||||
|
RPL_METADATAEND = "762"
|
||||||
|
ERR_METADATALIMIT = "764"
|
||||||
|
ERR_TARGETINVALID = "765"
|
||||||
|
ERR_NOMATCHINGKEY = "766"
|
||||||
|
ERR_KEYINVALID = "767"
|
||||||
|
ERR_KEYNOTSET = "768"
|
||||||
|
ERR_KEYNOPERMISSION = "769"
|
||||||
|
RPL_METADATASUBOK = "770"
|
||||||
|
RPL_METADATAUNSUBOK = "771"
|
||||||
|
RPL_METADATASUBS = "772"
|
||||||
|
ERR_METADATATOOMANYSUBS = "773"
|
||||||
|
ERR_METADATASYNCLATER = "774"
|
||||||
|
ERR_METADATARATELIMIT = "775"
|
||||||
RPL_LOGGEDIN = "900"
|
RPL_LOGGEDIN = "900"
|
||||||
RPL_LOGGEDOUT = "901"
|
RPL_LOGGEDOUT = "901"
|
||||||
ERR_NICKLOCKED = "902"
|
ERR_NICKLOCKED = "902"
|
||||||
|
@ -51,7 +51,7 @@ var (
|
|||||||
|
|
||||||
// SupportedCapabilities are the caps we advertise.
|
// SupportedCapabilities are the caps we advertise.
|
||||||
// MaxLine, SASL and STS are set during server startup.
|
// MaxLine, SASL and STS are set during server startup.
|
||||||
SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.UserhostInNames)
|
SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.Metadata, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.UserhostInNames)
|
||||||
|
|
||||||
// CapValues are the actual values we advertise to v3.2 clients.
|
// CapValues are the actual values we advertise to v3.2 clients.
|
||||||
// actual values are set during server startup.
|
// actual values are set during server startup.
|
||||||
@ -835,6 +835,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
|
|
||||||
server.languages = lm
|
server.languages = lm
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
CapValues.Set(caps.Metadata, "maxsub=10")
|
||||||
|
|
||||||
// SASL
|
// SASL
|
||||||
oldAccountConfig := server.AccountConfig()
|
oldAccountConfig := server.AccountConfig()
|
||||||
authPreviouslyEnabled := oldAccountConfig != nil && oldAccountConfig.AuthenticationEnabled
|
authPreviouslyEnabled := oldAccountConfig != nil && oldAccountConfig.AuthenticationEnabled
|
||||||
|
Loading…
Reference in New Issue
Block a user