2017-03-27 14:15:02 +02:00
|
|
|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
2016-10-16 12:14:56 +02:00
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
2017-10-04 06:57:03 +02:00
|
|
|
"errors"
|
2016-10-16 12:14:56 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2017-10-04 06:57:03 +02:00
|
|
|
"sync"
|
2016-10-16 12:14:56 +02:00
|
|
|
|
2017-06-15 18:14:19 +02:00
|
|
|
"github.com/goshuirc/irc-go/ircmsg"
|
2017-10-05 15:47:43 +02:00
|
|
|
"github.com/oragono/oragono/irc/utils"
|
2016-10-16 12:14:56 +02:00
|
|
|
)
|
|
|
|
|
2017-10-05 15:29:34 +02:00
|
|
|
// MonitorManager keeps track of who's monitoring which nicks.
|
2017-10-04 06:57:03 +02:00
|
|
|
type MonitorManager struct {
|
|
|
|
sync.RWMutex
|
|
|
|
// client -> nicks it's watching
|
|
|
|
watching map[*Client]map[string]bool
|
|
|
|
// nick -> clients watching it
|
|
|
|
watchedby map[string]map[*Client]bool
|
|
|
|
// (all nicks must be normalized externally by casefolding)
|
|
|
|
}
|
|
|
|
|
2017-10-05 15:29:34 +02:00
|
|
|
// NewMonitorManager returns a new MonitorManager.
|
2017-10-04 06:57:03 +02:00
|
|
|
func NewMonitorManager() *MonitorManager {
|
|
|
|
mm := MonitorManager{
|
|
|
|
watching: make(map[*Client]map[string]bool),
|
|
|
|
watchedby: make(map[string]map[*Client]bool),
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
2017-10-04 06:57:03 +02:00
|
|
|
return &mm
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
2017-10-05 15:29:34 +02:00
|
|
|
// ErrMonitorLimitExceeded is used when the monitor list exceeds our limit.
|
|
|
|
var ErrMonitorLimitExceeded = errors.New("Monitor limit exceeded")
|
2017-10-04 06:57:03 +02:00
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
// AlertAbout alerts everyone monitoring `client`'s nick that `client` is now {on,off}line.
|
|
|
|
func (manager *MonitorManager) AlertAbout(client *Client, online bool) {
|
2017-11-03 07:36:55 +01:00
|
|
|
cfnick := client.NickCasefolded()
|
|
|
|
nick := client.Nick()
|
2017-10-04 06:57:03 +02:00
|
|
|
var watchers []*Client
|
|
|
|
// safely copy the list of clients watching our nick
|
|
|
|
manager.RLock()
|
|
|
|
for client := range manager.watchedby[cfnick] {
|
|
|
|
watchers = append(watchers, client)
|
|
|
|
}
|
|
|
|
manager.RUnlock()
|
|
|
|
|
|
|
|
command := RPL_MONOFFLINE
|
|
|
|
if online {
|
|
|
|
command = RPL_MONONLINE
|
|
|
|
}
|
|
|
|
|
2017-10-30 10:21:47 +01:00
|
|
|
for _, mClient := range watchers {
|
2017-11-03 07:36:55 +01:00
|
|
|
mClient.Send(nil, client.server.name, command, mClient.Nick(), nick)
|
2017-10-30 10:21:47 +01:00
|
|
|
}
|
2017-10-04 06:57:03 +02:00
|
|
|
}
|
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
// Add registers `client` to receive notifications about `nick`.
|
|
|
|
func (manager *MonitorManager) Add(client *Client, nick string, limit int) error {
|
2017-10-04 06:57:03 +02:00
|
|
|
manager.Lock()
|
|
|
|
defer manager.Unlock()
|
|
|
|
|
|
|
|
if manager.watching[client] == nil {
|
|
|
|
manager.watching[client] = make(map[string]bool)
|
|
|
|
}
|
|
|
|
if manager.watchedby[nick] == nil {
|
|
|
|
manager.watchedby[nick] = make(map[*Client]bool)
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
2017-10-04 06:57:03 +02:00
|
|
|
if len(manager.watching[client]) >= limit {
|
2017-10-05 15:29:34 +02:00
|
|
|
return ErrMonitorLimitExceeded
|
2017-10-04 06:57:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
manager.watching[client][nick] = true
|
|
|
|
manager.watchedby[nick][client] = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
// Remove unregisters `client` from receiving notifications about `nick`.
|
|
|
|
func (manager *MonitorManager) Remove(client *Client, nick string) error {
|
2017-10-04 06:57:03 +02:00
|
|
|
manager.Lock()
|
|
|
|
defer manager.Unlock()
|
|
|
|
// deleting from nil maps is fine
|
|
|
|
delete(manager.watching[client], nick)
|
|
|
|
delete(manager.watchedby[nick], client)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
// RemoveAll unregisters `client` from receiving notifications about *all* nicks.
|
|
|
|
func (manager *MonitorManager) RemoveAll(client *Client) {
|
|
|
|
manager.Lock()
|
|
|
|
defer manager.Unlock()
|
|
|
|
|
2017-10-05 15:29:34 +02:00
|
|
|
for nick := range manager.watching[client] {
|
2017-10-04 08:59:59 +02:00
|
|
|
delete(manager.watchedby[nick], client)
|
|
|
|
}
|
|
|
|
delete(manager.watching, client)
|
|
|
|
}
|
|
|
|
|
|
|
|
// List lists all nicks that `client` is registered to receive notifications about.
|
|
|
|
func (manager *MonitorManager) List(client *Client) (nicks []string) {
|
2017-10-04 06:57:03 +02:00
|
|
|
manager.RLock()
|
|
|
|
defer manager.RUnlock()
|
|
|
|
for nick := range manager.watching[client] {
|
|
|
|
nicks = append(nicks, nick)
|
|
|
|
}
|
|
|
|
return nicks
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", msg.Params[0], "Unknown subcommand")
|
2016-10-16 12:14:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return handler(server, client, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func monitorRemoveHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|
|
|
if len(msg.Params) < 2 {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, "Not enough parameters")
|
2016-10-16 12:14:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
targets := strings.Split(msg.Params[1], ",")
|
2017-10-04 06:57:03 +02:00
|
|
|
for _, target := range targets {
|
|
|
|
cfnick, err := CasefoldName(target)
|
2016-10-16 12:14:56 +02:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2017-10-04 08:59:59 +02:00
|
|
|
server.monitorManager.Remove(client, cfnick)
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|
|
|
if len(msg.Params) < 2 {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, "Not enough parameters")
|
2016-10-16 12:14:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var online []string
|
|
|
|
var offline []string
|
|
|
|
|
2017-11-03 07:36:55 +01:00
|
|
|
limit := server.Limits().MonitorEntries
|
2017-10-04 06:57:03 +02:00
|
|
|
|
2016-10-16 12:14:56 +02:00
|
|
|
targets := strings.Split(msg.Params[1], ",")
|
2017-10-04 06:57:03 +02:00
|
|
|
for _, target := range targets {
|
2016-10-16 12:14:56 +02:00
|
|
|
// check name length
|
2017-10-04 06:57:03 +02:00
|
|
|
if len(target) < 1 || len(targets) > server.limits.NickLen {
|
2016-10-16 12:14:56 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// add target
|
2017-10-04 10:54:02 +02:00
|
|
|
casefoldedTarget, err := CasefoldName(target)
|
2016-10-16 12:14:56 +02:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
err = server.monitorManager.Add(client, casefoldedTarget, limit)
|
2017-10-05 15:29:34 +02:00
|
|
|
if err == ErrMonitorLimitExceeded {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, ERR_MONLISTFULL, client.Nick(), strconv.Itoa(server.limits.MonitorEntries), strings.Join(targets, ","))
|
2017-10-04 06:57:03 +02:00
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
continue
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// add to online / offline lists
|
2017-10-04 10:54:02 +02:00
|
|
|
if targetClient := server.clients.Get(casefoldedTarget); targetClient == nil {
|
|
|
|
offline = append(offline, target)
|
2016-10-16 12:14:56 +02:00
|
|
|
} else {
|
2017-11-03 07:36:55 +01:00
|
|
|
online = append(online, targetClient.Nick())
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(online) > 0 {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, RPL_MONONLINE, client.Nick(), strings.Join(online, ","))
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
if len(offline) > 0 {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, RPL_MONOFFLINE, client.Nick(), strings.Join(offline, ","))
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func monitorClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
2017-10-04 08:59:59 +02:00
|
|
|
server.monitorManager.RemoveAll(client)
|
2016-10-16 12:14:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func monitorListHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
2017-10-04 08:59:59 +02:00
|
|
|
monitorList := server.monitorManager.List(client)
|
2016-10-16 12:14:56 +02:00
|
|
|
|
2017-10-04 09:14:30 +02:00
|
|
|
var nickList []string
|
2017-10-05 15:29:34 +02:00
|
|
|
for _, cfnick := range monitorList {
|
2017-10-04 09:14:30 +02:00
|
|
|
replynick := cfnick
|
|
|
|
// report the uncasefolded nick if it's available, i.e., the client is online
|
|
|
|
if mclient := server.clients.Get(cfnick); mclient != nil {
|
2017-11-03 07:36:55 +01:00
|
|
|
replynick = mclient.Nick()
|
2017-10-04 09:14:30 +02:00
|
|
|
}
|
|
|
|
nickList = append(nickList, replynick)
|
|
|
|
}
|
|
|
|
|
2017-10-05 15:47:43 +02:00
|
|
|
for _, line := range utils.ArgsToStrings(maxLastArgLength, nickList, ",") {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, RPL_MONLIST, client.Nick(), line)
|
2016-10-23 12:24:02 +02:00
|
|
|
}
|
2016-10-16 12:14:56 +02:00
|
|
|
|
2017-10-04 06:57:03 +02:00
|
|
|
client.Send(nil, server.name, RPL_ENDOFMONLIST, "End of MONITOR list")
|
|
|
|
|
2016-10-16 12:14:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func monitorStatusHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|
|
|
var online []string
|
|
|
|
var offline []string
|
|
|
|
|
2017-10-04 08:59:59 +02:00
|
|
|
monitorList := server.monitorManager.List(client)
|
2017-09-25 03:29:27 +02:00
|
|
|
|
2017-10-04 06:57:03 +02:00
|
|
|
for _, name := range monitorList {
|
2016-10-16 12:14:56 +02:00
|
|
|
target := server.clients.Get(name)
|
|
|
|
if target == nil {
|
|
|
|
offline = append(offline, name)
|
|
|
|
} else {
|
2017-11-03 07:36:55 +01:00
|
|
|
online = append(online, target.Nick())
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(online) > 0 {
|
2017-10-05 15:47:43 +02:00
|
|
|
for _, line := range utils.ArgsToStrings(maxLastArgLength, online, ",") {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, RPL_MONONLINE, client.Nick(), line)
|
2016-10-23 12:24:02 +02:00
|
|
|
}
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
if len(offline) > 0 {
|
2017-10-05 15:47:43 +02:00
|
|
|
for _, line := range utils.ArgsToStrings(maxLastArgLength, offline, ",") {
|
2017-11-03 07:36:55 +01:00
|
|
|
client.Send(nil, server.name, RPL_MONOFFLINE, client.Nick(), line)
|
2016-10-23 12:24:02 +02:00
|
|
|
}
|
2016-10-16 12:14:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|