mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 10:42:52 +01:00
Add MONITOR command
This commit is contained in:
parent
859d0cd607
commit
1bab81091f
@ -14,7 +14,7 @@ Improved compatibility, more features, etc.
|
||||
### Added
|
||||
* Added integrated help (with the `/HELP` command).
|
||||
* Added support for IRCv3.2 [capability negotiation](http://ircv3.net/specs/core/capability-negotiation-3.2.html) including CAP values.
|
||||
* Added support for IRCv3 capability [`account-notify`](http://ircv3.net/specs/extensions/account-notify-3.1.html), [`invite-notify`](http://ircv3.net/specs/extensions/invite-notify-3.2.html), [`sasl`](http://ircv3.net/specs/extensions/sasl-3.2.html), and draft capability [`message-tags`](http://ircv3.net/specs/core/message-tags-3.3.html) as `draft/message-tags`.
|
||||
* Added support for IRCv3 capability [`account-notify`](http://ircv3.net/specs/extensions/account-notify-3.1.html), [`invite-notify`](http://ircv3.net/specs/extensions/invite-notify-3.2.html), [`monitor`](http://ircv3.net/specs/core/monitor-3.2.html), [`sasl`](http://ircv3.net/specs/extensions/sasl-3.2.html), and draft capability [`message-tags`](http://ircv3.net/specs/core/message-tags-3.3.html) as `draft/message-tags`.
|
||||
|
||||
### Changed
|
||||
* Casemapping changed from custom unicode mapping to preliminary [rfc7700](https://github.com/ircv3/ircv3-specifications/pull/272) mapping.
|
||||
|
@ -44,6 +44,7 @@ type Client struct {
|
||||
hops uint
|
||||
hostname string
|
||||
idleTimer *time.Timer
|
||||
monitoring map[string]bool
|
||||
nick string
|
||||
nickCasefolded string
|
||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||
@ -59,6 +60,7 @@ type Client struct {
|
||||
username string
|
||||
}
|
||||
|
||||
// NewClient returns a client with all the appropriate info setup.
|
||||
func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||
now := time.Now()
|
||||
socket := NewSocket(conn)
|
||||
@ -71,6 +73,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||
channels: make(ChannelSet),
|
||||
ctime: now,
|
||||
flags: make(map[UserMode]bool),
|
||||
monitoring: make(map[string]bool),
|
||||
server: server,
|
||||
socket: &socket,
|
||||
account: &NoAccount,
|
||||
@ -214,6 +217,8 @@ func (client *Client) Register() {
|
||||
}
|
||||
client.registered = true
|
||||
client.Touch()
|
||||
|
||||
client.alertMonitors()
|
||||
}
|
||||
|
||||
func (client *Client) IdleTime() time.Duration {
|
||||
@ -306,7 +311,6 @@ func (client *Client) ChangeNickname(nickname string) {
|
||||
client.nick = nickname
|
||||
client.updateNickMask()
|
||||
client.server.clients.Add(client)
|
||||
client.Send(nil, origNickMask, "NICK", nickname)
|
||||
for friend := range client.Friends() {
|
||||
friend.Send(nil, origNickMask, "NICK", nickname)
|
||||
}
|
||||
@ -332,6 +336,14 @@ func (client *Client) destroy() {
|
||||
friends := client.Friends()
|
||||
friends.Remove(client)
|
||||
|
||||
// alert monitors
|
||||
for _, mClient := range client.server.monitoring[client.nickCasefolded] {
|
||||
mClient.Send(nil, client.server.name, RPL_MONOFFLINE, mClient.nick, client.nick)
|
||||
}
|
||||
|
||||
// remove my monitors
|
||||
client.clearMonitorList()
|
||||
|
||||
// clean up channels
|
||||
for channel := range client.channels {
|
||||
channel.Quit(client)
|
||||
|
@ -43,6 +43,15 @@ func NewClientLookupSet() *ClientLookupSet {
|
||||
}
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) Has(nick string) bool {
|
||||
casefoldedName, err := CasefoldName(nick)
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, exists := clients.byNick[casefoldedName]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (clients *ClientLookupSet) Get(nick string) *Client {
|
||||
casefoldedName, err := CasefoldName(nick)
|
||||
if err == nil {
|
||||
|
@ -100,6 +100,10 @@ var Commands = map[string]Command{
|
||||
handler: modeHandler,
|
||||
minParams: 1,
|
||||
},
|
||||
"MONITOR": {
|
||||
handler: monitorHandler,
|
||||
minParams: 1,
|
||||
},
|
||||
"MOTD": {
|
||||
handler: motdHandler,
|
||||
minParams: 0,
|
||||
|
@ -93,12 +93,13 @@ type Config struct {
|
||||
Operator map[string]*PassConfig
|
||||
|
||||
Limits struct {
|
||||
NickLen int `yaml:"nicklen"`
|
||||
ChannelLen int `yaml:"channellen"`
|
||||
AwayLen int `yaml:"awaylen"`
|
||||
KickLen int `yaml:"kicklen"`
|
||||
TopicLen int `yaml:"topiclen"`
|
||||
WhowasEntries uint `yaml:"whowas-entries"`
|
||||
NickLen uint `yaml:"nicklen"`
|
||||
ChannelLen uint `yaml:"channellen"`
|
||||
AwayLen uint `yaml:"awaylen"`
|
||||
KickLen uint `yaml:"kicklen"`
|
||||
TopicLen uint `yaml:"topiclen"`
|
||||
WhowasEntries uint `yaml:"whowas-entries"`
|
||||
MonitorEntries uint `yaml:"monitor-entries"`
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +164,7 @@ func LoadConfig(filename string) (config *Config, err error) {
|
||||
if len(config.Server.Listen) == 0 {
|
||||
return nil, errors.New("Server listening addresses missing")
|
||||
}
|
||||
if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.TopicLen < 1 || config.Limits.TopicLen < 1 {
|
||||
if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 {
|
||||
return nil, errors.New("Limits aren't setup properly, check them and make them sane")
|
||||
}
|
||||
return config, nil
|
||||
|
205
irc/monitor.go
Normal file
205
irc/monitor.go
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircmsg"
|
||||
)
|
||||
|
||||
// alertMonitors alerts everyone monitoring us that we're online.
|
||||
func (client *Client) alertMonitors() {
|
||||
// alert monitors
|
||||
for _, mClient := range client.server.monitoring[client.nickCasefolded] {
|
||||
// don't have to notify ourselves
|
||||
if &mClient != client {
|
||||
mClient.Send(nil, client.server.name, RPL_MONONLINE, mClient.nick, client.nickMaskString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clearMonitorList clears our MONITOR list.
|
||||
func (client *Client) clearMonitorList() {
|
||||
for name := range client.monitoring {
|
||||
// just removes current client from the list
|
||||
orig := client.server.monitoring[name]
|
||||
var index int
|
||||
for i, cli := range orig {
|
||||
if &cli == client {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
client.server.monitoring[name] = append(orig[:index], orig[index+1:]...)
|
||||
}
|
||||
|
||||
client.monitoring = make(map[string]bool)
|
||||
}
|
||||
|
||||
var (
|
||||
metadataSubcommands = map[string]func(server *Server, client *Client, msg ircmsg.IrcMessage) bool{
|
||||
"-": monitorRemoveHandler,
|
||||
"+": monitorAddHandler,
|
||||
"c": monitorClearHandler,
|
||||
"l": monitorListHandler,
|
||||
"s": monitorStatusHandler,
|
||||
}
|
||||
)
|
||||
|
||||
func monitorHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
handler, exists := metadataSubcommands[strings.ToLower(msg.Params[0])]
|
||||
|
||||
if !exists {
|
||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MONITOR", msg.Params[0], "Unknown subcommand")
|
||||
return false
|
||||
}
|
||||
|
||||
return handler(server, client, msg)
|
||||
}
|
||||
|
||||
func monitorRemoveHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if len(msg.Params) < 2 {
|
||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
|
||||
return false
|
||||
}
|
||||
|
||||
targets := strings.Split(msg.Params[1], ",")
|
||||
for len(targets) > 0 {
|
||||
// check name length
|
||||
if len(targets[0]) < 1 {
|
||||
targets = targets[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
// remove target
|
||||
casefoldedTarget, err := CasefoldName(targets[0])
|
||||
if err != nil {
|
||||
// skip silently I guess
|
||||
targets = targets[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if client.monitoring[casefoldedTarget] {
|
||||
// just removes current client from the list
|
||||
orig := server.monitoring[casefoldedTarget]
|
||||
var index int
|
||||
for i, cli := range orig {
|
||||
if &cli == client {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
server.monitoring[casefoldedTarget] = append(orig[:index], orig[index+1:]...)
|
||||
|
||||
delete(client.monitoring, casefoldedTarget)
|
||||
}
|
||||
|
||||
// remove first element of targets list
|
||||
targets = targets[1:]
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
if len(msg.Params) < 2 {
|
||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
|
||||
return false
|
||||
}
|
||||
|
||||
var online []string
|
||||
var offline []string
|
||||
|
||||
targets := strings.Split(msg.Params[1], ",")
|
||||
for len(targets) > 0 {
|
||||
// check name length
|
||||
if len(targets[0]) < 1 {
|
||||
targets = targets[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
// check the monitor list length
|
||||
if len(client.monitoring) >= server.limits.MonitorEntries {
|
||||
client.Send(nil, server.name, ERR_MONLISTFULL, client.nick, strconv.Itoa(server.limits.MonitorEntries), strings.Join(targets, ","))
|
||||
break
|
||||
}
|
||||
|
||||
// add target
|
||||
casefoldedTarget, err := CasefoldName(targets[0])
|
||||
if err != nil {
|
||||
// skip silently I guess
|
||||
targets = targets[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if !client.monitoring[casefoldedTarget] {
|
||||
client.monitoring[casefoldedTarget] = true
|
||||
|
||||
orig := server.monitoring[casefoldedTarget]
|
||||
server.monitoring[casefoldedTarget] = append(orig, *client)
|
||||
}
|
||||
|
||||
// add to online / offline lists
|
||||
target := server.clients.Get(casefoldedTarget)
|
||||
if target == nil {
|
||||
offline = append(offline, targets[0])
|
||||
} else {
|
||||
online = append(online, target.nickMaskString)
|
||||
}
|
||||
|
||||
// remove first element of targets list
|
||||
targets = targets[1:]
|
||||
}
|
||||
|
||||
if len(online) > 0 {
|
||||
client.Send(nil, server.name, RPL_MONONLINE, client.nick, strings.Join(online, ","))
|
||||
}
|
||||
if len(offline) > 0 {
|
||||
client.Send(nil, server.name, RPL_MONOFFLINE, client.nick, strings.Join(offline, ","))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func monitorClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
client.clearMonitorList()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func monitorListHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
var monitorList []string
|
||||
for name := range client.monitoring {
|
||||
monitorList = append(monitorList, name)
|
||||
}
|
||||
|
||||
client.Send(nil, server.name, RPL_MONLIST, client.nick, strings.Join(monitorList, ","))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func monitorStatusHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
var online []string
|
||||
var offline []string
|
||||
|
||||
for name := range client.monitoring {
|
||||
target := server.clients.Get(name)
|
||||
if target == nil {
|
||||
offline = append(offline, name)
|
||||
} else {
|
||||
online = append(online, target.nickMaskString)
|
||||
}
|
||||
}
|
||||
|
||||
if len(online) > 0 {
|
||||
client.Send(nil, server.name, RPL_MONONLINE, client.nick, strings.Join(online, ","))
|
||||
}
|
||||
if len(offline) > 0 {
|
||||
client.Send(nil, server.name, RPL_MONOFFLINE, client.nick, strings.Join(offline, ","))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -43,6 +43,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||
|
||||
if client.registered {
|
||||
client.ChangeNickname(nicknameRaw)
|
||||
client.alertMonitors()
|
||||
} else {
|
||||
client.SetNickname(nicknameRaw)
|
||||
}
|
||||
|
@ -153,6 +153,11 @@ const (
|
||||
RPL_HELPSTART = "704"
|
||||
RPL_HELPTXT = "705"
|
||||
RPL_ENDOFHELP = "706"
|
||||
RPL_MONONLINE = "730"
|
||||
RPL_MONOFFLINE = "731"
|
||||
RPL_MONLIST = "732"
|
||||
RPL_ENDOFMONLIST = "733"
|
||||
ERR_MONLISTFULL = "734"
|
||||
RPL_LOGGEDIN = "900"
|
||||
RPL_LOGGEDOUT = "901"
|
||||
ERR_NICKLOCKED = "902"
|
||||
|
@ -26,11 +26,12 @@ import (
|
||||
|
||||
// Limits holds the maximum limits for various things such as topic lengths
|
||||
type Limits struct {
|
||||
AwayLen int
|
||||
ChannelLen int
|
||||
KickLen int
|
||||
NickLen int
|
||||
TopicLen int
|
||||
AwayLen int
|
||||
ChannelLen int
|
||||
KickLen int
|
||||
MonitorEntries int
|
||||
NickLen int
|
||||
TopicLen int
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
@ -42,6 +43,7 @@ type Server struct {
|
||||
store buntdb.DB
|
||||
idle chan *Client
|
||||
limits Limits
|
||||
monitoring map[string][]Client
|
||||
motdLines []string
|
||||
name string
|
||||
nameCasefolded string
|
||||
@ -85,12 +87,14 @@ func NewServer(config *Config) *Server {
|
||||
ctime: time.Now(),
|
||||
idle: make(chan *Client),
|
||||
limits: Limits{
|
||||
AwayLen: config.Limits.AwayLen,
|
||||
ChannelLen: config.Limits.ChannelLen,
|
||||
KickLen: config.Limits.KickLen,
|
||||
NickLen: config.Limits.NickLen,
|
||||
TopicLen: config.Limits.TopicLen,
|
||||
AwayLen: int(config.Limits.AwayLen),
|
||||
ChannelLen: int(config.Limits.ChannelLen),
|
||||
KickLen: int(config.Limits.KickLen),
|
||||
MonitorEntries: int(config.Limits.MonitorEntries),
|
||||
NickLen: int(config.Limits.NickLen),
|
||||
TopicLen: int(config.Limits.TopicLen),
|
||||
},
|
||||
monitoring: make(map[string][]Client),
|
||||
name: config.Server.Name,
|
||||
nameCasefolded: casefoldedName,
|
||||
newConns: make(chan clientConn),
|
||||
@ -172,15 +176,16 @@ func NewServer(config *Config) *Server {
|
||||
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
||||
server.isupport.Add("CASEMAPPING", "rfc7700")
|
||||
server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret}.String()}, ","))
|
||||
server.isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
|
||||
server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
||||
server.isupport.Add("CHANTYPES", "#")
|
||||
server.isupport.Add("EXCEPTS", "")
|
||||
server.isupport.Add("INVEX", "")
|
||||
server.isupport.Add("KICKLEN", strconv.Itoa(server.limits.KickLen))
|
||||
// server.isupport.Add("MAXLIST", "") //TODO(dan): Support max list length?
|
||||
// server.isupport.Add("MODES", "") //TODO(dan): Support max modes?
|
||||
server.isupport.Add("MONITOR", strconv.Itoa(server.limits.MonitorEntries))
|
||||
server.isupport.Add("NETWORK", config.Network.Name)
|
||||
server.isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
|
||||
server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
|
||||
server.isupport.Add("PREFIX", "(qaohv)~&@%+")
|
||||
// server.isupport.Add("STATUSMSG", "@+") //TODO(dan): Support STATUSMSG
|
||||
// server.isupport.Add("TARGMAX", "") //TODO(dan): Support this
|
||||
|
@ -86,5 +86,8 @@ limits:
|
||||
# topiclen is the maximum length of a channel topic
|
||||
topiclen: 390
|
||||
|
||||
# maximum number of monitor entries a client can have
|
||||
monitor-entries: 100
|
||||
|
||||
# whowas entries to store
|
||||
whowas-entries: 100
|
||||
|
Loading…
Reference in New Issue
Block a user