Relay INVITE to IRC listener users as an INVITE line (closes #147)
check / check (push) Waiting to run
check / check (push) Waiting to run
An invite for an IRC listener user arrived as a server NOTICE that did not name the inviter, so no IRC client treated it as an invite. The relay now sends it as an INVITE line from the inviter, with the channel taken from the message body, and sends nothing when the body names no channel. The IRC listener now queues the INVITE with the channel as its body, as the HTTP API already did, so an invite from either transport has the same shape. The README's IRC listener section says how an invite arrives. Model: opus-5-5
This commit is contained in:
@@ -2719,7 +2719,9 @@ Each line of a multi-line `body` reaches an IRC listener user as its own
|
||||
is sent, so it cannot start a line of its own. A mode change made over the HTTP
|
||||
API reaches IRC listener members as an IRC `MODE` line with its value as a
|
||||
separate parameter, e.g. `MODE #general +k secretpass` or
|
||||
`MODE #general +o bob`.
|
||||
`MODE #general +o bob`. An `INVITE` from either transport reaches the invited
|
||||
IRC listener user as an IRC `INVITE` line from the inviter, e.g.
|
||||
`:alice!alice@* INVITE bob #general`.
|
||||
|
||||
### Docker Usage
|
||||
|
||||
|
||||
@@ -1261,10 +1261,9 @@ func (c *Conn) handleInvite(
|
||||
irc.RplInviting, targetNick, channel,
|
||||
)
|
||||
|
||||
// Send INVITE notice to target via service fan-out.
|
||||
body, _ := json.Marshal( //nolint:errchkjson
|
||||
[]string{"You have been invited to " + channel},
|
||||
)
|
||||
// Queue the INVITE to the target with the body the HTTP
|
||||
// API uses.
|
||||
body, _ := json.Marshal([]string{channel}) //nolint:errchkjson
|
||||
|
||||
_, _, _ = c.svc.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, "INVITE", c.nick, targetNick, targetID, 0,
|
||||
|
||||
@@ -1373,6 +1373,42 @@ func TestIntegrationInviteUserOnChannel(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
// TestIntegrationInviteLine checks that an IRC listener user
|
||||
// invited by an IRC listener user, and then over the HTTP
|
||||
// API, gets an INVITE line from the inviter for the channel
|
||||
// each time.
|
||||
func TestIntegrationInviteLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := newTestEnv(t)
|
||||
|
||||
alice := env.dial(t)
|
||||
alice.register("alice")
|
||||
alice.joinAndDrain("#inv")
|
||||
|
||||
bob := env.dial(t)
|
||||
bob.register("bob")
|
||||
|
||||
alice.sendAndExpect("INVITE bob #inv", " 341 ")
|
||||
|
||||
got := bob.readLine()
|
||||
want := ":alice!alice@* INVITE bob #inv"
|
||||
|
||||
if got != want {
|
||||
t.Errorf("from the IRC listener: got %q, want %q", got, want)
|
||||
}
|
||||
|
||||
// The HTTP API queues an INVITE with the channel as its body.
|
||||
env.queueMessage(t, "bob", "INVITE", "carol", "bob", []string{"#inv"})
|
||||
|
||||
got = bob.readLine()
|
||||
want = ":carol!carol@* INVITE bob #inv"
|
||||
|
||||
if got != want {
|
||||
t.Errorf("over the HTTP API: got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntegrationModeSecretList checks that LIST shows a +s
|
||||
// channel only to its members.
|
||||
func TestIntegrationModeSecretList(t *testing.T) {
|
||||
|
||||
@@ -358,16 +358,24 @@ func (c *Conn) deliverKickMsg(
|
||||
}
|
||||
}
|
||||
|
||||
// deliverInviteMsg sends an INVITE notification.
|
||||
// deliverInviteMsg sends an INVITE line from the inviter
|
||||
// for the channel named in text, or nothing when text names
|
||||
// no channel.
|
||||
func (c *Conn) deliverInviteMsg(
|
||||
_ *db.IRCMessage,
|
||||
msg *db.IRCMessage,
|
||||
text string,
|
||||
) {
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
nick := c.nick
|
||||
c.mu.Unlock()
|
||||
|
||||
c.sendFromServer("NOTICE", nick, text)
|
||||
prefix := msg.From + "!" + msg.From + "@*"
|
||||
|
||||
c.send(FormatMessage(prefix, "INVITE", nick, text))
|
||||
}
|
||||
|
||||
// deliverWallops sends a WALLOPS notification.
|
||||
|
||||
@@ -211,6 +211,38 @@ func TestRelayModeParameters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRelayInviteWithoutChannel checks that an INVITE whose
|
||||
// body names no channel sends the invited IRC listener user
|
||||
// no INVITE line.
|
||||
func TestRelayInviteWithoutChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := newTestEnv(t)
|
||||
|
||||
bob := env.dial(t)
|
||||
bob.register("bob")
|
||||
|
||||
// A nil body is stored as JSON null, which is not an array.
|
||||
for _, body := range [][]string{nil, {}, {""}} {
|
||||
env.queueMessage(t, "bob", "INVITE", "carol", "bob", body)
|
||||
}
|
||||
|
||||
// The relay sends a user's queue in order, so this arrives
|
||||
// after anything sent for the INVITEs above.
|
||||
env.queueMessage(t, "bob", "PRIVMSG", "carol", "bob",
|
||||
[]string{"the end"})
|
||||
|
||||
lines := bob.readUntil(func(l string) bool {
|
||||
return strings.HasSuffix(l, " :the end")
|
||||
})
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, " INVITE ") {
|
||||
t.Errorf("got %q, want no INVITE line", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRelayLineBreaks checks that a CR, LF or NUL in a stored
|
||||
// body line, key or mask never reaches an IRC listener user
|
||||
// as a line break, so no extra line arrives, and that a body
|
||||
|
||||
Reference in New Issue
Block a user