mirror of https://github.com/fluffle/goirc
Split long messages at a configurable length. Fixes #29.
This commit is contained in:
parent
0b64613fe3
commit
5c56572b0d
|
@ -38,6 +38,46 @@ func cutNewLines(s string) string {
|
||||||
return r[0]
|
return r[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indexFragment looks for the last sentence split-point (defined as one of
|
||||||
|
// the punctuation characters .:;,!?"' followed by a space) in the string s
|
||||||
|
// and returns the index in the string after that split-point. If no split-
|
||||||
|
// point is found it returns the index after the last space in s, or -1.
|
||||||
|
func indexFragment(s string) int {
|
||||||
|
max := -1
|
||||||
|
for _, sep := range []string{". ", ": ", "; ", ", ", "! ", "? ", "\" ", "' "} {
|
||||||
|
if idx := strings.LastIndex(s, sep); idx > max {
|
||||||
|
max = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if max > 0 {
|
||||||
|
return max + 2
|
||||||
|
}
|
||||||
|
if idx := strings.LastIndex(s, " "); idx > 0 {
|
||||||
|
return idx + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitMessage splits a message > splitLen chars at:
|
||||||
|
// 1. the end of the last sentence fragment before splitLen
|
||||||
|
// 2. the end of the last word before splitLen
|
||||||
|
// 3. splitLen itself
|
||||||
|
func splitMessage(msg string, splitLen int) (msgs []string) {
|
||||||
|
// This is quite short ;-)
|
||||||
|
if splitLen < 10 {
|
||||||
|
splitLen = 10
|
||||||
|
}
|
||||||
|
for len(msg) > splitLen {
|
||||||
|
idx := indexFragment(msg[:splitLen])
|
||||||
|
if idx < 0 {
|
||||||
|
idx = splitLen
|
||||||
|
}
|
||||||
|
msgs = append(msgs, msg[:idx] + "...")
|
||||||
|
msg = msg[idx:]
|
||||||
|
}
|
||||||
|
return append(msgs, msg)
|
||||||
|
}
|
||||||
|
|
||||||
// Raw() sends a raw line to the server, should really only be used for
|
// Raw() sends a raw line to the server, should really only be used for
|
||||||
// debugging purposes but may well come in handy.
|
// debugging purposes but may well come in handy.
|
||||||
func (conn *Conn) Raw(rawline string) {
|
func (conn *Conn) Raw(rawline string) {
|
||||||
|
@ -93,29 +133,42 @@ func (conn *Conn) Whois(nick string) { conn.Raw(WHOIS + " " + nick) }
|
||||||
func (conn *Conn) Who(nick string) { conn.Raw(WHO + " " + nick) }
|
func (conn *Conn) Who(nick string) { conn.Raw(WHO + " " + nick) }
|
||||||
|
|
||||||
// Privmsg() sends a PRIVMSG to the target t
|
// Privmsg() sends a PRIVMSG to the target t
|
||||||
func (conn *Conn) Privmsg(t, msg string) { conn.Raw(PRIVMSG + " " + t + " :" + msg) }
|
func (conn *Conn) Privmsg(t, msg string) {
|
||||||
|
for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
|
||||||
|
conn.Raw(PRIVMSG + " " + t + " :" + s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Notice() sends a NOTICE to the target t
|
// Notice() sends a NOTICE to the target t
|
||||||
func (conn *Conn) Notice(t, msg string) { conn.Raw(NOTICE + " " + t + " :" + msg) }
|
func (conn *Conn) Notice(t, msg string) {
|
||||||
|
for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
|
||||||
|
conn.Raw(NOTICE + " " + t + " :" + s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ctcp() sends a (generic) CTCP message to the target t
|
// Ctcp() sends a (generic) CTCP message to the target t
|
||||||
// with an optional argument
|
// with an optional argument
|
||||||
func (conn *Conn) Ctcp(t, ctcp string, arg ...string) {
|
func (conn *Conn) Ctcp(t, ctcp string, arg ...string) {
|
||||||
msg := strings.Join(arg, " ")
|
// We need to split again here to ensure
|
||||||
if msg != "" {
|
for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
|
||||||
msg = " " + msg
|
if s != "" {
|
||||||
|
s = " " + s
|
||||||
|
}
|
||||||
|
// Using Raw rather than PRIVMSG here to avoid double-split problems.
|
||||||
|
conn.Raw(PRIVMSG + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
|
||||||
}
|
}
|
||||||
conn.Privmsg(t, "\001"+strings.ToUpper(ctcp)+msg+"\001")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CtcpReply() sends a generic CTCP reply to the target t
|
// CtcpReply() sends a generic CTCP reply to the target t
|
||||||
// with an optional argument
|
// with an optional argument
|
||||||
func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) {
|
func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) {
|
||||||
msg := strings.Join(arg, " ")
|
for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
|
||||||
if msg != "" {
|
if s != "" {
|
||||||
msg = " " + msg
|
s = " " + s
|
||||||
|
}
|
||||||
|
// Using Raw rather than NOTICE here to avoid double-split problems.
|
||||||
|
conn.Raw(NOTICE + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
|
||||||
}
|
}
|
||||||
conn.Notice(t, "\001"+strings.ToUpper(ctcp)+msg+"\001")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version() sends a CTCP "VERSION" to the target t
|
// Version() sends a CTCP "VERSION" to the target t
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestCutNewLines(t *testing.T) {
|
func TestCutNewLines(t *testing.T) {
|
||||||
tests := []struct{ in, out string }{
|
tests := []struct{ in, out string }{
|
||||||
|
@ -16,7 +19,58 @@ func TestCutNewLines(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
out := cutNewLines(test.in)
|
out := cutNewLines(test.in)
|
||||||
if test.out != out {
|
if test.out != out {
|
||||||
t.Errorf("test %d: expected '%s', got '%s'", i, test.out, out)
|
t.Errorf("test %d: expected %q, got %q", i, test.out, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndexFragment(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
out int
|
||||||
|
}{
|
||||||
|
{"", -1},
|
||||||
|
{"foobarbaz", -1},
|
||||||
|
{"foo bar baz", 8},
|
||||||
|
{"foo. bar baz", 5},
|
||||||
|
{"foo: bar baz", 5},
|
||||||
|
{"foo; bar baz", 5},
|
||||||
|
{"foo, bar baz", 5},
|
||||||
|
{"foo! bar baz", 5},
|
||||||
|
{"foo? bar baz", 5},
|
||||||
|
{"foo\" bar baz", 5},
|
||||||
|
{"foo' bar baz", 5},
|
||||||
|
{"foo. bar. baz beep", 10},
|
||||||
|
{"foo. bar, baz beep", 10},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
out := indexFragment(test.in)
|
||||||
|
if test.out != out {
|
||||||
|
t.Errorf("test %d: expected %d, got %d", i, test.out, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitMessage(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
sp int
|
||||||
|
out []string
|
||||||
|
}{
|
||||||
|
{"", 0, []string{""}},
|
||||||
|
{"foo", 0, []string{"foo"}},
|
||||||
|
{"foo bar baz beep", 0, []string{"foo bar ...", "baz beep"}},
|
||||||
|
{"foo bar baz beep", 13, []string{"foo bar baz ...", "beep"}},
|
||||||
|
{"foo. bar baz beep", 0, []string{"foo. ...", "bar baz ...", "beep"}},
|
||||||
|
{"foo bar, baz beep", 13, []string{"foo bar, ...", "baz beep"}},
|
||||||
|
{"0123456789012345", 0, []string{"0123456789...", "012345"}},
|
||||||
|
{"0123456789012345", 13, []string{"0123456789012...", "345"}},
|
||||||
|
{"0123456789012345", 20, []string{"0123456789012345"}},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
out := splitMessage(test.in, test.sp)
|
||||||
|
if !reflect.DeepEqual(test.out, out) {
|
||||||
|
t.Errorf("test %d: expected %q, got %q", i, test.out, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +79,10 @@ func TestClientCommands(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
|
// Avoid having to type ridiculously long lines to test that
|
||||||
|
// messages longer than SplitLen are correctly sent to the server.
|
||||||
|
c.cfg.SplitLen = 20
|
||||||
|
|
||||||
c.Pass("password")
|
c.Pass("password")
|
||||||
s.nc.Expect("PASS password")
|
s.nc.Expect("PASS password")
|
||||||
|
|
||||||
|
@ -59,12 +117,30 @@ func TestClientCommands(t *testing.T) {
|
||||||
c.Privmsg("#foo", "bar")
|
c.Privmsg("#foo", "bar")
|
||||||
s.nc.Expect("PRIVMSG #foo :bar")
|
s.nc.Expect("PRIVMSG #foo :bar")
|
||||||
|
|
||||||
|
// 0123456789012345678901234567890123
|
||||||
|
c.Privmsg("#foo", "foo bar baz blorp. woo woobly woo.")
|
||||||
|
s.nc.Expect("PRIVMSG #foo :foo bar baz blorp. ...")
|
||||||
|
s.nc.Expect("PRIVMSG #foo :woo woobly woo.")
|
||||||
|
|
||||||
c.Notice("somebody", "something")
|
c.Notice("somebody", "something")
|
||||||
s.nc.Expect("NOTICE somebody :something")
|
s.nc.Expect("NOTICE somebody :something")
|
||||||
|
|
||||||
|
// 01234567890123456789012345678901234567
|
||||||
|
c.Notice("somebody", "something much much longer that splits")
|
||||||
|
s.nc.Expect("NOTICE somebody :something much much ...")
|
||||||
|
s.nc.Expect("NOTICE somebody :longer that splits")
|
||||||
|
|
||||||
c.Ctcp("somebody", "ping", "123456789")
|
c.Ctcp("somebody", "ping", "123456789")
|
||||||
s.nc.Expect("PRIVMSG somebody :\001PING 123456789\001")
|
s.nc.Expect("PRIVMSG somebody :\001PING 123456789\001")
|
||||||
|
|
||||||
|
c.Ctcp("somebody", "ping", "123456789012345678901234567890")
|
||||||
|
s.nc.Expect("PRIVMSG somebody :\001PING 12345678901234567890...\001")
|
||||||
|
s.nc.Expect("PRIVMSG somebody :\001PING 1234567890\001")
|
||||||
|
|
||||||
|
c.CtcpReply("somebody", "pong", "123456789012345678901234567890")
|
||||||
|
s.nc.Expect("NOTICE somebody :\001PONG 12345678901234567890...\001")
|
||||||
|
s.nc.Expect("NOTICE somebody :\001PONG 1234567890\001")
|
||||||
|
|
||||||
c.CtcpReply("somebody", "pong", "123456789")
|
c.CtcpReply("somebody", "pong", "123456789")
|
||||||
s.nc.Expect("NOTICE somebody :\001PONG 123456789\001")
|
s.nc.Expect("NOTICE somebody :\001PONG 123456789\001")
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,10 @@ type Config struct {
|
||||||
|
|
||||||
// Configurable panic recovery for all handlers.
|
// Configurable panic recovery for all handlers.
|
||||||
Recover func(*Conn, *Line)
|
Recover func(*Conn, *Line)
|
||||||
|
|
||||||
|
// Split PRIVMSGs, NOTICEs and CTCPs longer than
|
||||||
|
// SplitLen characters over multiple lines.
|
||||||
|
SplitLen int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(nick string, args ...string) *Config {
|
func NewConfig(nick string, args ...string) *Config {
|
||||||
|
@ -79,6 +83,7 @@ func NewConfig(nick string, args ...string) *Config {
|
||||||
PingFreq: 3 * time.Minute,
|
PingFreq: 3 * time.Minute,
|
||||||
NewNick: func(s string) string { return s + "_" },
|
NewNick: func(s string) string { return s + "_" },
|
||||||
Recover: (*Conn).LogPanic, // in dispatch.go
|
Recover: (*Conn).LogPanic, // in dispatch.go
|
||||||
|
SplitLen: 450,
|
||||||
}
|
}
|
||||||
cfg.Me.Ident = "goirc"
|
cfg.Me.Ident = "goirc"
|
||||||
if len(args) > 0 && args[0] != "" {
|
if len(args) > 0 && args[0] != "" {
|
||||||
|
|
Loading…
Reference in New Issue