Browse Source

Initial METADATA work. Subs are totally broken and SYNC returns wrong data

devel+metadata
Daniel Oaks 6 years ago
parent
commit
2eb2467de2
10 changed files with 524 additions and 2 deletions
  1. 2
    0
      irc/caps/constants.go
  2. 2
    0
      irc/channel.go
  3. 2
    0
      irc/client.go
  4. 4
    0
      irc/commands.go
  5. 1
    0
      irc/errors.go
  6. 295
    1
      irc/handlers.go
  7. 7
    0
      irc/help.go
  8. 192
    0
      irc/metadata.go
  9. 15
    0
      irc/numerics.go
  10. 4
    1
      irc/server.go

+ 2
- 0
irc/caps/constants.go View File

@@ -36,6 +36,8 @@ const (
36 36
 	MaxLine Capability = "oragono.io/maxline"
37 37
 	// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
38 38
 	MessageTags Capability = "draft/message-tags-0.2"
39
+	// Metadata is this draft IRCv3 capability: https://github.com/jwheare/ircv3-specifications/blob/metadata/core/metadata-3.2.md
40
+	Metadata Capability = "draft/metadata"
39 41
 	// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
40 42
 	MultiPrefix Capability = "multi-prefix"
41 43
 	// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md

+ 2
- 0
irc/channel.go View File

@@ -25,6 +25,7 @@ type Channel struct {
25 25
 	members           MemberSet
26 26
 	membersCache      []*Client  // allow iteration over channel members without holding the lock
27 27
 	membersCacheMutex sync.Mutex // tier 2; see `regenerateMembersCache`
28
+	metadata          *MetadataManager
28 29
 	name              string
29 30
 	nameCasefolded    string
30 31
 	server            *Server
@@ -56,6 +57,7 @@ func NewChannel(s *Server, name string, addDefaultModes bool, regInfo *Registere
56 57
 			modes.InviteMask: NewUserMaskSet(),
57 58
 		},
58 59
 		members:        make(MemberSet),
60
+		metadata:       NewMetadataManager(),
59 61
 		name:           name,
60 62
 		nameCasefolded: casefoldedName,
61 63
 		server:         s,

+ 2
- 0
irc/client.go View File

@@ -60,6 +60,7 @@ type Client struct {
60 60
 	languages          []string
61 61
 	maxlenTags         uint32
62 62
 	maxlenRest         uint32
63
+	metadata           *MetadataManager
63 64
 	nick               string
64 65
 	nickCasefolded     string
65 66
 	nickMaskCasefolded string
@@ -100,6 +101,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
100 101
 		channels:       make(ChannelSet),
101 102
 		ctime:          now,
102 103
 		flags:          make(map[modes.Mode]bool),
104
+		metadata:       NewMetadataManager(),
103 105
 		server:         server,
104 106
 		socket:         &socket,
105 107
 		nick:           "*", // * is used until actual nick is given

+ 4
- 0
irc/commands.go View File

@@ -161,6 +161,10 @@ func init() {
161 161
 			handler:   lusersHandler,
162 162
 			minParams: 0,
163 163
 		},
164
+		"METADATA": {
165
+			handler:   metadataHandler,
166
+			minParams: 2,
167
+		},
164 168
 		"MODE": {
165 169
 			handler:   modeHandler,
166 170
 			minParams: 1,

+ 1
- 0
irc/errors.go View File

@@ -34,6 +34,7 @@ var (
34 34
 	errNoSuchChannel                  = errors.New("No such channel")
35 35
 	errRenamePrivsNeeded              = errors.New("Only chanops can rename channels")
36 36
 	errSaslFail                       = errors.New("SASL failed")
37
+	errTooManyKeys                    = errors.New("Too many metadata keys")
37 38
 )
38 39
 
39 40
 // Socket Errors

+ 295
- 1
irc/handlers.go View File

@@ -1311,6 +1311,300 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
1311 1311
 	return false
1312 1312
 }
1313 1313
 
1314
+// METADATA * SUB <key>{ <key>}
1315
+// METADATA * SUBS
1316
+// METADATA * UNSUB <key>{ <key>}
1317
+// METADATA <target> CLEAR
1318
+// METADATA <target> GET <key>{ <key>}
1319
+// METADATA <target> LIST
1320
+// METADATA <target> SET <key> [<value>]
1321
+// METADATA <target> SYNC
1322
+func metadataHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1323
+	handler, exists := metadataSubcommands[strings.ToLower(msg.Params[1])]
1324
+
1325
+	if !exists {
1326
+		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown subcommand"))
1327
+		return false
1328
+	}
1329
+
1330
+	return handler(server, client, msg, rb)
1331
+}
1332
+
1333
+// METADATA <target> CLEAR
1334
+func metadataClearHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1335
+	var mm *MetadataManager
1336
+
1337
+	targetString := msg.Params[0]
1338
+	target, err := CasefoldChannel(targetString)
1339
+	if err == nil {
1340
+		channel := server.channels.Get(target)
1341
+		if channel == nil {
1342
+			rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1343
+			return false
1344
+		}
1345
+		if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
1346
+			rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.nick, targetString, client.t("You're not a channel operator"))
1347
+			return false
1348
+		}
1349
+		mm = channel.metadata
1350
+	} else {
1351
+		target, err = CasefoldName(targetString)
1352
+		user := server.clients.Get(target)
1353
+		if err != nil || user == nil {
1354
+			if len(target) > 0 {
1355
+				rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1356
+			} else {
1357
+				rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
1358
+			}
1359
+			return false
1360
+		}
1361
+		if user != client {
1362
+			rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, "*", client.t("Permission denied"))
1363
+			return false
1364
+		}
1365
+		mm = user.metadata
1366
+	}
1367
+
1368
+	for _, key := range mm.Clear() {
1369
+		rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*")
1370
+	}
1371
+	rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
1372
+
1373
+	return false
1374
+}
1375
+
1376
+// METADATA <target> GET <key>{ <key>}
1377
+func metadataGetHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1378
+	if len(msg.Params) < 3 {
1379
+		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
1380
+		return false
1381
+	}
1382
+
1383
+	var mm *MetadataManager
1384
+
1385
+	targetString := msg.Params[0]
1386
+	target, err := CasefoldChannel(targetString)
1387
+	if err == nil {
1388
+		channel := server.channels.Get(target)
1389
+		if channel == nil {
1390
+			rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1391
+			return false
1392
+		}
1393
+		if !channel.hasClient(client) {
1394
+			rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
1395
+			return false
1396
+		}
1397
+		mm = channel.metadata
1398
+	} else {
1399
+		target, err = CasefoldName(targetString)
1400
+		user := server.clients.Get(target)
1401
+		if err != nil || user == nil {
1402
+			if len(target) > 0 {
1403
+				rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1404
+			} else {
1405
+				rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
1406
+			}
1407
+			return false
1408
+		}
1409
+		mm = user.metadata
1410
+	}
1411
+
1412
+	for i, key := range msg.Params {
1413
+		// only process actual keys, skip target and (sub)command name
1414
+		if 1 < i {
1415
+			key = strings.TrimSpace(strings.ToLower(key))
1416
+			if !metadataKeyValid(key) {
1417
+				rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
1418
+				continue
1419
+			}
1420
+			value, exists := mm.Get(key)
1421
+			if !exists {
1422
+				rb.Add(nil, server.name, ERR_NOMATCHINGKEY, client.nick, target, key, client.t("No matching key"))
1423
+				continue
1424
+			}
1425
+
1426
+			rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
1427
+		}
1428
+	}
1429
+	return false
1430
+}
1431
+
1432
+// METADATA <target> LIST
1433
+func metadataListHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1434
+	var mm *MetadataManager
1435
+
1436
+	targetString := msg.Params[0]
1437
+	target, err := CasefoldChannel(targetString)
1438
+	if err == nil {
1439
+		channel := server.channels.Get(target)
1440
+		if channel == nil {
1441
+			rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1442
+			return false
1443
+		}
1444
+		if !channel.hasClient(client) {
1445
+			rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
1446
+			return false
1447
+		}
1448
+		mm = channel.metadata
1449
+	} else {
1450
+		target, err = CasefoldName(targetString)
1451
+		user := server.clients.Get(target)
1452
+		if err != nil || user == nil {
1453
+			if len(target) > 0 {
1454
+				rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1455
+			} else {
1456
+				rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
1457
+			}
1458
+			return false
1459
+		}
1460
+		mm = user.metadata
1461
+	}
1462
+
1463
+	for key, value := range mm.List() {
1464
+		rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
1465
+	}
1466
+	rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
1467
+
1468
+	return false
1469
+}
1470
+
1471
+// METADATA <target> SET <key> [<value>]
1472
+func metadataSetHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1473
+	if len(msg.Params) < 3 {
1474
+		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
1475
+		return false
1476
+	}
1477
+
1478
+	// retrieve key/value
1479
+	key := strings.TrimSpace(strings.ToLower(msg.Params[2]))
1480
+	if !metadataKeyValid(key) {
1481
+		rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
1482
+		return false
1483
+	}
1484
+
1485
+	var settingValue bool
1486
+	var value string
1487
+	if 3 < len(msg.Params) {
1488
+		settingValue = true
1489
+		value = msg.Params[3]
1490
+	}
1491
+
1492
+	// work on target
1493
+	targetString := msg.Params[0]
1494
+	target, err := CasefoldChannel(targetString)
1495
+	if err == nil {
1496
+		channel := server.channels.Get(target)
1497
+		if channel == nil {
1498
+			rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1499
+			return false
1500
+		}
1501
+		if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
1502
+			rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.nick, targetString, client.t("You're not a channel operator"))
1503
+			return false
1504
+		}
1505
+		if settingValue {
1506
+			err := channel.metadata.Set(key, value, server.MetadataKeysLimit())
1507
+			if err == errTooManyKeys {
1508
+				rb.Add(nil, server.name, ERR_METADATALIMIT, client.nick, target, client.t("Metadata limit reached"))
1509
+				return false
1510
+			}
1511
+		} else {
1512
+			channel.metadata.Delete(key)
1513
+		}
1514
+	} else {
1515
+		target, err = CasefoldName(targetString)
1516
+		user := server.clients.Get(target)
1517
+		if err != nil || user == nil {
1518
+			if len(target) > 0 {
1519
+				rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1520
+			} else {
1521
+				rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
1522
+			}
1523
+			return false
1524
+		}
1525
+		if user != client {
1526
+			rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, "*", client.t("Permission denied"))
1527
+			return false
1528
+		}
1529
+		if settingValue {
1530
+			err := user.metadata.Set(key, value, server.MetadataKeysLimit())
1531
+			if err == errTooManyKeys {
1532
+				rb.Add(nil, server.name, ERR_METADATALIMIT, client.nick, target, client.t("Metadata limit reached"))
1533
+				return false
1534
+			}
1535
+		} else {
1536
+			user.metadata.Delete(key)
1537
+		}
1538
+	}
1539
+
1540
+	if settingValue {
1541
+		rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*", value)
1542
+	} else {
1543
+		rb.Add(nil, server.name, RPL_KEYVALUE, client.nick, target, key, "*")
1544
+	}
1545
+	rb.Add(nil, server.name, RPL_METADATAEND, client.nick, client.t("End of metadata"))
1546
+
1547
+	return false
1548
+}
1549
+
1550
+func metadataSubHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1551
+	client.Notice("METADATA SUB not yet implemented")
1552
+	return false
1553
+}
1554
+
1555
+func metadataSubsHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1556
+	client.Notice("METADATA SUBS not yet implemented")
1557
+	return false
1558
+}
1559
+
1560
+// METADATA <target> SYNC
1561
+//TODO(dan): SYNC also returns e.g. metadata keys of friends in the given channel, etc.
1562
+//Note:
1563
+// One difference with list is that you can’t get a whole channel full of members metadata with it.
1564
+// With sync you can, you get targets you didn’t explicitly request. It’s more of a trigger than a request.
1565
+// And it’s mainly only useful for the delayed synchronisation function.
1566
+func metadataSyncHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1567
+	var mm *MetadataManager
1568
+
1569
+	targetString := msg.Params[0]
1570
+	target, err := CasefoldChannel(targetString)
1571
+	if err == nil {
1572
+		channel := server.channels.Get(target)
1573
+		if channel == nil {
1574
+			rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1575
+			return false
1576
+		}
1577
+		if !channel.hasClient(client) {
1578
+			rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, target, client.t("You're not on that channel!"))
1579
+			return false
1580
+		}
1581
+		mm = channel.metadata
1582
+	} else {
1583
+		target, err = CasefoldName(targetString)
1584
+		user := server.clients.Get(target)
1585
+		if err != nil || user == nil {
1586
+			if len(target) > 0 {
1587
+				rb.Add(nil, server.name, ERR_TARGETINVALID, client.nick, target, client.t("Invalid metadata target"))
1588
+			} else {
1589
+				rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "METADATA", client.t("Unknown error"))
1590
+			}
1591
+			return false
1592
+		}
1593
+		mm = user.metadata
1594
+	}
1595
+
1596
+	for key, value := range mm.List() {
1597
+		rb.Add(nil, server.name, "METADATA", target, key, "*", value)
1598
+	}
1599
+
1600
+	return false
1601
+}
1602
+
1603
+func metadataUnsubHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1604
+	client.Notice("METADATA UNSUB not yet implemented")
1605
+	return false
1606
+}
1607
+
1314 1608
 // MODE <target> [<modestring> [<mode arguments>...]]
1315 1609
 func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1316 1610
 	_, errChan := CasefoldChannel(msg.Params[0])
@@ -1448,7 +1742,7 @@ func monitorHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
1448 1742
 	handler, exists := monitorSubcommands[strings.ToLower(msg.Params[0])]
1449 1743
 
1450 1744
 	if !exists {
1451
-		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", msg.Params[0], client.t("Unknown subcommand"))
1745
+		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", client.t("Unknown subcommand"))
1452 1746
 		return false
1453 1747
 	}
1454 1748
 

+ 7
- 0
irc/help.go View File

@@ -269,6 +269,13 @@ channels). <elistcond>s modify how the channels are selected.`,
269 269
 Shows statistics about the size of the network. If <mask> is given, only
270 270
 returns stats for servers matching the given mask.  If <server> is given, the
271 271
 command is processed by that server.`,
272
+	},
273
+	"metadata": {
274
+		text: `METADATA <target> <subcommand> [<param>...]
275
+
276
+Sets, removes and displays metadata about clients and channels. For more
277
+specific information, see the spec here:
278
+https://github.com/jwheare/ircv3-specifications/blob/metadata/core/metadata-3.2.md`,
272 279
 	},
273 280
 	"mode": {
274 281
 		text: `MODE <target> [<modestring> [<mode arguments>...]]

+ 192
- 0
irc/metadata.go View File

@@ -0,0 +1,192 @@
1
+// Copyright (c) 2018 Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"sync"
8
+
9
+	"github.com/goshuirc/irc-go/ircmsg"
10
+)
11
+
12
+var (
13
+	//TODO(dan): temporary hardcoded limits, make these configurable instead.
14
+	metadataKeysLimit = 20
15
+	metadataSubsLimit = 20
16
+)
17
+
18
+// MetadataKeysLimit returns how many metadata keys can be set on each client/channel.
19
+//TODO(dan): have this be configurable in the config file instead.
20
+func (server *Server) MetadataKeysLimit() int {
21
+	return metadataKeysLimit
22
+}
23
+
24
+// MetadataSubsLimit returns how many metadata keys can be subscribed to.
25
+//TODO(dan): have this be configurable in the config file instead.
26
+func (server *Server) MetadataSubsLimit() int {
27
+	return metadataSubsLimit
28
+}
29
+
30
+// MetadataManager manages metadata for a client or channel.
31
+type MetadataManager struct {
32
+	sync.RWMutex
33
+	// keyvals holds our values internally.
34
+	keyvals map[string]string
35
+}
36
+
37
+// NewMetadataManager returns a new MetadataManager.
38
+func NewMetadataManager() *MetadataManager {
39
+	var mm MetadataManager
40
+	mm.keyvals = make(map[string]string)
41
+	return &mm
42
+}
43
+
44
+// Clear deletes all keys, returning a list of the deleted keys.
45
+func (mm *MetadataManager) Clear() []string {
46
+	var keys []string
47
+
48
+	mm.Lock()
49
+	defer mm.Unlock()
50
+
51
+	for key := range mm.keyvals {
52
+		keys = append(keys, key)
53
+		delete(mm.keyvals, key)
54
+	}
55
+	return keys
56
+}
57
+
58
+// List returns all keys and values.
59
+func (mm *MetadataManager) List() map[string]string {
60
+	data := make(map[string]string)
61
+
62
+	mm.RLock()
63
+	defer mm.RUnlock()
64
+
65
+	for key, value := range mm.keyvals {
66
+		data[key] = value
67
+	}
68
+	return data
69
+}
70
+
71
+// Get returns the value of a single key.
72
+func (mm *MetadataManager) Get(key string) (string, bool) {
73
+	mm.RLock()
74
+	defer mm.RUnlock()
75
+
76
+	value, exists := mm.keyvals[key]
77
+	return value, exists
78
+}
79
+
80
+// Set sets the value of the given key. A limit of -1 means ignore any limits.
81
+func (mm *MetadataManager) Set(key, value string, limit int) error {
82
+	mm.Lock()
83
+	defer mm.Unlock()
84
+
85
+	_, currentlyExists := mm.keyvals[key]
86
+	if limit != -1 && !currentlyExists && limit < len(mm.keyvals)+1 {
87
+		return errTooManyKeys
88
+	}
89
+
90
+	mm.keyvals[key] = value
91
+
92
+	return nil
93
+}
94
+
95
+// Delete removes the given key.
96
+func (mm *MetadataManager) Delete(key string) {
97
+	mm.Lock()
98
+	defer mm.Unlock()
99
+
100
+	delete(mm.keyvals, key)
101
+}
102
+
103
+// MetadataSubsManager manages metadata key subscriptions.
104
+type MetadataSubsManager struct {
105
+	sync.RWMutex
106
+	// watchedKeys holds our list of watched (sub'd) keys.
107
+	watchedKeys map[string]bool
108
+}
109
+
110
+// NewMetadataSubsManager returns a new MetadataSubsManager.
111
+func NewMetadataSubsManager() *MetadataSubsManager {
112
+	var msm MetadataSubsManager
113
+	msm.watchedKeys = make(map[string]bool)
114
+	return &msm
115
+}
116
+
117
+// Sub subscribes to the given keys.
118
+func (msm *MetadataSubsManager) Sub(key ...string) {
119
+	msm.Lock()
120
+	defer msm.Unlock()
121
+
122
+	for _, k := range key {
123
+		msm.watchedKeys[k] = true
124
+	}
125
+}
126
+
127
+// Unsub ubsubscribes from the given keys.
128
+func (msm *MetadataSubsManager) Unsub(key ...string) {
129
+	msm.Lock()
130
+	defer msm.Unlock()
131
+
132
+	for _, k := range key {
133
+		delete(msm.watchedKeys, k)
134
+	}
135
+}
136
+
137
+// List returns a list of the currently-subbed keys.
138
+func (msm *MetadataSubsManager) List() []string {
139
+	var keys []string
140
+
141
+	msm.RLock()
142
+	defer msm.RUnlock()
143
+
144
+	for k := range msm.watchedKeys {
145
+		keys = append(keys, k)
146
+	}
147
+
148
+	return keys
149
+}
150
+
151
+var (
152
+	metadataValidChars = map[rune]bool{
153
+		'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
154
+		'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'o': true,
155
+		'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true,
156
+		'w': true, 'x': true, 'y': true, 'z': true, '0': true, '1': true, '2': true,
157
+		'3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true,
158
+		'_': true, '-': true, '.': true, ':': true,
159
+	}
160
+)
161
+
162
+// metadataKeyValid returns true if the given key is valid.
163
+func metadataKeyValid(key string) bool {
164
+	// key length
165
+	if len(key) < 1 {
166
+		return false
167
+	}
168
+	// invalid first character for a key
169
+	if key[0] == ':' {
170
+		return false
171
+	}
172
+	// name characters
173
+	for _, cha := range []rune(key) {
174
+		if metadataValidChars[rune(cha)] == false {
175
+			return false
176
+		}
177
+	}
178
+	return true
179
+}
180
+
181
+var (
182
+	metadataSubcommands = map[string]func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool{
183
+		"clear": metadataClearHandler,
184
+		"get":   metadataGetHandler,
185
+		"list":  metadataListHandler,
186
+		"set":   metadataSetHandler,
187
+		"sub":   metadataSubHandler,
188
+		"subs":  metadataSubsHandler,
189
+		"sync":  metadataSyncHandler,
190
+		"unsub": metadataUnsubHandler,
191
+	}
192
+)

+ 15
- 0
irc/numerics.go View File

@@ -173,6 +173,21 @@ const (
173 173
 	RPL_MONLIST                     = "732"
174 174
 	RPL_ENDOFMONLIST                = "733"
175 175
 	ERR_MONLISTFULL                 = "734"
176
+	RPL_WHOISKEYVALUE               = "760"
177
+	RPL_KEYVALUE                    = "761"
178
+	RPL_METADATAEND                 = "762"
179
+	ERR_METADATALIMIT               = "764"
180
+	ERR_TARGETINVALID               = "765"
181
+	ERR_NOMATCHINGKEY               = "766"
182
+	ERR_KEYINVALID                  = "767"
183
+	ERR_KEYNOTSET                   = "768"
184
+	ERR_KEYNOPERMISSION             = "769"
185
+	RPL_METADATASUBOK               = "770"
186
+	RPL_METADATAUNSUBOK             = "771"
187
+	RPL_METADATASUBS                = "772"
188
+	ERR_METADATATOOMANYSUBS         = "773"
189
+	ERR_METADATASYNCLATER           = "774"
190
+	ERR_METADATARATELIMIT           = "775"
176 191
 	RPL_LOGGEDIN                    = "900"
177 192
 	RPL_LOGGEDOUT                   = "901"
178 193
 	ERR_NICKLOCKED                  = "902"

+ 4
- 1
irc/server.go View File

@@ -51,7 +51,7 @@ var (
51 51
 
52 52
 	// SupportedCapabilities are the caps we advertise.
53 53
 	// MaxLine, SASL and STS are set during server startup.
54
-	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)
54
+	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)
55 55
 
56 56
 	// CapValues are the actual values we advertise to v3.2 clients.
57 57
 	// actual values are set during server startup.
@@ -835,6 +835,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
835 835
 
836 836
 	server.languages = lm
837 837
 
838
+	// Metadata
839
+	CapValues.Set(caps.Metadata, "maxsub=10")
840
+
838 841
 	// SASL
839 842
 	oldAccountConfig := server.AccountConfig()
840 843
 	authPreviouslyEnabled := oldAccountConfig != nil && oldAccountConfig.AuthenticationEnabled

Loading…
Cancel
Save