Added channels. Closes #1.
Support for the following Commands added. JOIN, PART, PRIVMSG, NOTICE, and WHO
This commit is contained in:
parent
acfc3f2c60
commit
c9d9191a5a
254
channel.go
Normal file
254
channel.go
Normal 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
|
||||
}
|
72
client.go
72
client.go
@ -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
|
||||
}
|
||||
|
189
commands.go
189
commands.go
@ -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
119
modes.go
Normal 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)
|
||||
|
||||
}
|
64
server.go
64
server.go
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user