mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-10-30 21:37:23 +01:00 
			
		
		
		
	massive refactor and basic channel support: join, part, message
This commit is contained in:
		
							parent
							
								
									8ac1909c19
								
							
						
					
					
						commit
						24ad2172a8
					
				
							
								
								
									
										94
									
								
								src/irc/channel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/irc/channel.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| package irc | ||||
| 
 | ||||
| type Channel struct { | ||||
| 	name       string | ||||
| 	key        string | ||||
| 	topic      string | ||||
| 	members    ClientSet | ||||
| 	inviteOnly bool | ||||
| 	invites    map[string]bool | ||||
| 	server     *Server | ||||
| } | ||||
| 
 | ||||
| type ChannelSet map[*Channel]bool | ||||
| 
 | ||||
| func NewChannel(s *Server, name string) *Channel { | ||||
| 	return &Channel{name: name, members: make(ClientSet), invites: make(map[string]bool), server: s} | ||||
| } | ||||
| 
 | ||||
| func (ch *Channel) Send(reply Reply, fromClient *Client) { | ||||
| 	for client := range ch.members { | ||||
| 		if client != fromClient { | ||||
| 			client.send <- reply | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // channel functionality | ||||
| 
 | ||||
| func (ch *Channel) Join(cl *Client, key string) { | ||||
| 	if ch.key != key { | ||||
| 		cl.send <- ErrInviteOnlyChannel(ch) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if ch.inviteOnly && !ch.invites[cl.nick] { | ||||
| 		cl.send <- ErrBadChannelKey(ch) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ch.members[cl] = true | ||||
| 	cl.channels[ch] = true | ||||
| 
 | ||||
| 	ch.Send(RplJoin(ch, cl), nil) | ||||
| 	ch.GetTopic(cl) | ||||
| 
 | ||||
| 	for member := range ch.members { | ||||
| 		cl.send <- RplNamReply(ch, member) | ||||
| 	} | ||||
| 	cl.send <- RplEndOfNames(ch.server) | ||||
| } | ||||
| 
 | ||||
| func (ch *Channel) Part(cl *Client, message string) { | ||||
| 	if !ch.members[cl] { | ||||
| 		cl.send <- ErrNotOnChannel(ch) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	delete(ch.members, cl) | ||||
| 	delete(cl.channels, ch) | ||||
| 
 | ||||
| 	ch.Send(RplPart(ch, cl, message), nil) | ||||
| } | ||||
| 
 | ||||
| func (ch *Channel) PrivMsg(cl *Client, message string) { | ||||
| 	ch.Send(RplPrivMsgChannel(ch, cl, message), cl) | ||||
| } | ||||
| 
 | ||||
| func (ch *Channel) GetTopic(cl *Client) { | ||||
| 	if !ch.members[cl] { | ||||
| 		cl.send <- ErrNotOnChannel(ch) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if ch.topic != "" { | ||||
| 		cl.send <- RplTopic(ch) | ||||
| 	} else { | ||||
| 		cl.send <- RplNoTopic(ch) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ch *Channel) ChangeTopic(cl *Client, newTopic string) { | ||||
| 	if !ch.members[cl] { | ||||
| 		cl.send <- ErrNotOnChannel(ch) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ch.topic = newTopic | ||||
| 
 | ||||
| 	if ch.topic != "" { | ||||
| 		ch.Send(RplTopic(ch), nil) | ||||
| 	} else { | ||||
| 		ch.Send(RplNoTopic(ch), nil) | ||||
| 	} | ||||
| } | ||||
| @ -3,34 +3,46 @@ package irc | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type Client struct { | ||||
| 	conn       net.Conn | ||||
| 	send       chan<- string | ||||
| 	hostname   string | ||||
| 	send       chan<- Reply | ||||
| 	recv       <-chan string | ||||
| 	username   string | ||||
| 	realname   string | ||||
| 	nick       string | ||||
| 	registered bool | ||||
| 	invisible  bool | ||||
| 	channels   ChannelSet | ||||
| } | ||||
| 
 | ||||
| type ClientSet map[*Client]bool | ||||
| 
 | ||||
| func NewClient(conn net.Conn) *Client { | ||||
| 	client := new(Client) | ||||
| 	client.conn = conn | ||||
| 	client.send = StringWriteChan(conn) | ||||
| 	client.recv = StringReadChan(conn) | ||||
| 	client := &Client{conn: conn, recv: StringReadChan(conn), channels: make(ChannelSet), hostname: LookupHostname(conn.RemoteAddr())} | ||||
| 	client.SetReplyToStringChan() | ||||
| 	return client | ||||
| } | ||||
| 
 | ||||
| func (c *Client) SetReplyToStringChan() { | ||||
| 	send := make(chan Reply) | ||||
| 	write := StringWriteChan(c.conn) | ||||
| 	go func() { | ||||
| 		for reply := range send { | ||||
| 			write <- reply.String(c) | ||||
| 		} | ||||
| 	}() | ||||
| 	c.send = send | ||||
| } | ||||
| 
 | ||||
| // Adapt `chan string` to a `chan Message`. | ||||
| func (c *Client) Communicate(server chan<- *ClientMessage) { | ||||
| func (c *Client) Communicate(server *Server) { | ||||
| 	for str := range c.recv { | ||||
| 		m := ParseMessage(str) | ||||
| 		if m != nil { | ||||
| 			server <- &ClientMessage{c, m} | ||||
| 			server.recv <- &ClientMessage{c, m} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -39,7 +51,7 @@ func (c *Client) Nick() string { | ||||
| 	if c.nick != "" { | ||||
| 		return c.nick | ||||
| 	} | ||||
| 	return "<guest>" | ||||
| 	return "*" | ||||
| } | ||||
| 
 | ||||
| func (c *Client) UModeString() string { | ||||
| @ -57,15 +69,10 @@ func (c *Client) HasUser() bool { | ||||
| 	return c.username != "" | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Hostname() string { | ||||
| 	addr := c.conn.RemoteAddr().String() | ||||
| 	index := strings.LastIndex(addr, ":") | ||||
| 	if index != -1 { | ||||
| 		return addr[0:index] | ||||
| 	} | ||||
| 	return addr | ||||
| func (c *Client) UserHost() string { | ||||
| 	return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) UserHost() string { | ||||
| 	return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.Hostname()) | ||||
| func (c *Client) Id() string { | ||||
| 	return c.UserHost() | ||||
| } | ||||
|  | ||||
| @ -4,68 +4,180 @@ type Message interface { | ||||
| 	Handle(s *Server, c *Client) | ||||
| } | ||||
| 
 | ||||
| func (m *NickMessage) Handle(s *Server, c *Client) { | ||||
| 	if s.nicks[m.nickname] != nil { | ||||
| 		c.send <- ErrNickNameInUse(m.nickname) | ||||
| 		return | ||||
| 	} | ||||
| 	oldNick := c.nick | ||||
| 	if c.nick != "" { | ||||
| 		delete(s.nicks, c.nick) | ||||
| 	} | ||||
| 	c.nick = m.nickname | ||||
| 	s.nicks[c.nick] = c | ||||
| 	if c.registered { | ||||
| 		c.send <- ReplyNick(oldNick, c) | ||||
| 	} else { | ||||
| 		tryRegister(s, c) | ||||
| 	} | ||||
| } | ||||
| // unknown | ||||
| 
 | ||||
| func (m *UserMessage) Handle(s *Server, c *Client) { | ||||
| 	if c.username != "" { | ||||
| 		c.send <- ErrAlreadyRegistered(c.Nick()) | ||||
| 		return | ||||
| 	} | ||||
| 	c.username, c.realname = m.user, m.realname | ||||
| 	tryRegister(s, c) | ||||
| } | ||||
| 
 | ||||
| func (m *QuitMessage) Handle(s *Server, c *Client) { | ||||
| 	c.send <- MessageError() | ||||
| 	c.conn.Close() | ||||
| 	delete(s.nicks, c.nick) | ||||
| type UnknownMessage struct { | ||||
| 	command string | ||||
| } | ||||
| 
 | ||||
| func (m *UnknownMessage) Handle(s *Server, c *Client) { | ||||
| 	c.send <- ErrUnknownCommand(c.Nick(), m.command) | ||||
| 	c.send <- ErrUnknownCommand(s, m.command) | ||||
| } | ||||
| 
 | ||||
| // PING | ||||
| 
 | ||||
| type PingMessage struct { | ||||
| 	server  string | ||||
| 	server2 string | ||||
| } | ||||
| 
 | ||||
| func (m *PingMessage) Handle(s *Server, c *Client) { | ||||
| 	c.send <- MessagePong() | ||||
| 	c.send <- RplPong(s) | ||||
| } | ||||
| 
 | ||||
| // PONG | ||||
| 
 | ||||
| type PongMessage struct { | ||||
| 	server1 string | ||||
| 	server2 string | ||||
| } | ||||
| 
 | ||||
| func (m *PongMessage) Handle(s *Server, c *Client) { | ||||
| 	// TODO update client atime | ||||
| } | ||||
| 
 | ||||
| // NICK | ||||
| 
 | ||||
| type NickMessage struct { | ||||
| 	nickname string | ||||
| } | ||||
| 
 | ||||
| func (m *NickMessage) Handle(s *Server, c *Client) { | ||||
| 	s.ChangeNick(c, m.nickname) | ||||
| } | ||||
| 
 | ||||
| // USER | ||||
| 
 | ||||
| type UserMessage struct { | ||||
| 	user     string | ||||
| 	mode     uint8 | ||||
| 	unused   string | ||||
| 	realname string | ||||
| } | ||||
| 
 | ||||
| func (m *UserMessage) Handle(s *Server, c *Client) { | ||||
| 	s.Register(c, m.user, m.realname) | ||||
| } | ||||
| 
 | ||||
| // QUIT | ||||
| 
 | ||||
| type QuitMessage struct { | ||||
| 	message string | ||||
| } | ||||
| 
 | ||||
| func (m *QuitMessage) Handle(s *Server, c *Client) { | ||||
| 	s.Quit(c, m.message) | ||||
| } | ||||
| 
 | ||||
| // MODE | ||||
| 
 | ||||
| type ModeMessage struct { | ||||
| 	nickname string | ||||
| 	modes    []string | ||||
| } | ||||
| 
 | ||||
| func (m *ModeMessage) Handle(s *Server, c *Client) { | ||||
| 	if m.nickname != c.nick { | ||||
| 		c.send <- ErrUsersDontMatch(c.Nick()) | ||||
| 		c.send <- ErrUsersDontMatch(s) | ||||
| 		return | ||||
| 	} | ||||
| 	for _, mode := range m.modes { | ||||
| 		if mode == "+i" { | ||||
| 			c.invisible = true | ||||
| 		} else if mode == "-i" { | ||||
| 			c.invisible = false | ||||
| 		} | ||||
| 	} | ||||
| 	c.send <- ReplyUModeIs(c) | ||||
| 	s.ChangeUserMode(c, m.modes) | ||||
| } | ||||
| 
 | ||||
| func tryRegister(s *Server, c *Client) { | ||||
| 	if (!c.registered && c.HasNick() && c.HasUser()) { | ||||
| 		c.registered = true | ||||
| 		c.send <- ReplyWelcome(c) | ||||
| 		c.send <- ReplyYourHost(c.Nick(), s.name) | ||||
| 		c.send <- ReplyCreated(c.Nick(), s.ctime) | ||||
| 		c.send <- ReplyMyInfo(c.Nick(), s.name) | ||||
| // JOIN | ||||
| 
 | ||||
| type JoinMessage struct { | ||||
| 	channels []string | ||||
| 	keys     []string | ||||
| 	zero     bool | ||||
| } | ||||
| 
 | ||||
| func (m *JoinMessage) Handle(s *Server, c *Client) { | ||||
| 	if m.zero { | ||||
| 		for channel := range c.channels { | ||||
| 			channel.Part(c, "") | ||||
| 		} | ||||
| 	} else { | ||||
| 		for i, name := range m.channels { | ||||
| 			key := "" | ||||
| 			if len(m.keys) > i { | ||||
| 				key = m.keys[i] | ||||
| 			} | ||||
| 
 | ||||
| 			s.GetOrMakeChannel(name).Join(c, key) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // PART | ||||
| 
 | ||||
| type PartMessage struct { | ||||
| 	channels []string | ||||
| 	message  string | ||||
| } | ||||
| 
 | ||||
| func (m *PartMessage) Handle(s *Server, c *Client) { | ||||
| 	for _, chname := range m.channels { | ||||
| 		channel := s.channels[chname] | ||||
| 
 | ||||
| 		if channel == nil { | ||||
| 			c.send <- ErrNoSuchChannel(s, chname) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		channel.Part(c, m.message) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // PRIVMSG | ||||
| 
 | ||||
| type PrivMsgMessage struct { | ||||
| 	target  string | ||||
| 	message string | ||||
| } | ||||
| 
 | ||||
| func (m *PrivMsgMessage) TargetIsChannel() bool { | ||||
| 	switch m.target[0] { | ||||
| 	case '&', '#', '+', '!': | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func (m *PrivMsgMessage) Handle(s *Server, c *Client) { | ||||
| 	if m.TargetIsChannel() { | ||||
| 		channel := s.channels[m.target] | ||||
| 		if channel != nil { | ||||
| 			channel.PrivMsg(c, m.message) | ||||
| 		} else { | ||||
| 			c.send <- ErrNoSuchNick(s, m.target) | ||||
| 		} | ||||
| 	} else { | ||||
| 		client := s.nicks[m.target] | ||||
| 		if client != nil { | ||||
| 			client.send <- RplPrivMsg(client, m.message) | ||||
| 		} else { | ||||
| 			c.send <- ErrNoSuchNick(s, m.target) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TOPIC | ||||
| 
 | ||||
| type TopicMessage struct { | ||||
| 	channel string | ||||
| 	topic   string | ||||
| } | ||||
| 
 | ||||
| func (m *TopicMessage) Handle(s *Server, c *Client) { | ||||
| 	channel := s.channels[m.channel] | ||||
| 	if channel == nil { | ||||
| 		c.send <- ErrNoSuchChannel(s, m.channel) | ||||
| 		return | ||||
| 	} | ||||
| 	if m.topic == "" { | ||||
| 		channel.GetTopic(c) | ||||
| 	} else { | ||||
| 		channel.ChangeTopic(c, m.topic) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| // http://tools.ietf.org/html/rfc2812 | ||||
| // channel management: http://tools.ietf.org/html/rfc2811 | ||||
| // client protocol:    http://tools.ietf.org/html/rfc2812 | ||||
| // server protocol:    http://tools.ietf.org/html/rfc2813 | ||||
| package irc | ||||
| 
 | ||||
| const ( | ||||
| @ -6,22 +8,30 @@ const ( | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	RPL_WELCOME  = "001" | ||||
| 	RPL_YOURHOST = "002" | ||||
| 	RPL_CREATED  = "003" | ||||
| 	RPL_MYINFO   = "004" | ||||
| 	RPL_UMODEIS  = "221" | ||||
| 	RPL_INFO     = "371" | ||||
| 	RPL_NICK     = "NICK" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	ERR_NOSUCHNICK       = "401" | ||||
| 	ERR_NOSUCHSERVER     = "402" | ||||
| 	ERR_NOSUCHCHANNEL    = "403" | ||||
| 	ERR_UNKNOWNCOMMAND   = "421" | ||||
| 	ERR_NICKNAMEINUSE    = "433" | ||||
| 	ERR_NEEDMOREPARAMS   = "461" | ||||
| 	ERR_ALREADYREGISTRED = "462" | ||||
| 	ERR_USERSDONTMATCH   = "502" | ||||
| 	RPL_WELCOME           = "001" | ||||
| 	RPL_YOURHOST          = "002" | ||||
| 	RPL_CREATED           = "003" | ||||
| 	RPL_MYINFO            = "004" | ||||
| 	RPL_UMODEIS           = "221" | ||||
| 	RPL_NOTOPIC           = "331" | ||||
| 	RPL_TOPIC             = "332" | ||||
| 	RPL_NAMREPLY          = "353" | ||||
| 	RPL_ENDOFNAMES        = "366" | ||||
| 	RPL_INFO              = "371" | ||||
| 	ERR_NOSUCHNICK        = "401" | ||||
| 	ERR_NOSUCHSERVER      = "402" | ||||
| 	ERR_NOSUCHCHANNEL     = "403" | ||||
| 	ERR_UNKNOWNCOMMAND    = "421" | ||||
| 	ERR_NICKNAMEINUSE     = "433" | ||||
| 	ERR_NOTONCHANNEL      = "442" | ||||
| 	ERR_NEEDMOREPARAMS    = "461" | ||||
| 	ERR_ALREADYREGISTRED  = "462" | ||||
| 	ERR_INVITEONLYCHANNEL = "473" | ||||
| 	ERR_BADCHANNELKEY     = "475" | ||||
| 	ERR_USERSDONTMATCH    = "502" | ||||
| 	RPL_JOIN              = "JOIN" | ||||
| 	RPL_NICK              = "NICK" | ||||
| 	RPL_PART              = "PART" | ||||
| 	RPL_PONG              = "PONG" | ||||
| 	RPL_PRIVMSG           = "PRIVMSG" | ||||
| ) | ||||
|  | ||||
| @ -1,27 +0,0 @@ | ||||
| package irc | ||||
| 
 | ||||
| type NickMessage struct { | ||||
| 	nickname string | ||||
| } | ||||
| 
 | ||||
| type UserMessage struct { | ||||
| 	user string | ||||
| 	mode uint8 | ||||
| 	unused string | ||||
| 	realname string | ||||
| } | ||||
| 
 | ||||
| type QuitMessage struct { | ||||
| 	message string | ||||
| } | ||||
| 
 | ||||
| type UnknownMessage struct { | ||||
| 	command string | ||||
| } | ||||
| 
 | ||||
| type PingMessage struct {} | ||||
| 
 | ||||
| type ModeMessage struct { | ||||
| 	nickname string | ||||
| 	modes []string | ||||
| } | ||||
| @ -3,8 +3,8 @@ package irc | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"log" | ||||
| 	"strings" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| func readTrimmedLine(reader *bufio.Reader) (string, error) { | ||||
| @ -20,7 +20,7 @@ func StringReadChan(conn net.Conn) <-chan string { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			line, err := readTrimmedLine(reader) | ||||
| 			if (line != "") { | ||||
| 			if line != "" { | ||||
| 				ch <- line | ||||
| 				log.Printf("%s -> %s", addr, line) | ||||
| 			} | ||||
| @ -50,3 +50,16 @@ func StringWriteChan(conn net.Conn) chan<- string { | ||||
| 
 | ||||
| 	return ch | ||||
| } | ||||
| 
 | ||||
| func LookupHostname(addr net.Addr) string { | ||||
| 	addrStr := addr.String() | ||||
| 	ipaddr, _, err := net.SplitHostPort(addrStr) | ||||
| 	if err != nil { | ||||
| 		return addrStr | ||||
| 	} | ||||
| 	names, err := net.LookupAddr(ipaddr) | ||||
| 	if err != nil { | ||||
| 		return ipaddr | ||||
| 	} | ||||
| 	return names[0] | ||||
| } | ||||
|  | ||||
| @ -7,12 +7,17 @@ import ( | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var commands = map[string]func([]string) Message { | ||||
| 	"MODE": NewModeMessage, | ||||
| 	"NICK": NewNickMessage, | ||||
| 	"PING": NewPingMessage, | ||||
| 	"QUIT": NewQuitMessage, | ||||
| 	"USER": NewUserMessage, | ||||
| var commands = map[string]func([]string) Message{ | ||||
| 	"JOIN":    NewJoinMessage, | ||||
| 	"MODE":    NewModeMessage, | ||||
| 	"NICK":    NewNickMessage, | ||||
| 	"PART":    NewPartMessage, | ||||
| 	"PING":    NewPingMessage, | ||||
| 	"PONG":    NewPongMessage, | ||||
| 	"PRIVMSG": NewPrivMsgMessage, | ||||
| 	"QUIT":    NewQuitMessage, | ||||
| 	"TOPIC":   NewTopicMessage, | ||||
| 	"USER":    NewUserMessage, | ||||
| } | ||||
| 
 | ||||
| func ParseMessage(line string) Message { | ||||
| @ -54,7 +59,6 @@ func parseLine(line string) (string, []string) { | ||||
| 	return args[0], args[1:] | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // []string => Message constructors | ||||
| 
 | ||||
| func NewNickMessage(args []string) Message { | ||||
| @ -65,7 +69,25 @@ func NewNickMessage(args []string) Message { | ||||
| } | ||||
| 
 | ||||
| func NewPingMessage(args []string) Message { | ||||
| 	return &PingMessage{} | ||||
| 	if len(args) < 1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	message := &PingMessage{server: args[0]} | ||||
| 	if len(args) > 1 { | ||||
| 		message.server2 = args[1] | ||||
| 	} | ||||
| 	return message | ||||
| } | ||||
| 
 | ||||
| func NewPongMessage(args []string) Message { | ||||
| 	if len(args) < 1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	message := &PongMessage{server1: args[0]} | ||||
| 	if len(args) > 1 { | ||||
| 		message.server2 = args[1] | ||||
| 	} | ||||
| 	return message | ||||
| } | ||||
| 
 | ||||
| func NewQuitMessage(args []string) Message { | ||||
| @ -112,3 +134,55 @@ func NewModeMessage(args []string) Message { | ||||
| 	} | ||||
| 	return msg | ||||
| } | ||||
| 
 | ||||
| func NewJoinMessage(args []string) Message { | ||||
| 	msg := new(JoinMessage) | ||||
| 
 | ||||
| 	if len(args) > 0 { | ||||
| 		if args[0] == "0" { | ||||
| 			msg.zero = true | ||||
| 		} else { | ||||
| 			msg.channels = strings.Split(args[0], ",") | ||||
| 		} | ||||
| 
 | ||||
| 		if len(args) > 1 { | ||||
| 			msg.keys = strings.Split(args[1], ",") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return msg | ||||
| } | ||||
| 
 | ||||
| func NewPartMessage(args []string) Message { | ||||
| 	if len(args) < 1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	msg := new(PartMessage) | ||||
| 	msg.channels = strings.Split(args[0], ",") | ||||
| 
 | ||||
| 	if len(args) > 1 { | ||||
| 		msg.message = args[1] | ||||
| 	} | ||||
| 
 | ||||
| 	return msg | ||||
| } | ||||
| 
 | ||||
| func NewPrivMsgMessage(args []string) Message { | ||||
| 	if len(args) < 2 { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return &PrivMsgMessage{target: args[0], message: args[1]} | ||||
| } | ||||
| 
 | ||||
| func NewTopicMessage(args []string) Message { | ||||
| 	if len(args) < 1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	message := &TopicMessage{channel: args[0]} | ||||
| 	if len(args) > 1 { | ||||
| 		message.topic = args[1] | ||||
| 	} | ||||
| 	return message | ||||
| } | ||||
|  | ||||
							
								
								
									
										148
									
								
								src/irc/reply.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								src/irc/reply.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Identifier interface { | ||||
| 	Id() string | ||||
| } | ||||
| 
 | ||||
| type Reply interface { | ||||
| 	String(client *Client) string | ||||
| } | ||||
| 
 | ||||
| type BasicReply struct { | ||||
| 	source  Identifier | ||||
| 	code    string | ||||
| 	message string | ||||
| } | ||||
| 
 | ||||
| func (reply *BasicReply) String(client *Client) string { | ||||
| 	prefix := fmt.Sprintf(":%s %s %s ", reply.source.Id(), reply.code, client.Nick()) | ||||
| 	return prefix + reply.message | ||||
| } | ||||
| 
 | ||||
| type ChannelReply struct { | ||||
| 	*BasicReply | ||||
| 	channel *Channel | ||||
| } | ||||
| 
 | ||||
| func (reply *ChannelReply) String(client *Client) string { | ||||
| 	prefix := fmt.Sprintf(":%s %s %s ", reply.source.Id(), reply.code, reply.channel.name) | ||||
| 	return prefix + reply.message | ||||
| } | ||||
| 
 | ||||
| func NewReply(source Identifier, code string, message string) *BasicReply { | ||||
| 	return &BasicReply{source, code, message} | ||||
| } | ||||
| 
 | ||||
| // messaging | ||||
| 
 | ||||
| func RplPrivMsg(source *Client, message string) Reply { | ||||
| 	return NewReply(source, RPL_PRIVMSG, ":"+message) | ||||
| } | ||||
| 
 | ||||
| func RplNick(client *Client, newNick string) Reply { | ||||
| 	return NewReply(client, RPL_NICK, ":"+newNick) | ||||
| } | ||||
| 
 | ||||
| func RplPrivMsgChannel(channel *Channel, source *Client, message string) Reply { | ||||
| 	return &ChannelReply{NewReply(source, RPL_PRIVMSG, ":"+message), channel} | ||||
| } | ||||
| 
 | ||||
| func RplJoin(channel *Channel, client *Client) Reply { | ||||
| 	return &ChannelReply{NewReply(client, RPL_JOIN, channel.name), channel} | ||||
| } | ||||
| 
 | ||||
| func RplPart(channel *Channel, client *Client, message string) Reply { | ||||
| 	return &ChannelReply{NewReply(client, RPL_PART, ":"+message), channel} | ||||
| } | ||||
| 
 | ||||
| // Server Info | ||||
| 
 | ||||
| func RplWelcome(source Identifier, client *Client) Reply { | ||||
| 	return NewReply(source, RPL_WELCOME, "Welcome to the Internet Relay Network "+client.Id()) | ||||
| } | ||||
| 
 | ||||
| func RplYourHost(server *Server, target *Client) Reply { | ||||
| 	return NewReply(server, RPL_YOURHOST, fmt.Sprintf("Your host is %s, running version %s", server.hostname, VERSION)) | ||||
| } | ||||
| 
 | ||||
| func RplCreated(server *Server) Reply { | ||||
| 	return NewReply(server, RPL_CREATED, "This server was created "+server.ctime.Format(time.RFC1123)) | ||||
| } | ||||
| 
 | ||||
| func RplMyInfo(server *Server) Reply { | ||||
| 	return NewReply(server, RPL_MYINFO, server.name+" i ik") | ||||
| } | ||||
| 
 | ||||
| func RplUModeIs(server *Server, client *Client) Reply { | ||||
| 	return NewReply(server, RPL_UMODEIS, client.UModeString()) | ||||
| } | ||||
| 
 | ||||
| // channel operations | ||||
| 
 | ||||
| func RplNoTopic(channel *Channel) Reply { | ||||
| 	return &ChannelReply{NewReply(channel.server, RPL_NOTOPIC, channel.name+" :No topic is set"), channel} | ||||
| } | ||||
| 
 | ||||
| func RplTopic(channel *Channel) Reply { | ||||
| 	return &ChannelReply{NewReply(channel.server, RPL_TOPIC, fmt.Sprintf("%s :%s", channel.name, channel.topic)), channel} | ||||
| } | ||||
| 
 | ||||
| func RplNamReply(channel *Channel, client *Client) Reply { | ||||
| 	// TODO multiple names and splitting based on message size | ||||
| 	return NewReply(channel.server, RPL_NAMREPLY, fmt.Sprintf("=%s :+%s", channel.name, client.Nick())) | ||||
| } | ||||
| 
 | ||||
| func RplEndOfNames(source Identifier) Reply { | ||||
| 	return NewReply(source, RPL_ENDOFNAMES, ":End of NAMES list") | ||||
| } | ||||
| 
 | ||||
| func RplPong(server *Server) Reply { | ||||
| 	return NewReply(server, RPL_PONG, "") | ||||
| } | ||||
| 
 | ||||
| // errors | ||||
| 
 | ||||
| func ErrAlreadyRegistered(source Identifier) Reply { | ||||
| 	return NewReply(source, ERR_ALREADYREGISTRED, ":You may not reregister") | ||||
| } | ||||
| 
 | ||||
| func ErrNickNameInUse(source Identifier, nick string) Reply { | ||||
| 	return NewReply(source, ERR_NICKNAMEINUSE, nick+" :Nickname is already in use") | ||||
| } | ||||
| 
 | ||||
| func ErrUnknownCommand(source Identifier, command string) Reply { | ||||
| 	return NewReply(source, ERR_UNKNOWNCOMMAND, command+" :Unknown command") | ||||
| } | ||||
| 
 | ||||
| func ErrUsersDontMatch(source Identifier) Reply { | ||||
| 	return NewReply(source, ERR_USERSDONTMATCH, ":Cannot change mode for other users") | ||||
| } | ||||
| 
 | ||||
| func ErrNeedMoreParams(source Identifier, command string) Reply { | ||||
| 	return NewReply(source, ERR_NEEDMOREPARAMS, command+"%s :Not enough parameters") | ||||
| } | ||||
| 
 | ||||
| func ErrNoSuchChannel(source Identifier, channel string) Reply { | ||||
| 	return NewReply(source, ERR_NOSUCHCHANNEL, channel+" :No such channel") | ||||
| } | ||||
| 
 | ||||
| func ErrNotOnChannel(channel *Channel) Reply { | ||||
| 	return NewReply(channel.server, ERR_NOTONCHANNEL, channel.name+" :You're not on that channel") | ||||
| } | ||||
| 
 | ||||
| func ErrInviteOnlyChannel(channel *Channel) Reply { | ||||
| 	return NewReply(channel.server, ERR_INVITEONLYCHANNEL, channel.name+" :Cannot join channel (+i)") | ||||
| } | ||||
| 
 | ||||
| func ErrBadChannelKey(channel *Channel) Reply { | ||||
| 	return NewReply(channel.server, ERR_BADCHANNELKEY, channel.name+" :Cannot join channel (+k)") | ||||
| } | ||||
| 
 | ||||
| func ErrNoSuchNick(source Identifier, nick string) Reply { | ||||
| 	return NewReply(source, ERR_NOSUCHNICK, nick+" :No such nick/channel") | ||||
| } | ||||
| @ -1,54 +0,0 @@ | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func ReplyNick(oldNick string, c *Client) string { | ||||
| 	return fmt.Sprintf(":%s!%s@%s %s :%s", oldNick, c.username, c.Hostname(), RPL_NICK, c.Nick()) | ||||
| } | ||||
| 
 | ||||
| func ReplyWelcome(c *Client) string { | ||||
| 	return fmt.Sprintf("%s %s Welcome to the Internet Relay Network %s!%s@%s", RPL_WELCOME, c.Nick(), c.Nick(), c.username, c.Hostname()) | ||||
| } | ||||
| 
 | ||||
| func ReplyYourHost(nick string, server string) string { | ||||
| 	return fmt.Sprintf("%s %s Your host is %s, running version %s", RPL_YOURHOST, nick, server, VERSION) | ||||
| } | ||||
| 
 | ||||
| func ReplyCreated(nick string, ctime time.Time) string { | ||||
| 	return fmt.Sprintf("%s %s This server was created %s", RPL_CREATED, nick, ctime.Format(time.RFC1123)) | ||||
| } | ||||
| 
 | ||||
| func ReplyMyInfo(nick string, servername string) string { | ||||
| 	return fmt.Sprintf("%s %s %s %s i <channel modes>", RPL_MYINFO, nick, servername, VERSION) | ||||
| } | ||||
| 
 | ||||
| func ReplyUModeIs(c *Client) string { | ||||
| 	return fmt.Sprintf("%s %s %s", RPL_UMODEIS, c.Nick(), c.UModeString()) | ||||
| } | ||||
| 
 | ||||
| func ErrAlreadyRegistered(nick string) string { | ||||
| 	return fmt.Sprintf("%s %s :You may not reregister", ERR_ALREADYREGISTRED, nick) | ||||
| } | ||||
| 
 | ||||
| func ErrNickNameInUse(nick string) string { | ||||
| 	return fmt.Sprintf("%s %s :Nickname is already in use", ERR_NICKNAMEINUSE, nick) | ||||
| } | ||||
| 
 | ||||
| func ErrUnknownCommand(nick string, command string) string { | ||||
| 	return fmt.Sprintf("%s %s %s :Unknown command", ERR_UNKNOWNCOMMAND, nick, command) | ||||
| } | ||||
| 
 | ||||
| func ErrUsersDontMatch(nick string) string { | ||||
| 	return fmt.Sprintf("%s %s :Cannot change mode for other users", ERR_USERSDONTMATCH, nick) | ||||
| } | ||||
| 
 | ||||
| func MessagePong() string { | ||||
| 	return "PONG" | ||||
| } | ||||
| 
 | ||||
| func MessageError() string { | ||||
| 	return "ERROR :Bye" | ||||
| } | ||||
| @ -7,24 +7,27 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| type Server struct { | ||||
| 	ctime time.Time | ||||
| 	name string | ||||
| 	ch chan *ClientMessage | ||||
| 	nicks map[string]*Client | ||||
| 	hostname string | ||||
| 	ctime    time.Time | ||||
| 	name     string | ||||
| 	recv     chan<- *ClientMessage | ||||
| 	nicks    map[string]*Client | ||||
| 	channels map[string]*Channel | ||||
| } | ||||
| 
 | ||||
| type ClientMessage struct { | ||||
| 	client *Client | ||||
| 	client  *Client | ||||
| 	message Message | ||||
| } | ||||
| 
 | ||||
| func NewServer(name string) *Server { | ||||
| 	server := new(Server) | ||||
| 	server.ctime = time.Now() | ||||
| 	server.name = name | ||||
| 	server.ch = make(chan *ClientMessage) | ||||
| 	server.nicks = make(map[string]*Client) | ||||
| 	go server.Receive() | ||||
| 	recv := make(chan *ClientMessage) | ||||
| 	server := &Server{ctime: time.Now(), name: name, recv: recv, nicks: make(map[string]*Client), channels: make(map[string]*Channel)} | ||||
| 	go func() { | ||||
| 		for m := range recv { | ||||
| 			m.message.Handle(server, m.client) | ||||
| 		} | ||||
| 	}() | ||||
| 	return server | ||||
| } | ||||
| 
 | ||||
| @ -34,18 +37,104 @@ func (s *Server) Listen(addr string) { | ||||
| 		log.Fatal("Server.Listen: ", err) | ||||
| 	} | ||||
| 
 | ||||
| 	s.hostname = LookupHostname(listener.Addr()) | ||||
| 	log.Print("Server.Listen: listening on ", addr) | ||||
| 
 | ||||
| 	for { | ||||
| 		conn, err := listener.Accept() | ||||
| 		if err != nil { | ||||
| 			log.Print("Server.Listen: ", err) | ||||
| 			continue | ||||
| 		} | ||||
| 		go NewClient(conn).Communicate(s.ch) | ||||
| 		log.Print("Server.Listen: accepted ", conn.RemoteAddr()) | ||||
| 		go NewClient(conn).Communicate(s) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Receive() { | ||||
| 	for m := range s.ch { | ||||
| 		m.message.Handle(s, m.client) | ||||
| func (s *Server) GetOrMakeChannel(name string) *Channel { | ||||
| 	channel := s.channels[name] | ||||
| 
 | ||||
| 	if channel == nil { | ||||
| 		channel = NewChannel(s, name) | ||||
| 		s.channels[name] = channel | ||||
| 	} | ||||
| 
 | ||||
| 	return channel | ||||
| } | ||||
| 
 | ||||
| // Send a message to clients of channels fromClient is a member. | ||||
| func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) { | ||||
| 	clients := make(map[*Client]bool) | ||||
| 	for channel := range fromClient.channels { | ||||
| 		for client := range channel.members { | ||||
| 			clients[client] = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for client := range clients { | ||||
| 		client.send <- reply | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // server functionality | ||||
| 
 | ||||
| func (s *Server) ChangeNick(c *Client, newNick string) { | ||||
| 	if s.nicks[newNick] != nil { | ||||
| 		c.send <- ErrNickNameInUse(s, newNick) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	s.SendToInterestedClients(c, RplNick(c, newNick)) | ||||
| 
 | ||||
| 	if c.nick != "" { | ||||
| 		delete(s.nicks, c.nick) | ||||
| 	} | ||||
| 	c.nick = newNick | ||||
| 	s.nicks[c.nick] = c | ||||
| 
 | ||||
| 	s.TryRegister(c) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Register(c *Client, user string, realName string) { | ||||
| 	if c.username != "" { | ||||
| 		c.send <- ErrAlreadyRegistered(s) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	c.username, c.realname = user, realName | ||||
| 	s.TryRegister(c) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) TryRegister(c *Client) { | ||||
| 	if !c.registered && c.HasNick() && c.HasUser() { | ||||
| 		c.registered = true | ||||
| 		c.send <- RplWelcome(s, c) | ||||
| 		c.send <- RplYourHost(s, c) | ||||
| 		c.send <- RplCreated(s) | ||||
| 		c.send <- RplMyInfo(s) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Quit(c *Client, message string) { | ||||
| 	for channel := range c.channels { | ||||
| 		channel.Part(c, message) | ||||
| 	} | ||||
| 	delete(s.nicks, c.nick) | ||||
| 
 | ||||
| 	c.conn.Close() | ||||
| } | ||||
| 
 | ||||
| func (s *Server) ChangeUserMode(c *Client, modes []string) { | ||||
| 	for _, mode := range modes { | ||||
| 		if mode == "+i" { | ||||
| 			c.invisible = true | ||||
| 		} else if mode == "-i" { | ||||
| 			c.invisible = false | ||||
| 		} | ||||
| 	} | ||||
| 	c.send <- RplUModeIs(s, c) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) Id() string { | ||||
| 	return s.hostname | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jeremy Latt
						Jeremy Latt