diff --git a/irc/channel.go b/irc/channel.go index d41cc5a8..247e7cba 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -411,6 +411,46 @@ func (channel *Channel) sendMessage(cmd string, minPrefix *ChannelMode, clientOn } } +// SplitPrivMsg sends a private message to everyone in this channel. +func (channel *Channel) SplitPrivMsg(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { + channel.sendSplitMessage("PRIVMSG", minPrefix, clientOnlyTags, client, message) +} + +// SplitNotice sends a private message to everyone in this channel. +func (channel *Channel) SplitNotice(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { + channel.sendSplitMessage("NOTICE", minPrefix, clientOnlyTags, client, message) +} + +func (channel *Channel) sendSplitMessage(cmd string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { + if !channel.CanSpeak(client) { + client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") + return + } + + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + + // for STATUSMSG + var minPrefixMode ChannelMode + if minPrefix != nil { + minPrefixMode = *minPrefix + } + for member := range channel.members { + if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) { + // STATUSMSG + continue + } + if member == client && !client.capabilities[EchoMessage] { + continue + } + if member.capabilities[MessageTags] { + member.SendSplitMsgFromClient(client, clientOnlyTags, cmd, channel.name, message) + } else { + member.SendSplitMsgFromClient(client, nil, cmd, channel.name, message) + } + } +} + func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, op ModeOp) bool { if !channel.ClientIsAtLeast(client, ChannelOperator) { diff --git a/irc/client.go b/irc/client.go index 70985f85..346c1fff 100644 --- a/irc/client.go +++ b/irc/client.go @@ -142,10 +142,10 @@ func (client *Client) maxlens() (int, int) { maxlenTags = 4096 } if client.capabilities[MaxLine] { - if maxLineTagsLength > maxlenTags { - maxlenTags = maxLineTagsLength + if client.server.limits.LineLen.Tags > maxlenTags { + maxlenTags = client.server.limits.LineLen.Tags } - maxlenRest = maxLineRestLength + maxlenRest = client.server.limits.LineLen.Rest } return maxlenTags, maxlenRest } @@ -496,6 +496,18 @@ func (client *Client) destroy() { } } +// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client. +// Adds account-tag to the line as well. +func (client *Client) SendSplitMsgFromClient(from *Client, tags *map[string]ircmsg.TagValue, command, target string, message SplitMessage) { + if client.capabilities[MaxLine] { + client.SendFromClient(from, tags, from.nickMaskString, command, target, message.ForMaxLine) + } else { + for _, str := range message.For512 { + client.SendFromClient(from, tags, from.nickMaskString, command, target, str) + } + } +} + // SendFromClient sends an IRC line coming from a specific client. // Adds account-tag to the line as well. func (client *Client) SendFromClient(from *Client, tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error { diff --git a/irc/constants.go b/irc/constants.go index c8468cce..771878e5 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -16,9 +16,6 @@ var ( // Ver is the full version of Oragono, used in responses to clients. Ver = fmt.Sprintf("oragono-%s", SemVer) - // Used as the standard maximum line length unless overridden at runtime. - maxLineTagsLength = 512 - maxLineRestLength = 512 // maxLastArgLength is used to simply cap off the final argument when creating general messages where we need to select a limit. // for instance, in MONITOR lists, RPL_ISUPPORT lists, etc. maxLastArgLength = 400 diff --git a/irc/server.go b/irc/server.go index 74c930f5..ed585721 100644 --- a/irc/server.go +++ b/irc/server.go @@ -847,12 +847,76 @@ func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return false } +// func wordWrap(text string, lineWidth int) []string { +// var split []string +// var cache, cacheLastWord string + +// for _, char := range text { +// if char == " " { +// cache += cacheLastWord + char +// continue +// } + +// cacheLastWord += char +// if cache + cacheLastWord == + +// if len(cacheLastWord) >= lineWidth +// } +// } + +// taken from https://gist.github.com/kennwhite/306317d81ab4a885a965e25aa835b8ef +func wordWrap(text string, lineWidth int) []string { + var split []string + words := strings.Fields(text) + if len(words) == 0 { + return split + } + cache := words[0] + spaceLeft := lineWidth - len(cache) + for _, word := range words[1:] { + if len(word)+1 > spaceLeft { + split = append(split, cache) + cache = word + spaceLeft = lineWidth - len(word) + } else { + cache += " " + word + spaceLeft -= 1 + len(word) + } + } + split = append(split, cache) + + return split +} + +// SplitMessage represents a message that's been split for sending. +type SplitMessage struct { + For512 []string + ForMaxLine string +} + +func (server *Server) splitMessage(original string) SplitMessage { + var newSplit SplitMessage + + newSplit.ForMaxLine = original + + if len(original) > 400 { + newSplit.For512 = wordWrap(original, 400) + } else { + newSplit.For512 = []string{original} + } + + return newSplit +} + // PRIVMSG {,} func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { clientOnlyTags := GetClientOnlyTags(msg.Tags) targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] + // split privmsg + splitMsg := server.splitMessage(message) + for i, targetString := range targets { // max of four targets per privmsg if i > maxTargets-1 { @@ -873,7 +937,7 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel") continue } - channel.PrivMsg(lowestPrefix, clientOnlyTags, client, message) + channel.SplitPrivMsg(lowestPrefix, clientOnlyTags, client, splitMsg) } else { target, err = CasefoldName(targetString) user := server.clients.Get(target) @@ -886,7 +950,7 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool if !user.capabilities[MessageTags] { clientOnlyTags = nil } - user.SendFromClient(client, clientOnlyTags, client.nickMaskString, "PRIVMSG", user.nick, message) + user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) if client.capabilities[EchoMessage] { client.SendFromClient(client, clientOnlyTags, client.nickMaskString, "PRIVMSG", user.nick, message) } @@ -1116,7 +1180,7 @@ func (server *Server) rehash() error { } // line lengths cannot be changed after launching the server - if maxLineTagsLength != config.Limits.LineLen.Tags || maxLineRestLength != config.Limits.LineLen.Rest { + if server.limits.LineLen.Tags != config.Limits.LineLen.Tags || server.limits.LineLen.Rest != config.Limits.LineLen.Rest { return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted") } @@ -1376,6 +1440,9 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] + // split privmsg + splitMsg := server.splitMessage(message) + for i, targetString := range targets { // max of four targets per privmsg if i > maxTargets-1 { @@ -1391,7 +1458,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // errors silently ignored with NOTICE as per RFC continue } - channel.Notice(lowestPrefix, clientOnlyTags, client, message) + channel.SplitNotice(lowestPrefix, clientOnlyTags, client, splitMsg) } else { target, err := CasefoldName(targetString) if err != nil { @@ -1406,7 +1473,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if !user.capabilities[MessageTags] { clientOnlyTags = nil } - user.SendFromClient(client, clientOnlyTags, client.nickMaskString, "NOTICE", user.nick, message) + user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg) if client.capabilities[EchoMessage] { client.SendFromClient(client, clientOnlyTags, client.nickMaskString, "NOTICE", user.nick, message) }