mirror of
https://github.com/ergochat/ergo.git
synced 2025-06-17 04:07:29 +02:00
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
package irc
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/ergochat/ergo/irc/caps"
|
|
"github.com/ergochat/ergo/irc/modes"
|
|
"github.com/ergochat/ergo/irc/utils"
|
|
)
|
|
|
|
var (
|
|
errMetadataTooManySubs = errors.New("too many subscriptions")
|
|
errMetadataNotFound = errors.New("key not found")
|
|
)
|
|
|
|
type MetadataHaver = interface {
|
|
SetMetadata(key string, value string)
|
|
GetMetadata(key string) (string, bool)
|
|
DeleteMetadata(key string)
|
|
ListMetadata() map[string]string
|
|
ClearMetadata() map[string]string
|
|
CountMetadata() int
|
|
}
|
|
|
|
func notifySubscribers(server *Server, session *Session, target string, key string, value string) {
|
|
var notify utils.HashSet[*Session] = make(utils.HashSet[*Session])
|
|
targetChannel := server.channels.Get(target)
|
|
targetClient := server.clients.Get(target)
|
|
|
|
if targetClient != nil {
|
|
notify = targetClient.FriendsMonitors(caps.Metadata)
|
|
// notify clients about changes regarding themselves
|
|
for _, s := range targetClient.Sessions() {
|
|
notify.Add(s)
|
|
}
|
|
}
|
|
if targetChannel != nil {
|
|
members := targetChannel.Members()
|
|
for _, m := range members {
|
|
for _, s := range m.Sessions() {
|
|
if s.capabilities.Has(caps.Metadata) {
|
|
notify.Add(s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't notify the session that made the change
|
|
notify.Remove(session)
|
|
|
|
for s := range notify {
|
|
if !s.isSubscribedTo(key) {
|
|
continue
|
|
}
|
|
|
|
if value != "" {
|
|
s.Send(nil, server.name, "METADATA", target, key, "*", value)
|
|
} else {
|
|
s.Send(nil, server.name, "METADATA", target, key, "*")
|
|
}
|
|
}
|
|
}
|
|
|
|
func syncClientMetadata(server *Server, rb *ResponseBuffer, target *Client) {
|
|
if len(rb.session.MetadataSubscriptions()) == 0 {
|
|
return
|
|
}
|
|
|
|
batchId := rb.StartNestedBatch("metadata")
|
|
defer rb.EndNestedBatch(batchId)
|
|
|
|
values := target.ListMetadata()
|
|
for k, v := range values {
|
|
if rb.session.isSubscribedTo(k) {
|
|
visibility := "*"
|
|
rb.Add(nil, server.name, "METADATA", target.Nick(), k, visibility, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func syncChannelMetadata(server *Server, rb *ResponseBuffer, target *Channel) {
|
|
if len(rb.session.MetadataSubscriptions()) == 0 {
|
|
return
|
|
}
|
|
|
|
batchId := rb.StartNestedBatch("metadata")
|
|
defer rb.EndNestedBatch(batchId)
|
|
|
|
values := target.ListMetadata()
|
|
for k, v := range values {
|
|
if rb.session.isSubscribedTo(k) {
|
|
visibility := "*"
|
|
rb.Add(nil, server.name, "METADATA", target.Name(), k, visibility, v)
|
|
}
|
|
}
|
|
|
|
for _, client := range target.Members() {
|
|
values := client.ListMetadata()
|
|
for k, v := range values {
|
|
if rb.session.isSubscribedTo(k) {
|
|
visibility := "*"
|
|
rb.Add(nil, server.name, "METADATA", client.Nick(), k, visibility, v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var metadataEvilCharsRegexp = regexp.MustCompile("[^A-Za-z0-9_./:-]+")
|
|
|
|
func metadataKeyIsEvil(key string) bool {
|
|
key = strings.TrimSpace(key) // just in case
|
|
|
|
return len(key) == 0 || // key needs to contain stuff
|
|
key[0] == ':' || // key can't start with a colon
|
|
metadataEvilCharsRegexp.MatchString(key) // key can't contain the stuff it can't contain
|
|
}
|
|
|
|
func metadataCanIEditThisKey(client *Client, target string, _ string) bool {
|
|
if !metadataCanIEditThisTarget(client, target) { // you can't edit keys on targets you can't edit.
|
|
return false
|
|
}
|
|
|
|
// todo: we don't actually do anything regarding visibility yet so there's not much to do here
|
|
|
|
return true
|
|
}
|
|
|
|
func metadataCanIEditThisTarget(client *Client, target string) bool {
|
|
if !metadataCanISeeThisTarget(client, target) { // you can't edit what you can't see. a wise man told me this once
|
|
return false
|
|
}
|
|
|
|
if client.HasRoleCapabs("sajoin") { // sajoin opers can do whatever they want
|
|
return true
|
|
}
|
|
|
|
if target == client.Nick() { // your right to swing your fist ends where my nose begins
|
|
return true
|
|
}
|
|
|
|
// if you're a channel operator, knock yourself out
|
|
channel := client.server.channels.Get(target)
|
|
if channel != nil && channel.ClientIsAtLeast(client, modes.Operator) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func metadataCanISeeThisTarget(client *Client, target string) bool {
|
|
if client.HasRoleCapabs("sajoin") { // sajoin opers can do whatever they want
|
|
return true
|
|
}
|
|
|
|
// check if the user is in the channel
|
|
channel := client.server.channels.Get(target)
|
|
if channel != nil && !channel.hasClient(client) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|