1
0
Fork 0
forked from External/ergo

organize like a proper go package

This commit is contained in:
Jeremy Latt 2014-02-08 13:18:11 -08:00
parent f04dd7c5d5
commit b9cb539219
13 changed files with 57 additions and 123 deletions

180
irc/channel.go Normal file
View file

@ -0,0 +1,180 @@
package irc
import (
"log"
)
type Channel struct {
commands chan<- ChannelCommand
key string
members ClientSet
name string
noOutside bool
password string
replies chan<- Reply
server *Server
topic string
}
type ChannelSet map[*Channel]bool
type ChannelCommand interface {
Command
HandleChannel(channel *Channel)
}
// NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server.
func NewChannel(s *Server, name string) *Channel {
commands := make(chan ChannelCommand)
replies := make(chan Reply)
channel := &Channel{
name: name,
members: make(ClientSet),
server: s,
commands: commands,
replies: replies,
}
go channel.receiveCommands(commands)
go channel.receiveReplies(replies)
return channel
}
func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) {
for command := range commands {
if DEBUG_CHANNEL {
log.Printf("%s → %s : %s", command.Source(), channel, command)
}
command.HandleChannel(channel)
}
}
func (channel *Channel) receiveReplies(replies <-chan Reply) {
for reply := range replies {
if DEBUG_CHANNEL {
log.Printf("%s ← %s : %s", channel, reply.Source(), reply)
}
for client := range channel.members {
client.replies <- reply
}
}
}
func (channel *Channel) IsEmpty() bool {
return len(channel.members) == 0
}
func (channel *Channel) GetTopic(replier Replier) {
if channel.topic == "" {
replier.Replies() <- RplNoTopic(channel)
return
}
replier.Replies() <- RplTopic(channel)
}
func (channel *Channel) GetUsers(replier Replier) {
replier.Replies() <- NewNamesReply(channel)
}
func (channel *Channel) Nicks() []string {
nicks := make([]string, len(channel.members))
i := 0
for client := range channel.members {
nicks[i] = client.Nick()
i += 1
}
return nicks
}
func (channel *Channel) Replies() chan<- Reply {
return channel.replies
}
func (channel *Channel) Id() string {
return channel.name
}
func (channel *Channel) Nick() string {
return channel.name
}
func (channel *Channel) PublicId() string {
return channel.name
}
func (channel *Channel) String() string {
return channel.Id()
}
func (channel *Channel) Join(client *Client) {
channel.members[client] = true
client.channels[channel] = true
reply := RplJoin(channel, client)
channel.replies <- reply
channel.GetTopic(client)
channel.GetUsers(client)
}
func (channel *Channel) HasMember(client *Client) bool {
return channel.members[client]
}
//
// commands
//
func (m *JoinCommand) HandleChannel(channel *Channel) {
client := m.Client()
if channel.key != m.channels[channel.name] {
client.replies <- ErrBadChannelKey(channel)
return
}
channel.Join(client)
}
func (m *PartCommand) HandleChannel(channel *Channel) {
c := m.Client()
if !channel.HasMember(c) {
c.replies <- ErrNotOnChannel(channel)
return
}
msg := m.message
if msg == "" {
msg = c.Nick()
}
channel.replies <- RplPart(channel, c, msg)
delete(channel.members, c)
delete(c.channels, channel)
if channel.IsEmpty() { // TODO persistent channels
channel.server.DeleteChannel(channel)
}
}
func (m *TopicCommand) HandleChannel(channel *Channel) {
client := m.Client()
if !channel.HasMember(client) {
client.replies <- ErrNotOnChannel(channel)
return
}
if m.topic == "" {
channel.GetTopic(client)
return
}
channel.topic = m.topic
channel.GetTopic(channel)
}
func (m *PrivMsgCommand) HandleChannel(channel *Channel) {
channel.replies <- RplPrivMsgChannel(channel, m.Client(), m.message)
}

121
irc/client.go Normal file
View file

@ -0,0 +1,121 @@
package irc
import (
"fmt"
"log"
"net"
"time"
)
type Client struct {
atime time.Time
away bool
channels ChannelSet
conn net.Conn
hostname string
nick string
realname string
registered bool
replies chan<- Reply
server *Server
serverPass bool
username string
}
type ClientSet map[*Client]bool
func NewClient(server *Server, conn net.Conn) *Client {
read := StringReadChan(conn)
write := StringWriteChan(conn)
replies := make(chan Reply)
client := &Client{
channels: make(ChannelSet),
conn: conn,
hostname: LookupHostname(conn.RemoteAddr()),
replies: replies,
server: server,
}
go client.readConn(read)
go client.writeConn(write, replies)
return client
}
func (c *Client) readConn(recv <-chan string) {
for str := range recv {
m, err := ParseCommand(str)
if err != nil {
if err == NotEnoughArgsError {
c.replies <- ErrNeedMoreParams(c.server, str)
} else {
c.replies <- ErrUnknownCommand(c.server, str)
}
continue
}
m.SetBase(c)
c.server.commands <- m
}
}
func (c *Client) writeConn(write chan<- string, replies <-chan Reply) {
for reply := range replies {
if DEBUG_CLIENT {
log.Printf("%s ← %s : %s", c, reply.Source(), reply)
}
reply.Format(c, write)
}
}
func (c *Client) Replies() chan<- Reply {
return c.replies
}
func (c *Client) Server() *Server {
return c.server
}
func (c *Client) Nick() string {
if c.HasNick() {
return c.nick
}
return "guest"
}
func (c *Client) UModeString() string {
return ""
}
func (c *Client) HasNick() bool {
return c.nick != ""
}
func (c *Client) HasUsername() bool {
return c.username != ""
}
func (c *Client) Username() string {
if c.HasUsername() {
return c.username
}
return "guest"
}
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()
}
func (c *Client) String() string {
return c.UserHost()
}
func (c *Client) PublicId() string {
return fmt.Sprintf("%s!%s@%s", c.Nick(), c.Nick(), c.server.Id())
}

398
irc/commands.go Normal file
View file

@ -0,0 +1,398 @@
package irc
import (
"errors"
"fmt"
"strconv"
"strings"
)
type Command interface {
Client() *Client
Source() Identifier
Reply(Reply)
HandleServer(*Server)
}
type EditableCommand interface {
Command
SetBase(*Client)
}
var (
NotEnoughArgsError = errors.New("not enough arguments")
ErrParseCommand = errors.New("failed to parse message")
parseCommandFuncs = map[string]func([]string) (EditableCommand, error){
"JOIN": NewJoinCommand,
"MODE": NewModeCommand,
"NICK": NewNickCommand,
"PART": NewPartCommand,
"PASS": NewPassCommand,
"PING": NewPingCommand,
"PONG": NewPongCommand,
"PRIVMSG": NewPrivMsgCommand,
"QUIT": NewQuitCommand,
"TOPIC": NewTopicCommand,
"USER": NewUserMsgCommand,
}
)
type BaseCommand struct {
client *Client
}
func (command *BaseCommand) Client() *Client {
return command.client
}
func (command *BaseCommand) SetBase(c *Client) {
*command = BaseCommand{c}
}
func (command *BaseCommand) Source() Identifier {
return command.client
}
func (command *BaseCommand) Reply(reply Reply) {
command.client.Replies() <- reply
}
func ParseCommand(line string) (EditableCommand, error) {
command, args := parseLine(line)
constructor := parseCommandFuncs[command]
if constructor == nil {
return NewUnknownCommand(command, args), nil
}
return constructor(args)
}
func parseArg(line string) (arg string, rest string) {
if line == "" {
return
}
if strings.HasPrefix(line, ":") {
arg = line[1:]
} else {
parts := strings.SplitN(line, " ", 2)
arg = parts[0]
if len(parts) > 1 {
rest = parts[1]
}
}
return
}
func parseLine(line string) (command string, args []string) {
args = make([]string, 0)
for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) {
args = append(args, arg)
}
command, args = strings.ToUpper(args[0]), args[1:]
return
}
// <command> [args...]
type UnknownCommand struct {
BaseCommand
command string
args []string
}
func (cmd *UnknownCommand) String() string {
return fmt.Sprintf("UNKNOWN(command=%s, args=%s)", cmd.command, cmd.args)
}
func NewUnknownCommand(command string, args []string) *UnknownCommand {
return &UnknownCommand{
command: command,
args: args,
}
}
// PING <server1> [ <server2> ]
type PingCommand struct {
BaseCommand
server string
server2 string
}
func (cmd *PingCommand) String() string {
return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2)
}
func NewPingCommand(args []string) (EditableCommand, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
msg := &PingCommand{
server: args[0],
}
if len(args) > 1 {
msg.server2 = args[1]
}
return msg, nil
}
// PONG <server> [ <server2> ]
type PongCommand struct {
BaseCommand
server1 string
server2 string
}
func (cmd *PongCommand) String() string {
return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2)
}
func NewPongCommand(args []string) (EditableCommand, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
message := &PongCommand{
server1: args[0],
}
if len(args) > 1 {
message.server2 = args[1]
}
return message, nil
}
// PASS <password>
type PassCommand struct {
BaseCommand
password string
}
func (cmd *PassCommand) String() string {
return fmt.Sprintf("PASS(password=%s)", cmd.password)
}
func NewPassCommand(args []string) (EditableCommand, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
return &PassCommand{
password: args[0],
}, nil
}
// NICK <nickname>
type NickCommand struct {
BaseCommand
nickname string
}
func (m *NickCommand) String() string {
return fmt.Sprintf("NICK(nickname=%s)", m.nickname)
}
func NewNickCommand(args []string) (EditableCommand, error) {
if len(args) != 1 {
return nil, NotEnoughArgsError
}
return &NickCommand{
nickname: args[0],
}, nil
}
// USER <user> <mode> <unused> <realname>
type UserMsgCommand struct {
BaseCommand
user string
mode uint8
unused string
realname string
}
func (cmd *UserMsgCommand) String() string {
return fmt.Sprintf("USER(user=%s, mode=%o, unused=%s, realname=%s)",
cmd.user, cmd.mode, cmd.unused, cmd.realname)
}
func NewUserMsgCommand(args []string) (EditableCommand, error) {
if len(args) != 4 {
return nil, NotEnoughArgsError
}
msg := &UserMsgCommand{
user: args[0],
unused: args[2],
realname: args[3],
}
mode, err := strconv.ParseUint(args[1], 10, 8)
if err == nil {
msg.mode = uint8(mode)
}
return msg, nil
}
// QUIT [ <Quit Command> ]
type QuitCommand struct {
BaseCommand
message string
}
func (cmd *QuitCommand) String() string {
return fmt.Sprintf("QUIT(message=%s)", cmd.message)
}
func NewQuitCommand(args []string) (EditableCommand, error) {
msg := &QuitCommand{}
if len(args) > 0 {
msg.message = args[0]
}
return msg, nil
}
// JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
type JoinCommand struct {
BaseCommand
channels map[string]string
zero bool
}
func (cmd *JoinCommand) String() string {
return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero)
}
func NewJoinCommand(args []string) (EditableCommand, error) {
msg := &JoinCommand{
channels: make(map[string]string),
}
if len(args) == 0 {
return nil, NotEnoughArgsError
}
if args[0] == "0" {
msg.zero = true
return msg, nil
}
channels := strings.Split(args[0], ",")
keys := make([]string, len(channels))
if len(args) > 1 {
for i, key := range strings.Split(args[1], ",") {
keys[i] = key
}
}
for i, channel := range channels {
msg.channels[channel] = keys[i]
}
return msg, nil
}
// PART <channel> *( "," <channel> ) [ <Part Command> ]
type PartCommand struct {
BaseCommand
channels []string
message string
}
func (cmd *PartCommand) String() string {
return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message)
}
func NewPartCommand(args []string) (EditableCommand, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
msg := &PartCommand{
channels: strings.Split(args[0], ","),
}
if len(args) > 1 {
msg.message = args[1]
}
return msg, nil
}
// PRIVMSG <target> <message>
type PrivMsgCommand struct {
BaseCommand
target string
message string
}
func (cmd *PrivMsgCommand) String() string {
return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message)
}
func NewPrivMsgCommand(args []string) (EditableCommand, error) {
if len(args) < 2 {
return nil, NotEnoughArgsError
}
return &PrivMsgCommand{
target: args[0],
message: args[1],
}, nil
}
func (m *PrivMsgCommand) TargetIsChannel() bool {
switch m.target[0] {
case '&', '#', '+', '!':
return true
}
return false
}
// TOPIC [newtopic]
type TopicCommand struct {
BaseCommand
channel string
topic string
}
func (cmd *TopicCommand) String() string {
return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic)
}
func NewTopicCommand(args []string) (EditableCommand, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
msg := &TopicCommand{
channel: args[0],
}
if len(args) > 1 {
msg.topic = args[1]
}
return msg, nil
}
type ModeCommand struct {
BaseCommand
nickname string
modes string
}
func (cmd *ModeCommand) String() string {
return fmt.Sprintf("MODE(nickname=%s, modes=%s)", cmd.nickname, cmd.modes)
}
func NewModeCommand(args []string) (EditableCommand, error) {
if len(args) == 0 {
return nil, NotEnoughArgsError
}
cmd := &ModeCommand{
nickname: args[0],
}
if len(args) > 1 {
cmd.modes = args[1]
}
return cmd, nil
}

160
irc/constants.go Normal file
View file

@ -0,0 +1,160 @@
package irc
var (
DEBUG_NET = false
DEBUG_CLIENT = false
DEBUG_CHANNEL = false
DEBUG_SERVER = false
)
const (
VERSION = "irc-1"
)
const (
// numeric codes
RPL_WELCOME = 1
RPL_YOURHOST = 2
RPL_CREATED = 3
RPL_MYINFO = 4
RPL_BOUNCE = 5
RPL_TRACELINK = 200
RPL_TRACECONNECTING = 201
RPL_TRACEHANDSHAKE = 202
RPL_TRACEUNKNOWN = 203
RPL_TRACEOPERATOR = 204
RPL_TRACEUSER = 205
RPL_TRACESERVER = 206
RPL_TRACESERVICE = 207
RPL_TRACENEWTYPE = 208
RPL_TRACECLASS = 209
RPL_TRACERECONNECT = 210
RPL_STATSLINKINFO = 211
RPL_STATSCOMMANDS = 212
RPL_ENDOFSTATS = 219
RPL_UMODEIS = 221
RPL_SERVLIST = 234
RPL_SERVLISTEND = 235
RPL_STATSUPTIME = 242
RPL_STATSOLINE = 243
RPL_LUSERCLIENT = 251
RPL_LUSEROP = 252
RPL_LUSERUNKNOWN = 253
RPL_LUSERCHANNELS = 254
RPL_LUSERME = 255
RPL_ADMINME = 256
RPL_ADMINLOC1 = 257
RPL_ADMINLOC2 = 258
RPL_ADMINEMAIL = 259
RPL_TRACELOG = 261
RPL_TRACEEND = 262
RPL_TRYAGAIN = 263
RPL_AWAY = 301
RPL_USERHOST = 302
RPL_ISON = 303
RPL_UNAWAY = 305
RPL_NOWAWAY = 306
RPL_WHOISUSER = 311
RPL_WHOISSERVER = 312
RPL_WHOISOPERATOR = 313
RPL_WHOWASUSER = 314
RPL_ENDOFWHO = 315
RPL_WHOISIDLE = 317
RPL_ENDOFWHOIS = 318
RPL_WHOISCHANNELS = 319
RPL_LIST = 322
RPL_LISTEND = 323
RPL_CHANNELMODEIS = 324
RPL_UNIQOPIS = 325
RPL_NOTOPIC = 331
RPL_TOPIC = 332
RPL_INVITING = 341
RPL_SUMMONING = 342
RPL_INVITELIST = 346
RPL_ENDOFINVITELIST = 347
RPL_EXCEPTLIST = 348
RPL_ENDOFEXCEPTLIST = 349
RPL_VERSION = 351
RPL_WHOREPLY = 352
RPL_NAMREPLY = 353
RPL_LINKS = 364
RPL_ENDOFLINKS = 365
RPL_ENDOFNAMES = 366
RPL_BANLIST = 367
RPL_ENDOFBANLIST = 368
RPL_ENDOFWHOWAS = 369
RPL_INFO = 371
RPL_MOTD = 372
RPL_ENDOFINFO = 374
RPL_MOTDSTART = 375
RPL_ENDOFMOTD = 376
RPL_YOUREOPER = 381
RPL_REHASHING = 382
RPL_YOURESERVICE = 383
RPL_TIME = 391
RPL_USERSSTART = 392
RPL_USERS = 393
RPL_ENDOFUSERS = 394
RPL_NOUSERS = 395
ERR_NOSUCHNICK = 401
ERR_NOSUCHSERVER = 402
ERR_NOSUCHCHANNEL = 403
ERR_CANNOTSENDTOCHAN = 404
ERR_TOOMANYCHANNELS = 405
ERR_WASNOSUCHNICK = 406
ERR_TOOMANYTARGETS = 407
ERR_NOSUCHSERVICE = 408
ERR_NOORIGIN = 409
ERR_NORECIPIENT = 411
ERR_NOTEXTTOSEND = 412
ERR_NOTOPLEVEL = 413
ERR_WILDTOPLEVEL = 414
ERR_BADMASK = 415
ERR_UNKNOWNCOMMAND = 421
ERR_NOMOTD = 422
ERR_NOADMININFO = 423
ERR_FILEERROR = 424
ERR_NONICKNAMEGIVEN = 431
ERR_ERRONEUSNICKNAME = 432
ERR_NICKNAMEINUSE = 433
ERR_NICKCOLLISION = 436
ERR_UNAVAILRESOURCE = 437
ERR_USERNOTINCHANNEL = 441
ERR_NOTONCHANNEL = 442
ERR_USERONCHANNEL = 443
ERR_NOLOGIN = 444
ERR_SUMMONDISABLED = 445
ERR_USERSDISABLED = 446
ERR_NOTREGISTERED = 451
ERR_NEEDMOREPARAMS = 461
ERR_ALREADYREGISTRED = 462
ERR_NOPERMFORHOST = 463
ERR_PASSWDMISMATCH = 464
ERR_YOUREBANNEDCREEP = 465
ERR_YOUWILLBEBANNED = 466
ERR_KEYSET = 467
ERR_CHANNELISFULL = 471
ERR_UNKNOWNMODE = 472
ERR_INVITEONLYCHAN = 473
ERR_BANNEDFROMCHAN = 474
ERR_BADCHANNELKEY = 475
ERR_BADCHANMASK = 476
ERR_NOCHANMODES = 477
ERR_BANLISTFULL = 478
ERR_NOPRIVILEGES = 481
ERR_CHANOPRIVSNEEDED = 482
ERR_CANTKILLSERVER = 483
ERR_RESTRICTED = 484
ERR_UNIQOPPRIVSNEEDED = 485
ERR_NOOPERHOST = 491
ERR_UMODEUNKNOWNFLAG = 501
ERR_USERSDONTMATCH = 502
// message codes
RPL_INVITE = "INVITE"
RPL_JOIN = "JOIN"
RPL_NICK = "NICK"
RPL_PART = "PART"
RPL_PONG = "PONG"
RPL_PRIVMSG = "PRIVMSG"
RPL_QUIT = "QUIT"
)

68
irc/net.go Normal file
View file

@ -0,0 +1,68 @@
package irc
import (
"bufio"
"log"
"net"
"strings"
)
func readTrimmedLine(reader *bufio.Reader) (string, error) {
line, err := reader.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(line), nil
}
// Adapt `net.Conn` to a `chan string`.
func StringReadChan(conn net.Conn) <-chan string {
ch := make(chan string)
reader := bufio.NewReader(conn)
go func() {
for {
line, err := readTrimmedLine(reader)
if err != nil {
break
}
if DEBUG_NET {
log.Printf("%s → %s : %s", conn.RemoteAddr(), conn.LocalAddr(), line)
}
ch <- line
}
close(ch)
}()
return ch
}
func StringWriteChan(conn net.Conn) chan<- string {
ch := make(chan string)
writer := bufio.NewWriter(conn)
go func() {
for str := range ch {
if DEBUG_NET {
log.Printf("%s ← %s : %s", conn.RemoteAddr(), conn.LocalAddr(), str)
}
if _, err := writer.WriteString(str + "\r\n"); err != nil {
break
}
writer.Flush()
}
close(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.LookupHost(ipaddr)
if err != nil {
return ipaddr
}
return names[0]
}

301
irc/reply.go Normal file
View file

@ -0,0 +1,301 @@
package irc
import (
"fmt"
"strings"
"time"
)
type Identifier interface {
Id() string
PublicId() string
Nick() string
}
type Replier interface {
Replies() chan<- Reply
}
type Reply interface {
Format(*Client, chan<- string)
Source() Identifier
}
type BaseReply struct {
source Identifier
message string
}
func (reply *BaseReply) Source() Identifier {
return reply.source
}
type StringReply struct {
*BaseReply
code string
}
func NewStringReply(source Identifier, code string,
format string, args ...interface{}) *StringReply {
message := fmt.Sprintf(format, args...)
fullMessage := fmt.Sprintf(":%s %s %s", source.Id(), code, message)
return &StringReply{
BaseReply: &BaseReply{source, fullMessage},
code: code,
}
}
func (reply *StringReply) Format(client *Client, write chan<- string) {
write <- reply.message
}
func (reply *StringReply) String() string {
return fmt.Sprintf("Reply(source=%s, code=%s, message=%s)",
reply.source, reply.code, reply.message)
}
type NumericReply struct {
*BaseReply
code int
}
func NewNumericReply(source Identifier, code int, format string,
args ...interface{}) *NumericReply {
return &NumericReply{
BaseReply: &BaseReply{source, fmt.Sprintf(format, args...)},
code: code,
}
}
func (reply *NumericReply) Format(client *Client, write chan<- string) {
write <- reply.FormatString(client)
}
func (reply *NumericReply) FormatString(client *Client) string {
return fmt.Sprintf(":%s %03d %s %s", reply.Source().Id(), reply.code,
client.Nick(), reply.message)
}
func (reply *NumericReply) String() string {
return fmt.Sprintf("Reply(source=%s, code=%d, message=%s)",
reply.source, reply.code, reply.message)
}
// names reply
type NamesReply struct {
*BaseReply
channel *Channel
}
func NewNamesReply(channel *Channel) Reply {
return &NamesReply{
BaseReply: &BaseReply{
source: channel,
},
channel: channel,
}
}
const (
MAX_REPLY_LEN = 510 // 512 - CRLF
)
func joinedLen(names []string) int {
var l = len(names) - 1 // " " between names
for _, name := range names {
l += len(name)
}
return l
}
func (reply *NamesReply) Format(client *Client, write chan<- string) {
base := RplNamReply(reply.channel, []string{})
baseLen := len(base.FormatString(client))
tooLong := func(names []string) bool {
return (baseLen + joinedLen(names)) > MAX_REPLY_LEN
}
from, to := 0, 1
nicks := reply.channel.Nicks()
for to < len(nicks) {
if (from < (to - 1)) && tooLong(nicks[from:to]) {
RplNamReply(reply.channel, nicks[from:to-1]).Format(client, write)
from, to = to-1, to
} else {
to += 1
}
}
if from < len(nicks) {
RplNamReply(reply.channel, nicks[from:]).Format(client, write)
}
RplEndOfNames(reply.channel).Format(client, write)
}
func (reply *NamesReply) String() string {
return fmt.Sprintf("NamesReply(channel=%s, names=%s)",
reply.channel, reply.channel.Nicks())
}
// messaging replies
func RplPrivMsg(source Identifier, target Identifier, message string) Reply {
return NewStringReply(source, RPL_PRIVMSG, "%s :%s", target.Nick(), message)
}
func RplNick(source Identifier, newNick string) Reply {
return NewStringReply(source, RPL_NICK, newNick)
}
func RplPrivMsgChannel(channel *Channel, source Identifier, message string) Reply {
return NewStringReply(source, RPL_PRIVMSG, "%s :%s", channel.name, message)
}
func RplJoin(channel *Channel, client *Client) Reply {
return NewStringReply(client, RPL_JOIN, channel.name)
}
func RplPart(channel *Channel, client *Client, message string) Reply {
return NewStringReply(client, RPL_PART, "%s :%s", channel.name, message)
}
func RplPong(server *Server) Reply {
return NewStringReply(server, RPL_PONG, server.Id())
}
func RplQuit(client *Client, message string) Reply {
return NewStringReply(client, RPL_QUIT, ":%s", message)
}
func RplInviteMsg(channel *Channel, inviter *Client) Reply {
return NewStringReply(inviter, RPL_INVITE, channel.name)
}
// numeric replies
func RplWelcome(source Identifier, client *Client) Reply {
return NewNumericReply(source, RPL_WELCOME,
"Welcome to the Internet Relay Network %s", client.Id())
}
func RplYourHost(server *Server) Reply {
return NewNumericReply(server, RPL_YOURHOST,
"Your host is %s, running version %s", server.name, VERSION)
}
func RplCreated(server *Server) Reply {
return NewNumericReply(server, RPL_CREATED,
"This server was created %s", server.ctime.Format(time.RFC1123))
}
func RplMyInfo(server *Server) Reply {
return NewNumericReply(server, RPL_MYINFO,
"%s %s a kn", server.name, VERSION)
}
func RplUModeIs(server *Server, client *Client) Reply {
return NewNumericReply(server, RPL_UMODEIS, client.UModeString())
}
func RplNoTopic(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_NOTOPIC,
"%s :No topic is set", channel.name)
}
func RplTopic(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_TOPIC,
"%s :%s", channel.name, channel.topic)
}
func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
return NewNumericReply(channel.server, RPL_INVITING,
"%s %s", channel.name, invitee.Nick())
}
func RplNamReply(channel *Channel, names []string) *NumericReply {
return NewNumericReply(channel.server, RPL_NAMREPLY, "= %s :%s",
channel.name, strings.Join(names, " "))
}
func RplEndOfNames(channel *Channel) Reply {
return NewNumericReply(channel, RPL_ENDOFNAMES,
"%s :End of NAMES list", channel.name)
}
func RplYoureOper(server *Server) Reply {
return NewNumericReply(server, RPL_YOUREOPER,
":You are now an IRC operator")
}
// errors (also numeric)
func ErrAlreadyRegistered(source Identifier) Reply {
return NewNumericReply(source, ERR_ALREADYREGISTRED,
":You may not reregister")
}
func ErrNickNameInUse(source Identifier, nick string) Reply {
return NewNumericReply(source, ERR_NICKNAMEINUSE,
"%s :Nickname is already in use", nick)
}
func ErrUnknownCommand(source Identifier, command string) Reply {
return NewNumericReply(source, ERR_UNKNOWNCOMMAND,
"%s :Unknown command", command)
}
func ErrUsersDontMatch(source Identifier) Reply {
return NewNumericReply(source, ERR_USERSDONTMATCH,
":Cannot change mode for other users")
}
func ErrNeedMoreParams(source Identifier, command string) Reply {
return NewNumericReply(source, ERR_NEEDMOREPARAMS,
"%s :Not enough parameters", command)
}
func ErrNoSuchChannel(source Identifier, channel string) Reply {
return NewNumericReply(source, ERR_NOSUCHCHANNEL,
"%s :No such channel", channel)
}
func ErrUserOnChannel(channel *Channel, member *Client) Reply {
return NewNumericReply(channel.server, ERR_USERONCHANNEL,
"%s %s :is already on channel", member.nick, channel.name)
}
func ErrNotOnChannel(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_NOTONCHANNEL,
"%s :You're not on that channel", channel.name)
}
func ErrInviteOnlyChannel(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_INVITEONLYCHAN,
"%s :Cannot join channel (+i)", channel.name)
}
func ErrBadChannelKey(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_BADCHANNELKEY,
"%s :Cannot join channel (+k)", channel.name)
}
func ErrNoSuchNick(source Identifier, nick string) Reply {
return NewNumericReply(source, ERR_NOSUCHNICK,
"%s :No such nick/channel", nick)
}
func ErrPasswdMismatch(server *Server) Reply {
return NewNumericReply(server, ERR_PASSWDMISMATCH, ":Password incorrect")
}
func ErrNoChanModes(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_NOCHANMODES,
"%s :Channel doesn't support modes", channel.name)
}
func ErrNoPrivileges(server *Server) Reply {
return NewNumericReply(server, ERR_NOPRIVILEGES, ":Permission Denied")
}
func ErrRestricted(server *Server) Reply {
return NewNumericReply(server, ERR_RESTRICTED, ":Your connection is restricted!")
}

270
irc/server.go Normal file
View file

@ -0,0 +1,270 @@
package irc
import (
"log"
"net"
"time"
)
type ChannelNameMap map[string]*Channel
type ClientNameMap map[string]*Client
type Server struct {
channels ChannelNameMap
commands chan<- Command
ctime time.Time
hostname string
name string
password string
clients ClientNameMap
}
func NewServer(name string) *Server {
commands := make(chan Command)
server := &Server{
ctime: time.Now(),
name: name,
commands: commands,
clients: make(ClientNameMap),
channels: make(ChannelNameMap),
}
go server.receiveCommands(commands)
return server
}
func (server *Server) receiveCommands(commands <-chan Command) {
for command := range commands {
if DEBUG_SERVER {
log.Printf("%s → %s : %s", command.Client(), server, command)
}
command.Client().atime = time.Now()
command.HandleServer(server)
}
}
func (s *Server) Listen(addr string) {
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal("Server.Listen: ", err)
}
s.hostname = LookupHostname(listener.Addr())
if DEBUG_SERVER {
log.Print("Server.Listen: listening on ", addr)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print("Server.Listen: ", err)
continue
}
if DEBUG_SERVER {
log.Print("Server.Listen: accepted ", conn.RemoteAddr())
}
NewClient(s, conn)
}
}
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) interestedClients(fromClient *Client) ClientSet {
clients := make(ClientSet)
clients[fromClient] = true
for channel := range fromClient.channels {
for client := range channel.members {
clients[client] = true
}
}
return clients
}
// server functionality
func (s *Server) tryRegister(c *Client) {
if !c.registered && c.HasNick() && c.HasUsername() && c.serverPass {
c.registered = true
replies := []Reply{
RplWelcome(s, c),
RplYourHost(s),
RplCreated(s),
RplMyInfo(s),
}
for _, reply := range replies {
c.Replies() <- reply
}
}
}
func (s *Server) Id() string {
return s.name
}
func (s *Server) String() string {
return s.Id()
}
func (s *Server) PublicId() string {
return s.Id()
}
func (s *Server) Nick() string {
return s.name
}
func (s *Server) DeleteChannel(channel *Channel) {
delete(s.channels, channel.name)
}
//
// commands
//
func (m *UnknownCommand) HandleServer(s *Server) {
m.Client().Replies() <- ErrUnknownCommand(s, m.command)
}
func (m *PingCommand) HandleServer(s *Server) {
m.Client().Replies() <- RplPong(s)
}
func (m *PongCommand) HandleServer(s *Server) {
// no-op
}
func (m *PassCommand) HandleServer(s *Server) {
if s.password != m.password {
m.Client().Replies() <- ErrPasswdMismatch(s)
// TODO disconnect
return
}
m.Client().serverPass = true
// no reply?
}
func (m *NickCommand) HandleServer(s *Server) {
c := m.Client()
if s.clients[m.nickname] != nil {
c.replies <- ErrNickNameInUse(s, m.nickname)
return
}
reply := RplNick(c, m.nickname)
for iclient := range s.interestedClients(c) {
iclient.replies <- reply
}
if c.HasNick() {
delete(s.clients, c.nick)
}
s.clients[m.nickname] = c
c.nick = m.nickname
s.tryRegister(c)
}
func (m *UserMsgCommand) HandleServer(s *Server) {
c := m.Client()
if c.registered {
c.replies <- ErrAlreadyRegistered(s)
return
}
c.username, c.realname = m.user, m.realname
s.tryRegister(c)
}
func (m *QuitCommand) HandleServer(s *Server) {
c := m.Client()
reply := RplQuit(c, m.message)
for client := range s.interestedClients(c) {
client.replies <- reply
}
cmd := &PartCommand{
BaseCommand: BaseCommand{c},
}
for channel := range c.channels {
channel.commands <- cmd
}
c.conn.Close()
delete(s.clients, c.nick)
}
func (m *JoinCommand) HandleServer(s *Server) {
c := m.Client()
if m.zero {
cmd := &PartCommand{
BaseCommand: BaseCommand{c},
}
for channel := range c.channels {
channel.commands <- cmd
}
return
}
for name := range m.channels {
s.GetOrMakeChannel(name).commands <- m
}
}
func (m *PartCommand) HandleServer(s *Server) {
for _, chname := range m.channels {
channel := s.channels[chname]
if channel == nil {
m.Client().replies <- ErrNoSuchChannel(s, channel.name)
continue
}
channel.commands <- m
}
}
func (m *TopicCommand) HandleServer(s *Server) {
channel := s.channels[m.channel]
if channel == nil {
m.Client().replies <- ErrNoSuchChannel(s, m.channel)
return
}
channel.commands <- m
}
func (m *PrivMsgCommand) HandleServer(s *Server) {
if m.TargetIsChannel() {
channel := s.channels[m.target]
if channel == nil {
m.Client().replies <- ErrNoSuchChannel(s, m.target)
return
}
channel.commands <- m
return
}
target := s.clients[m.target]
if target == nil {
m.Client().replies <- ErrNoSuchNick(s, m.target)
return
}
target.replies <- RplPrivMsg(m.Client(), target, m.message)
}
func (m *ModeCommand) HandleServer(s *Server) {
m.Client().replies <- RplUModeIs(s, m.Client())
}