Bladeren bron

Add MONITOR command

tags/v0.2.0
Daniel Oaks 7 jaren geleden
bovenliggende
commit
1bab81091f
10 gewijzigde bestanden met toevoegingen van 266 en 21 verwijderingen
  1. 1
    1
      CHANGELOG.md
  2. 13
    1
      irc/client.go
  3. 9
    0
      irc/client_lookup_set.go
  4. 4
    0
      irc/commands.go
  5. 8
    7
      irc/config.go
  6. 205
    0
      irc/monitor.go
  7. 1
    0
      irc/nickname.go
  8. 5
    0
      irc/numerics.go
  9. 17
    12
      irc/server.go
  10. 3
    0
      oragono.yaml

+ 1
- 1
CHANGELOG.md Bestand weergeven

@@ -14,7 +14,7 @@ Improved compatibility, more features, etc.
14 14
 ### Added
15 15
 * Added integrated help (with the `/HELP` command).
16 16
 * Added support for IRCv3.2 [capability negotiation](http://ircv3.net/specs/core/capability-negotiation-3.2.html) including CAP values.
17
-* 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`.
17
+* 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`.
18 18
 
19 19
 ### Changed
20 20
 * Casemapping changed from custom unicode mapping to preliminary [rfc7700](https://github.com/ircv3/ircv3-specifications/pull/272) mapping.

+ 13
- 1
irc/client.go Bestand weergeven

@@ -44,6 +44,7 @@ type Client struct {
44 44
 	hops               uint
45 45
 	hostname           string
46 46
 	idleTimer          *time.Timer
47
+	monitoring         map[string]bool
47 48
 	nick               string
48 49
 	nickCasefolded     string
49 50
 	nickMaskString     string // cache for nickmask string since it's used with lots of replies
@@ -59,6 +60,7 @@ type Client struct {
59 60
 	username           string
60 61
 }
61 62
 
63
+// NewClient returns a client with all the appropriate info setup.
62 64
 func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
63 65
 	now := time.Now()
64 66
 	socket := NewSocket(conn)
@@ -71,6 +73,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
71 73
 		channels:       make(ChannelSet),
72 74
 		ctime:          now,
73 75
 		flags:          make(map[UserMode]bool),
76
+		monitoring:     make(map[string]bool),
74 77
 		server:         server,
75 78
 		socket:         &socket,
76 79
 		account:        &NoAccount,
@@ -214,6 +217,8 @@ func (client *Client) Register() {
214 217
 	}
215 218
 	client.registered = true
216 219
 	client.Touch()
220
+
221
+	client.alertMonitors()
217 222
 }
218 223
 
219 224
 func (client *Client) IdleTime() time.Duration {
@@ -306,7 +311,6 @@ func (client *Client) ChangeNickname(nickname string) {
306 311
 	client.nick = nickname
307 312
 	client.updateNickMask()
308 313
 	client.server.clients.Add(client)
309
-	client.Send(nil, origNickMask, "NICK", nickname)
310 314
 	for friend := range client.Friends() {
311 315
 		friend.Send(nil, origNickMask, "NICK", nickname)
312 316
 	}
@@ -332,6 +336,14 @@ func (client *Client) destroy() {
332 336
 	friends := client.Friends()
333 337
 	friends.Remove(client)
334 338
 
339
+	// alert monitors
340
+	for _, mClient := range client.server.monitoring[client.nickCasefolded] {
341
+		mClient.Send(nil, client.server.name, RPL_MONOFFLINE, mClient.nick, client.nick)
342
+	}
343
+
344
+	// remove my monitors
345
+	client.clearMonitorList()
346
+
335 347
 	// clean up channels
336 348
 	for channel := range client.channels {
337 349
 		channel.Quit(client)

+ 9
- 0
irc/client_lookup_set.go Bestand weergeven

@@ -43,6 +43,15 @@ func NewClientLookupSet() *ClientLookupSet {
43 43
 	}
44 44
 }
45 45
 
46
+func (clients *ClientLookupSet) Has(nick string) bool {
47
+	casefoldedName, err := CasefoldName(nick)
48
+	if err == nil {
49
+		return false
50
+	}
51
+	_, exists := clients.byNick[casefoldedName]
52
+	return exists
53
+}
54
+
46 55
 func (clients *ClientLookupSet) Get(nick string) *Client {
47 56
 	casefoldedName, err := CasefoldName(nick)
48 57
 	if err == nil {

+ 4
- 0
irc/commands.go Bestand weergeven

@@ -100,6 +100,10 @@ var Commands = map[string]Command{
100 100
 		handler:   modeHandler,
101 101
 		minParams: 1,
102 102
 	},
103
+	"MONITOR": {
104
+		handler:   monitorHandler,
105
+		minParams: 1,
106
+	},
103 107
 	"MOTD": {
104 108
 		handler:   motdHandler,
105 109
 		minParams: 0,

+ 8
- 7
irc/config.go Bestand weergeven

@@ -93,12 +93,13 @@ type Config struct {
93 93
 	Operator map[string]*PassConfig
94 94
 
95 95
 	Limits struct {
96
-		NickLen       int  `yaml:"nicklen"`
97
-		ChannelLen    int  `yaml:"channellen"`
98
-		AwayLen       int  `yaml:"awaylen"`
99
-		KickLen       int  `yaml:"kicklen"`
100
-		TopicLen      int  `yaml:"topiclen"`
101
-		WhowasEntries uint `yaml:"whowas-entries"`
96
+		NickLen        uint `yaml:"nicklen"`
97
+		ChannelLen     uint `yaml:"channellen"`
98
+		AwayLen        uint `yaml:"awaylen"`
99
+		KickLen        uint `yaml:"kicklen"`
100
+		TopicLen       uint `yaml:"topiclen"`
101
+		WhowasEntries  uint `yaml:"whowas-entries"`
102
+		MonitorEntries uint `yaml:"monitor-entries"`
102 103
 	}
103 104
 }
104 105
 
@@ -163,7 +164,7 @@ func LoadConfig(filename string) (config *Config, err error) {
163 164
 	if len(config.Server.Listen) == 0 {
164 165
 		return nil, errors.New("Server listening addresses missing")
165 166
 	}
166
-	if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.TopicLen < 1 || config.Limits.TopicLen < 1 {
167
+	if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 {
167 168
 		return nil, errors.New("Limits aren't setup properly, check them and make them sane")
168 169
 	}
169 170
 	return config, nil

+ 205
- 0
irc/monitor.go Bestand weergeven

@@ -0,0 +1,205 @@
1
+// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"strconv"
8
+	"strings"
9
+
10
+	"github.com/DanielOaks/girc-go/ircmsg"
11
+)
12
+
13
+// alertMonitors alerts everyone monitoring us that we're online.
14
+func (client *Client) alertMonitors() {
15
+	// alert monitors
16
+	for _, mClient := range client.server.monitoring[client.nickCasefolded] {
17
+		// don't have to notify ourselves
18
+		if &mClient != client {
19
+			mClient.Send(nil, client.server.name, RPL_MONONLINE, mClient.nick, client.nickMaskString)
20
+		}
21
+	}
22
+}
23
+
24
+// clearMonitorList clears our MONITOR list.
25
+func (client *Client) clearMonitorList() {
26
+	for name := range client.monitoring {
27
+		// just removes current client from the list
28
+		orig := client.server.monitoring[name]
29
+		var index int
30
+		for i, cli := range orig {
31
+			if &cli == client {
32
+				index = i
33
+				break
34
+			}
35
+		}
36
+		client.server.monitoring[name] = append(orig[:index], orig[index+1:]...)
37
+	}
38
+
39
+	client.monitoring = make(map[string]bool)
40
+}
41
+
42
+var (
43
+	metadataSubcommands = map[string]func(server *Server, client *Client, msg ircmsg.IrcMessage) bool{
44
+		"-": monitorRemoveHandler,
45
+		"+": monitorAddHandler,
46
+		"c": monitorClearHandler,
47
+		"l": monitorListHandler,
48
+		"s": monitorStatusHandler,
49
+	}
50
+)
51
+
52
+func monitorHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
53
+	handler, exists := metadataSubcommands[strings.ToLower(msg.Params[0])]
54
+
55
+	if !exists {
56
+		client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MONITOR", msg.Params[0], "Unknown subcommand")
57
+		return false
58
+	}
59
+
60
+	return handler(server, client, msg)
61
+}
62
+
63
+func monitorRemoveHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
64
+	if len(msg.Params) < 2 {
65
+		client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
66
+		return false
67
+	}
68
+
69
+	targets := strings.Split(msg.Params[1], ",")
70
+	for len(targets) > 0 {
71
+		// check name length
72
+		if len(targets[0]) < 1 {
73
+			targets = targets[1:]
74
+			continue
75
+		}
76
+
77
+		// remove target
78
+		casefoldedTarget, err := CasefoldName(targets[0])
79
+		if err != nil {
80
+			// skip silently I guess
81
+			targets = targets[1:]
82
+			continue
83
+		}
84
+
85
+		if client.monitoring[casefoldedTarget] {
86
+			// just removes current client from the list
87
+			orig := server.monitoring[casefoldedTarget]
88
+			var index int
89
+			for i, cli := range orig {
90
+				if &cli == client {
91
+					index = i
92
+					break
93
+				}
94
+			}
95
+			server.monitoring[casefoldedTarget] = append(orig[:index], orig[index+1:]...)
96
+
97
+			delete(client.monitoring, casefoldedTarget)
98
+		}
99
+
100
+		// remove first element of targets list
101
+		targets = targets[1:]
102
+	}
103
+
104
+	return false
105
+}
106
+
107
+func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
108
+	if len(msg.Params) < 2 {
109
+		client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
110
+		return false
111
+	}
112
+
113
+	var online []string
114
+	var offline []string
115
+
116
+	targets := strings.Split(msg.Params[1], ",")
117
+	for len(targets) > 0 {
118
+		// check name length
119
+		if len(targets[0]) < 1 {
120
+			targets = targets[1:]
121
+			continue
122
+		}
123
+
124
+		// check the monitor list length
125
+		if len(client.monitoring) >= server.limits.MonitorEntries {
126
+			client.Send(nil, server.name, ERR_MONLISTFULL, client.nick, strconv.Itoa(server.limits.MonitorEntries), strings.Join(targets, ","))
127
+			break
128
+		}
129
+
130
+		// add target
131
+		casefoldedTarget, err := CasefoldName(targets[0])
132
+		if err != nil {
133
+			// skip silently I guess
134
+			targets = targets[1:]
135
+			continue
136
+		}
137
+
138
+		if !client.monitoring[casefoldedTarget] {
139
+			client.monitoring[casefoldedTarget] = true
140
+
141
+			orig := server.monitoring[casefoldedTarget]
142
+			server.monitoring[casefoldedTarget] = append(orig, *client)
143
+		}
144
+
145
+		// add to online / offline lists
146
+		target := server.clients.Get(casefoldedTarget)
147
+		if target == nil {
148
+			offline = append(offline, targets[0])
149
+		} else {
150
+			online = append(online, target.nickMaskString)
151
+		}
152
+
153
+		// remove first element of targets list
154
+		targets = targets[1:]
155
+	}
156
+
157
+	if len(online) > 0 {
158
+		client.Send(nil, server.name, RPL_MONONLINE, client.nick, strings.Join(online, ","))
159
+	}
160
+	if len(offline) > 0 {
161
+		client.Send(nil, server.name, RPL_MONOFFLINE, client.nick, strings.Join(offline, ","))
162
+	}
163
+
164
+	return false
165
+}
166
+
167
+func monitorClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
168
+	client.clearMonitorList()
169
+
170
+	return false
171
+}
172
+
173
+func monitorListHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
174
+	var monitorList []string
175
+	for name := range client.monitoring {
176
+		monitorList = append(monitorList, name)
177
+	}
178
+
179
+	client.Send(nil, server.name, RPL_MONLIST, client.nick, strings.Join(monitorList, ","))
180
+
181
+	return false
182
+}
183
+
184
+func monitorStatusHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
185
+	var online []string
186
+	var offline []string
187
+
188
+	for name := range client.monitoring {
189
+		target := server.clients.Get(name)
190
+		if target == nil {
191
+			offline = append(offline, name)
192
+		} else {
193
+			online = append(online, target.nickMaskString)
194
+		}
195
+	}
196
+
197
+	if len(online) > 0 {
198
+		client.Send(nil, server.name, RPL_MONONLINE, client.nick, strings.Join(online, ","))
199
+	}
200
+	if len(offline) > 0 {
201
+		client.Send(nil, server.name, RPL_MONOFFLINE, client.nick, strings.Join(offline, ","))
202
+	}
203
+
204
+	return false
205
+}

+ 1
- 0
irc/nickname.go Bestand weergeven

@@ -43,6 +43,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
43 43
 
44 44
 	if client.registered {
45 45
 		client.ChangeNickname(nicknameRaw)
46
+		client.alertMonitors()
46 47
 	} else {
47 48
 		client.SetNickname(nicknameRaw)
48 49
 	}

+ 5
- 0
irc/numerics.go Bestand weergeven

@@ -153,6 +153,11 @@ const (
153 153
 	RPL_HELPSTART                   = "704"
154 154
 	RPL_HELPTXT                     = "705"
155 155
 	RPL_ENDOFHELP                   = "706"
156
+	RPL_MONONLINE                   = "730"
157
+	RPL_MONOFFLINE                  = "731"
158
+	RPL_MONLIST                     = "732"
159
+	RPL_ENDOFMONLIST                = "733"
160
+	ERR_MONLISTFULL                 = "734"
156 161
 	RPL_LOGGEDIN                    = "900"
157 162
 	RPL_LOGGEDOUT                   = "901"
158 163
 	ERR_NICKLOCKED                  = "902"

+ 17
- 12
irc/server.go Bestand weergeven

@@ -26,11 +26,12 @@ import (
26 26
 
27 27
 // Limits holds the maximum limits for various things such as topic lengths
28 28
 type Limits struct {
29
-	AwayLen    int
30
-	ChannelLen int
31
-	KickLen    int
32
-	NickLen    int
33
-	TopicLen   int
29
+	AwayLen        int
30
+	ChannelLen     int
31
+	KickLen        int
32
+	MonitorEntries int
33
+	NickLen        int
34
+	TopicLen       int
34 35
 }
35 36
 
36 37
 type Server struct {
@@ -42,6 +43,7 @@ type Server struct {
42 43
 	store               buntdb.DB
43 44
 	idle                chan *Client
44 45
 	limits              Limits
46
+	monitoring          map[string][]Client
45 47
 	motdLines           []string
46 48
 	name                string
47 49
 	nameCasefolded      string
@@ -85,12 +87,14 @@ func NewServer(config *Config) *Server {
85 87
 		ctime:    time.Now(),
86 88
 		idle:     make(chan *Client),
87 89
 		limits: Limits{
88
-			AwayLen:    config.Limits.AwayLen,
89
-			ChannelLen: config.Limits.ChannelLen,
90
-			KickLen:    config.Limits.KickLen,
91
-			NickLen:    config.Limits.NickLen,
92
-			TopicLen:   config.Limits.TopicLen,
90
+			AwayLen:        int(config.Limits.AwayLen),
91
+			ChannelLen:     int(config.Limits.ChannelLen),
92
+			KickLen:        int(config.Limits.KickLen),
93
+			MonitorEntries: int(config.Limits.MonitorEntries),
94
+			NickLen:        int(config.Limits.NickLen),
95
+			TopicLen:       int(config.Limits.TopicLen),
93 96
 		},
97
+		monitoring:     make(map[string][]Client),
94 98
 		name:           config.Server.Name,
95 99
 		nameCasefolded: casefoldedName,
96 100
 		newConns:       make(chan clientConn),
@@ -172,15 +176,16 @@ func NewServer(config *Config) *Server {
172 176
 	server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
173 177
 	server.isupport.Add("CASEMAPPING", "rfc7700")
174 178
 	server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret}.String()}, ","))
175
-	server.isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
179
+	server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
176 180
 	server.isupport.Add("CHANTYPES", "#")
177 181
 	server.isupport.Add("EXCEPTS", "")
178 182
 	server.isupport.Add("INVEX", "")
179 183
 	server.isupport.Add("KICKLEN", strconv.Itoa(server.limits.KickLen))
180 184
 	// server.isupport.Add("MAXLIST", "") //TODO(dan): Support max list length?
181 185
 	// server.isupport.Add("MODES", "")   //TODO(dan): Support max modes?
186
+	server.isupport.Add("MONITOR", strconv.Itoa(server.limits.MonitorEntries))
182 187
 	server.isupport.Add("NETWORK", config.Network.Name)
183
-	server.isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
188
+	server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
184 189
 	server.isupport.Add("PREFIX", "(qaohv)~&@%+")
185 190
 	// server.isupport.Add("STATUSMSG", "@+") //TODO(dan): Support STATUSMSG
186 191
 	// server.isupport.Add("TARGMAX", "")  //TODO(dan): Support this

+ 3
- 0
oragono.yaml Bestand weergeven

@@ -86,5 +86,8 @@ limits:
86 86
     # topiclen is the maximum length of a channel topic
87 87
     topiclen: 390
88 88
 
89
+    # maximum number of monitor entries a client can have
90
+    monitor-entries: 100
91
+
89 92
     # whowas entries to store
90 93
     whowas-entries: 100

Laden…
Annuleren
Opslaan