Introduce an xirc package
This commit is contained in:
parent
89412187d4
commit
b92afa7cca
@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
|
|
||||||
|
"git.sr.ht/~emersion/soju/xirc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel) {
|
func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel) {
|
||||||
@ -27,7 +29,7 @@ func forwardChannel(ctx context.Context, dc *downstreamConn, ch *upstreamChannel
|
|||||||
} else {
|
} else {
|
||||||
timestampStr := "*"
|
timestampStr := "*"
|
||||||
if r != nil {
|
if r != nil {
|
||||||
timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
|
timestampStr = fmt.Sprintf("timestamp=%s", xirc.FormatServerTime(r.Timestamp))
|
||||||
}
|
}
|
||||||
dc.SendMessage(&irc.Message{
|
dc.SendMessage(&irc.Message{
|
||||||
Prefix: dc.prefix(),
|
Prefix: dc.prefix(),
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
|
"git.sr.ht/~emersion/soju/xirc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ircError struct {
|
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)
|
dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
|
||||||
|
|
||||||
broadcastTags := tags.Copy()
|
broadcastTags := tags.Copy()
|
||||||
broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
|
broadcastTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
|
||||||
broadcastMsg := &irc.Message{
|
broadcastMsg := &irc.Message{
|
||||||
Tags: broadcastTags,
|
Tags: broadcastTags,
|
||||||
Prefix: servicePrefix,
|
Prefix: servicePrefix,
|
||||||
@ -2498,7 +2499,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
|
|||||||
if casemapASCII(name) == serviceNickCM {
|
if casemapASCII(name) == serviceNickCM {
|
||||||
if dc.caps.IsEnabled("echo-message") {
|
if dc.caps.IsEnabled("echo-message") {
|
||||||
echoTags := tags.Copy()
|
echoTags := tags.Copy()
|
||||||
echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
|
echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
|
||||||
dc.SendMessage(&irc.Message{
|
dc.SendMessage(&irc.Message{
|
||||||
Tags: echoTags,
|
Tags: echoTags,
|
||||||
Prefix: dc.prefix(),
|
Prefix: dc.prefix(),
|
||||||
@ -2547,7 +2548,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
|
|||||||
}
|
}
|
||||||
|
|
||||||
echoTags := tags.Copy()
|
echoTags := tags.Copy()
|
||||||
echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
|
echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
|
||||||
if uc.account != "" {
|
if uc.account != "" {
|
||||||
echoTags["account"] = irc.TagValue(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},
|
Tags: irc.Tags{"batch": batchRef},
|
||||||
Prefix: dc.srv.prefix(),
|
Prefix: dc.srv.prefix(),
|
||||||
Command: "CHATHISTORY",
|
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 {
|
if err != nil {
|
||||||
return ircError{&irc.Message{
|
return ircError{&irc.Message{
|
||||||
Command: "FAIL",
|
Command: "FAIL",
|
||||||
@ -2967,7 +2968,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
|
|||||||
|
|
||||||
timestampStr := "*"
|
timestampStr := "*"
|
||||||
if !r.Timestamp.IsZero() {
|
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) {
|
network.forEachDownstream(func(d *downstreamConn) {
|
||||||
if broadcast || dc.id == d.id {
|
if broadcast || dc.id == d.id {
|
||||||
@ -3001,7 +3002,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
|
|||||||
value := string(v)
|
value := string(v)
|
||||||
switch name {
|
switch name {
|
||||||
case "before", "after":
|
case "before", "after":
|
||||||
timestamp, err := time.Parse(serverTimeLayout, value)
|
timestamp, err := time.Parse(xirc.ServerTimeLayout, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ircError{&irc.Message{
|
return ircError{&irc.Message{
|
||||||
Command: "FAIL",
|
Command: "FAIL",
|
||||||
|
34
irc.go
34
irc.go
@ -11,8 +11,11 @@ import (
|
|||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
|
"git.sr.ht/~emersion/soju/xirc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: generalize and move helpers to the xirc package
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rpl_statsping = "246"
|
rpl_statsping = "246"
|
||||||
rpl_localusers = "265"
|
rpl_localusers = "265"
|
||||||
@ -42,13 +45,6 @@ const (
|
|||||||
maxSASLLength = 400
|
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
|
type userModes string
|
||||||
|
|
||||||
func (ms userModes) Has(c byte) bool {
|
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]
|
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
|
type casemapping func(string) string
|
||||||
|
|
||||||
func casemapNone(name string) string {
|
func casemapNone(name string) string {
|
||||||
@ -728,7 +702,7 @@ func parseChatHistoryBound(param string) time.Time {
|
|||||||
}
|
}
|
||||||
switch parts[0] {
|
switch parts[0] {
|
||||||
case "timestamp":
|
case "timestamp":
|
||||||
timestamp, err := time.Parse(serverTimeLayout, parts[1])
|
timestamp, err := time.Parse(xirc.ServerTimeLayout, parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
|
"git.sr.ht/~emersion/soju/xirc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -135,7 +136,7 @@ func (ms *fsMessageStore) Append(network *database.Network, entity string, msg *
|
|||||||
var t time.Time
|
var t time.Time
|
||||||
if tag, ok := msg.Tags["time"]; ok {
|
if tag, ok := msg.Tags["time"]; ok {
|
||||||
var err error
|
var err error
|
||||||
t, err = time.Parse(serverTimeLayout, string(tag))
|
t, err = time.Parse(xirc.ServerTimeLayout, string(tag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to parse message time tag: %v", err)
|
return "", fmt.Errorf("failed to parse message time tag: %v", err)
|
||||||
}
|
}
|
||||||
@ -245,7 +246,7 @@ func formatMessage(msg *irc.Message) string {
|
|||||||
case "NOTICE":
|
case "NOTICE":
|
||||||
return fmt.Sprintf("-%s- %s", msg.Prefix.Name, msg.Params[1])
|
return fmt.Sprintf("-%s- %s", msg.Prefix.Name, msg.Params[1])
|
||||||
case "PRIVMSG":
|
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)
|
return fmt.Sprintf("* %s %s", msg.Prefix.Name, params)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("<%s> %s", msg.Prefix.Name, msg.Params[1])
|
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{
|
msg := &irc.Message{
|
||||||
Tags: map[string]irc.TagValue{
|
Tags: map[string]irc.TagValue{
|
||||||
"time": irc.TagValue(formatServerTime(t)),
|
"time": irc.TagValue(xirc.FormatServerTime(t)),
|
||||||
},
|
},
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Command: cmd,
|
Command: cmd,
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
|
"git.sr.ht/~emersion/soju/xirc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// permanentUpstreamCaps is the static list of upstream capabilities always
|
// 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) {
|
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 {
|
switch msg.Command {
|
||||||
|
39
xirc/xirc.go
Normal file
39
xirc/xirc.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user