3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-25 21:39:25 +01:00

implement channel modes and messages properly

This commit is contained in:
Jeremy Latt 2014-02-15 19:49:20 -08:00
parent 0bfa2fb98f
commit 488b2ccf8f
6 changed files with 156 additions and 80 deletions

View File

@ -84,6 +84,14 @@ func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) {
}
}
func IsPrivMsg(reply Reply) bool {
strReply, ok := reply.(*StringReply)
if !ok {
return false
}
return strReply.code == "PRIVMSG"
}
func (channel *Channel) receiveReplies(replies <-chan Reply) {
for reply := range replies {
if channel.destroyed {
@ -98,9 +106,10 @@ func (channel *Channel) receiveReplies(replies <-chan Reply) {
}
channel.mutex.Lock()
for client := range channel.members {
if reply.Source() != Identifier(client) {
client.Reply(reply)
if IsPrivMsg(reply) && (reply.Source() == Identifier(client)) {
continue
}
client.Reply(reply)
}
channel.mutex.Unlock()
}
@ -187,9 +196,7 @@ func (channel *Channel) Join(client *Client) {
channel.mutex.Unlock()
client.channels.Add(channel)
reply := RplJoin(client, channel)
client.Reply(reply)
channel.Reply(reply)
channel.Reply(RplJoin(client, channel))
channel.GetTopic(client)
channel.GetUsers(client)
}
@ -216,9 +223,7 @@ func (m *PartCommand) HandleChannel(channel *Channel) {
return
}
reply := RplPart(client, channel, m.Message())
client.Reply(reply)
channel.Reply(reply)
channel.Reply(RplPart(client, channel, m.Message()))
channel.members.Remove(client)
client.channels.Remove(channel)
@ -245,9 +250,7 @@ func (m *TopicCommand) HandleChannel(channel *Channel) {
channel.topic = m.topic
channel.GetTopic(client)
reply := RplTopicMsg(client, channel)
client.Reply(reply)
channel.Reply(reply)
channel.Reply(RplTopicMsg(client, channel))
return
}
@ -267,10 +270,18 @@ func (m *PrivMsgCommand) HandleChannel(channel *Channel) {
func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
client := msg.Client()
for _, modeOp := range msg.modeOps {
switch modeOp.mode {
if len(msg.changes) == 0 {
client.Reply(RplChannelModeIs(channel))
return
}
changes := make(ChannelModeChanges, 0)
for _, change := range msg.changes {
switch change.mode {
case BanMask:
// TODO add/remove
for _, banMask := range channel.banList {
client.Reply(RplBanList(channel, banMask))
}
@ -282,12 +293,14 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
continue
}
switch modeOp.op {
switch change.op {
case Add:
channel.flags[modeOp.mode] = true
channel.flags[change.mode] = true
changes = append(changes, change)
case Remove:
delete(channel.flags, modeOp.mode)
delete(channel.flags, change.mode)
changes = append(changes, change)
}
case Key:
@ -296,34 +309,33 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
continue
}
switch modeOp.op {
switch change.op {
case Add:
if modeOp.arg == "" {
if change.arg == "" {
// TODO err reply
continue
}
channel.key = modeOp.arg
channel.key = change.arg
changes = append(changes, change)
case Remove:
channel.key = ""
changes = append(changes, change)
}
}
mmode := ChannelMemberMode(modeOp.mode)
switch mmode {
case ChannelOperator, Voice:
if !channel.ClientIsOperator(client) {
client.Reply(ErrChanOPrivIsNeeded(channel))
continue
}
if modeOp.arg == "" {
if change.arg == "" {
// TODO err reply
continue
}
target := channel.server.clients[modeOp.arg]
target := channel.server.clients[change.arg]
if target == nil {
// TODO err reply
continue
@ -334,16 +346,21 @@ func (msg *ChannelModeCommand) HandleChannel(channel *Channel) {
continue
}
switch modeOp.op {
switch change.op {
case Add:
channel.members[target][mmode] = true
channel.members[target][change.mode] = true
changes = append(changes, change)
case Remove:
channel.members[target][mmode] = false
channel.members[target][change.mode] = false
changes = append(changes, change)
}
}
}
client.Reply(RplChannelModeIs(channel))
if len(changes) > 0 {
channel.Reply(RplChannelMode(client, channel, changes))
}
}
func (m *NoticeCommand) HandleChannel(channel *Channel) {

View File

@ -402,6 +402,26 @@ func (change *ModeChange) String() string {
return fmt.Sprintf("%s%s", change.op, change.mode)
}
type ModeChanges []ModeChange
func (changes ModeChanges) String() string {
if len(changes) == 0 {
return ""
}
op := changes[0].op
str := changes[0].op.String()
for _, change := range changes {
if change.op == op {
str += change.mode.String()
} else {
op = change.op
str += " " + change.op.String()
}
}
return str
}
type ModeCommand struct {
BaseCommand
nickname string
@ -436,27 +456,47 @@ func (cmd *ModeCommand) String() string {
return fmt.Sprintf("MODE(nickname=%s, changes=%s)", cmd.nickname, cmd.changes)
}
type ChannelModeOp struct {
type ChannelModeChange struct {
mode ChannelMode
op ModeOp
arg string
}
func (op *ChannelModeOp) String() string {
func (op *ChannelModeChange) String() string {
return fmt.Sprintf("{%s %s %s}", op.op, op.mode, op.arg)
}
type ChannelModeChanges []ChannelModeChange
func (changes ChannelModeChanges) String() string {
if len(changes) == 0 {
return ""
}
str := "+"
if changes[0].op == Remove {
str = "-"
}
for _, change := range changes {
str += change.mode.String()
}
for _, change := range changes {
str += " " + change.arg
}
return str
}
type ChannelModeCommand struct {
BaseCommand
channel string
modeOps []ChannelModeOp
changes ChannelModeChanges
}
// MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
func NewChannelModeCommand(args []string) (editableCommand, error) {
cmd := &ChannelModeCommand{
channel: args[0],
modeOps: make([]ChannelModeOp, 0),
changes: make(ChannelModeChanges, 0),
}
args = args[1:]
@ -472,18 +512,18 @@ func NewChannelModeCommand(args []string) (editableCommand, error) {
skipArgs := 1
for _, mode := range modeArg {
modeOp := ChannelModeOp{
change := ChannelModeChange{
mode: ChannelMode(mode),
op: op,
}
switch modeOp.mode {
switch change.mode {
case Key, BanMask, ExceptionMask, InviteMask, UserLimit:
if len(args) > skipArgs {
modeOp.arg = args[skipArgs]
change.arg = args[skipArgs]
skipArgs += 1
}
}
cmd.modeOps = append(cmd.modeOps, modeOp)
cmd.changes = append(cmd.changes, change)
}
args = args[skipArgs:]
}
@ -492,7 +532,7 @@ func NewChannelModeCommand(args []string) (editableCommand, error) {
}
func (msg *ChannelModeCommand) String() string {
return fmt.Sprintf("MODE(channel=%s, modeOps=%s)", msg.channel, msg.modeOps)
return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes)
}
func NewModeCommand(args []string) (editableCommand, error) {

View File

@ -173,24 +173,23 @@ const (
ServerNotice UserMode = 's'
WallOps UserMode = 'w'
Anonymous ChannelMode = 'a' // flag
BanMask ChannelMode = 'b' // arg
ExceptionMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag
UserLimit ChannelMode = 'l' // flag arg
ChannelCreator ChannelMemberMode = 'O' // flag
ChannelOperator ChannelMemberMode = 'o' // arg
Voice ChannelMemberMode = 'v' // arg
Anonymous ChannelMode = 'a' // flag
BanMask ChannelMode = 'b' // arg
ChannelCreator ChannelMode = 'O' // flag
ChannelOperator ChannelMode = 'o' // arg
ExceptionMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag
UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg
)
const (

View File

@ -144,11 +144,20 @@ func RplJoin(client *Client, channel *Channel) Reply {
}
func RplPart(client *Client, channel *Channel, message string) Reply {
return NewStringReply(client, "PART", "%s :%s", channel.name, message)
return NewStringReply(client, "PART", "%s :%s", channel, message)
}
func RplMode(client *Client, changes ModeChanges) Reply {
return NewStringReply(client, "MODE", "%s :%s", client.Nick(), changes)
}
func RplChannelMode(client *Client, channel *Channel,
changes ChannelModeChanges) Reply {
return NewStringReply(client, "MODE", "%s %s", channel, changes)
}
func RplTopicMsg(source Identifier, channel *Channel) Reply {
return NewStringReply(source, "TOPIC", "%s :%s", channel.name, channel.topic)
return NewStringReply(source, "TOPIC", "%s :%s", channel, channel.topic)
}
func RplPing(server *Server, target Identifier) Reply {
@ -207,9 +216,11 @@ func RplTopic(channel *Channel) Reply {
"%s :%s", channel.name, channel.topic)
}
// <nick> <channel>
// NB: correction in errata
func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
return NewNumericReply(channel.server, RPL_INVITING,
"%s %s", channel.name, invitee.Nick())
"%s %s", invitee.Nick(), channel.name)
}
func RplNamReply(channel *Channel, names []string) *NumericReply {
@ -238,7 +249,7 @@ func RplEndOfWhois(server *Server) Reply {
func RplChannelModeIs(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s",
channel.name, channel.ModeString())
channel, channel.ModeString())
}
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]

View File

@ -270,7 +270,6 @@ func (m *NickCommand) HandleRegServer(s *Server) {
client.nick = m.nickname
s.clients.Add(client)
client.Reply(RplNick(client, m.nickname))
s.tryRegister(client)
}
@ -414,22 +413,31 @@ func (m *PrivMsgCommand) HandleServer(s *Server) {
func (m *ModeCommand) HandleServer(s *Server) {
client := m.Client()
target := s.clients[m.nickname]
if client == target {
for _, change := range m.changes {
if change.mode == Invisible {
switch change.op {
case Add:
client.invisible = true
case Remove:
client.invisible = false
}
}
}
client.Reply(RplUModeIs(s, client))
// TODO other auth
if client != target {
client.Reply(ErrUsersDontMatch(s))
return
}
client.Reply(ErrUsersDontMatch(s))
changes := make(ModeChanges, 0)
for _, change := range m.changes {
if change.mode == Invisible {
switch change.op {
case Add:
client.invisible = true
changes = append(changes, change)
case Remove:
client.invisible = false
changes = append(changes, change)
}
}
}
if len(changes) > 0 {
client.Reply(RplMode(client, changes))
}
}
func (m *WhoisCommand) HandleServer(server *Server) {

View File

@ -15,6 +15,10 @@ type Mask string
// add, remove, list modes
type ModeOp rune
func (op ModeOp) String() string {
return string(op)
}
// user mode flags
type UserMode rune
@ -37,9 +41,6 @@ func (mode ChannelMode) String() string {
return fmt.Sprintf("%c", mode)
}
// user-channel mode flags
type ChannelMemberMode rune
type ChannelNameMap map[string]*Channel
func (channels ChannelNameMap) Add(channel *Channel) error {
@ -84,19 +85,19 @@ func (clients ClientNameMap) Remove(client *Client) error {
return nil
}
type ChannelMemberModeSet map[ChannelMemberMode]bool
type ChannelModeSet map[ChannelMode]bool
type ClientSet map[*Client]ChannelMemberModeSet
type ClientSet map[*Client]ChannelModeSet
func (clients ClientSet) Add(client *Client) {
clients[client] = make(ChannelMemberModeSet)
clients[client] = make(ChannelModeSet)
}
func (clients ClientSet) Remove(client *Client) {
delete(clients, client)
}
func (clients ClientSet) HasMode(client *Client, mode ChannelMemberMode) bool {
func (clients ClientSet) HasMode(client *Client, mode ChannelMode) bool {
modes, ok := clients[client]
if !ok {
return false