mirror of
https://github.com/ergochat/ergo.git
synced 2025-12-20 02:00:11 -08:00
organize like a proper go package
This commit is contained in:
parent
f04dd7c5d5
commit
b9cb539219
13 changed files with 57 additions and 123 deletions
180
irc/channel.go
Normal file
180
irc/channel.go
Normal 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
121
irc/client.go
Normal 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
398
irc/commands.go
Normal 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
160
irc/constants.go
Normal 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
68
irc/net.go
Normal 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
301
irc/reply.go
Normal 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
270
irc/server.go
Normal 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())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue