mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-14 07:59:31 +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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
send chan<- string
|
hostname string
|
||||||
|
send chan<- Reply
|
||||||
recv <-chan string
|
recv <-chan string
|
||||||
username string
|
username string
|
||||||
realname string
|
realname string
|
||||||
nick string
|
nick string
|
||||||
registered bool
|
registered bool
|
||||||
invisible bool
|
invisible bool
|
||||||
|
channels ChannelSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientSet map[*Client]bool
|
||||||
|
|
||||||
func NewClient(conn net.Conn) *Client {
|
func NewClient(conn net.Conn) *Client {
|
||||||
client := new(Client)
|
client := &Client{conn: conn, recv: StringReadChan(conn), channels: make(ChannelSet), hostname: LookupHostname(conn.RemoteAddr())}
|
||||||
client.conn = conn
|
client.SetReplyToStringChan()
|
||||||
client.send = StringWriteChan(conn)
|
|
||||||
client.recv = StringReadChan(conn)
|
|
||||||
return client
|
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`.
|
// 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 {
|
for str := range c.recv {
|
||||||
m := ParseMessage(str)
|
m := ParseMessage(str)
|
||||||
if m != nil {
|
if m != nil {
|
||||||
server <- &ClientMessage{c, m}
|
server.recv <- &ClientMessage{c, m}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +51,7 @@ func (c *Client) Nick() string {
|
|||||||
if c.nick != "" {
|
if c.nick != "" {
|
||||||
return c.nick
|
return c.nick
|
||||||
}
|
}
|
||||||
return "<guest>"
|
return "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UModeString() string {
|
func (c *Client) UModeString() string {
|
||||||
@ -57,15 +69,10 @@ func (c *Client) HasUser() bool {
|
|||||||
return c.username != ""
|
return c.username != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Hostname() string {
|
func (c *Client) UserHost() string {
|
||||||
addr := c.conn.RemoteAddr().String()
|
return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname)
|
||||||
index := strings.LastIndex(addr, ":")
|
|
||||||
if index != -1 {
|
|
||||||
return addr[0:index]
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UserHost() string {
|
func (c *Client) Id() string {
|
||||||
return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.Hostname())
|
return c.UserHost()
|
||||||
}
|
}
|
||||||
|
@ -4,68 +4,180 @@ type Message interface {
|
|||||||
Handle(s *Server, c *Client)
|
Handle(s *Server, c *Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *NickMessage) Handle(s *Server, c *Client) {
|
// unknown
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UserMessage) Handle(s *Server, c *Client) {
|
type UnknownMessage struct {
|
||||||
if c.username != "" {
|
command string
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UnknownMessage) Handle(s *Server, c *Client) {
|
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) {
|
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) {
|
func (m *ModeMessage) Handle(s *Server, c *Client) {
|
||||||
if m.nickname != c.nick {
|
if m.nickname != c.nick {
|
||||||
c.send <- ErrUsersDontMatch(c.Nick())
|
c.send <- ErrUsersDontMatch(s)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, mode := range m.modes {
|
s.ChangeUserMode(c, m.modes)
|
||||||
if mode == "+i" {
|
|
||||||
c.invisible = true
|
|
||||||
} else if mode == "-i" {
|
|
||||||
c.invisible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.send <- ReplyUModeIs(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryRegister(s *Server, c *Client) {
|
// JOIN
|
||||||
if (!c.registered && c.HasNick() && c.HasUser()) {
|
|
||||||
c.registered = true
|
type JoinMessage struct {
|
||||||
c.send <- ReplyWelcome(c)
|
channels []string
|
||||||
c.send <- ReplyYourHost(c.Nick(), s.name)
|
keys []string
|
||||||
c.send <- ReplyCreated(c.Nick(), s.ctime)
|
zero bool
|
||||||
c.send <- ReplyMyInfo(c.Nick(), s.name)
|
}
|
||||||
|
|
||||||
|
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
|
package irc
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -6,22 +8,30 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RPL_WELCOME = "001"
|
RPL_WELCOME = "001"
|
||||||
RPL_YOURHOST = "002"
|
RPL_YOURHOST = "002"
|
||||||
RPL_CREATED = "003"
|
RPL_CREATED = "003"
|
||||||
RPL_MYINFO = "004"
|
RPL_MYINFO = "004"
|
||||||
RPL_UMODEIS = "221"
|
RPL_UMODEIS = "221"
|
||||||
RPL_INFO = "371"
|
RPL_NOTOPIC = "331"
|
||||||
RPL_NICK = "NICK"
|
RPL_TOPIC = "332"
|
||||||
)
|
RPL_NAMREPLY = "353"
|
||||||
|
RPL_ENDOFNAMES = "366"
|
||||||
const (
|
RPL_INFO = "371"
|
||||||
ERR_NOSUCHNICK = "401"
|
ERR_NOSUCHNICK = "401"
|
||||||
ERR_NOSUCHSERVER = "402"
|
ERR_NOSUCHSERVER = "402"
|
||||||
ERR_NOSUCHCHANNEL = "403"
|
ERR_NOSUCHCHANNEL = "403"
|
||||||
ERR_UNKNOWNCOMMAND = "421"
|
ERR_UNKNOWNCOMMAND = "421"
|
||||||
ERR_NICKNAMEINUSE = "433"
|
ERR_NICKNAMEINUSE = "433"
|
||||||
ERR_NEEDMOREPARAMS = "461"
|
ERR_NOTONCHANNEL = "442"
|
||||||
ERR_ALREADYREGISTRED = "462"
|
ERR_NEEDMOREPARAMS = "461"
|
||||||
ERR_USERSDONTMATCH = "502"
|
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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readTrimmedLine(reader *bufio.Reader) (string, error) {
|
func readTrimmedLine(reader *bufio.Reader) (string, error) {
|
||||||
@ -20,7 +20,7 @@ func StringReadChan(conn net.Conn) <-chan string {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
line, err := readTrimmedLine(reader)
|
line, err := readTrimmedLine(reader)
|
||||||
if (line != "") {
|
if line != "" {
|
||||||
ch <- line
|
ch <- line
|
||||||
log.Printf("%s -> %s", addr, line)
|
log.Printf("%s -> %s", addr, line)
|
||||||
}
|
}
|
||||||
@ -50,3 +50,16 @@ func StringWriteChan(conn net.Conn) chan<- string {
|
|||||||
|
|
||||||
return ch
|
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"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var commands = map[string]func([]string) Message {
|
var commands = map[string]func([]string) Message{
|
||||||
"MODE": NewModeMessage,
|
"JOIN": NewJoinMessage,
|
||||||
"NICK": NewNickMessage,
|
"MODE": NewModeMessage,
|
||||||
"PING": NewPingMessage,
|
"NICK": NewNickMessage,
|
||||||
"QUIT": NewQuitMessage,
|
"PART": NewPartMessage,
|
||||||
"USER": NewUserMessage,
|
"PING": NewPingMessage,
|
||||||
|
"PONG": NewPongMessage,
|
||||||
|
"PRIVMSG": NewPrivMsgMessage,
|
||||||
|
"QUIT": NewQuitMessage,
|
||||||
|
"TOPIC": NewTopicMessage,
|
||||||
|
"USER": NewUserMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseMessage(line string) Message {
|
func ParseMessage(line string) Message {
|
||||||
@ -54,7 +59,6 @@ func parseLine(line string) (string, []string) {
|
|||||||
return args[0], args[1:]
|
return args[0], args[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// []string => Message constructors
|
// []string => Message constructors
|
||||||
|
|
||||||
func NewNickMessage(args []string) Message {
|
func NewNickMessage(args []string) Message {
|
||||||
@ -65,7 +69,25 @@ func NewNickMessage(args []string) Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPingMessage(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 {
|
func NewQuitMessage(args []string) Message {
|
||||||
@ -112,3 +134,55 @@ func NewModeMessage(args []string) Message {
|
|||||||
}
|
}
|
||||||
return msg
|
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 {
|
type Server struct {
|
||||||
ctime time.Time
|
hostname string
|
||||||
name string
|
ctime time.Time
|
||||||
ch chan *ClientMessage
|
name string
|
||||||
nicks map[string]*Client
|
recv chan<- *ClientMessage
|
||||||
|
nicks map[string]*Client
|
||||||
|
channels map[string]*Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientMessage struct {
|
type ClientMessage struct {
|
||||||
client *Client
|
client *Client
|
||||||
message Message
|
message Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(name string) *Server {
|
func NewServer(name string) *Server {
|
||||||
server := new(Server)
|
recv := make(chan *ClientMessage)
|
||||||
server.ctime = time.Now()
|
server := &Server{ctime: time.Now(), name: name, recv: recv, nicks: make(map[string]*Client), channels: make(map[string]*Channel)}
|
||||||
server.name = name
|
go func() {
|
||||||
server.ch = make(chan *ClientMessage)
|
for m := range recv {
|
||||||
server.nicks = make(map[string]*Client)
|
m.message.Handle(server, m.client)
|
||||||
go server.Receive()
|
}
|
||||||
|
}()
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,18 +37,104 @@ func (s *Server) Listen(addr string) {
|
|||||||
log.Fatal("Server.Listen: ", err)
|
log.Fatal("Server.Listen: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.hostname = LookupHostname(listener.Addr())
|
||||||
|
log.Print("Server.Listen: listening on ", addr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("Server.Listen: ", err)
|
log.Print("Server.Listen: ", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go NewClient(conn).Communicate(s.ch)
|
log.Print("Server.Listen: accepted ", conn.RemoteAddr())
|
||||||
|
go NewClient(conn).Communicate(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Receive() {
|
func (s *Server) GetOrMakeChannel(name string) *Channel {
|
||||||
for m := range s.ch {
|
channel := s.channels[name]
|
||||||
m.message.Handle(s, m.client)
|
|
||||||
|
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…
Reference in New Issue
Block a user