Compare commits
1 Commits
d340bc49da
...
pyrex-rewr
Author | SHA1 | Date | |
---|---|---|---|
5b29c26102 |
@@ -1,61 +0,0 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrNotANick = fmt.Errorf("does not look like a nickname")
|
||||
|
||||
var regexpNick = regexp.MustCompile("^[a-zA-Z0-9]+$") // NOTE: more constrained than real character set
|
||||
|
||||
type Nick string
|
||||
type CanonicalNick string
|
||||
|
||||
func ValidateNick(s string) (Nick, error) {
|
||||
// TODO: Fail if the string doesn't look like a nick
|
||||
if !regexpNick.MatchString(s) {
|
||||
return "", fmt.Errorf("%w: %s", ErrNotANick, s)
|
||||
}
|
||||
return Nick(s), nil
|
||||
}
|
||||
|
||||
func (n Nick) Canonize() CanonicalNick {
|
||||
return CanonicalNick(strings.ToLower(string(n)))
|
||||
}
|
||||
|
||||
func (n *Nick) CanonizeNullable() *CanonicalNick {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
result := n.Canonize()
|
||||
return &result
|
||||
}
|
||||
|
||||
var ErrNotAChannel = fmt.Errorf("does not look like a channel name")
|
||||
|
||||
var regexpChannel = regexp.MustCompile("^#[a-zA-Z0-9]+$") // NOTE: more constrained than real character set
|
||||
|
||||
type Channel string
|
||||
type CanonicalChannel string
|
||||
|
||||
func ValidateChannel(s string) (Channel, error) {
|
||||
// TODO: Fail if the string doesn't look like a channel name
|
||||
if !regexpChannel.MatchString(s) {
|
||||
return "", fmt.Errorf("%w: %s", ErrNotAChannel, s)
|
||||
}
|
||||
return Channel(s), nil
|
||||
}
|
||||
|
||||
func (c Channel) Canonize() CanonicalChannel {
|
||||
return CanonicalChannel(strings.ToLower(string(c)))
|
||||
}
|
||||
|
||||
func (c *Channel) CanonizeNullable() *CanonicalChannel {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
result := c.Canonize()
|
||||
return &result
|
||||
}
|
@@ -1,183 +1 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/errors"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type UserId uint64
|
||||
|
||||
type UsersSystem struct {
|
||||
clientIdIndex map[transport.ClientId]*User
|
||||
nickIndex map[canonicalNick]*User
|
||||
channelNameIndex map[canonicalChannelName](map[*User]struct{})
|
||||
}
|
||||
|
||||
type User struct {
|
||||
users *UsersSystem
|
||||
|
||||
clientId transport.ClientId
|
||||
nick *Nick
|
||||
username *string
|
||||
realName *string
|
||||
|
||||
hasReceivedAuthHandshakeReply bool
|
||||
|
||||
channels []ChannelName
|
||||
}
|
||||
|
||||
func NewUsersSystem() *UsersSystem {
|
||||
return &UsersSystem{
|
||||
clientIdIndex: make(map[transport.ClientId]*User),
|
||||
nickIndex: make(map[canonicalNick]*User),
|
||||
channelNameIndex: make(map[canonicalChannelName]map[*User]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByClientIdOrCreate(clientId transport.ClientId) *User {
|
||||
existing, ok := users.clientIdIndex[clientId]
|
||||
|
||||
if ok {
|
||||
return existing
|
||||
}
|
||||
|
||||
user := &User{
|
||||
users: users,
|
||||
|
||||
clientId: clientId,
|
||||
nick: nil,
|
||||
username: nil,
|
||||
realName: nil,
|
||||
|
||||
hasReceivedAuthHandshakeReply: false,
|
||||
|
||||
channels: nil,
|
||||
}
|
||||
users.clientIdIndex[clientId] = user
|
||||
return user
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByNick(nick Nick) *User {
|
||||
return users.nickIndex[nick.canonical]
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByChannel(channelName ChannelName) map[*User]struct{} {
|
||||
return users.channelNameIndex[channelName.canonical]
|
||||
}
|
||||
|
||||
func (user *User) GetClientId() transport.ClientId {
|
||||
return user.clientId
|
||||
}
|
||||
|
||||
func (user *User) GetNick() *Nick {
|
||||
return user.nick
|
||||
}
|
||||
|
||||
func (user *User) SetNick(newNick *Nick) error {
|
||||
users := user.users
|
||||
oldNick := user.nick
|
||||
|
||||
// check if already in use -- if so, refuse
|
||||
_, alreadyInUse := users.nickIndex[newNick.canonical]
|
||||
if alreadyInUse {
|
||||
if oldNick != nil && newNick.canonical == oldNick.canonical {
|
||||
// it's fine, this is the user who held that nick
|
||||
// so continue as before
|
||||
} else {
|
||||
return fmt.Errorf("%w: %s", errors.ErrNickAlreadyInUse, newNick.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// update indexes
|
||||
if oldNick != nil {
|
||||
delete(users.nickIndex, oldNick.canonical)
|
||||
}
|
||||
if newNick != nil {
|
||||
users.nickIndex[newNick.canonical] = user
|
||||
}
|
||||
|
||||
// update me
|
||||
user.nick = newNick
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) GetUsername() *string {
|
||||
return user.username
|
||||
}
|
||||
|
||||
func (user *User) SetUsername(username *string) {
|
||||
user.username = username
|
||||
}
|
||||
|
||||
func (user *User) GetRealName() *string {
|
||||
return user.realName
|
||||
}
|
||||
|
||||
func (user *User) SetRealName(realName *string) {
|
||||
user.realName = realName
|
||||
}
|
||||
|
||||
func (user *User) GetHasReceivedAuthHandshakeReply() bool {
|
||||
return user.hasReceivedAuthHandshakeReply
|
||||
}
|
||||
|
||||
func (user *User) SetHasReceivedAuthHandshakeReply(value bool) {
|
||||
user.hasReceivedAuthHandshakeReply = value
|
||||
}
|
||||
|
||||
func (user *User) IsInChannel(channelName ChannelName) bool {
|
||||
return slices.ContainsFunc(user.channels, func(existingChannel ChannelName) bool {
|
||||
return channelName.canonical == existingChannel.canonical
|
||||
})
|
||||
}
|
||||
|
||||
func (user *User) Join(channelName ChannelName) error {
|
||||
users := user.users
|
||||
|
||||
// if I'm already in this channel, don't join
|
||||
if user.IsInChannel(channelName) {
|
||||
return fmt.Errorf("%w: %s", errors.ErrAlreadyInChannel, channelName.Value)
|
||||
}
|
||||
|
||||
// update indexes
|
||||
existing, ok := users.channelNameIndex[channelName.canonical]
|
||||
if !ok {
|
||||
existing = make(map[*User]struct{})
|
||||
users.channelNameIndex[channelName.canonical] = existing
|
||||
}
|
||||
_, wasInChannel := existing[user]
|
||||
if wasInChannel {
|
||||
panic("tried to join a channel, but I was mysteriously already in it")
|
||||
}
|
||||
existing[user] = struct{}{}
|
||||
|
||||
// update me
|
||||
user.channels = append(user.channels, channelName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) Part(channelName ChannelName) error {
|
||||
users := user.users
|
||||
|
||||
// if i'm not in this channel, don't part
|
||||
if !user.IsInChannel(channelName) {
|
||||
return fmt.Errorf("%w: %s", errors.ErrNotInChannel, channelName.Value)
|
||||
}
|
||||
|
||||
// update indexes
|
||||
existing, ok := users.channelNameIndex[channelName.canonical]
|
||||
if ok {
|
||||
delete(existing, user)
|
||||
if len(existing) == 0 {
|
||||
delete(users.channelNameIndex, channelName.canonical)
|
||||
}
|
||||
} else {
|
||||
panic("tried to part from a channel, but was mysteriously absent from it")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
24
src/irc/v2/broadcast.go
Normal file
24
src/irc/v2/broadcast.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package v2
|
||||
|
||||
import "git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
|
||||
type BroadcastGroup struct {
|
||||
channels []canonicalChannelName
|
||||
users []*User
|
||||
}
|
||||
|
||||
func NewBroadcastGroup() *BroadcastGroup {
|
||||
return &BroadcastGroup{}
|
||||
}
|
||||
|
||||
func (bcg *BroadcastGroup) AddChannels(names ...canonicalChannelName) {
|
||||
bcg.channels = append(bcg.channels, names...)
|
||||
}
|
||||
|
||||
func (bcg *BroadcastGroup) AddUsers(users ...*User) {
|
||||
bcg.users = append(bcg.users, users...)
|
||||
}
|
||||
|
||||
func Broadcast(bcg *BroadcastGroup, content transport.Content) {
|
||||
panic("TODO")
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
package errors
|
||||
package v2
|
||||
|
||||
import "fmt"
|
||||
|
||||
var ErrAlreadyInChannel = fmt.Errorf("already in channel")
|
||||
var ErrNotANick = fmt.Errorf("does not look like a nickname")
|
||||
var ErrNickAlreadyInUse = fmt.Errorf("nick already in use")
|
||||
var ErrNotInChannel = fmt.Errorf("not in channel")
|
30
src/irc/v2/globals.go
Normal file
30
src/irc/v2/globals.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package v2
|
||||
|
||||
type Dispatcher interface {
|
||||
Salient() []interface{}
|
||||
}
|
||||
|
||||
type uninitializedDispatcher struct{}
|
||||
|
||||
func (u uninitializedDispatcher) Salient() []interface{} {
|
||||
panic("dispatcher should have been published")
|
||||
}
|
||||
|
||||
var GlobalDispatcher Dispatcher = uninitializedDispatcher{}
|
||||
|
||||
func Dispatch[T any](askPermission func(T) error, act func(T)) error {
|
||||
for _, handler := range GlobalDispatcher.Salient() {
|
||||
if h, ok := handler.(T); ok {
|
||||
err := askPermission(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, handler := range GlobalDispatcher.Salient() {
|
||||
if h, ok := handler.(T); ok {
|
||||
act(h)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
38
src/irc/v2/handlerTypes.go
Normal file
38
src/irc/v2/handlerTypes.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package v2
|
||||
|
||||
type Handler interface {
|
||||
AssertTypes()
|
||||
}
|
||||
|
||||
type NickChangeHandler interface {
|
||||
AskPermissionForNickChange(user *User, oldNick *Nick, newNick *Nick) error
|
||||
HandleNickChange(user *User, oldNick *Nick, newNick *Nick)
|
||||
}
|
||||
|
||||
type PartJoinHandler interface {
|
||||
AskPermissionForPart(user *User, channelName ChannelName, reason *string) error
|
||||
HandlePart(user *User, channelName ChannelName, reason *string)
|
||||
AskPermissionForJoin(user *User, channelName ChannelName) error
|
||||
HandleJoin(user *User, channelName ChannelName)
|
||||
}
|
||||
|
||||
type ChatMode string
|
||||
|
||||
const (
|
||||
ChatModePrivmsg ChatMode = "PRIVMSG"
|
||||
ChatModeNotice = "NOTICE"
|
||||
)
|
||||
|
||||
type ChatHandler interface {
|
||||
AskPermissionForUserMessage(user *User, mode ChatMode, destination *User, message string) error
|
||||
HandleUserMessage(user *User, mode ChatMode, destination *User, message string)
|
||||
|
||||
AskPermissionForChannelMessage(user *User, mode ChatMode, destination ChannelName, message string) error
|
||||
HandleChannelMessage(user *User, mode ChatMode, destination ChannelName, message string)
|
||||
}
|
||||
|
||||
/*
|
||||
type BroadcastHandler interface {
|
||||
HandleBroadcastMessage(channel string) error
|
||||
}
|
||||
*/
|
109
src/irc/v2/notificationsSystem.go
Normal file
109
src/irc/v2/notificationsSystem.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package v2
|
||||
|
||||
import "git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
|
||||
type NotificationsSystem struct {
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AssertTypes() {
|
||||
var _ NickChangeHandler = notifications
|
||||
var _ PartJoinHandler = notifications
|
||||
var _ ChatHandler = notifications
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AskPermissionForNickChange(user *User, oldNick *Nick, newNick *Nick) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) HandleNickChange(user *User, oldNick *Nick, newNick *Nick) {
|
||||
if newNick == nil {
|
||||
return // not expressible in the IRC protocol; probably shouldn't be allowed?
|
||||
}
|
||||
|
||||
src := user.GetSourceString()
|
||||
content := transport.Content{
|
||||
Source: &src,
|
||||
Command: "NICK",
|
||||
Arguments: []string{newNick.Value},
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(user.GetChannels()...)
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, content)
|
||||
}
|
||||
|
||||
// TODO: Always present the channel name in its _established_ notation
|
||||
func (notifications *NotificationsSystem) AskPermissionForPart(user *User, channelName ChannelName, reason *string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) HandlePart(user *User, channelName ChannelName, reason *string) {
|
||||
src := user.GetSourceString()
|
||||
|
||||
var args []string
|
||||
args = append(args, string(channelName.Value))
|
||||
if reason != nil {
|
||||
args = append(args, *reason)
|
||||
}
|
||||
content := transport.Content{
|
||||
Source: &src,
|
||||
Command: "PART",
|
||||
Arguments: args,
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(user.GetChannels()...)
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, content)
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AskPermissionForJoin(user *User, channelName ChannelName) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) HandleJoin(user *User, channelName ChannelName) {
|
||||
src := user.GetSourceString()
|
||||
|
||||
content := transport.Content{
|
||||
Source: &src,
|
||||
Command: "JOIN",
|
||||
Arguments: []string{string(channelName.Value)},
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(user.GetChannels()...)
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, content)
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AskPermissionForUserMessage(user *User, mode ChatMode, destination *User, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) HandleUserMessage(user *User, mode ChatMode, destination *User, message string) {
|
||||
src := user.GetSourceString()
|
||||
|
||||
content := transport.Content{
|
||||
Source: &src,
|
||||
Command: string(mode),
|
||||
Arguments: []string{destination.GetNick().Value, message},
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddUsers(destination)
|
||||
Broadcast(group, content)
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AskPermissionForChannelMessage(user *User, mode ChatMode, destination canonicalChannelName, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) HandleChannelMessage(user *User, mode ChatMode, destination canonicalChannelName, message string) {
|
||||
src := user.GetSourceString()
|
||||
|
||||
content := transport.Content{
|
||||
Source: &src,
|
||||
Command: string(mode),
|
||||
Arguments: []string{string(destination), message},
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(destination)
|
||||
Broadcast(group, content)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package users
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -12,7 +12,6 @@ type Nick struct {
|
||||
}
|
||||
type canonicalNick string
|
||||
|
||||
var ErrNotANick = fmt.Errorf("does not look like a nickname")
|
||||
var regexpNick = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // NOTE: more constrained than real character set
|
||||
|
||||
func ValidateNick(input string) (Nick, error) {
|
108
src/irc/v2/user.go
Normal file
108
src/irc/v2/user.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
clientId transport.ClientId
|
||||
sourceString string
|
||||
nick *Nick
|
||||
username *string
|
||||
realName *string
|
||||
|
||||
hasReceivedAuthHandshakeReply bool
|
||||
|
||||
channels []canonicalChannelName
|
||||
}
|
||||
|
||||
func NewUsersSystem() *UsersSystem {
|
||||
return &UsersSystem{
|
||||
clientIdIndex: make(map[transport.ClientId]*User),
|
||||
nickIndex: make(map[canonicalNick]*User),
|
||||
channelNameIndex: make(map[canonicalChannelName]map[*User]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) GetClientId() transport.ClientId {
|
||||
return user.clientId
|
||||
}
|
||||
|
||||
func (user *User) GetSourceString() string {
|
||||
return user.sourceString
|
||||
}
|
||||
|
||||
func (user *User) GetNick() *Nick {
|
||||
return user.nick
|
||||
}
|
||||
|
||||
func (user *User) SetNick(newNick *Nick) error {
|
||||
oldNick := user.nick
|
||||
return Dispatch(
|
||||
func(nch NickChangeHandler) error {
|
||||
return nch.AskPermissionForNickChange(user, oldNick, newNick)
|
||||
},
|
||||
func(nch NickChangeHandler) {
|
||||
nch.HandleNickChange(user, oldNick, newNick)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (user *User) GetUsername() *string {
|
||||
return user.username
|
||||
}
|
||||
|
||||
func (user *User) SetUsername(username *string) {
|
||||
user.username = username
|
||||
}
|
||||
|
||||
func (user *User) GetRealName() *string {
|
||||
return user.realName
|
||||
}
|
||||
|
||||
func (user *User) SetRealName(realName *string) {
|
||||
user.realName = realName
|
||||
}
|
||||
|
||||
func (user *User) GetHasReceivedAuthHandshakeReply() bool {
|
||||
return user.hasReceivedAuthHandshakeReply
|
||||
}
|
||||
|
||||
func (user *User) SetHasReceivedAuthHandshakeReply(value bool) {
|
||||
user.hasReceivedAuthHandshakeReply = value
|
||||
}
|
||||
|
||||
func (user *User) IsInChannel(channelName ChannelName) bool {
|
||||
return slices.ContainsFunc(user.channels, func(existingChannel canonicalChannelName) bool {
|
||||
return channelName.canonical == existingChannel
|
||||
})
|
||||
}
|
||||
|
||||
func (user *User) GetChannels() []canonicalChannelName {
|
||||
return user.channels
|
||||
|
||||
}
|
||||
|
||||
func (user *User) Join(channelName ChannelName) error {
|
||||
return Dispatch(
|
||||
func(pjh PartJoinHandler) error {
|
||||
return pjh.AskPermissionForJoin(user, channelName)
|
||||
},
|
||||
func(pjh PartJoinHandler) {
|
||||
pjh.HandleJoin(user, channelName)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (user *User) Part(channelName ChannelName, reason *string) error {
|
||||
return Dispatch(
|
||||
func(pjh PartJoinHandler) error {
|
||||
return pjh.AskPermissionForPart(user, channelName, reason)
|
||||
},
|
||||
func(pjh PartJoinHandler) {
|
||||
pjh.HandlePart(user, channelName, reason)
|
||||
},
|
||||
)
|
||||
}
|
113
src/irc/v2/usersSystem.go
Normal file
113
src/irc/v2/usersSystem.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type UsersSystem struct {
|
||||
clientIdIndex map[transport.ClientId]*User
|
||||
nickIndex map[canonicalNick]*User
|
||||
channelNameIndex map[canonicalChannelName](map[*User]struct{})
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByClientIdOrCreate(clientId transport.ClientId) *User {
|
||||
existing, ok := users.clientIdIndex[clientId]
|
||||
|
||||
if ok {
|
||||
return existing
|
||||
}
|
||||
|
||||
user := &User{
|
||||
clientId: clientId,
|
||||
nick: nil,
|
||||
username: nil,
|
||||
realName: nil,
|
||||
|
||||
hasReceivedAuthHandshakeReply: false,
|
||||
|
||||
channels: nil,
|
||||
}
|
||||
users.clientIdIndex[clientId] = user
|
||||
return user
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByNick(nick Nick) *User {
|
||||
return users.nickIndex[nick.canonical]
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByChannel(channelName ChannelName) map[*User]struct{} {
|
||||
return users.channelNameIndex[channelName.canonical]
|
||||
}
|
||||
|
||||
func (users *UsersSystem) AssertTypes() {
|
||||
// statically assert that we implement the types we believe we do
|
||||
var _ NickChangeHandler = users
|
||||
var _ PartJoinHandler = users
|
||||
|
||||
}
|
||||
|
||||
func (users *UsersSystem) AskPermissionForNickChange(user *User, oldNick *Nick, newNick *Nick) error {
|
||||
if oldNick != nil && newNick != nil && oldNick.canonical == newNick.canonical {
|
||||
// you're _always_ allowed to change your nick to the nick you currently have
|
||||
return nil
|
||||
}
|
||||
|
||||
// is this someone else's nick?
|
||||
_, ok := users.nickIndex[newNick.canonical]
|
||||
if ok {
|
||||
return fmt.Errorf("%w: %s", ErrNickAlreadyInUse, newNick.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (users *UsersSystem) HandleNickChange(user *User, oldNick *Nick, newNick *Nick) {
|
||||
if oldNick != nil {
|
||||
delete(users.nickIndex, oldNick.canonical)
|
||||
}
|
||||
user.nick = newNick
|
||||
|
||||
if newNick != nil {
|
||||
users.nickIndex[newNick.canonical] = user
|
||||
}
|
||||
}
|
||||
|
||||
func (users *UsersSystem) AskPermissionForPart(user *User, channelName ChannelName, reason *string) error {
|
||||
if !slices.Contains(user.channels, channelName.canonical) {
|
||||
return fmt.Errorf("%w: %s", ErrNotInChannel, channelName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (users *UsersSystem) HandlePart(user *User, channelName ChannelName, reason *string) {
|
||||
name := channelName.canonical
|
||||
user.channels = slices.DeleteFunc(user.channels, func(ccn canonicalChannelName) bool {
|
||||
return ccn == name
|
||||
})
|
||||
|
||||
channelUsers := users.channelNameIndex[name]
|
||||
delete(channelUsers, user)
|
||||
if len(channelUsers) == 0 {
|
||||
delete(users.channelNameIndex, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (users *UsersSystem) AskPermissionForJoin(user *User, channelName ChannelName) error {
|
||||
if slices.Contains(user.channels, channelName.canonical) {
|
||||
return fmt.Errorf("%w: %s", ErrAlreadyInChannel, channelName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (users *UsersSystem) HandleJoin(user *User, channelName ChannelName) {
|
||||
name := channelName.canonical
|
||||
existing, ok := users.channelNameIndex[name]
|
||||
if !ok {
|
||||
existing = make(map[*User]struct{})
|
||||
users.channelNameIndex[name] = existing
|
||||
}
|
||||
existing[user] = struct{}{}
|
||||
}
|
Reference in New Issue
Block a user