Added channels. Closes #1.

Support for the following Commands added.
JOIN, PART, PRIVMSG, NOTICE, and WHO
This commit is contained in:
justin 2015-08-01 22:46:58 -04:00
parent acfc3f2c60
commit c9d9191a5a
5 changed files with 651 additions and 47 deletions

254
channel.go Normal file
View File

@ -0,0 +1,254 @@
package irc
import (
"strings"
"sync"
"github.com/sorcix/irc"
)
// Channel represents an IRC channel or room
type Channel struct {
Name string
Modes ChannelModeSet
Topic string
Key string
members map[string]ChannelModeSet
membersMutex sync.RWMutex
Server *Server
}
// NewChannel creates and returns a new Channel
func NewChannel(s *Server, creator *Client) *Channel {
c := &Channel{}
c.members = map[string]ChannelModeSet{}
c.Server = s
c.Modes = NewChannelModeSet()
return c
}
// Join handles a client joining the channel and notifies other channel members
func (c *Channel) Join(client *Client, key string) {
_, ok := c.members[client.Nickname]
if ok { // client is already in this channel
return
}
if len(c.Key) != 0 { //if key is required, verify that client provided matching key
if c.Key != key {
m := irc.Message{Command: irc.ERR_BADCHANNELKEY}
err := client.Encode(&m)
if err != nil {
println(err.Error())
}
}
}
operator := len(c.members) == 0
c.AddMember(client)
client.AddChannel(c)
if operator { // Client is creating channel
// Creator should be a channel operator - Maybe check if it is a "safe" channel
c.AddMemberMode(client, ChannelModeOperator)
}
var m irc.Message
// Send topic if it exists
if len(c.Topic) != 0 {
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_TOPIC, Params: []string{c.Name}, Trailing: c.Topic}
client.Encode(&m)
}
//Notify existing members that new member is joining
allMembers := make([]string, len(c.members))
i := 0
for member := range c.members {
allMembers[i] = member
i++
}
// send list of users in channel
for i := 0; i < (len(c.members)/20)+1; i++ {
memberStr := "= " + c.Name + " :"
end := (i + 1) * 20
if end > len(c.members) {
end = len(c.members)
}
m := irc.Message{Prefix: client.Prefix, Command: irc.JOIN, Params: []string{c.Name}}
for _, member := range allMembers[i*20 : end] {
mClient, _ := client.Server.GetClientByNick(member)
if mClient != nil {
memberStr += mClient.Nickname + " "
mClient.Encode(&m)
}
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, memberStr}}
client.Encode(&m)
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{client.Nickname, c.Name}, Trailing: "End of NAMES list"}
client.Encode(&m)
}
// Part handles when a client leaves a channel
func (c *Channel) Part(client *Client, message string) {
_, ok := c.members[client.Nickname]
if !ok { // client is not in this channel
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"}
client.Encode(&m)
return
}
m := irc.Message{Prefix: client.Prefix, Command: irc.PART, Params: []string{c.Name}}
if len(message) != 0 {
m.Trailing = message
}
c.SendMessage(&m)
c.RemoveMember(client)
client.RemoveChannel(c)
}
// Quit is when a client quits the server - at channel level, similar to part
func (c *Channel) Quit(client *Client, message string) {
_, ok := c.members[client.Nickname]
if !ok { // client is not in this channel
//m := irc.Message{Prefix: &irc.Prefix{Name: client.Server.config.Name}, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"}
//client.Encode(&m)
return
}
m := irc.Message{Prefix: client.Prefix, Command: irc.QUIT}
if len(message) != 0 {
m.Trailing = message
}
c.SendMessage(&m)
c.RemoveMember(client)
}
// Message is when a Private Message is directed for this channel - forward the message to each member
func (c *Channel) Message(client *Client, message string) {
m := irc.Message{Prefix: client.Prefix, Command: irc.PRIVMSG, Params: []string{c.Name}, Trailing: message}
c.SendMessageToOthers(&m, client)
}
// Notice is when a Notice is directed for this channel - forward the notice to each member
func (c *Channel) Notice(client *Client, message string) {
m := irc.Message{Prefix: client.Prefix, Command: irc.NOTICE, Params: []string{c.Name}, Trailing: message}
c.SendMessageToOthers(&m, client)
}
// SendMessage allows sending an IRC message to all channel members
func (c *Channel) SendMessage(m *irc.Message) {
for member := range c.members {
mClient, _ := c.Server.GetClientByNick(member)
if mClient != nil {
mClient.Encode(m)
}
}
}
// SendMessageToOthers allows sending an IRC message to all other channel members
func (c *Channel) SendMessageToOthers(m *irc.Message, client *Client) {
for member := range c.members {
if member == client.Nickname {
continue
}
mClient, _ := c.Server.GetClientByNick(member)
if mClient != nil {
mClient.Encode(m)
}
}
}
// AddMember adds a member to the channel
func (c *Channel) AddMember(client *Client) {
c.membersMutex.Lock()
defer c.membersMutex.Unlock()
_, ok := c.members[client.Nickname]
if ok { // client is already a member
return
}
c.members[client.Nickname] = NewChannelModeSet()
}
// RemoveMember removes a member from the channel
func (c *Channel) RemoveMember(client *Client) {
c.membersMutex.Lock()
defer c.membersMutex.Unlock()
delete(c.members, client.Nickname)
if len(c.members) == 0 { // NO more members
c.delete()
}
}
// AddMemberMode adds a mode for the member of the channel
func (c *Channel) AddMemberMode(client *Client, mode ChannelMode) {
c.membersMutex.Lock()
defer c.membersMutex.Unlock()
m, ok := c.members[client.Nickname]
if ok {
m.AddMode(mode)
c.members[client.Nickname] = m
}
}
// RemoveMemberMode removes a mode from the member of the channel
func (c *Channel) RemoveMemberMode(client *Client, mode ChannelMode) {
c.membersMutex.Lock()
defer c.membersMutex.Unlock()
m, ok := c.members[client.Nickname]
if ok {
m.RemoveMode(mode)
c.members[client.Nickname] = m
}
}
func (c *Channel) delete() {
if len(c.members) != 0 {
return
}
c.Server.RemoveChannel(c)
}
var channelStarters = map[uint8]interface{}{'&': nil, '#': nil, '+': nil, '!': nil}
// validName checks if it meets parameters found in rfc2812 1.3
func (c *Channel) validName() bool {
_, ok := channelStarters[c.Name[0]]
if !ok {
return false
}
if strings.Contains(c.Name, " ") {
return false
}
if strings.ContainsRune(c.Name, '\a') { // \a is the same as ^G
return false
}
c.Name = strings.TrimSuffix(c.Name, ",") //trim comma off of the end
return true
}

View File

@ -5,6 +5,7 @@ import (
"io"
"net"
"strings"
"sync"
"time"
"github.com/sorcix/irc"
@ -22,26 +23,30 @@ type Client struct {
Prefix *irc.Prefix
server *Server
Server *Server
authorized bool
idleTimer *time.Timer
quitTimer *time.Timer
awayMessage string
AwayMessage string
channels map[string]*Channel
channelMutex sync.Mutex
}
func (s *Server) newClient(ircConn *irc.Conn, conn net.Conn) *Client {
client := &Client{Conn: ircConn, conn: conn, server: s}
client.authorized = len(s.config.Password) == 0
client := &Client{Conn: ircConn, conn: conn, Server: s}
client.authorized = len(s.Config.Password) == 0
client.idleTimer = time.AfterFunc(time.Minute, client.idle)
client.channels = map[string]*Channel{}
return client
}
// Close cleans up the IRC client and closes the connection
func (c *Client) Close() error {
c.server.RemoveClient(c)
c.server.RemoveClientNick(c)
c.Server.RemoveClient(c)
c.Server.RemoveClientNick(c)
return c.Conn.Close()
}
@ -59,7 +64,7 @@ func (c *Client) Pong() {
}
func (c *Client) handleIncoming() {
c.server.AddClient(c)
c.Server.AddClient(c)
for {
message, err := c.Decode()
if err != nil || message == nil {
@ -81,7 +86,7 @@ func (c *Client) handleIncoming() {
c.quitTimer = nil
}
c.server.CommandsMux.ServeIRC(message, c)
c.Server.CommandsMux.ServeIRC(message, c)
}
@ -93,12 +98,16 @@ func (c *Client) idle() {
}
func (c *Client) quit() {
// Have client leave/part each channel
for _, channel := range c.GetChannels() {
channel.Quit(c, "")
}
c.Quit()
}
// Quit sends the IRC Quit command and closes the connection
func (c *Client) Quit() {
m := irc.Message{Prefix: &irc.Prefix{Name: c.server.config.Name}, Command: irc.QUIT,
m := irc.Message{Prefix: &irc.Prefix{Name: c.Server.Config.Name}, Command: irc.QUIT,
Params: []string{c.Nickname}}
c.Encode(&m)
@ -112,55 +121,55 @@ func (c *Client) Welcome() {
// Have all client info now
c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Username, Host: c.Host}
m := irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_WELCOME,
Params: []string{c.Nickname, c.server.config.Welcome}}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WELCOME,
Params: []string{c.Nickname, c.Server.Config.Welcome}}
err := c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_YOURHOST,
Params: []string{c.Nickname, fmt.Sprintf("Your host is %s", c.server.config.Name)}}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_YOURHOST,
Params: []string{c.Nickname, fmt.Sprintf("Your host is %s", c.Server.Config.Name)}}
err = c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_CREATED,
Params: []string{c.Nickname, fmt.Sprintf("This server was created %s", c.server.created)}}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_CREATED,
Params: []string{c.Nickname, fmt.Sprintf("This server was created %s", c.Server.created)}}
err = c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_MYINFO,
Params: []string{c.Nickname, fmt.Sprintf("%s - Golang IRC server", c.server.config.Name)}}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MYINFO,
Params: []string{c.Nickname, fmt.Sprintf("%s - Golang IRC server", c.Server.Config.Name)}}
err = c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_MOTDSTART,
Params: []string{c.Nickname, fmt.Sprintf("%s - Message of the day", c.server.config.Name)}}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTDSTART,
Params: []string{c.Nickname, fmt.Sprintf("%s - Message of the day", c.Server.Config.Name)}}
err = c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_MOTD,
Params: []string{c.Nickname, c.server.config.MOTD}}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTD,
Params: []string{c.Nickname, c.Server.Config.MOTD}}
err = c.Encode(&m)
if err != nil {
return
}
m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_ENDOFMOTD,
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFMOTD,
Params: []string{c.Nickname, "End of MOTD"}}
err = c.Encode(&m)
@ -168,3 +177,22 @@ func (c *Client) Welcome() {
return
}
}
// AddChannel adds a channel to the client's active list
func (c *Client) AddChannel(channel *Channel) {
c.channelMutex.Lock()
defer c.channelMutex.Unlock()
c.channels[channel.Name] = channel
}
// RemoveChannel removes a channel to the client's active list
func (c *Client) RemoveChannel(channel *Channel) {
c.channelMutex.Lock()
defer c.channelMutex.Unlock()
delete(c.channels, channel.Name)
}
// GetChannels gets a list of channels this client is joined to
func (c *Client) GetChannels() map[string]*Channel {
return c.channels
}

View File

@ -2,6 +2,7 @@ package irc
import (
"fmt"
"strings"
"github.com/sorcix/irc"
)
@ -37,7 +38,15 @@ func PongHandler(message *irc.Message, client *Client) {
// Implemented according to RFC 1459 4.1.6 and RFC 2812 3.1.7
func QuitHandler(message *irc.Message, client *Client) {
m := irc.Message{Prefix: client.server.Prefix, Command: irc.ERROR, Trailing: "quit"}
var leavingMessage string
if len(message.Params) != 0 {
leavingMessage = message.Params[0]
}
for _, channel := range client.GetChannels() {
channel.Quit(client, leavingMessage)
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERROR, Trailing: "quit"}
client.Encode(&m)
client.Close()
@ -48,7 +57,7 @@ func QuitHandler(message *irc.Message, client *Client) {
func NickHandler(message *irc.Message, client *Client) {
var m irc.Message
name := client.server.config.Name
name := client.Server.Config.Name
nickname := client.Nickname
if len(message.Params) == 0 {
@ -59,7 +68,7 @@ func NickHandler(message *irc.Message, client *Client) {
newNickname := message.Params[0]
_, found := client.server.ClientsByNick[newNickname]
_, found := client.Server.GetClientByNick(newNickname)
switch {
case !client.authorized:
@ -72,11 +81,11 @@ func NickHandler(message *irc.Message, client *Client) {
default:
if len(client.Nickname) == 0 && len(client.Username) != 0 { // Client is connected now, show MOTD ...
client.Nickname = newNickname
client.server.AddClientNick(client)
client.Server.AddClientNick(client)
client.Welcome()
} else { //change client name
client.Nickname = newNickname
client.server.UpdateClientNick(client, nickname)
client.Server.UpdateClientNick(client, nickname)
//fmt.Println("Updating client name")
}
}
@ -91,7 +100,7 @@ func NickHandler(message *irc.Message, client *Client) {
// Implemented according to RFC 1459 4.1.3 and RFC 2812 3.1.3
func UserHandler(message *irc.Message, client *Client) {
var m irc.Message
serverName := client.server.config.Name
serverName := client.Server.Config.Name
//nickname := client.Nickname
if len(client.Username) != 0 { // Already registered
@ -125,3 +134,171 @@ func UserHandler(message *irc.Message, client *Client) {
}
}
// JoinHandler is a CommandHandler to respond to IRC JOIN commands from a client
// Implemented according to RFC 1459 4.2.1 and RFC 2812 3.2.1
func JoinHandler(message *irc.Message, client *Client) {
channelNames := message.Params[0]
if channelNames == "0" { // Leave all channels
for _, channel := range client.GetChannels() {
channel.Part(client, "")
}
return
}
channelList := strings.Split(channelNames, ",")
keys := ""
if len(message.Params) >= 2 {
keys = message.Params[1]
}
keyList := strings.Split(keys, ",")
for i, cName := range channelList {
var key string
if len(keyList) > i {
key = keyList[i]
}
channel, ok := client.Server.GetChannel(cName)
if !ok { // Channel doesn't exist yet
channel = NewChannel(client.Server, client)
channel.Name = cName
//channel.Members[client.Nickname] = client.Nickname
channel.Key = key
client.Server.AddChannel(channel)
} else { //Channel already exists
}
//Notify channel members of new member
channel.Join(client, key)
}
}
// PartHandler is a CommandHandler to respond to IRC PART commands from a client
// Implemented according to RFC 1459 4.2.2 and RFC 2812 3.2.2
func PartHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
for _, cName := range message.Params {
channel, ok := client.Server.GetChannel(cName)
if !ok { // Channel doesn't exist yet
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Trailing: "You're not on that channel"}
client.Encode(&m)
continue
} else { //Channel already exists
}
channel.Part(client, message.Trailing)
}
}
// PrivMsgHandler is a CommandHandler to respond to IRC PRIVMSG commands from a client
// Implemented according to RFC 1459 4.4.1 and RFC 2812 3.3.1
func PrivMsgHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
return
}
if len(message.Params) > 1 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_TOOMANYTARGETS, Params: []string{client.Nickname}}
client.Encode(&m)
return
}
if len(message.Trailing) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTEXTTOSEND, Params: []string{client.Nickname}, Trailing: "No text to send"}
client.Encode(&m)
return
}
to := message.Params[0]
ch, ok := client.Server.GetChannel(to)
if ok { // message is to a channel
ch.Message(client, message.Trailing)
return
}
// message to a user?
cl, ok := client.Server.GetClientByNick(to)
if ok {
m := irc.Message{Prefix: client.Prefix, Command: irc.PRIVMSG, Params: []string{cl.Nickname}, Trailing: message.Trailing}
cl.Encode(&m)
return
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHNICK, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
}
// NoticeHandler is a CommandHandler to respond to IRC NOTICE commands from a client
// Implemented according to RFC 1459 4.4.2 and RFC 2812 3.3.2
func NoticeHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
return
}
if len(message.Params) > 1 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_TOOMANYTARGETS, Params: []string{client.Nickname}}
client.Encode(&m)
return
}
if len(message.Trailing) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTEXTTOSEND, Params: []string{client.Nickname}, Trailing: "No text to send"}
client.Encode(&m)
return
}
to := message.Params[0]
ch, ok := client.Server.GetChannel(to)
if ok { // message is to a channel
ch.Notice(client, message.Trailing)
return
}
// message to a user?
cl, ok := client.Server.GetClientByNick(to)
if ok {
m := irc.Message{Prefix: client.Prefix, Command: irc.NOTICE, Params: []string{cl.Nickname}, Trailing: message.Trailing}
cl.Encode(&m)
return
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHNICK, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
}
// WhoHandler is a CommandHandler to respond to IRC WHO commands from a client
// Implemented according to RFC 1459 4.5.1 and RFC 2812 3.6.1
func WhoHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
//return listing of all users
return
}
ch, ok := client.Server.GetChannel(message.Params[0])
if ok { //Channel exists
for clientName := range ch.members {
cl, found := client.Server.GetClientByNick(clientName)
if found {
msg := fmt.Sprintf("%s %s %s %s %s %s %s%s :%d %s", client.Nickname, ch.Name, cl.Name, cl.Host, client.Server.Config.Name, cl.Nickname, "H", "", 0, cl.RealName)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)}
client.Encode(&m)
}
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFWHO, Params: []string{client.Nickname, ch.Name}, Trailing: "End of WHO list"}
client.Encode(&m)
}
}

119
modes.go Normal file
View File

@ -0,0 +1,119 @@
package irc
import "sync"
// UserMode - RFC 1459 4.2.3.2 and RFC 2812 3.1.5
type UserMode rune
const (
UserModeAway UserMode = 'a'
UserModeInvisible UserMode = 'i'
UserModeWallOps UserMode = 'w'
UserModeRestricted UserMode = 'r'
UserModeOperator UserMode = 'o'
UserModeLocalOperator UserMode = 'O'
UserModeServerNotice UserMode = 's' //obsolete
)
var UserModes = map[UserMode]interface{}{
UserModeAway: nil,
UserModeInvisible: nil,
UserModeWallOps: nil,
UserModeRestricted: nil,
UserModeOperator: nil,
UserModeLocalOperator: nil,
UserModeServerNotice: nil,
}
type UserModeSet struct {
userModes map[UserMode]interface{}
mutex sync.Mutex
}
func NewUserModeSet() UserModeSet {
u := UserModeSet{}
u.userModes = map[UserMode]interface{}{}
return u
}
func (u *UserModeSet) AddMode(mode UserMode) {
u.mutex.Lock()
defer u.mutex.Unlock()
u.userModes[mode] = nil
}
func (u *UserModeSet) RemoveMode(mode UserMode) {
u.mutex.Lock()
defer u.mutex.Unlock()
delete(u.userModes, mode)
}
func (u *UserModeSet) String() string {
s := ""
for m, _ := range u.userModes {
s += string(m)
}
return s
}
// ModeModifier - RFC 1459 4.2.3 and RFC 2812 3.1.5
type ModeModifier rune
const (
ModeModifierAdd ModeModifier = '+'
ModeModifierRemove ModeModifier = '-'
)
// ChannelMode - RFC 1459 4.2.3.1 and RFC 2812 3.2.3 and RFC 2811 4
type ChannelMode rune
const (
ChannelModeCreator ChannelMode = 'O'
ChannelModeOperator ChannelMode = 'o'
ChannelModeVoice ChannelMode = 'v'
ChannelModeAnonymous ChannelMode = 'a'
ChannelModeInviteOnly ChannelMode = 'i'
ChannelModeModerated ChannelMode = 'm'
ChannelModeNoOutsideMessages ChannelMode = 'n'
ChannelModeQuiet ChannelMode = 'q'
ChannelModePrivate ChannelMode = 'p'
ChannelModeSecret ChannelMode = 's'
ChannelModeReOp ChannelMode = 'r'
ChannelModeTopic ChannelMode = 't'
ChannelModeKey ChannelMode = 'k'
ChannelModeLimit ChannelMode = 'l'
ChannelModeBan ChannelMode = 'b'
ChannelModeExceptionMask ChannelMode = 'e'
ChannelModeInvitationMask ChannelMode = 'I'
)
type ChannelModeSet struct {
Modes map[ChannelMode]interface{}
mutex sync.Mutex
}
func NewChannelModeSet() ChannelModeSet {
c := ChannelModeSet{}
c.Modes = map[ChannelMode]interface{}{}
return c
}
func (c *ChannelModeSet) AddMode(mode ChannelMode) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.Modes[mode] = nil
}
func (c *ChannelModeSet) RemoveMode(mode ChannelMode) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.Modes, mode)
}

View File

@ -12,17 +12,20 @@ import (
// Server represents an IRC server
type Server struct {
config ServerConfig
Config ServerConfig
Clients map[net.Addr]*Client
clients map[net.Addr]*Client
clientMutex sync.RWMutex
ClientsByNick map[string]*Client
clientsByNick map[string]*Client
clientByNickMutex sync.RWMutex
Prefix *irc.Prefix
CommandsMux CommandsMux
created time.Time
channels map[string]*Channel
channelMutex sync.RWMutex
}
// ServerConfig contains configuration data for seeding a server
@ -39,13 +42,13 @@ type ServerConfig struct {
// NewServer creates and returns a new Server based on the provided config
func NewServer(config ServerConfig) *Server {
s := Server{}
s.config = config
s.Clients = map[net.Addr]*Client{}
s.Config = config
s.clients = map[net.Addr]*Client{}
s.CommandsMux = NewCommandsMux()
s.created = time.Now()
s.ClientsByNick = map[string]*Client{}
s.clientsByNick = map[string]*Client{}
s.Prefix = &irc.Prefix{Name: config.Name}
s.channels = map[string]*Channel{}
return &s
}
@ -53,60 +56,61 @@ func NewServer(config ServerConfig) *Server {
func (s *Server) AddClient(client *Client) {
s.clientMutex.Lock()
defer s.clientMutex.Unlock()
s.Clients[client.conn.RemoteAddr()] = client
s.clients[client.conn.RemoteAddr()] = client
}
// RemoveClient removes a client
func (s *Server) RemoveClient(client *Client) {
s.clientMutex.Lock()
defer s.clientMutex.Unlock()
delete(s.Clients, client.conn.RemoteAddr())
delete(s.clients, client.conn.RemoteAddr())
}
// GetClient finds a client by its address and returns it
func (s *Server) GetClient(addr net.Addr) *Client {
s.clientMutex.RLock()
defer s.clientMutex.RUnlock()
return s.Clients[addr]
return s.clients[addr]
}
// AddClientNick adds a client based on its nickname
func (s *Server) AddClientNick(client *Client) {
s.clientByNickMutex.Lock()
defer s.clientByNickMutex.Unlock()
s.ClientsByNick[client.Nickname] = client
s.clientsByNick[client.Nickname] = client
}
// RemoveClientNick removes a client based on its nickname
func (s *Server) RemoveClientNick(client *Client) {
s.clientByNickMutex.Lock()
defer s.clientByNickMutex.Unlock()
delete(s.ClientsByNick, client.Nickname)
delete(s.clientsByNick, client.Nickname)
}
// UpdateClientNick updates the nickname of a client as it is stored by the server
func (s *Server) UpdateClientNick(client *Client, oldNick string) {
s.clientByNickMutex.Lock()
defer s.clientByNickMutex.Unlock()
delete(s.ClientsByNick, oldNick)
s.ClientsByNick[client.Nickname] = client
delete(s.clientsByNick, oldNick)
s.clientsByNick[client.Nickname] = client
}
// GetClientByNick returns a client with the corresponding nickname
func (s *Server) GetClientByNick(nick string) *Client {
func (s *Server) GetClientByNick(nick string) (*Client, bool) {
s.clientByNickMutex.RLock()
defer s.clientByNickMutex.RUnlock()
return s.ClientsByNick[nick]
c, ok := s.clientsByNick[nick]
return c, ok
}
// Start the server listening on the configured port
func (s *Server) Start() {
var listener net.Listener
var err error
if s.config.TLSConfig != nil {
listener, err = tls.Listen("tcp", s.config.Addr, s.config.TLSConfig)
if s.Config.TLSConfig != nil {
listener, err = tls.Listen("tcp", s.Config.Addr, s.Config.TLSConfig)
} else {
listener, err = net.Listen("tcp", s.config.Addr)
listener, err = net.Listen("tcp", s.Config.Addr)
}
if err != nil {
@ -134,3 +138,25 @@ func (s *Server) Start() {
}
}
// AddChannel adds an active channel
func (s *Server) AddChannel(channel *Channel) {
s.channelMutex.Lock()
defer s.channelMutex.Unlock()
s.channels[channel.Name] = channel
}
// RemoveChannel removes a channel from the active listing
func (s *Server) RemoveChannel(channel *Channel) {
s.channelMutex.Lock()
defer s.channelMutex.Unlock()
delete(s.channels, channel.Name)
}
// GetChannel finds and returns an active channel with a matching name if it exists
func (s *Server) GetChannel(channelName string) (*Channel, bool) {
s.channelMutex.RLock()
defer s.channelMutex.RUnlock()
c, ok := s.channels[channelName]
return c, ok
}