Introduce an xirc package

This commit is contained in:
Simon Ser 2022-05-09 16:15:00 +02:00
parent 89412187d4
commit b92afa7cca
6 changed files with 60 additions and 42 deletions

View file

@ -7,6 +7,8 @@ import (
"strings"
"gopkg.in/irc.v3"
"git.sr.ht/~emersion/soju/xirc"
)
func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel) {
@ -27,7 +29,7 @@ func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel
} else {
timestampStr := "*"
if r != nil {
timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
timestampStr = fmt.Sprintf("timestamp=%s", xirc.FormatServerTime(r.Timestamp))
}
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),

View file

@ -18,6 +18,7 @@ import (
"gopkg.in/irc.v3"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/xirc"
)
type ircError struct {
@ -2472,7 +2473,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
broadcastTags := tags.Copy()
broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
broadcastTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
broadcastMsg := &irc.Message{
Tags: broadcastTags,
Prefix: servicePrefix,
@ -2498,7 +2499,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
if casemapASCII(name) == serviceNickCM {
if dc.caps.IsEnabled("echo-message") {
echoTags := tags.Copy()
echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
dc.SendMessage(&irc.Message{
Tags: echoTags,
Prefix: dc.prefix(),
@ -2547,7 +2548,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
}
echoTags := tags.Copy()
echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
if uc.account != "" {
echoTags["account"] = irc.TagValue(uc.account)
}
@ -2871,7 +2872,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
Tags: irc.Tags{"batch": batchRef},
Prefix: dc.srv.prefix(),
Command: "CHATHISTORY",
Params: []string{"TARGETS", target.Name, formatServerTime(target.LatestMessage)},
Params: []string{"TARGETS", target.Name, xirc.FormatServerTime(target.LatestMessage)},
})
}
})
@ -2941,7 +2942,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
}}
}
timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
timestamp, err := time.Parse(xirc.ServerTimeLayout, criteriaParts[1])
if err != nil {
return ircError{&irc.Message{
Command: "FAIL",
@ -2967,7 +2968,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
timestampStr := "*"
if !r.Timestamp.IsZero() {
timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
timestampStr = fmt.Sprintf("timestamp=%s", xirc.FormatServerTime(r.Timestamp))
}
network.forEachDownstream(func(d *downstreamConn) {
if broadcast || dc.id == d.id {
@ -3001,7 +3002,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
value := string(v)
switch name {
case "before", "after":
timestamp, err := time.Parse(serverTimeLayout, value)
timestamp, err := time.Parse(xirc.ServerTimeLayout, value)
if err != nil {
return ircError{&irc.Message{
Command: "FAIL",

34
irc.go
View file

@ -11,8 +11,11 @@ import (
"gopkg.in/irc.v3"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/xirc"
)
// TODO: generalize and move helpers to the xirc package
const (
rpl_statsping = "246"
rpl_localusers = "265"
@ -42,13 +45,6 @@ const (
maxSASLLength = 400
)
// The server-time layout, as defined in the IRCv3 spec.
const serverTimeLayout = "2006-01-02T15:04:05.000Z"
func formatServerTime(t time.Time) string {
return t.UTC().Format(serverTimeLayout)
}
type userModes string
func (ms userModes) Has(c byte) bool {
@ -479,28 +475,6 @@ func (js *joinSorter) Swap(i, j int) {
js.keys[i], js.keys[j] = js.keys[j], js.keys[i]
}
// parseCTCPMessage parses a CTCP message. CTCP is defined in
// https://tools.ietf.org/html/draft-oakley-irc-ctcp-02
func parseCTCPMessage(msg *irc.Message) (cmd string, params string, ok bool) {
if (msg.Command != "PRIVMSG" && msg.Command != "NOTICE") || len(msg.Params) < 2 {
return "", "", false
}
text := msg.Params[1]
if !strings.HasPrefix(text, "\x01") {
return "", "", false
}
text = strings.Trim(text, "\x01")
words := strings.SplitN(text, " ", 2)
cmd = strings.ToUpper(words[0])
if len(words) > 1 {
params = words[1]
}
return cmd, params, true
}
type casemapping func(string) string
func casemapNone(name string) string {
@ -728,7 +702,7 @@ func parseChatHistoryBound(param string) time.Time {
}
switch parts[0] {
case "timestamp":
timestamp, err := time.Parse(serverTimeLayout, parts[1])
timestamp, err := time.Parse(xirc.ServerTimeLayout, parts[1])
if err != nil {
return time.Time{}
}

View file

@ -15,6 +15,7 @@ import (
"gopkg.in/irc.v3"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/xirc"
)
const (
@ -135,7 +136,7 @@ func (ms *fsMessageStore) Append(network *database.Network, entity string, msg *
var t time.Time
if tag, ok := msg.Tags["time"]; ok {
var err error
t, err = time.Parse(serverTimeLayout, string(tag))
t, err = time.Parse(xirc.ServerTimeLayout, string(tag))
if err != nil {
return "", fmt.Errorf("failed to parse message time tag: %v", err)
}
@ -245,7 +246,7 @@ func formatMessage(msg *irc.Message) string {
case "NOTICE":
return fmt.Sprintf("-%s- %s", msg.Prefix.Name, msg.Params[1])
case "PRIVMSG":
if cmd, params, ok := parseCTCPMessage(msg); ok && cmd == "ACTION" {
if cmd, params, ok := xirc.ParseCTCPMessage(msg); ok && cmd == "ACTION" {
return fmt.Sprintf("* %s %s", msg.Prefix.Name, params)
} else {
return fmt.Sprintf("<%s> %s", msg.Prefix.Name, msg.Params[1])
@ -392,7 +393,7 @@ func (ms *fsMessageStore) parseMessage(line string, network *database.Network, e
msg := &irc.Message{
Tags: map[string]irc.TagValue{
"time": irc.TagValue(formatServerTime(t)),
"time": irc.TagValue(xirc.FormatServerTime(t)),
},
Prefix: prefix,
Command: cmd,

View file

@ -19,6 +19,7 @@ import (
"gopkg.in/irc.v3"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/xirc"
)
// permanentUpstreamCaps is the static list of upstream capabilities always
@ -464,7 +465,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
}
if _, ok := msg.Tags["time"]; !ok && !isNumeric(msg.Command) {
msg.Tags["time"] = irc.TagValue(formatServerTime(time.Now()))
msg.Tags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
}
switch msg.Command {

39
xirc/xirc.go Normal file
View file

@ -0,0 +1,39 @@
// Package xirc contains an extended IRC library.
package xirc
import (
"strings"
"time"
"gopkg.in/irc.v3"
)
// The server-time layout, as defined in the IRCv3 spec.
const ServerTimeLayout = "2006-01-02T15:04:05.000Z"
// FormatServerTime formats a time with the server-time layout.
func FormatServerTime(t time.Time) string {
return t.UTC().Format(ServerTimeLayout)
}
// ParseCTCPMessage parses a CTCP message. CTCP is defined in
// https://tools.ietf.org/html/draft-oakley-irc-ctcp-02
func ParseCTCPMessage(msg *irc.Message) (cmd string, params string, ok bool) {
if (msg.Command != "PRIVMSG" && msg.Command != "NOTICE") || len(msg.Params) < 2 {
return "", "", false
}
text := msg.Params[1]
if !strings.HasPrefix(text, "\x01") {
return "", "", false
}
text = strings.Trim(text, "\x01")
words := strings.SplitN(text, " ", 2)
cmd = strings.ToUpper(words[0])
if len(words) > 1 {
params = words[1]
}
return cmd, params, true
}