mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 10:42:52 +01:00
Merge pull request #442 from slingamn/message_tags.5
upgrade message-tags to non-draft version
This commit is contained in:
commit
baa7e5af0b
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -27,7 +27,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6bcd7bcd5e14cc9552fbf83b2f77f24935c0c502d009c9825b6c212c3f8eb967"
|
||||
digest = "1:e6ed6eaa63211bb90847d8c5f11d7412e56c96b5befb7402ee7a7a8ad02700ec"
|
||||
name = "github.com/goshuirc/irc-go"
|
||||
packages = [
|
||||
"ircfmt",
|
||||
@ -35,7 +35,7 @@
|
||||
"ircmsg",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "cf199aea7186fd960d0ed5abbf579bb0f9d890d1"
|
||||
revision = "ca74bf6a176d2d1dce6f28f99901a2d48d8da2bd"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
||||
|
@ -83,15 +83,15 @@ CAPDEFS = [
|
||||
),
|
||||
CapDef(
|
||||
identifier="MaxLine",
|
||||
name="oragono.io/maxline",
|
||||
url="https://oragono.io/maxline",
|
||||
name="oragono.io/maxline-2",
|
||||
url="https://oragono.io/maxline-2",
|
||||
standard="Oragono-specific",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MessageTags",
|
||||
name="draft/message-tags-0.2",
|
||||
url="https://ircv3.net/specs/core/message-tags-3.3.html",
|
||||
standard="draft IRCv3",
|
||||
name="message-tags",
|
||||
url="https://ircv3.net/specs/extensions/message-tags.html",
|
||||
standard="IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MultiPrefix",
|
||||
|
@ -57,12 +57,12 @@ const (
|
||||
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
||||
Languages Capability = iota
|
||||
|
||||
// MaxLine is the Oragono-specific capability named "oragono.io/maxline":
|
||||
// https://oragono.io/maxline
|
||||
// MaxLine is the Oragono-specific capability named "oragono.io/maxline-2":
|
||||
// https://oragono.io/maxline-2
|
||||
MaxLine Capability = iota
|
||||
|
||||
// MessageTags is the draft IRCv3 capability named "draft/message-tags-0.2":
|
||||
// https://ircv3.net/specs/core/message-tags-3.3.html
|
||||
// MessageTags is the IRCv3 capability named "message-tags":
|
||||
// https://ircv3.net/specs/extensions/message-tags.html
|
||||
MessageTags Capability = iota
|
||||
|
||||
// MultiPrefix is the IRCv3 capability named "multi-prefix":
|
||||
@ -112,8 +112,8 @@ var (
|
||||
"invite-notify",
|
||||
"draft/labeled-response",
|
||||
"draft/languages",
|
||||
"oragono.io/maxline",
|
||||
"draft/message-tags-0.2",
|
||||
"oragono.io/maxline-2",
|
||||
"message-tags",
|
||||
"multi-prefix",
|
||||
"draft/rename",
|
||||
"draft/resume-0.3",
|
||||
|
135
irc/channel.go
135
irc/channel.go
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/goshuirc/irc-go/ircmsg"
|
||||
"github.com/oragono/oragono/irc/caps"
|
||||
"github.com/oragono/oragono/irc/history"
|
||||
"github.com/oragono/oragono/irc/modes"
|
||||
@ -425,11 +424,13 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
|
||||
channel.regenerateMembersCache()
|
||||
|
||||
message := utils.SplitMessage{}
|
||||
message.Msgid = details.realname
|
||||
channel.history.Add(history.Item{
|
||||
Type: history.Join,
|
||||
Nick: details.nickMask,
|
||||
AccountName: details.accountName,
|
||||
Msgid: details.realname,
|
||||
Message: message,
|
||||
})
|
||||
|
||||
return
|
||||
@ -603,16 +604,17 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
||||
serverTime := client.capabilities.Has(caps.ServerTime)
|
||||
|
||||
for _, item := range items {
|
||||
var tags Tags
|
||||
var tags map[string]string
|
||||
if serverTime {
|
||||
tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat))
|
||||
tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
|
||||
}
|
||||
|
||||
// TODO(#437) support history.Tagmsg
|
||||
switch item.Type {
|
||||
case history.Privmsg:
|
||||
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message)
|
||||
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message)
|
||||
case history.Notice:
|
||||
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message)
|
||||
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message)
|
||||
case history.Join:
|
||||
nick := stripMaskFromNick(item.Nick)
|
||||
var message string
|
||||
@ -624,16 +626,16 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
||||
rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
|
||||
case history.Part:
|
||||
nick := stripMaskFromNick(item.Nick)
|
||||
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Original)
|
||||
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
|
||||
rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
|
||||
case history.Quit:
|
||||
nick := stripMaskFromNick(item.Nick)
|
||||
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Original)
|
||||
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
|
||||
rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
|
||||
case history.Kick:
|
||||
nick := stripMaskFromNick(item.Nick)
|
||||
// XXX Msgid is the kick target
|
||||
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Msgid, item.Message.Original)
|
||||
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Message.Msgid, item.Message.Message)
|
||||
rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
|
||||
}
|
||||
}
|
||||
@ -717,13 +719,20 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// TagMsg sends a tag message to everyone in this channel who can accept them.
|
||||
func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, rb *ResponseBuffer) {
|
||||
channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil, rb)
|
||||
}
|
||||
func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||
var histType history.ItemType
|
||||
switch command {
|
||||
case "PRIVMSG":
|
||||
histType = history.Privmsg
|
||||
case "NOTICE":
|
||||
histType = history.Notice
|
||||
case "TAGMSG":
|
||||
histType = history.Tagmsg
|
||||
default:
|
||||
channel.server.logger.Error("internal", "unrecognized Channel.SendSplitMessage command", command)
|
||||
return
|
||||
}
|
||||
|
||||
// sendMessage sends a given message to everyone on this channel.
|
||||
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string, rb *ResponseBuffer) {
|
||||
if !channel.CanSpeak(client) {
|
||||
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||
return
|
||||
@ -736,85 +745,16 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capab
|
||||
}
|
||||
// send echo-message
|
||||
if client.capabilities.Has(caps.EchoMessage) {
|
||||
var messageTagsToUse *map[string]ircmsg.TagValue
|
||||
if client.capabilities.Has(caps.MessageTags) {
|
||||
messageTagsToUse = clientOnlyTags
|
||||
}
|
||||
|
||||
nickMaskString := client.NickMaskString()
|
||||
accountName := client.AccountName()
|
||||
if message == nil {
|
||||
rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name)
|
||||
} else {
|
||||
rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name, *message)
|
||||
}
|
||||
}
|
||||
for _, member := range channel.Members() {
|
||||
if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
|
||||
// STATUSMSG
|
||||
continue
|
||||
}
|
||||
// echo-message is handled above, so skip sending the msg to the user themselves as well
|
||||
if member == client {
|
||||
continue
|
||||
}
|
||||
|
||||
canReceive := true
|
||||
for _, capName := range requiredCaps {
|
||||
if !member.capabilities.Has(capName) {
|
||||
canReceive = false
|
||||
}
|
||||
}
|
||||
if !canReceive {
|
||||
continue
|
||||
}
|
||||
|
||||
var messageTagsToUse *map[string]ircmsg.TagValue
|
||||
if member.capabilities.Has(caps.MessageTags) {
|
||||
messageTagsToUse = clientOnlyTags
|
||||
}
|
||||
|
||||
if message == nil {
|
||||
member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name)
|
||||
} else {
|
||||
member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name, *message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SplitPrivMsg sends a private message to everyone in this channel.
|
||||
func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||
channel.sendSplitMessage(msgid, "PRIVMSG", history.Privmsg, minPrefix, clientOnlyTags, client, &message, rb)
|
||||
}
|
||||
|
||||
// SplitNotice sends a private message to everyone in this channel.
|
||||
func (channel *Channel) SplitNotice(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||
channel.sendSplitMessage(msgid, "NOTICE", history.Notice, minPrefix, clientOnlyTags, client, &message, rb)
|
||||
}
|
||||
|
||||
func (channel *Channel) sendSplitMessage(msgid, cmd string, histType history.ItemType, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *utils.SplitMessage, rb *ResponseBuffer) {
|
||||
if !channel.CanSpeak(client) {
|
||||
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||
return
|
||||
}
|
||||
|
||||
// for STATUSMSG
|
||||
var minPrefixMode modes.Mode
|
||||
if minPrefix != nil {
|
||||
minPrefixMode = *minPrefix
|
||||
}
|
||||
// send echo-message
|
||||
if client.capabilities.Has(caps.EchoMessage) {
|
||||
var tagsToUse *map[string]ircmsg.TagValue
|
||||
var tagsToUse map[string]string
|
||||
if client.capabilities.Has(caps.MessageTags) {
|
||||
tagsToUse = clientOnlyTags
|
||||
}
|
||||
nickMaskString := client.NickMaskString()
|
||||
accountName := client.AccountName()
|
||||
if message == nil {
|
||||
rb.AddFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name)
|
||||
if command == "TAGMSG" && client.capabilities.Has(caps.MessageTags) {
|
||||
rb.AddFromClient(message.Msgid, nickMaskString, accountName, tagsToUse, command, channel.name)
|
||||
} else {
|
||||
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name, *message)
|
||||
rb.AddSplitMessageFromClient(nickMaskString, accountName, tagsToUse, command, channel.name, message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -832,22 +772,23 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, histType history.Ite
|
||||
if member == client {
|
||||
continue
|
||||
}
|
||||
var tagsToUse *map[string]ircmsg.TagValue
|
||||
var tagsToUse map[string]string
|
||||
if member.capabilities.Has(caps.MessageTags) {
|
||||
tagsToUse = clientOnlyTags
|
||||
} else if command == "TAGMSG" {
|
||||
continue
|
||||
}
|
||||
|
||||
if message == nil {
|
||||
member.sendFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name)
|
||||
if command == "TAGMSG" {
|
||||
member.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, channel.name)
|
||||
} else {
|
||||
member.sendSplitMsgFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name, *message)
|
||||
member.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, channel.name, message)
|
||||
}
|
||||
}
|
||||
|
||||
channel.history.Add(history.Item{
|
||||
Type: histType,
|
||||
Msgid: msgid,
|
||||
Message: *message,
|
||||
Message: message,
|
||||
Nick: nickmask,
|
||||
AccountName: account,
|
||||
Time: now,
|
||||
@ -980,12 +921,14 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
|
||||
member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment)
|
||||
}
|
||||
|
||||
message := utils.SplitMessage{}
|
||||
message.Message = comment
|
||||
message.Msgid = targetNick // XXX abuse this field
|
||||
channel.history.Add(history.Item{
|
||||
Type: history.Kick,
|
||||
Nick: clientMask,
|
||||
Message: utils.MakeSplitMessage(comment, true),
|
||||
AccountName: target.AccountName(),
|
||||
Msgid: targetNick, // XXX abuse this field
|
||||
Message: message,
|
||||
})
|
||||
|
||||
channel.Quit(target)
|
||||
|
132
irc/client.go
132
irc/client.go
@ -62,14 +62,13 @@ type Client struct {
|
||||
hasQuit bool
|
||||
hops int
|
||||
hostname string
|
||||
idletimer *IdleTimer
|
||||
idletimer IdleTimer
|
||||
invitedTo map[string]bool
|
||||
isDestroyed bool
|
||||
isTor bool
|
||||
isQuitting bool
|
||||
languages []string
|
||||
loginThrottle connection_limits.GenericThrottle
|
||||
maxlenTags uint32
|
||||
maxlenRest uint32
|
||||
nick string
|
||||
nickCasefolded string
|
||||
@ -122,8 +121,9 @@ type ClientDetails struct {
|
||||
func RunNewClient(server *Server, conn clientConn) {
|
||||
now := time.Now()
|
||||
config := server.Config()
|
||||
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
|
||||
socket := NewSocket(conn.Conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
|
||||
fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
|
||||
// give them 1k of grace over the limit:
|
||||
socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
|
||||
client := &Client{
|
||||
atime: now,
|
||||
capabilities: caps.NewSet(),
|
||||
@ -246,30 +246,21 @@ func (client *Client) IPString() string {
|
||||
// command goroutine
|
||||
//
|
||||
|
||||
func (client *Client) recomputeMaxlens() (int, int) {
|
||||
maxlenTags := 512
|
||||
func (client *Client) recomputeMaxlens() int {
|
||||
maxlenRest := 512
|
||||
if client.capabilities.Has(caps.MessageTags) {
|
||||
maxlenTags = 4096
|
||||
}
|
||||
if client.capabilities.Has(caps.MaxLine) {
|
||||
limits := client.server.Limits()
|
||||
if limits.LineLen.Tags > maxlenTags {
|
||||
maxlenTags = limits.LineLen.Tags
|
||||
}
|
||||
maxlenRest = limits.LineLen.Rest
|
||||
maxlenRest = client.server.Limits().LineLen.Rest
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&client.maxlenTags, uint32(maxlenTags))
|
||||
atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
|
||||
|
||||
return maxlenTags, maxlenRest
|
||||
return maxlenRest
|
||||
}
|
||||
|
||||
// allow these negotiated length limits to be read without locks; this is a convenience
|
||||
// so that Client.Send doesn't have to acquire any Client locks
|
||||
func (client *Client) maxlens() (int, int) {
|
||||
return int(atomic.LoadUint32(&client.maxlenTags)), int(atomic.LoadUint32(&client.maxlenRest))
|
||||
func (client *Client) MaxlenRest() int {
|
||||
return int(atomic.LoadUint32(&client.maxlenRest))
|
||||
}
|
||||
|
||||
func (client *Client) run() {
|
||||
@ -292,8 +283,7 @@ func (client *Client) run() {
|
||||
client.destroy(false)
|
||||
}()
|
||||
|
||||
client.idletimer = NewIdleTimer(client)
|
||||
client.idletimer.Start()
|
||||
client.idletimer.Initialize(client)
|
||||
|
||||
client.nickTimer.Initialize(client)
|
||||
|
||||
@ -302,7 +292,7 @@ func (client *Client) run() {
|
||||
firstLine := true
|
||||
|
||||
for {
|
||||
maxlenTags, maxlenRest := client.recomputeMaxlens()
|
||||
maxlenRest := client.recomputeMaxlens()
|
||||
|
||||
line, err = client.socket.Read()
|
||||
if err != nil {
|
||||
@ -331,9 +321,12 @@ func (client *Client) run() {
|
||||
}
|
||||
}
|
||||
|
||||
msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest)
|
||||
msg, err = ircmsg.ParseLineStrict(line, true, maxlenRest)
|
||||
if err == ircmsg.ErrorLineIsEmpty {
|
||||
continue
|
||||
} else if err == ircmsg.ErrorLineTooLong {
|
||||
client.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.nick, client.t("Input line too long"))
|
||||
continue
|
||||
} else if err != nil {
|
||||
client.Quit(client.t("Received malformed line"))
|
||||
break
|
||||
@ -539,11 +532,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
|
||||
default:
|
||||
continue
|
||||
}
|
||||
var tags Tags
|
||||
var tags map[string]string
|
||||
if serverTime {
|
||||
tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat))
|
||||
tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
|
||||
}
|
||||
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, command, nick, item.Message)
|
||||
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
|
||||
}
|
||||
if !complete {
|
||||
rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
|
||||
@ -841,17 +834,18 @@ func (client *Client) Quit(message string) {
|
||||
return
|
||||
}
|
||||
|
||||
var quitLine string
|
||||
var finalData []byte
|
||||
// #364: don't send QUIT lines to unregistered clients
|
||||
if registered {
|
||||
quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
|
||||
quitLine, _ = quitMsg.Line()
|
||||
finalData, _ = quitMsg.LineBytesStrict(false, 512)
|
||||
}
|
||||
|
||||
errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
|
||||
errorLine, _ := errorMsg.Line()
|
||||
errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
|
||||
finalData = append(finalData, errorMsgBytes...)
|
||||
|
||||
client.socket.SetFinalData(quitLine + errorLine)
|
||||
client.socket.SetFinalData(finalData)
|
||||
}
|
||||
|
||||
// destroy gets rid of a client, removes them from server lists etc.
|
||||
@ -962,50 +956,45 @@ func (client *Client) destroy(beingResumed bool) {
|
||||
|
||||
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
|
||||
// Adds account-tag to the line as well.
|
||||
func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags Tags, command, target string, message utils.SplitMessage) {
|
||||
client.sendSplitMsgFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, target, message)
|
||||
func (client *Client) SendSplitMsgFromClient(from *Client, tags map[string]string, command, target string, message utils.SplitMessage) {
|
||||
client.sendSplitMsgFromClientInternal(false, time.Time{}, from.NickMaskString(), from.AccountName(), tags, command, target, message)
|
||||
}
|
||||
|
||||
func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command, target string, message utils.SplitMessage) {
|
||||
func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
|
||||
if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
|
||||
client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, message.Original)
|
||||
client.sendFromClientInternal(blocking, serverTime, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
|
||||
} else {
|
||||
for _, str := range message.Wrapped {
|
||||
client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, str)
|
||||
for _, messagePair := range message.Wrapped {
|
||||
client.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendFromClient sends an IRC line coming from a specific client.
|
||||
// Adds account-tag to the line as well.
|
||||
func (client *Client) SendFromClient(msgid string, from *Client, tags Tags, command string, params ...string) error {
|
||||
func (client *Client) SendFromClient(msgid string, from *Client, tags map[string]string, command string, params ...string) error {
|
||||
return client.sendFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, params...)
|
||||
}
|
||||
|
||||
// helper to add a tag to `tags` (or create a new tag set if the current one is nil)
|
||||
func ensureTag(tags Tags, tagName, tagValue string) (result Tags) {
|
||||
if tags == nil {
|
||||
result = ircmsg.MakeTags(tagName, tagValue)
|
||||
} else {
|
||||
result = tags
|
||||
(*tags)[tagName] = ircmsg.MakeTagValue(tagValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// XXX this is a hack where we allow overriding the client's nickmask
|
||||
// this is to support CHGHOST, which requires that we send the *original* nickmask with the response
|
||||
func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command string, params ...string) error {
|
||||
// this is SendFromClient, but directly exposing nickmask and accountName,
|
||||
// for things like history replay and CHGHOST where they no longer (necessarily)
|
||||
// correspond to the current state of a client
|
||||
func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) error {
|
||||
msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
|
||||
// attach account-tag
|
||||
if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
|
||||
tags = ensureTag(tags, "account", accountName)
|
||||
msg.SetTag("account", accountName)
|
||||
}
|
||||
// attach message-id
|
||||
if len(msgid) > 0 && client.capabilities.Has(caps.MessageTags) {
|
||||
tags = ensureTag(tags, "draft/msgid", msgid)
|
||||
if msgid != "" && client.capabilities.Has(caps.MessageTags) {
|
||||
msg.SetTag("draft/msgid", msgid)
|
||||
}
|
||||
// attach server-time
|
||||
if client.capabilities.Has(caps.ServerTime) {
|
||||
msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
|
||||
return client.sendInternal(blocking, serverTime, tags, nickmask, command, params...)
|
||||
return client.SendRawMessage(msg, blocking)
|
||||
}
|
||||
|
||||
var (
|
||||
@ -1025,24 +1014,24 @@ var (
|
||||
func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
|
||||
// use dumb hack to force the last param to be a trailing param if required
|
||||
var usedTrailingHack bool
|
||||
if commandsThatMustUseTrailing[strings.ToUpper(message.Command)] && len(message.Params) > 0 {
|
||||
if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
|
||||
lastParam := message.Params[len(message.Params)-1]
|
||||
// to force trailing, we ensure the final param contains a space
|
||||
if !strings.Contains(lastParam, " ") {
|
||||
if strings.IndexByte(lastParam, ' ') == -1 {
|
||||
message.Params[len(message.Params)-1] = lastParam + " "
|
||||
usedTrailingHack = true
|
||||
}
|
||||
}
|
||||
|
||||
// assemble message
|
||||
maxlenTags, maxlenRest := client.maxlens()
|
||||
line, err := message.LineMaxLenBytes(maxlenTags, maxlenRest)
|
||||
maxlenRest := client.MaxlenRest()
|
||||
line, err := message.LineBytesStrict(false, maxlenRest)
|
||||
if err != nil {
|
||||
logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
|
||||
client.server.logger.Error("internal", logline)
|
||||
|
||||
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
||||
line, _ := message.LineBytes()
|
||||
line, _ := message.LineBytesStrict(false, 0)
|
||||
|
||||
if blocking {
|
||||
client.socket.BlockingWrite(line)
|
||||
@ -1054,7 +1043,7 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) e
|
||||
|
||||
// if we used the trailing hack, we need to strip the final space we appended earlier on
|
||||
if usedTrailingHack {
|
||||
copy(line[len(line)-3:], []byte{'\r', '\n'})
|
||||
copy(line[len(line)-3:], "\r\n")
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
|
||||
@ -1070,24 +1059,13 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) e
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) sendInternal(blocking bool, serverTime time.Time, tags Tags, prefix string, command string, params ...string) error {
|
||||
// attach server time
|
||||
if client.capabilities.Has(caps.ServerTime) {
|
||||
if serverTime.IsZero() {
|
||||
serverTime = time.Now()
|
||||
}
|
||||
tags = ensureTag(tags, "time", serverTime.UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
|
||||
// send out the message
|
||||
message := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||
client.SendRawMessage(message, blocking)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends an IRC line to the client.
|
||||
func (client *Client) Send(tags Tags, prefix string, command string, params ...string) error {
|
||||
return client.sendInternal(false, time.Time{}, tags, prefix, command, params...)
|
||||
func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) error {
|
||||
msg := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||
if client.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
|
||||
msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
return client.SendRawMessage(msg, false)
|
||||
}
|
||||
|
||||
// Notice sends the client a notice from the server.
|
||||
|
@ -202,7 +202,6 @@ type OperConfig struct {
|
||||
|
||||
// LineLenConfig controls line lengths.
|
||||
type LineLenLimits struct {
|
||||
Tags int
|
||||
Rest int
|
||||
}
|
||||
|
||||
@ -553,9 +552,6 @@ func LoadConfig(filename string) (config *Config, err error) {
|
||||
}
|
||||
config.Server.WebIRC = newWebIRC
|
||||
// process limits
|
||||
if config.Limits.LineLen.Tags < 512 {
|
||||
config.Limits.LineLen.Tags = 512
|
||||
}
|
||||
if config.Limits.LineLen.Rest < 512 {
|
||||
config.Limits.LineLen.Rest = 512
|
||||
}
|
||||
|
@ -1874,7 +1874,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
||||
|
||||
// NOTICE <target>{,<target>} <message>
|
||||
func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
|
||||
clientOnlyTags := msg.ClientOnlyTags()
|
||||
targets := strings.Split(msg.Params[0], ",")
|
||||
message := msg.Params[1]
|
||||
|
||||
@ -1883,7 +1883,6 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
return false
|
||||
}
|
||||
|
||||
// split privmsg
|
||||
splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
|
||||
|
||||
for i, targetString := range targets {
|
||||
@ -1905,8 +1904,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
// errors silently ignored with NOTICE as per RFC
|
||||
continue
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
channel.SplitNotice(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
|
||||
channel.SendSplitMessage("NOTICE", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
|
||||
} else {
|
||||
target, err := CasefoldName(targetString)
|
||||
if err != nil {
|
||||
@ -1926,23 +1924,21 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
if !user.capabilities.Has(caps.MessageTags) {
|
||||
clientOnlyTags = nil
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
// restrict messages appropriately when +R is set
|
||||
// intentionally make the sending user think the message went through fine
|
||||
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
|
||||
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
|
||||
if allowedPlusR && allowedTor {
|
||||
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
||||
user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
||||
}
|
||||
nickMaskString := client.NickMaskString()
|
||||
accountName := client.AccountName()
|
||||
if client.capabilities.Has(caps.EchoMessage) {
|
||||
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
||||
rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
||||
}
|
||||
|
||||
user.history.Add(history.Item{
|
||||
Type: history.Notice,
|
||||
Msgid: msgid,
|
||||
Message: splitMsg,
|
||||
Nick: nickMaskString,
|
||||
AccountName: accountName,
|
||||
@ -2096,7 +2092,7 @@ func isRestrictedCTCPMessage(message string) bool {
|
||||
|
||||
// PRIVMSG <target>{,<target>} <message>
|
||||
func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
|
||||
clientOnlyTags := msg.ClientOnlyTags()
|
||||
targets := strings.Split(msg.Params[0], ",")
|
||||
message := msg.Params[1]
|
||||
|
||||
@ -2133,8 +2129,7 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
||||
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||
continue
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
|
||||
channel.SendSplitMessage("PRIVMSG", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
|
||||
} else {
|
||||
target, err = CasefoldName(targetString)
|
||||
if service, isService := OragonoServices[target]; isService {
|
||||
@ -2151,18 +2146,17 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
||||
if !user.capabilities.Has(caps.MessageTags) {
|
||||
clientOnlyTags = nil
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
// restrict messages appropriately when +R is set
|
||||
// intentionally make the sending user think the message went through fine
|
||||
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
|
||||
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
|
||||
if allowedPlusR && allowedTor {
|
||||
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||
user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||
}
|
||||
nickMaskString := client.NickMaskString()
|
||||
accountName := client.AccountName()
|
||||
if client.capabilities.Has(caps.EchoMessage) {
|
||||
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||
rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||
}
|
||||
if user.HasMode(modes.Away) {
|
||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||
@ -2171,7 +2165,6 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
||||
|
||||
user.history.Add(history.Item{
|
||||
Type: history.Privmsg,
|
||||
Msgid: msgid,
|
||||
Message: splitMsg,
|
||||
Nick: nickMaskString,
|
||||
AccountName: accountName,
|
||||
@ -2353,7 +2346,7 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
||||
|
||||
// TAGMSG <target>{,<target>}
|
||||
func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
|
||||
clientOnlyTags := msg.ClientOnlyTags()
|
||||
// no client-only tags, so we can drop it
|
||||
if clientOnlyTags == nil {
|
||||
return false
|
||||
@ -2362,6 +2355,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
targets := strings.Split(msg.Params[0], ",")
|
||||
|
||||
cnick := client.Nick()
|
||||
message := utils.MakeSplitMessage("", true) // assign consistent message ID
|
||||
for i, targetString := range targets {
|
||||
// max of four targets per privmsg
|
||||
if i > maxTargets-1 {
|
||||
@ -2386,9 +2380,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||
continue
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
|
||||
channel.TagMsg(msgid, lowestPrefix, clientOnlyTags, client, rb)
|
||||
channel.SendSplitMessage("TAGMSG", lowestPrefix, clientOnlyTags, client, message, rb)
|
||||
} else {
|
||||
target, err = CasefoldName(targetString)
|
||||
user := server.clients.Get(target)
|
||||
@ -2398,19 +2390,19 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
}
|
||||
continue
|
||||
}
|
||||
msgid := server.generateMessageID()
|
||||
|
||||
// end user can't receive tagmsgs
|
||||
if !user.capabilities.Has(caps.MessageTags) {
|
||||
continue
|
||||
}
|
||||
user.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
|
||||
unick := user.Nick()
|
||||
user.SendSplitMsgFromClient(client, clientOnlyTags, "TAGMSG", unick, message)
|
||||
if client.capabilities.Has(caps.EchoMessage) {
|
||||
rb.AddFromClient(msgid, client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", user.nick)
|
||||
rb.AddSplitMessageFromClient(client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", unick, message)
|
||||
}
|
||||
if user.HasMode(modes.Away) {
|
||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||
rb.Add(nil, server.name, RPL_AWAY, cnick, user.Nick(), user.AwayMessage())
|
||||
rb.Add(nil, server.name, RPL_AWAY, cnick, unick, user.AwayMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ const (
|
||||
Kick
|
||||
Quit
|
||||
Mode
|
||||
Tagmsg
|
||||
)
|
||||
|
||||
// Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
|
||||
@ -33,14 +34,13 @@ type Item struct {
|
||||
AccountName string
|
||||
Message utils.SplitMessage
|
||||
// for non-privmsg items, we may stuff some other data in here
|
||||
Msgid string
|
||||
}
|
||||
|
||||
// HasMsgid tests whether a message has the message id `msgid`.
|
||||
func (item *Item) HasMsgid(msgid string) bool {
|
||||
// XXX we stuff other data in the Msgid field sometimes,
|
||||
// don't match it by accident
|
||||
return (item.Type == Privmsg || item.Type == Notice) && item.Msgid == msgid
|
||||
return (item.Type == Privmsg || item.Type == Notice) && item.Message.Msgid == msgid
|
||||
}
|
||||
|
||||
type Predicate func(item Item) (matches bool)
|
||||
|
@ -54,14 +54,17 @@ type IdleTimer struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// NewIdleTimer sets up a new IdleTimer using constant timeouts.
|
||||
func NewIdleTimer(client *Client) *IdleTimer {
|
||||
it := IdleTimer{
|
||||
registerTimeout: RegisterTimeout,
|
||||
client: client,
|
||||
}
|
||||
// Initialize sets up an IdleTimer and starts counting idle time;
|
||||
// if there is no activity from the client, it will eventually be stopped.
|
||||
func (it *IdleTimer) Initialize(client *Client) {
|
||||
it.client = client
|
||||
it.registerTimeout = RegisterTimeout
|
||||
it.idleTimeout, it.quitTimeout = it.recomputeDurations()
|
||||
return &it
|
||||
|
||||
it.Lock()
|
||||
defer it.Unlock()
|
||||
it.state = TimerUnregistered
|
||||
it.resetTimeout()
|
||||
}
|
||||
|
||||
// recomputeDurations recomputes the idle and quit durations, given the client's caps.
|
||||
@ -82,15 +85,6 @@ func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duratio
|
||||
return
|
||||
}
|
||||
|
||||
// Start starts counting idle time; if there is no activity from the client,
|
||||
// it will eventually be stopped.
|
||||
func (it *IdleTimer) Start() {
|
||||
it.Lock()
|
||||
defer it.Unlock()
|
||||
it.state = TimerUnregistered
|
||||
it.resetTimeout()
|
||||
}
|
||||
|
||||
func (it *IdleTimer) Touch() {
|
||||
idleTimeout, quitTimeout := it.recomputeDurations()
|
||||
|
||||
|
@ -175,7 +175,7 @@ func (lm *Manager) Translators() []string {
|
||||
tlist = append(tlist, fmt.Sprintf("%s (%s): %s", info.Name, info.Code, info.Contributors))
|
||||
}
|
||||
|
||||
sort.Sort(tlist)
|
||||
tlist.Sort()
|
||||
return tlist
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ func (lm *Manager) Translate(languages []string, originalString string) string {
|
||||
}
|
||||
|
||||
func (lm *Manager) CapValue() string {
|
||||
langCodes := make([]string, len(lm.Languages)+1)
|
||||
langCodes := make(sort.StringSlice, len(lm.Languages)+1)
|
||||
langCodes[0] = strconv.Itoa(len(lm.Languages))
|
||||
i := 1
|
||||
for _, info := range lm.Languages {
|
||||
@ -239,5 +239,6 @@ func (lm *Manager) CapValue() string {
|
||||
langCodes[i] = codeToken
|
||||
i += 1
|
||||
}
|
||||
langCodes.Sort()
|
||||
return strings.Join(langCodes, ",")
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ const (
|
||||
ERR_NOTOPLEVEL = "413"
|
||||
ERR_WILDTOPLEVEL = "414"
|
||||
ERR_BADMASK = "415"
|
||||
ERR_INPUTTOOLONG = "417"
|
||||
ERR_UNKNOWNCOMMAND = "421"
|
||||
ERR_NOMOTD = "422"
|
||||
ERR_NOADMININFO = "423"
|
||||
|
@ -32,7 +32,8 @@ type ResponseBuffer struct {
|
||||
|
||||
// GetLabel returns the label from the given message.
|
||||
func GetLabel(msg ircmsg.IrcMessage) string {
|
||||
return msg.Tags[caps.LabelTagName].Value
|
||||
_, value := msg.GetTag(caps.LabelTagName)
|
||||
return value
|
||||
}
|
||||
|
||||
// NewResponseBuffer returns a new ResponseBuffer.
|
||||
@ -42,8 +43,7 @@ func NewResponseBuffer(target *Client) *ResponseBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a standard new message to our queue.
|
||||
func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
|
||||
func (rb *ResponseBuffer) AddMessage(msg ircmsg.IrcMessage) {
|
||||
if rb.finalized {
|
||||
rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior")
|
||||
debug.PrintStack()
|
||||
@ -52,33 +52,38 @@ func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, c
|
||||
return
|
||||
}
|
||||
|
||||
message := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||
rb.messages = append(rb.messages, message)
|
||||
rb.messages = append(rb.messages, msg)
|
||||
}
|
||||
|
||||
// Add adds a standard new message to our queue.
|
||||
func (rb *ResponseBuffer) Add(tags map[string]string, prefix string, command string, params ...string) {
|
||||
rb.AddMessage(ircmsg.MakeMessage(tags, prefix, command, params...))
|
||||
}
|
||||
|
||||
// AddFromClient adds a new message from a specific client to our queue.
|
||||
func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, command string, params ...string) {
|
||||
func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags map[string]string, command string, params ...string) {
|
||||
msg := ircmsg.MakeMessage(nil, fromNickMask, command, params...)
|
||||
msg.UpdateTags(tags)
|
||||
|
||||
// attach account-tag
|
||||
if rb.target.capabilities.Has(caps.AccountTag) {
|
||||
if fromAccount != "*" {
|
||||
tags = ensureTag(tags, "account", fromAccount)
|
||||
}
|
||||
if rb.target.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
|
||||
msg.SetTag("account", fromAccount)
|
||||
}
|
||||
// attach message-id
|
||||
if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
|
||||
tags = ensureTag(tags, "draft/msgid", msgid)
|
||||
msg.SetTag("draft/msgid", msgid)
|
||||
}
|
||||
|
||||
rb.Add(tags, fromNickMask, command, params...)
|
||||
rb.AddMessage(msg)
|
||||
}
|
||||
|
||||
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
||||
func (rb *ResponseBuffer) AddSplitMessageFromClient(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, command string, target string, message utils.SplitMessage) {
|
||||
func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
|
||||
if rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
|
||||
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, message.Original)
|
||||
rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
|
||||
} else {
|
||||
for _, str := range message.Wrapped {
|
||||
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, str)
|
||||
for _, messagePair := range message.Wrapped {
|
||||
rb.AddFromClient(messagePair.Msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +108,7 @@ func (rb *ResponseBuffer) sendBatchStart(batchType string, blocking bool) {
|
||||
|
||||
message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "+"+rb.batchID, batchType)
|
||||
if rb.Label != "" {
|
||||
message.Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
|
||||
message.SetTag(caps.LabelTagName, rb.Label)
|
||||
}
|
||||
rb.target.SendRawMessage(message, blocking)
|
||||
}
|
||||
@ -149,7 +154,7 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
|
||||
|
||||
// if label but no batch, add label to first message
|
||||
if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" {
|
||||
rb.messages[0].Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
|
||||
rb.messages[0].SetTag(caps.LabelTagName, rb.Label)
|
||||
} else if useBatch {
|
||||
rb.sendBatchStart(defaultBatchType, blocking)
|
||||
}
|
||||
@ -157,16 +162,13 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
|
||||
// send each message out
|
||||
for _, message := range rb.messages {
|
||||
// attach server-time if needed
|
||||
if rb.target.capabilities.Has(caps.ServerTime) {
|
||||
if !message.Tags["time"].HasValue {
|
||||
t := time.Now().UTC().Format(IRCv3TimestampFormat)
|
||||
message.Tags["time"] = ircmsg.MakeTagValue(t)
|
||||
}
|
||||
if rb.target.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
|
||||
message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
|
||||
// attach batch ID
|
||||
if rb.batchID != "" {
|
||||
message.Tags["batch"] = ircmsg.MakeTagValue(rb.batchID)
|
||||
message.SetTag("batch", rb.batchID)
|
||||
}
|
||||
|
||||
// send message out
|
||||
|
@ -387,11 +387,6 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, isTor b
|
||||
return &wrapper, nil
|
||||
}
|
||||
|
||||
// generateMessageID returns a network-unique message ID.
|
||||
func (server *Server) generateMessageID() string {
|
||||
return utils.GenerateSecretToken()
|
||||
}
|
||||
|
||||
//
|
||||
// server functionality
|
||||
//
|
||||
@ -623,7 +618,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
|
||||
} else {
|
||||
// enforce configs that can't be changed after launch:
|
||||
currentLimits := server.Limits()
|
||||
if currentLimits.LineLen.Tags != config.Limits.LineLen.Tags || currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
|
||||
if currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
|
||||
return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
|
||||
} else if server.name != config.Server.Name {
|
||||
return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
|
||||
@ -703,9 +698,9 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
|
||||
}
|
||||
|
||||
// MaxLine
|
||||
if config.Limits.LineLen.Tags != 512 || config.Limits.LineLen.Rest != 512 {
|
||||
if config.Limits.LineLen.Rest != 512 {
|
||||
SupportedCapabilities.Enable(caps.MaxLine)
|
||||
value := fmt.Sprintf("%d,%d", config.Limits.LineLen.Tags, config.Limits.LineLen.Rest)
|
||||
value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
|
||||
CapValues.Set(caps.MaxLine, value)
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
var (
|
||||
handshakeTimeout, _ = time.ParseDuration("5s")
|
||||
errSendQExceeded = errors.New("SendQ exceeded")
|
||||
|
||||
sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
|
||||
)
|
||||
|
||||
// Socket represents an IRC socket.
|
||||
@ -38,7 +40,7 @@ type Socket struct {
|
||||
totalLength int
|
||||
closed bool
|
||||
sendQExceeded bool
|
||||
finalData string // what to send when we die
|
||||
finalData []byte // what to send when we die
|
||||
finalized bool
|
||||
}
|
||||
|
||||
@ -196,7 +198,7 @@ func (socket *Socket) wakeWriter() {
|
||||
}
|
||||
|
||||
// SetFinalData sets the final data to send when the SocketWriter closes.
|
||||
func (socket *Socket) SetFinalData(data string) {
|
||||
func (socket *Socket) SetFinalData(data []byte) {
|
||||
socket.Lock()
|
||||
defer socket.Unlock()
|
||||
socket.finalData = data
|
||||
@ -271,7 +273,7 @@ func (socket *Socket) finalize() {
|
||||
socket.finalized = true
|
||||
finalData := socket.finalData
|
||||
if socket.sendQExceeded {
|
||||
finalData = "\r\nERROR :SendQ Exceeded\r\n"
|
||||
finalData = sendQExceededMessage
|
||||
}
|
||||
socket.Unlock()
|
||||
|
||||
@ -279,8 +281,8 @@ func (socket *Socket) finalize() {
|
||||
return
|
||||
}
|
||||
|
||||
if finalData != "" {
|
||||
socket.conn.Write([]byte(finalData))
|
||||
if len(finalData) != 0 {
|
||||
socket.conn.Write(finalData)
|
||||
}
|
||||
|
||||
// close the connection
|
||||
|
@ -6,7 +6,6 @@
|
||||
package irc
|
||||
|
||||
import "github.com/oragono/oragono/irc/modes"
|
||||
import "github.com/goshuirc/irc-go/ircmsg"
|
||||
|
||||
// ClientSet is a set of clients.
|
||||
type ClientSet map[*Client]bool
|
||||
@ -57,5 +56,3 @@ func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
|
||||
|
||||
// ChannelSet is a set of channels.
|
||||
type ChannelSet map[*Channel]bool
|
||||
|
||||
type Tags *map[string]ircmsg.TagValue
|
||||
|
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the MIT license
|
||||
|
||||
package utils
|
||||
|
||||
import "github.com/goshuirc/irc-go/ircmsg"
|
||||
|
||||
// GetClientOnlyTags takes a tag map and returns a map containing just the client-only tags from it.
|
||||
func GetClientOnlyTags(tags map[string]ircmsg.TagValue) *map[string]ircmsg.TagValue {
|
||||
if len(tags) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
clientOnlyTags := make(map[string]ircmsg.TagValue)
|
||||
|
||||
for name, value := range tags {
|
||||
if len(name) > 1 && name[0] == '+' {
|
||||
clientOnlyTags[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientOnlyTags) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &clientOnlyTags
|
||||
}
|
@ -50,17 +50,32 @@ func WordWrap(text string, lineWidth int) []string {
|
||||
return lines
|
||||
}
|
||||
|
||||
// SplitMessage represents a message that's been split for sending.
|
||||
type SplitMessage struct {
|
||||
Original string
|
||||
Wrapped []string // if this is nil, Original didn't need wrapping and can be sent to anyone
|
||||
type MessagePair struct {
|
||||
Message string
|
||||
Msgid string
|
||||
}
|
||||
|
||||
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
|
||||
result.Original = original
|
||||
// SplitMessage represents a message that's been split for sending.
|
||||
type SplitMessage struct {
|
||||
MessagePair
|
||||
Wrapped []MessagePair // if this is nil, `Message` didn't need wrapping and can be sent to anyone
|
||||
}
|
||||
|
||||
if !origIs512 {
|
||||
result.Wrapped = WordWrap(original, 400)
|
||||
const defaultLineWidth = 400
|
||||
|
||||
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
|
||||
result.Message = original
|
||||
result.Msgid = GenerateSecretToken()
|
||||
|
||||
if !origIs512 && defaultLineWidth < len(original) {
|
||||
wrapped := WordWrap(original, defaultLineWidth)
|
||||
result.Wrapped = make([]MessagePair, len(wrapped))
|
||||
for i, wrappedMessage := range wrapped {
|
||||
result.Wrapped[i] = MessagePair{
|
||||
Message: wrappedMessage,
|
||||
Msgid: GenerateSecretToken(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -487,10 +487,8 @@ limits:
|
||||
# maximum length of IRC lines
|
||||
# this should generally be 1024-2048, and will only apply when negotiated by clients
|
||||
linelen:
|
||||
# tags section
|
||||
tags: 2048
|
||||
|
||||
# rest of the message
|
||||
# ratified version of the message-tags cap fixes the max tag length at 8191 bytes
|
||||
# configurable length for the rest of the message:
|
||||
rest: 2048
|
||||
|
||||
# fakelag: prevents clients from spamming commands too rapidly
|
||||
|
2
vendor
2
vendor
@ -1 +1 @@
|
||||
Subproject commit 72043bab39044196e57bff1fe5e59c9ec81e59f3
|
||||
Subproject commit 8ddbb531841add50f8b7aff8fe00bef311448aaa
|
Loading…
Reference in New Issue
Block a user