diff options
| author | Přemysl Janouch <p@janouch.name> | 2018-07-29 17:49:57 +0200 | 
|---|---|---|
| committer | Přemysl Janouch <p@janouch.name> | 2018-07-29 17:49:57 +0200 | 
| commit | 5429c0b09d9821490fac3343dc9091323de3eae2 (patch) | |
| tree | cd204533726f5c46032e8fff87047fa8d90d70f4 | |
| parent | b28c20a250158937e530a8175280e11c6329adf0 (diff) | |
| download | haven-5429c0b09d9821490fac3343dc9091323de3eae2.tar.gz haven-5429c0b09d9821490fac3343dc9091323de3eae2.tar.xz haven-5429c0b09d9821490fac3343dc9091323de3eae2.zip | |
hid: port PART, KICK, INVITE, JOIN, AWAY, ISON, ADMIN, DIE
| -rw-r--r-- | hid/main.go | 336 | 
1 files changed, 329 insertions, 7 deletions
| diff --git a/hid/main.go b/hid/main.go index 75f4231..4c9176b 100644 --- a/hid/main.go +++ b/hid/main.go @@ -556,14 +556,40 @@ func initiateQuit() {  	quitting = true  } -// TODO: ircChannelCreate +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) { -	// TODO +	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) { | 
