Compare commits
1 Commits
pyrex-rewr
...
5b0b6a48a9
Author | SHA1 | Date | |
---|---|---|---|
5b0b6a48a9 |
46
src/irc/broadcast.go
Normal file
46
src/irc/broadcast.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type BroadcastGroup struct {
|
||||
channels []canonicalChannelName
|
||||
users []*User
|
||||
specificallyExcludedUsers []*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 (bcg *BroadcastGroup) AddSpecificallyExcludedUsers(users ...*User) {
|
||||
bcg.specificallyExcludedUsers = append(bcg.users, users...)
|
||||
}
|
||||
|
||||
func Broadcast(bcg *BroadcastGroup, content transport.Content) {
|
||||
g := GetGlobals()
|
||||
allUsers := make(map[*User]struct{})
|
||||
for _, c := range bcg.channels {
|
||||
for u := range g.Users.ByCanonicalChannel(c) {
|
||||
allUsers[u] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, u := range bcg.users {
|
||||
allUsers[u] = struct{}{}
|
||||
}
|
||||
for _, u := range bcg.specificallyExcludedUsers {
|
||||
delete(allUsers, u)
|
||||
}
|
||||
for u := range allUsers {
|
||||
g.Server.SendMessage(u.clientId, content)
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/world"
|
||||
)
|
||||
|
||||
func HandleCommands(msg world.WrappedMessage) {
|
||||
handleAuthCommands(msg)
|
||||
handleJoinPartCommands(msg)
|
||||
handlePrivmsgNotifyCommands(msg)
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/users"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/world"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
func handleAuthCommands(msg world.WrappedMessage) {
|
||||
handleNickAndUser(msg)
|
||||
completeHandshakeIfPossible(msg)
|
||||
}
|
||||
|
||||
func handleNickAndUser(msg world.WrappedMessage) {
|
||||
if msg.Sender.GetHasReceivedAuthHandshakeReply() {
|
||||
// TODO: Send an error reply
|
||||
return
|
||||
}
|
||||
|
||||
if msg.Content.Command == "NICK" {
|
||||
args := msg.Content.Arguments
|
||||
if len(args) != 1 {
|
||||
// TODO: Send an error reply
|
||||
return
|
||||
}
|
||||
|
||||
nick := args[0]
|
||||
validNick, err := users.ValidateNick(nick)
|
||||
if err != nil {
|
||||
// TODO: Send an error reply
|
||||
return
|
||||
}
|
||||
|
||||
err = msg.Sender.SetNick(&validNick)
|
||||
if err != nil {
|
||||
msg.World.Server.TerminateClient(msg.Sender.GetClientId(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Content.Command == "USER" {
|
||||
args := msg.Content.Arguments
|
||||
if len(args) != 4 {
|
||||
// TODO: Send an error reply
|
||||
return
|
||||
}
|
||||
|
||||
username := args[0]
|
||||
zero := args[1]
|
||||
star := args[2]
|
||||
realName := args[3]
|
||||
|
||||
if zero != "0" || star != "*" {
|
||||
// TODO: Send an error reply
|
||||
return
|
||||
}
|
||||
|
||||
msg.Sender.SetUsername(&username)
|
||||
msg.Sender.SetRealName(&realName)
|
||||
|
||||
// TODO: Validation? I wonder if it matters.
|
||||
}
|
||||
}
|
||||
|
||||
func completeHandshakeIfPossible(msg world.WrappedMessage) {
|
||||
sender := msg.Sender
|
||||
if msg.Sender.GetHasReceivedAuthHandshakeReply() {
|
||||
return
|
||||
}
|
||||
|
||||
isReady := sender.GetNick() != nil && sender.GetUsername() != nil && sender.GetRealName() != nil
|
||||
if !isReady {
|
||||
return
|
||||
}
|
||||
|
||||
sender.SetHasReceivedAuthHandshakeReply(true)
|
||||
msg.World.Server.SendMessage(sender.GetClientId(), transport.Content{
|
||||
Command: "NICK",
|
||||
Arguments: []string{sender.GetNick().Value},
|
||||
})
|
||||
msg.World.Server.SendMessage(msg.Sender.GetClientId(), transport.Content{
|
||||
Command: "USER",
|
||||
Arguments: []string{*sender.GetUsername(), "0", "*", *sender.GetRealName()},
|
||||
})
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/users"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/world"
|
||||
)
|
||||
|
||||
func handleJoinPartCommands(msg world.WrappedMessage) {
|
||||
if msg.Content.Command == "JOIN" {
|
||||
if len(msg.Content.Arguments) != 1 {
|
||||
// TODO: Wrong number of arguments
|
||||
return
|
||||
}
|
||||
channelsToJoin := parseChannelList(msg.Content.Arguments[0])
|
||||
|
||||
for _, channel := range channelsToJoin {
|
||||
err := msg.Sender.Join(channel)
|
||||
if err != nil {
|
||||
msg.World.Server.TerminateClient(msg.Sender.GetClientId(), err)
|
||||
return
|
||||
}
|
||||
msg.World.RelayToChannel(msg, channel, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Content.Command == "PART" {
|
||||
n := len(msg.Content.Arguments)
|
||||
if !(n == 1 || n == 2) {
|
||||
return
|
||||
}
|
||||
channelsToPart := parseChannelList(msg.Content.Arguments[0])
|
||||
|
||||
for _, channel := range channelsToPart {
|
||||
err := msg.Sender.Part(channel)
|
||||
if err != nil {
|
||||
msg.World.Server.TerminateClient(msg.Sender.GetClientId(), err)
|
||||
return
|
||||
}
|
||||
msg.World.RelayToChannel(msg, channel, nil)
|
||||
// the user won't see their own #part because they left, so send it
|
||||
msg.World.RelayToClient(msg, msg.Sender.GetClientId(), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseChannelList(arg string) []users.ChannelName {
|
||||
var channels []users.ChannelName
|
||||
for _, channelName := range strings.Split(arg, ",") {
|
||||
validChannel, err := users.ValidateChannelName(channelName)
|
||||
if err != nil { // can't join, not a channel
|
||||
continue
|
||||
}
|
||||
|
||||
channels = append(channels, validChannel)
|
||||
}
|
||||
return channels
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/world"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
func handlePrivmsgNotifyCommands(msg world.WrappedMessage) {
|
||||
if msg.Content.Command == "PRIVMSG" || msg.Content.Command == "NOTIFY" || msg.Content.Command == "CTCP" {
|
||||
log.Printf("message-like command")
|
||||
if len(msg.Content.Arguments) == 0 {
|
||||
// TODO: Error reply
|
||||
return
|
||||
}
|
||||
|
||||
// Was this message to a user?
|
||||
msg.World.RelayToVagueDestination(msg, msg.Content.Arguments[0], []transport.ClientId{msg.Sender.GetClientId()})
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
import "fmt"
|
||||
|
||||
var ErrAlreadyInChannel = fmt.Errorf("already in channel")
|
||||
var ErrMalformedCommand = fmt.Errorf("malformed command")
|
||||
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")
|
55
src/irc/globals.go
Normal file
55
src/irc/globals.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type Globals struct {
|
||||
Server *transport.Server
|
||||
Users *UsersSystem
|
||||
Notifications *NotificationsSystem
|
||||
}
|
||||
|
||||
var globals *Globals = nil
|
||||
|
||||
func InitializeGlobals(server *transport.Server) {
|
||||
globals = &Globals{
|
||||
Server: server,
|
||||
Users: NewUsersSystem(),
|
||||
Notifications: NewNotificationsSystem(),
|
||||
}
|
||||
}
|
||||
|
||||
func GetGlobals() *Globals {
|
||||
if globals == nil {
|
||||
panic("globals not initialized")
|
||||
}
|
||||
return globals
|
||||
}
|
||||
|
||||
func (g *Globals) getHandlers() []Handler {
|
||||
return []Handler{
|
||||
g.Users,
|
||||
g.Notifications,
|
||||
}
|
||||
}
|
||||
|
||||
func Dispatch[T any](askPermission func(T) error, act func(T)) error {
|
||||
globals := GetGlobals()
|
||||
handlers := globals.getHandlers()
|
||||
|
||||
for _, handler := range handlers {
|
||||
if h, ok := handler.(T); ok {
|
||||
err := askPermission(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, handler := range handlers {
|
||||
if h, ok := handler.(T); ok {
|
||||
act(h)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
type Handler interface {
|
||||
AssertTypes()
|
||||
@@ -20,15 +20,15 @@ type ChatMode string
|
||||
|
||||
const (
|
||||
ChatModePrivmsg ChatMode = "PRIVMSG"
|
||||
ChatModeNotice = "NOTICE"
|
||||
ChatModeNotice ChatMode = "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)
|
||||
AskPermissionForChannelMessage(user *User, mode ChatMode, destination canonicalChannelName, message string) error
|
||||
HandleChannelMessage(user *User, mode ChatMode, destination canonicalChannelName, message string)
|
||||
}
|
||||
|
||||
/*
|
186
src/irc/main.go
186
src/irc/main.go
@@ -1,15 +1,15 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/commands"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/world"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
func ServeIrc(server *transport.Server) {
|
||||
world := world.NewWorld(server)
|
||||
InitializeGlobals(server)
|
||||
|
||||
for {
|
||||
rawMessage, err := server.ReceiveMessage()
|
||||
@@ -18,8 +18,184 @@ func ServeIrc(server *transport.Server) {
|
||||
return
|
||||
}
|
||||
|
||||
wrappedMessage := world.Wrap(rawMessage)
|
||||
err = handleMessage(rawMessage)
|
||||
if err != nil {
|
||||
server.TerminateClient(rawMessage.Sender, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.HandleCommands(wrappedMessage)
|
||||
func handleMessage(m transport.IncomingMessage) error {
|
||||
g := GetGlobals()
|
||||
user := g.Users.ByClientIdOrCreate(m.Sender)
|
||||
command := m.Content.Command
|
||||
args := m.Content.Arguments
|
||||
|
||||
if command == "NICK" {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("%w: needs 1 argument", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
nick, err := ValidateNick(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = user.SetNick(&nick)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return completeHandshakeIfPossible(user)
|
||||
}
|
||||
|
||||
if command == "USER" {
|
||||
if len(args) != 4 {
|
||||
return fmt.Errorf("%w: needs 4 arguments", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
username := args[0]
|
||||
zero := args[1]
|
||||
star := args[2]
|
||||
realName := args[3]
|
||||
|
||||
if zero != "0" || star != "*" {
|
||||
return fmt.Errorf("%w: needs zero and star", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
user.SetUsername(&username)
|
||||
user.SetRealName(&realName)
|
||||
return completeHandshakeIfPossible(user)
|
||||
}
|
||||
|
||||
if command == "JOIN" {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("%w: needs 1 argument", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
channelsToJoin := parseChannelList(args[0])
|
||||
for _, channel := range channelsToJoin {
|
||||
err := user.Join(channel)
|
||||
log.Printf("joining %s %v", channel, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if command == "PART" {
|
||||
if len(args) != 1 && len(args) != 2 {
|
||||
return fmt.Errorf("%w: needs 1 or 2 arguments", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
channelsToPart := parseChannelList(args[0])
|
||||
var reason *string
|
||||
if len(args) == 2 {
|
||||
reason = &args[1]
|
||||
}
|
||||
|
||||
for _, channel := range channelsToPart {
|
||||
err := user.Part(channel, reason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if command == string(ChatModePrivmsg) || command == string(ChatModeNotice) {
|
||||
mode := ChatMode(command)
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("%w: needs 2 arguments", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
channelName, err := ValidateChannelName(args[0])
|
||||
if err == nil {
|
||||
return handleChannelMessage(user, mode, channelName.canonical, args[1])
|
||||
}
|
||||
|
||||
nick, err := ValidateNick(args[0])
|
||||
if err == nil {
|
||||
other := g.Users.ByNick(nick)
|
||||
if other == nil {
|
||||
// TODO: Error for missing user
|
||||
return nil
|
||||
}
|
||||
return handleUserMessage(user, mode, other, args[1])
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: needs nick or channel name", ErrMalformedCommand)
|
||||
}
|
||||
|
||||
// Unrecognized command
|
||||
return nil
|
||||
}
|
||||
|
||||
func completeHandshakeIfPossible(user *User) error {
|
||||
if user.GetHasReceivedAuthHandshakeReply() {
|
||||
log.Printf("has already completed handshake!")
|
||||
return nil
|
||||
}
|
||||
|
||||
nick := user.GetNick()
|
||||
username := user.GetUsername()
|
||||
realName := user.GetRealName()
|
||||
|
||||
isReady := !(nick == nil || username == nil || realName == nil)
|
||||
|
||||
if !isReady {
|
||||
log.Printf("not ready to complete handshake!")
|
||||
return nil
|
||||
}
|
||||
log.Printf("time to complete handshake!!")
|
||||
|
||||
user.SetHasReceivedAuthHandshakeReply(true)
|
||||
group := NewBroadcastGroup()
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, transport.Content{
|
||||
Command: "NICK",
|
||||
Arguments: []string{nick.Value},
|
||||
})
|
||||
Broadcast(group, transport.Content{
|
||||
Command: "USER",
|
||||
Arguments: []string{nick.Value, "0", "*", *realName},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseChannelList(arg string) []ChannelName {
|
||||
var channels []ChannelName
|
||||
for _, channelName := range strings.Split(arg, ",") {
|
||||
validChannel, err := ValidateChannelName(channelName)
|
||||
if err != nil { // can't join, not a channel
|
||||
continue
|
||||
}
|
||||
|
||||
channels = append(channels, validChannel)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
func handleChannelMessage(sender *User, mode ChatMode, destination canonicalChannelName, message string) error {
|
||||
return Dispatch(
|
||||
func(ch ChatHandler) error {
|
||||
return ch.AskPermissionForChannelMessage(sender, mode, destination, message)
|
||||
},
|
||||
func(ch ChatHandler) {
|
||||
ch.HandleChannelMessage(sender, mode, destination, message)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func handleUserMessage(sender *User, mode ChatMode, destination *User, message string) error {
|
||||
return Dispatch(
|
||||
func(ch ChatHandler) error {
|
||||
return ch.AskPermissionForUserMessage(sender, mode, destination, message)
|
||||
},
|
||||
func(ch ChatHandler) {
|
||||
ch.HandleUserMessage(sender, mode, destination, message)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@@ -1,10 +1,18 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
import "git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type NotificationsSystem struct {
|
||||
}
|
||||
|
||||
func NewNotificationsSystem() *NotificationsSystem {
|
||||
return &NotificationsSystem{}
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AssertTypes() {
|
||||
var _ NickChangeHandler = notifications
|
||||
var _ PartJoinHandler = notifications
|
||||
@@ -51,7 +59,7 @@ func (notifications *NotificationsSystem) HandlePart(user *User, channelName Cha
|
||||
Arguments: args,
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(user.GetChannels()...)
|
||||
group.AddChannels(channelName.canonical)
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, content)
|
||||
}
|
||||
@@ -69,9 +77,46 @@ func (notifications *NotificationsSystem) HandleJoin(user *User, channelName Cha
|
||||
Arguments: []string{string(channelName.Value)},
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(user.GetChannels()...)
|
||||
group.AddChannels(channelName.canonical)
|
||||
group.AddUsers(user)
|
||||
Broadcast(group, content)
|
||||
|
||||
// tell the user who is here
|
||||
group = NewBroadcastGroup()
|
||||
group.AddUsers(user)
|
||||
src2 := "server"
|
||||
|
||||
var nameList strings.Builder
|
||||
var i = 0
|
||||
for user := range GetGlobals().Users.ByChannel(channelName) {
|
||||
if i != 0 {
|
||||
nameList.WriteString(" ")
|
||||
}
|
||||
nameList.WriteString(user.nick.Value)
|
||||
i += 1
|
||||
}
|
||||
|
||||
Broadcast(group, transport.Content{
|
||||
Source: &src2,
|
||||
Command: "332",
|
||||
Arguments: []string{
|
||||
user.nick.Value, channelName.Value, "TODO: topic!",
|
||||
},
|
||||
})
|
||||
Broadcast(group, transport.Content{
|
||||
Source: &src2,
|
||||
Command: "353",
|
||||
Arguments: []string{
|
||||
user.nick.Value, "=", channelName.Value, nameList.String(),
|
||||
},
|
||||
})
|
||||
Broadcast(group, transport.Content{
|
||||
Source: &src2,
|
||||
Command: "366",
|
||||
Arguments: []string{
|
||||
user.nick.Value, channelName.Value, "End of /NAMES list",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (notifications *NotificationsSystem) AskPermissionForUserMessage(user *User, mode ChatMode, destination *User, message string) error {
|
||||
@@ -105,5 +150,6 @@ func (notifications *NotificationsSystem) HandleChannelMessage(user *User, mode
|
||||
}
|
||||
group := NewBroadcastGroup()
|
||||
group.AddChannels(destination)
|
||||
group.AddSpecificallyExcludedUsers(user)
|
||||
Broadcast(group, content)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
@@ -1,6 +1,7 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
@@ -8,7 +9,8 @@ import (
|
||||
|
||||
type User struct {
|
||||
clientId transport.ClientId
|
||||
sourceString string
|
||||
hostString *string
|
||||
sourceString *string
|
||||
nick *Nick
|
||||
username *string
|
||||
realName *string
|
||||
@@ -26,12 +28,27 @@ func NewUsersSystem() *UsersSystem {
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) recomputeSourceString() {
|
||||
nick := "unknown"
|
||||
if user.nick != nil {
|
||||
nick = (*user.nick).Value
|
||||
}
|
||||
hstr := fmt.Sprintf("clients/%d", user.clientId)
|
||||
sstr := fmt.Sprintf("%s!%s", nick, hstr)
|
||||
user.hostString = &hstr
|
||||
user.sourceString = &sstr
|
||||
}
|
||||
|
||||
func (user *User) GetClientId() transport.ClientId {
|
||||
return user.clientId
|
||||
}
|
||||
|
||||
func (user *User) GetHostString() string {
|
||||
return *user.hostString
|
||||
}
|
||||
|
||||
func (user *User) GetSourceString() string {
|
||||
return user.sourceString
|
||||
return *user.sourceString
|
||||
}
|
||||
|
||||
func (user *User) GetNick() *Nick {
|
@@ -1 +0,0 @@
|
||||
package users
|
@@ -1,4 +1,4 @@
|
||||
package v2
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -30,6 +30,7 @@ func (users *UsersSystem) ByClientIdOrCreate(clientId transport.ClientId) *User
|
||||
|
||||
channels: nil,
|
||||
}
|
||||
user.recomputeSourceString()
|
||||
users.clientIdIndex[clientId] = user
|
||||
return user
|
||||
}
|
||||
@@ -42,6 +43,10 @@ func (users *UsersSystem) ByChannel(channelName ChannelName) map[*User]struct{}
|
||||
return users.channelNameIndex[channelName.canonical]
|
||||
}
|
||||
|
||||
func (users *UsersSystem) ByCanonicalChannel(channelName canonicalChannelName) map[*User]struct{} {
|
||||
return users.channelNameIndex[channelName]
|
||||
}
|
||||
|
||||
func (users *UsersSystem) AssertTypes() {
|
||||
// statically assert that we implement the types we believe we do
|
||||
var _ NickChangeHandler = users
|
||||
@@ -69,6 +74,7 @@ func (users *UsersSystem) HandleNickChange(user *User, oldNick *Nick, newNick *N
|
||||
delete(users.nickIndex, oldNick.canonical)
|
||||
}
|
||||
user.nick = newNick
|
||||
user.recomputeSourceString()
|
||||
|
||||
if newNick != nil {
|
||||
users.nickIndex[newNick.canonical] = user
|
@@ -1,24 +0,0 @@
|
||||
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,30 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,122 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/irc/users"
|
||||
"git.chromaticdragon.app/pyrex/minimal-irc-server/v2/src/transport"
|
||||
)
|
||||
|
||||
type World struct {
|
||||
Server *transport.Server
|
||||
UsersSystem *users.UsersSystem
|
||||
}
|
||||
|
||||
type WrappedMessage struct {
|
||||
World *World
|
||||
Sender *users.User
|
||||
Content transport.Content
|
||||
}
|
||||
|
||||
func NewWorld(server *transport.Server) *World {
|
||||
usersSystem := users.NewUsersSystem()
|
||||
|
||||
return &World{
|
||||
Server: server,
|
||||
UsersSystem: usersSystem,
|
||||
}
|
||||
}
|
||||
|
||||
func (world *World) Wrap(msg transport.IncomingMessage) WrappedMessage {
|
||||
sender := world.UsersSystem.ByClientIdOrCreate(msg.Sender)
|
||||
|
||||
return WrappedMessage{
|
||||
World: world,
|
||||
Sender: sender,
|
||||
Content: msg.Content,
|
||||
}
|
||||
}
|
||||
|
||||
// transmission of messages
|
||||
func (world *World) RelayToVagueDestination(
|
||||
msg WrappedMessage,
|
||||
name string,
|
||||
exclude []transport.ClientId,
|
||||
) {
|
||||
nick, err := users.ValidateNick(name)
|
||||
if err == nil {
|
||||
// so it's a nick!
|
||||
world.RelayToNick(msg, nick, exclude)
|
||||
return
|
||||
}
|
||||
|
||||
channel, err := users.ValidateChannelName(name)
|
||||
if err == nil {
|
||||
// so it's a channel!
|
||||
world.RelayToChannel(msg, channel, exclude)
|
||||
return
|
||||
}
|
||||
|
||||
log.Fatalf("not sure how to send to %s", name)
|
||||
// TODO: Error response: "what is this?"
|
||||
}
|
||||
|
||||
func (world *World) RelayToClient(
|
||||
msg WrappedMessage,
|
||||
client transport.ClientId,
|
||||
exclude []transport.ClientId,
|
||||
) {
|
||||
content := createAnnotatedContent(msg)
|
||||
if slices.Contains(exclude, client) {
|
||||
return // don't relay
|
||||
}
|
||||
world.Server.SendMessage(client, content)
|
||||
}
|
||||
|
||||
func (world *World) RelayToNick(
|
||||
msg WrappedMessage,
|
||||
nick users.Nick,
|
||||
exclude []transport.ClientId,
|
||||
) {
|
||||
content := createAnnotatedContent(msg)
|
||||
|
||||
user := world.UsersSystem.ByNick(nick)
|
||||
if user == nil {
|
||||
// TODO: Send an error reply. The user didn't exist
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(exclude, user.GetClientId()) {
|
||||
return // don't relay
|
||||
}
|
||||
|
||||
world.Server.SendMessage(user.GetClientId(), content)
|
||||
}
|
||||
|
||||
func (world *World) RelayToChannel(
|
||||
msg WrappedMessage,
|
||||
channelName users.ChannelName,
|
||||
exclude []transport.ClientId,
|
||||
) {
|
||||
content := createAnnotatedContent(msg)
|
||||
|
||||
members := world.UsersSystem.ByChannel(channelName)
|
||||
log.Printf("Members of %s: %v\n", channelName, members)
|
||||
for member := range members {
|
||||
if slices.Contains(exclude, member.GetClientId()) {
|
||||
return // don't relay
|
||||
}
|
||||
world.Server.SendMessage(member.GetClientId(), content)
|
||||
}
|
||||
}
|
||||
|
||||
func createAnnotatedContent(
|
||||
msg WrappedMessage,
|
||||
) transport.Content {
|
||||
content := msg.Content
|
||||
fullSource := fmt.Sprintf("%s!clients/%d", msg.Sender.GetNick().Value, msg.Sender.GetClientId())
|
||||
content.Source = &fullSource
|
||||
return content
|
||||
}
|
@@ -29,7 +29,7 @@ func NewServer(address string) (*Server, error) {
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
connectedClients: newConnectedClients(),
|
||||
incomingMessages: make(chan IncomingMessage),
|
||||
incomingMessages: make(chan IncomingMessage, 1024),
|
||||
}
|
||||
|
||||
go (func() {
|
||||
@@ -55,7 +55,7 @@ func (server *Server) handleConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
clientCtx, cancel := context.WithCancelCause(server.ctx)
|
||||
outgoingMessages := make(chan OutgoingMessage)
|
||||
outgoingMessages := make(chan OutgoingMessage, 1024)
|
||||
|
||||
clientId := server.connectedClients.Enroll(func(id ClientId) ConnectedClient {
|
||||
return ConnectedClient{
|
||||
@@ -141,7 +141,9 @@ func (server *Server) SendMessage(client ClientId, content Content) {
|
||||
Content: content,
|
||||
}
|
||||
server.connectedClients.BorrowIfPresent(client, func(connectedClient *ConnectedClient) {
|
||||
log.Printf("putting in outgoing")
|
||||
connectedClient.outgoingMessages <- outgoing
|
||||
log.Printf("done putting in outgoing")
|
||||
})
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user