3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00

more channel mode parsing and bad listing

This commit is contained in:
Jeremy Latt 2014-02-08 22:06:10 -08:00
parent d370abcd4c
commit 93f4b6859a
7 changed files with 205 additions and 65 deletions

View File

@ -5,6 +5,7 @@ import (
) )
type Channel struct { type Channel struct {
banList []UserMask
commands chan<- ChannelCommand commands chan<- ChannelCommand
key string key string
members ClientSet members ClientSet
@ -47,11 +48,12 @@ func NewChannel(s *Server, name string) *Channel {
commands := make(chan ChannelCommand) commands := make(chan ChannelCommand)
replies := make(chan Reply) replies := make(chan Reply)
channel := &Channel{ channel := &Channel{
name: name, banList: make([]UserMask, 0),
members: make(ClientSet),
server: s,
commands: commands, commands: commands,
members: make(ClientSet),
name: name,
replies: replies, replies: replies,
server: s,
} }
go channel.receiveCommands(commands) go channel.receiveCommands(commands)
go channel.receiveReplies(replies) go channel.receiveReplies(replies)
@ -199,3 +201,20 @@ func (m *TopicCommand) HandleChannel(channel *Channel) {
func (m *PrivMsgCommand) HandleChannel(channel *Channel) { func (m *PrivMsgCommand) HandleChannel(channel *Channel) {
channel.replies <- RplPrivMsg(m.Client(), channel, m.message) channel.replies <- RplPrivMsg(m.Client(), channel, m.message)
} }
func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
client := msg.Client()
for _, modeOp := range msg.modeOps {
switch modeOp.mode {
case BanMask:
// TODO add/remove
for _, banMask := range channel.banList {
client.replies <- RplBanList(channel, banMask)
}
client.replies <- RplEndOfBanList(channel)
}
}
client.replies <- RplChannelModeIs(channel)
}

View File

@ -8,22 +8,17 @@ import (
"unicode/utf8" "unicode/utf8"
) )
type Command interface { type editableCommand interface {
Client() *Client
Source() Identifier
Reply(Reply)
HandleServer(*Server)
}
type EditableCommand interface {
Command Command
SetBase(*Client) SetBase(*Client)
} }
type parseCommandFunc func([]string) (editableCommand, error)
var ( var (
NotEnoughArgsError = errors.New("not enough arguments") NotEnoughArgsError = errors.New("not enough arguments")
ErrParseCommand = errors.New("failed to parse message") ErrParseCommand = errors.New("failed to parse message")
parseCommandFuncs = map[string]func([]string) (EditableCommand, error){ parseCommandFuncs = map[string]parseCommandFunc{
"JOIN": NewJoinCommand, "JOIN": NewJoinCommand,
"MODE": NewModeCommand, "MODE": NewModeCommand,
"NICK": NewNickCommand, "NICK": NewNickCommand,
@ -60,7 +55,7 @@ func (command *BaseCommand) Reply(reply Reply) {
command.client.Replies() <- reply command.client.Replies() <- reply
} }
func ParseCommand(line string) (EditableCommand, error) { func ParseCommand(line string) (editableCommand, error) {
command, args := parseLine(line) command, args := parseLine(line)
constructor := parseCommandFuncs[command] constructor := parseCommandFuncs[command]
if constructor == nil { if constructor == nil {
@ -126,7 +121,7 @@ func (cmd *PingCommand) String() string {
return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2) return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2)
} }
func NewPingCommand(args []string) (EditableCommand, error) { func NewPingCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -151,7 +146,7 @@ func (cmd *PongCommand) String() string {
return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2) return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2)
} }
func NewPongCommand(args []string) (EditableCommand, error) { func NewPongCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -175,7 +170,7 @@ func (cmd *PassCommand) String() string {
return fmt.Sprintf("PASS(password=%s)", cmd.password) return fmt.Sprintf("PASS(password=%s)", cmd.password)
} }
func NewPassCommand(args []string) (EditableCommand, error) { func NewPassCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -195,7 +190,7 @@ func (m *NickCommand) String() string {
return fmt.Sprintf("NICK(nickname=%s)", m.nickname) return fmt.Sprintf("NICK(nickname=%s)", m.nickname)
} }
func NewNickCommand(args []string) (EditableCommand, error) { func NewNickCommand(args []string) (editableCommand, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -219,7 +214,7 @@ func (cmd *UserMsgCommand) String() string {
cmd.user, cmd.mode, cmd.unused, cmd.realname) cmd.user, cmd.mode, cmd.unused, cmd.realname)
} }
func NewUserMsgCommand(args []string) (EditableCommand, error) { func NewUserMsgCommand(args []string) (editableCommand, error) {
if len(args) != 4 { if len(args) != 4 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -246,7 +241,7 @@ func (cmd *QuitCommand) String() string {
return fmt.Sprintf("QUIT(message=%s)", cmd.message) return fmt.Sprintf("QUIT(message=%s)", cmd.message)
} }
func NewQuitCommand(args []string) (EditableCommand, error) { func NewQuitCommand(args []string) (editableCommand, error) {
msg := &QuitCommand{} msg := &QuitCommand{}
if len(args) > 0 { if len(args) > 0 {
msg.message = args[0] msg.message = args[0]
@ -266,7 +261,7 @@ func (cmd *JoinCommand) String() string {
return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero) return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero)
} }
func NewJoinCommand(args []string) (EditableCommand, error) { func NewJoinCommand(args []string) (editableCommand, error) {
msg := &JoinCommand{ msg := &JoinCommand{
channels: make(map[string]string), channels: make(map[string]string),
} }
@ -313,7 +308,7 @@ func (cmd *PartCommand) String() string {
return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message) return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message)
} }
func NewPartCommand(args []string) (EditableCommand, error) { func NewPartCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -338,7 +333,7 @@ func (cmd *PrivMsgCommand) String() string {
return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message) return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message)
} }
func NewPrivMsgCommand(args []string) (EditableCommand, error) { func NewPrivMsgCommand(args []string) (editableCommand, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -364,7 +359,7 @@ func (cmd *TopicCommand) String() string {
return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic) return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic)
} }
func NewTopicCommand(args []string) (EditableCommand, error) { func NewTopicCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -377,20 +372,8 @@ func NewTopicCommand(args []string) (EditableCommand, error) {
return msg, nil return msg, nil
} }
type Mode rune
const (
Away Mode = 'a'
Invisible Mode = 'i'
WallOps Mode = 'w'
Restricted Mode = 'r'
Operator Mode = 'o'
LocalOperator Mode = 'O'
ServerNotice Mode = 's'
)
type ModeChange struct { type ModeChange struct {
mode Mode mode UserMode
add bool // false => remove add bool // false => remove
} }
@ -425,22 +408,68 @@ func stringToRunes(str string) <-chan rune {
return runes return runes
} }
type ChannelModeOp struct {
mode ChannelMode
op ModeOp
arg string
}
func (op *ChannelModeOp) String() string {
return fmt.Sprintf("{%s %s %s}", op.op, op.mode, op.arg)
}
type ChannelModeCommand struct { type ChannelModeCommand struct {
BaseCommand BaseCommand
channel string channel string
modeOps []ChannelModeOp
} }
// MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
func NewChannelModeCommand(args []string) (EditableCommand, error) { func NewChannelModeCommand(args []string) (editableCommand, error) {
cmd := &ChannelModeCommand{ cmd := &ChannelModeCommand{
channel: args[0], channel: args[0],
modeOps: make([]ChannelModeOp, 0),
} }
// TODO implement channel mode changes args = args[1:]
for len(args) > 0 {
modeArg := args[0]
op := List
switch modeArg[0] {
case '+':
op = Add
modeArg = modeArg[1:]
case '-':
op = Remove
modeArg = modeArg[1:]
}
skipArgs := 1
for mode := range stringToRunes(modeArg) {
modeOp := ChannelModeOp{
mode: ChannelMode(mode),
op: op,
}
switch modeOp.mode {
case Key, BanMask, ExceptionMask, InviteMask:
if len(args) > skipArgs {
modeOp.arg = args[skipArgs]
skipArgs += 1
}
}
cmd.modeOps = append(cmd.modeOps, modeOp)
}
args = args[skipArgs:]
}
return cmd, nil return cmd, nil
} }
func (msg *ChannelModeCommand) String() string {
return fmt.Sprintf("MODE(channel=%s, modeOps=%s)", msg.channel, msg.modeOps)
}
// MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) ) // MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
func NewUserModeCommand(args []string) (EditableCommand, error) { func NewUserModeCommand(args []string) (editableCommand, error) {
cmd := &ModeCommand{ cmd := &ModeCommand{
nickname: args[0], nickname: args[0],
changes: make([]ModeChange, changes: make([]ModeChange,
@ -458,7 +487,7 @@ func NewUserModeCommand(args []string) (EditableCommand, error) {
add := sig == '+' add := sig == '+'
for mode := range modeChange { for mode := range modeChange {
cmd.changes[index] = ModeChange{ cmd.changes[index] = ModeChange{
mode: Mode(mode), mode: UserMode(mode),
add: add, add: add,
} }
index += 1 index += 1
@ -468,7 +497,7 @@ func NewUserModeCommand(args []string) (EditableCommand, error) {
return cmd, nil return cmd, nil
} }
func NewModeCommand(args []string) (EditableCommand, error) { func NewModeCommand(args []string) (editableCommand, error) {
if len(args) == 0 { if len(args) == 0 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -487,7 +516,7 @@ type WhoisCommand struct {
} }
// WHOIS [ <target> ] <mask> *( "," <mask> ) // WHOIS [ <target> ] <mask> *( "," <mask> )
func NewWhoisCommand(args []string) (EditableCommand, error) { func NewWhoisCommand(args []string) (editableCommand, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -508,6 +537,10 @@ func NewWhoisCommand(args []string) (EditableCommand, error) {
}, nil }, nil
} }
func (msg *WhoisCommand) String() string {
return fmt.Sprintf("WHOIS(target=%s, masks=%s)", msg.target, msg.masks)
}
type WhoCommand struct { type WhoCommand struct {
BaseCommand BaseCommand
mask string mask string
@ -515,7 +548,7 @@ type WhoCommand struct {
} }
// WHO [ <mask> [ "o" ] ] // WHO [ <mask> [ "o" ] ]
func NewWhoCommand(args []string) (EditableCommand, error) { func NewWhoCommand(args []string) (editableCommand, error) {
cmd := &WhoCommand{} cmd := &WhoCommand{}
if len(args) > 0 { if len(args) > 0 {
@ -528,3 +561,7 @@ func NewWhoCommand(args []string) (EditableCommand, error) {
return cmd, nil return cmd, nil
} }
func (msg *WhoCommand) String() string {
return fmt.Sprintf("WHO(mask=%s, operatorOnly=%s)", msg.mask, msg.operatorOnly)
}

View File

@ -9,9 +9,7 @@ var (
const ( const (
VERSION = "ergonomadic-1" VERSION = "ergonomadic-1"
)
const (
// numeric codes // numeric codes
RPL_WELCOME = 1 RPL_WELCOME = 1
RPL_YOURHOST = 2 RPL_YOURHOST = 2
@ -149,6 +147,7 @@ const (
ERR_NOOPERHOST = 491 ERR_NOOPERHOST = 491
ERR_UMODEUNKNOWNFLAG = 501 ERR_UMODEUNKNOWNFLAG = 501
ERR_USERSDONTMATCH = 502 ERR_USERSDONTMATCH = 502
// message codes // message codes
RPL_ERROR = "ERROR" RPL_ERROR = "ERROR"
RPL_INVITE = "INVITE" RPL_INVITE = "INVITE"
@ -158,4 +157,35 @@ const (
RPL_PONG = "PONG" RPL_PONG = "PONG"
RPL_PRIVMSG = "PRIVMSG" RPL_PRIVMSG = "PRIVMSG"
RPL_QUIT = "QUIT" RPL_QUIT = "QUIT"
List ModeOp = 'l'
Add ModeOp = 'a'
Remove ModeOp = 'r'
Away UserMode = 'a'
Invisible UserMode = 'i'
WallOps UserMode = 'w'
Restricted UserMode = 'r'
Operator UserMode = 'o'
LocalOperator UserMode = 'O'
ServerNotice UserMode = 's'
Anonymous ChannelMode = 'a'
InviteOnly ChannelMode = 'i'
Moderated ChannelMode = 'm'
NoOutside ChannelMode = 'n'
Quiet ChannelMode = 'q'
Private ChannelMode = 'p'
Secret ChannelMode = 's'
ReOp ChannelMode = 'r'
OpOnlyTopic ChannelMode = 't'
Key ChannelMode = 'k'
UserLimit ChannelMode = 'l'
BanMask ChannelMode = 'b'
ExceptionMask ChannelMode = 'e'
InviteMask ChannelMode = 'i'
ChannelCreator UserChannelMode = 'O'
ChannelOperator UserChannelMode = 'o'
Voice UserChannelMode = 'v'
) )

View File

@ -2,56 +2,68 @@ package irc
import ( import (
"bufio" "bufio"
"io"
"log" "log"
"net" "net"
"strings" "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`. // Adapt `net.Conn` to a `chan string`.
func StringReadChan(conn net.Conn) <-chan string { func StringReadChan(conn net.Conn) <-chan string {
ch := make(chan string) ch := make(chan string)
reader := bufio.NewReader(conn) reader := bufio.NewReader(conn)
go func() { go func() {
defer conn.Close()
defer close(ch) defer close(ch)
for { for {
line, err := readTrimmedLine(reader) line, err := reader.ReadString('\n')
if err != nil { if err != nil {
log.Printf("%s → %s error: %s", conn.RemoteAddr(), conn.LocalAddr(), err) if err != io.EOF {
log.Printf("%s → %s error: %s", conn.RemoteAddr(), conn.LocalAddr(), err)
}
break break
} }
if DEBUG_NET { if DEBUG_NET {
log.Printf("%s → %s %s", conn.RemoteAddr(), conn.LocalAddr(), line) log.Printf("%s → %s %s", conn.RemoteAddr(), conn.LocalAddr(), line)
} }
ch <- line
ch <- strings.TrimSpace(line)
} }
}() }()
return ch return ch
} }
const (
CRLF = "\r\n"
)
func maybeLogWriteError(conn net.Conn, err error) bool {
if err != nil {
if err != io.EOF {
log.Printf("%s ← %s error: %s", conn.RemoteAddr(), conn.LocalAddr(), err)
}
return true
}
return false
}
func StringWriteChan(conn net.Conn) chan<- string { func StringWriteChan(conn net.Conn) chan<- string {
ch := make(chan string) ch := make(chan string)
writer := bufio.NewWriter(conn) writer := bufio.NewWriter(conn)
go func() { go func() {
defer conn.Close()
defer close(ch) defer close(ch)
for str := range ch { for str := range ch {
if DEBUG_NET { if DEBUG_NET {
log.Printf("%s ← %s %s", conn.RemoteAddr(), conn.LocalAddr(), str) log.Printf("%s ← %s %s", conn.RemoteAddr(), conn.LocalAddr(), str)
} }
if _, err := writer.WriteString(str + "\r\n"); err != nil { if _, err := writer.WriteString(str); maybeLogWriteError(conn, err) {
log.Printf("%s ← %s error: %s", conn.RemoteAddr(), conn.LocalAddr(), err) break
}
if _, err := writer.WriteString(CRLF); maybeLogWriteError(conn, err) {
break
}
if err := writer.Flush(); maybeLogWriteError(conn, err) {
break break
} }
writer.Flush()
} }
}() }()
return ch return ch

View File

@ -234,8 +234,8 @@ func RplEndOfWhois(server *Server) Reply {
return NewNumericReply(server, RPL_ENDOFWHOIS, ":End of WHOIS list") return NewNumericReply(server, RPL_ENDOFWHOIS, ":End of WHOIS list")
} }
func RplChannelModeIs(server *Server, channel *Channel) Reply { func RplChannelModeIs(channel *Channel) Reply {
return NewNumericReply(server, RPL_CHANNELMODEIS, "%s %s", return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s",
channel.name, channel.ModeString()) channel.name, channel.ModeString())
} }
@ -252,6 +252,15 @@ func RplEndOfWho(server *Server, name string) Reply {
return NewNumericReply(server, RPL_ENDOFWHO, "%s :End of WHO list", name) return NewNumericReply(server, RPL_ENDOFWHO, "%s :End of WHO list", name)
} }
func RplBanList(channel *Channel, ban UserMask) Reply {
return NewNumericReply(channel.server, RPL_BANLIST, "%s %s", channel.name, ban)
}
func RplEndOfBanList(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_ENDOFBANLIST, "%s :End of channel ban list",
channel.name)
}
// errors (also numeric) // errors (also numeric)
func ErrAlreadyRegistered(source Identifier) Reply { func ErrAlreadyRegistered(source Identifier) Reply {

View File

@ -307,8 +307,7 @@ func (msg *ChannelModeCommand) HandleServer(server *Server) {
client.replies <- ErrNoSuchChannel(server, msg.channel) client.replies <- ErrNoSuchChannel(server, msg.channel)
return return
} }
channel.commands <- msg
client.replies <- RplChannelModeIs(server, channel)
} }
func whoChannel(client *Client, server *Server, channel *Channel) { func whoChannel(client *Client, server *Server, channel *Channel) {

34
irc/types.go Normal file
View File

@ -0,0 +1,34 @@
package irc
import (
"fmt"
)
// simple types
type ModeOp rune
type UserMode rune
type ChannelMode rune
type UserChannelMode rune
type Mask string
// interfaces
type Command interface {
Client() *Client
Source() Identifier
Reply(Reply)
HandleServer(*Server)
}
// structs
type UserMask struct {
nickname Mask
username Mask
hostname Mask
}
func (mask *UserMask) String() string {
return fmt.Sprintf("%s!%s@%s", mask.nickname, mask.username, mask.hostname)
}