hid: port PART, KICK, INVITE, JOIN, AWAY, ISON, ADMIN, DIE
This commit is contained in:
		
							
								
								
									
										340
									
								
								hid/main.go
									
									
									
									
									
								
							
							
						
						
									
										340
									
								
								hid/main.go
									
									
									
									
									
								
							@@ -556,14 +556,40 @@ func initiateQuit() {
 | 
			
		||||
	quitting = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: ircChannelCreate
 | 
			
		||||
 | 
			
		||||
func ircChannelDestroyIfEmpty(ch *channel) {
 | 
			
		||||
	// TODO
 | 
			
		||||
func ircChannelCreate(name string) *channel {
 | 
			
		||||
	ch := &channel{
 | 
			
		||||
		name:      name,
 | 
			
		||||
		created:   time.Now().UnixNano(),
 | 
			
		||||
		userLimit: -1,
 | 
			
		||||
	}
 | 
			
		||||
	channels[ircToCanon(name)] = ch
 | 
			
		||||
	return ch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircChannelDestroyIfEmpty(ch *channel) {
 | 
			
		||||
	if len(ch.userModes) == 0 {
 | 
			
		||||
		delete(channels, ircToCanon(ch.name))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Improve the name as it takes mode +q into account.
 | 
			
		||||
func ircSendToRoommates(c *client, message string) {
 | 
			
		||||
	// TODO
 | 
			
		||||
	targets := make(map[*client]bool)
 | 
			
		||||
	for _, ch := range channels {
 | 
			
		||||
		_, present := ch.userModes[c]
 | 
			
		||||
		if !present || 0 != ch.modes&ircChanModeQuiet {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for client := range ch.userModes {
 | 
			
		||||
			targets[client] = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for roommate := range targets {
 | 
			
		||||
		if roommate != c {
 | 
			
		||||
			roommate.send(message)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Clients (continued) -----------------------------------------------------
 | 
			
		||||
@@ -1662,13 +1688,248 @@ func ircHandleTOPIC(msg *message, c *client) {
 | 
			
		||||
	ircChannelMulticast(ch, message, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: All the various real command handlers.
 | 
			
		||||
func ircTryPart(c *client, target string, reason string) {
 | 
			
		||||
	if reason == "" {
 | 
			
		||||
		reason = c.nickname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func ircHandleX(msg *message, c *client) {
 | 
			
		||||
	ch := channels[ircToCanon(target)]
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, present := ch.userModes[c]; !present {
 | 
			
		||||
		c.sendReply(ERR_NOTONCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message := fmt.Sprintf(":%s@%s@%s PART %s :%s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, target, reason)
 | 
			
		||||
	if 0 == ch.modes&ircChanModeQuiet {
 | 
			
		||||
		ircChannelMulticast(ch, message, nil)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.send(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(ch.userModes, c)
 | 
			
		||||
	ircChannelDestroyIfEmpty(ch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircPartAllChannels(c *client) {
 | 
			
		||||
	for _, ch := range channels {
 | 
			
		||||
		if _, present := ch.userModes[c]; present {
 | 
			
		||||
			ircTryPart(c, ch.name, "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandlePART(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reason := ""
 | 
			
		||||
	if len(msg.params) > 1 {
 | 
			
		||||
		reason = msg.params[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, target := range splitString(msg.params[0], ",", true) {
 | 
			
		||||
		ircTryPart(c, target, reason)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Undo the rename from channelName to target, also in ircTryPart.
 | 
			
		||||
func ircTryKick(c *client, target, nick, reason string) {
 | 
			
		||||
	ch := channels[ircToCanon(target)]
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if modes, present := ch.userModes[c]; !present {
 | 
			
		||||
		c.sendReply(ERR_NOTONCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	} else if 0 == modes&ircChanModeOperator {
 | 
			
		||||
		c.sendReply(ERR_CHANOPRIVSNEEDED, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := users[ircToCanon(nick)]
 | 
			
		||||
	if _, present := ch.userModes[client]; client == nil || !present {
 | 
			
		||||
		c.sendReply(ERR_USERNOTINCHANNEL, nick, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message := fmt.Sprintf(":%s@%s@%s KICK %s %s :%s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, target, nick, reason)
 | 
			
		||||
	if 0 == ch.modes&ircChanModeQuiet {
 | 
			
		||||
		ircChannelMulticast(ch, message, nil)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.send(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(ch.userModes, client)
 | 
			
		||||
	ircChannelDestroyIfEmpty(ch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleKICK(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 2 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reason := c.nickname
 | 
			
		||||
	if len(msg.params) > 2 {
 | 
			
		||||
		reason = msg.params[2]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	targetChannels := splitString(msg.params[0], ",", true)
 | 
			
		||||
	targetUsers := splitString(msg.params[1], ",", true)
 | 
			
		||||
 | 
			
		||||
	if len(channels) == 1 {
 | 
			
		||||
		for i := 0; i < len(targetUsers); i++ {
 | 
			
		||||
			ircTryKick(c, targetChannels[0], targetUsers[i], reason)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for i := 0; i < len(channels) && i < len(targetUsers); i++ {
 | 
			
		||||
			ircTryKick(c, targetChannels[i], targetUsers[i], reason)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendInviteNotifications(ch *channel, c, target *client) {
 | 
			
		||||
	for client := range ch.userModes {
 | 
			
		||||
		if client != target && 0 != client.capsEnabled&ircCapInviteNotify {
 | 
			
		||||
			client.sendf(":%s!%s@%s INVITE %s %s",
 | 
			
		||||
				c.nickname, c.username, c.hostname, target.nickname, ch.name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleINVITE(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 2 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target, channelName := msg.params[0], msg.params[1]
 | 
			
		||||
	client := users[ircToCanon(target)]
 | 
			
		||||
	if client == nil {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHNICK, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ch := channels[ircToCanon(channelName)]; ch != nil {
 | 
			
		||||
		invitingModes, invitingPresent := ch.userModes[c]
 | 
			
		||||
		if !invitingPresent {
 | 
			
		||||
			c.sendReply(ERR_NOTONCHANNEL, channelName)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if _, present := ch.userModes[client]; present {
 | 
			
		||||
			c.sendReply(ERR_USERONCHANNEL, target, channelName)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if 0 != invitingModes&ircChanModeOperator {
 | 
			
		||||
			client.invites[ircToCanon(channelName)] = true
 | 
			
		||||
		} else if 0 != ch.modes&ircChanModeInviteOnly {
 | 
			
		||||
			c.sendReply(ERR_CHANOPRIVSNEEDED, channelName)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// It's not specified when and how we should send out invite-notify.
 | 
			
		||||
		if 0 != ch.modes&ircChanModeInviteOnly {
 | 
			
		||||
			ircSendInviteNotifications(ch, c, client)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client.sendf(":%s!%s@%s INVITE %s %s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, client.nickname, channelName)
 | 
			
		||||
	if client.awayMessage != "" {
 | 
			
		||||
		c.sendReply(RPL_AWAY, client.nickname, client.awayMessage)
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_INVITING, client.nickname, channelName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircTryJoin(c *client, channelName, key string) {
 | 
			
		||||
	ch := channels[ircToCanon(channelName)]
 | 
			
		||||
	var userMode uint
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		if !ircIsValidChannelName(channelName) {
 | 
			
		||||
			c.sendReply(ERR_BADCHANMASK, channelName)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ch = ircChannelCreate(channelName)
 | 
			
		||||
		userMode = ircChanModeOperator
 | 
			
		||||
	} else if _, present := ch.userModes[c]; present {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, invitedByChanop := c.invites[ircToCanon(channelName)]
 | 
			
		||||
	if 0 != ch.modes&ircChanModeInviteOnly && c.inMaskList(ch.inviteList) &&
 | 
			
		||||
		!invitedByChanop {
 | 
			
		||||
		c.sendReply(ERR_INVITEONLYCHAN, channelName)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ch.key != "" && (key == "" || key != ch.key) {
 | 
			
		||||
		c.sendReply(ERR_BADCHANNELKEY, channelName)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ch.userLimit != -1 && len(ch.userModes) >= ch.userLimit {
 | 
			
		||||
		c.sendReply(ERR_CHANNELISFULL, channelName)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if c.inMaskList(ch.banList) && !c.inMaskList(ch.exceptionList) &&
 | 
			
		||||
		!invitedByChanop {
 | 
			
		||||
		c.sendReply(ERR_BANNEDFROMCHAN, channelName)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Destroy any invitation as there's no other way to get rid of it.
 | 
			
		||||
	delete(c.invites, ircToCanon(channelName))
 | 
			
		||||
 | 
			
		||||
	ch.userModes[c] = userMode
 | 
			
		||||
 | 
			
		||||
	message := fmt.Sprintf(":%s!%s@%s JOIN %s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, channelName)
 | 
			
		||||
	if 0 == ch.modes&ircChanModeQuiet {
 | 
			
		||||
		ircChannelMulticast(ch, message, nil)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.send(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ircSendRPLTOPIC(c, ch)
 | 
			
		||||
	ircSendRPLNAMREPLY(c, ch, nil)
 | 
			
		||||
	c.sendReply(RPL_ENDOFNAMES, ch.name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleJOIN(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if msg.params[0] == "0" {
 | 
			
		||||
		ircPartAllChannels(c)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	targetChannels := splitString(msg.params[0], ",", true)
 | 
			
		||||
 | 
			
		||||
	var keys []string
 | 
			
		||||
	if len(msg.params) > 1 {
 | 
			
		||||
		keys = splitString(msg.params[1], ",", true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, name := range targetChannels {
 | 
			
		||||
		key := ""
 | 
			
		||||
		if i < len(keys) {
 | 
			
		||||
			key = keys[i]
 | 
			
		||||
		}
 | 
			
		||||
		ircTryJoin(c, name, key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleSUMMON(msg *message, c *client) {
 | 
			
		||||
@@ -1679,11 +1940,63 @@ func ircHandleUSERS(msg *message, c *client) {
 | 
			
		||||
	c.sendReply(ERR_USERSDISABLED)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleAWAY(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.awayMessage = ""
 | 
			
		||||
		c.sendReply(RPL_UNAWAY)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.awayMessage = msg.params[0]
 | 
			
		||||
		c.sendReply(RPL_NOWAWAY)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleISON(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var on []string
 | 
			
		||||
	for _, nick := range msg.params {
 | 
			
		||||
		if client := users[ircToCanon(nick)]; client != nil {
 | 
			
		||||
			on = append(on, nick)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_ISON, strings.Join(on, " "))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleADMIN(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) > 0 && !isThisMe(msg.params[0]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(ERR_NOADMININFO, serverName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: All the remaining command handlers.
 | 
			
		||||
 | 
			
		||||
func ircHandleX(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleDIE(msg *message, c *client) {
 | 
			
		||||
	if 0 == c.mode&ircUserModeOperator {
 | 
			
		||||
		c.sendReply(ERR_NOPRIVILEGES)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !quitting {
 | 
			
		||||
		initiateQuit()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// TODO: Add an index for IRC_ERR_NOSUCHSERVER validation?
 | 
			
		||||
// TODO: Add a minimal parameter count?
 | 
			
		||||
// TODO: Add a field for oper-only commands?
 | 
			
		||||
// TODO: Add a field for oper-only commands? Use flags?
 | 
			
		||||
var ircHandlers = map[string]*ircCommand{
 | 
			
		||||
	"CAP":  {false, ircHandleCAP, 0, 0},
 | 
			
		||||
	"PASS": {false, ircHandlePASS, 0, 0},
 | 
			
		||||
@@ -1700,16 +2013,25 @@ var ircHandlers = map[string]*ircCommand{
 | 
			
		||||
	"VERSION":  {true, ircHandleVERSION, 0, 0},
 | 
			
		||||
	"USERS":    {true, ircHandleUSERS, 0, 0},
 | 
			
		||||
	"SUMMON":   {true, ircHandleSUMMON, 0, 0},
 | 
			
		||||
	"AWAY":     {true, ircHandleAWAY, 0, 0},
 | 
			
		||||
	"ADMIN":    {true, ircHandleADMIN, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"MODE":    {true, ircHandleMODE, 0, 0},
 | 
			
		||||
	"PRIVMSG": {true, ircHandlePRIVMSG, 0, 0},
 | 
			
		||||
	"NOTICE":  {true, ircHandleNOTICE, 0, 0},
 | 
			
		||||
	"JOIN":    {true, ircHandleJOIN, 0, 0},
 | 
			
		||||
	"PART":    {true, ircHandlePART, 0, 0},
 | 
			
		||||
	"KICK":    {true, ircHandleKICK, 0, 0},
 | 
			
		||||
	"INVITE":  {true, ircHandleINVITE, 0, 0},
 | 
			
		||||
	"TOPIC":   {true, ircHandleTOPIC, 0, 0},
 | 
			
		||||
	"LIST":    {true, ircHandleLIST, 0, 0},
 | 
			
		||||
	"NAMES":   {true, ircHandleNAMES, 0, 0},
 | 
			
		||||
	"WHO":     {true, ircHandleWHO, 0, 0},
 | 
			
		||||
	"WHOIS":   {true, ircHandleWHOIS, 0, 0},
 | 
			
		||||
	"WHOWAS":  {true, ircHandleWHOWAS, 0, 0},
 | 
			
		||||
	"ISON":    {true, ircHandleISON, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"DIE": {true, ircHandleDIE, 0, 0},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircProcessMessage(c *client, msg *message, raw string) {
 | 
			
		||||
@@ -1739,7 +2061,7 @@ func ircProcessMessage(c *client, msg *message, raw string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- ? -----------------------------------------------------------------------
 | 
			
		||||
// --- Network I/O -------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// Handle the results from initializing the client's connection.
 | 
			
		||||
func (c *client) onPrepared(host string, isTLS bool) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user