Show an IRC listener user what their session sends from other clients (closes #179)
check / check (push) Waiting to run
check / check (push) Waiting to run
The relay dropped every PRIVMSG, NOTICE, JOIN, PART, NICK, QUIT, TOPIC, MODE and KICK sent under the connection's own nick, so an IRC listener connection never saw what its session did from another client. The relay now drops nothing by nick. Instead, the service methods that fan out a user's own command take the client whose queue to skip: the IRC listener passes its own client, which has already written the echo, and the HTTP API passes 0. Channel messages and PARTs now also go to the sender's own session, so the HTTP handler no longer queues the channel-message echo itself, SendChannelMessage no longer returns the message's row ID, and neoirc-cli ignores the PART event for its own part, which it has already shown. Model: opus-5-5
This commit is contained in:
+64
-68
@@ -312,7 +312,7 @@ func (s *Service) SendWallops(
|
||||
|
||||
_, _, err = s.FanOut(
|
||||
ctx, irc.CmdWallops, nick, "*", 0, 0,
|
||||
nil, body, nil, recipients,
|
||||
nil, body, nil, recipients, 0,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wallops: %w", err)
|
||||
@@ -347,12 +347,19 @@ type DirectMsgResult struct {
|
||||
// session IDs, notifying each via the broker. toSessionID
|
||||
// and toChannelID are the recipient InsertMessage records
|
||||
// for history, 0 when there is none.
|
||||
//
|
||||
// skipClientID is the client whose queue the message is
|
||||
// not added to: the IRC listener connection that sent the
|
||||
// command, which has already shown it to its user. The HTTP
|
||||
// API passes 0, as its clients see their own commands
|
||||
// through their queues.
|
||||
func (s *Service) FanOut(
|
||||
ctx context.Context,
|
||||
command, from, to string,
|
||||
toSessionID, toChannelID int64,
|
||||
params, body, meta json.RawMessage,
|
||||
sessionIDs []int64,
|
||||
skipClientID int64,
|
||||
) (int64, string, error) {
|
||||
dbID, msgUUID, err := s.db.InsertMessage(
|
||||
ctx, command, from, to,
|
||||
@@ -363,58 +370,40 @@ func (s *Service) FanOut(
|
||||
}
|
||||
|
||||
for _, sid := range sessionIDs {
|
||||
_ = s.db.EnqueueToSession(ctx, sid, dbID)
|
||||
_ = s.db.EnqueueToSession(ctx, sid, dbID, skipClientID)
|
||||
s.broker.Notify(sid)
|
||||
}
|
||||
|
||||
return dbID, msgUUID, nil
|
||||
}
|
||||
|
||||
// excludeSession returns a copy of ids without the given
|
||||
// session.
|
||||
func excludeSession(
|
||||
ids []int64,
|
||||
exclude int64,
|
||||
) []int64 {
|
||||
out := make([]int64, 0, len(ids))
|
||||
|
||||
for _, id := range ids {
|
||||
if id != exclude {
|
||||
out = append(out, id)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// SendChannelMessage checks membership, bans and
|
||||
// moderation, then the hashcash stamp of a PRIVMSG to a +H
|
||||
// channel, then fans out a message to all channel members
|
||||
// except the sender. The stamp is checked after the other
|
||||
// checks so that a message they refuse does not spend it.
|
||||
// Returns the database row ID, message UUID, and any error.
|
||||
// The dbID lets callers enqueue the same message to the
|
||||
// sender when echo is needed (HTTP transport).
|
||||
// channel, then fans out a message to all channel members,
|
||||
// the sender's session included, skipping client
|
||||
// skipClientID as FanOut does. The stamp is checked after
|
||||
// the other checks so that a message they refuse does not
|
||||
// spend it. Returns the message UUID and any error.
|
||||
func (s *Service) SendChannelMessage(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, command, channel string,
|
||||
body, meta json.RawMessage,
|
||||
) (int64, string, error) {
|
||||
) (string, error) {
|
||||
noSuchChannel := &IRCError{
|
||||
irc.ErrNoSuchChannel, []string{channel}, msgNoSuchChannel,
|
||||
}
|
||||
|
||||
chID, err := s.db.GetChannelByName(ctx, channel)
|
||||
if err != nil {
|
||||
return 0, "", noSuchChannel
|
||||
return "", noSuchChannel
|
||||
}
|
||||
|
||||
isMember, _ := s.db.IsChannelMember(
|
||||
ctx, chID, sessionID,
|
||||
)
|
||||
if !isMember {
|
||||
return 0, "", &IRCError{
|
||||
return "", &IRCError{
|
||||
irc.ErrCannotSendToChan,
|
||||
[]string{channel},
|
||||
"Cannot send to channel",
|
||||
@@ -426,7 +415,7 @@ func (s *Service) SendChannelMessage(
|
||||
ctx, chID, sessionID,
|
||||
)
|
||||
if banErr == nil && isBanned {
|
||||
return 0, "", &IRCError{
|
||||
return "", &IRCError{
|
||||
irc.ErrCannotSendToChan,
|
||||
[]string{channel},
|
||||
"Cannot send to channel (+b)",
|
||||
@@ -443,7 +432,7 @@ func (s *Service) SendChannelMessage(
|
||||
)
|
||||
|
||||
if !isOp && !isVoiced {
|
||||
return 0, "", &IRCError{
|
||||
return "", &IRCError{
|
||||
irc.ErrCannotSendToChan,
|
||||
[]string{channel},
|
||||
"Cannot send to channel (+m)",
|
||||
@@ -456,35 +445,35 @@ func (s *Service) SendChannelMessage(
|
||||
ctx, chID, channel, body, meta,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
memberIDs, _ := s.db.GetChannelMemberIDs(ctx, chID)
|
||||
recipients := excludeSession(memberIDs, sessionID)
|
||||
|
||||
dbID, uuid, fanErr := s.FanOut(
|
||||
_, uuid, fanErr := s.FanOut(
|
||||
ctx, command, nick, channel, 0, chID,
|
||||
nil, body, meta, recipients,
|
||||
nil, body, meta, memberIDs, skipClientID,
|
||||
)
|
||||
// The channel was deleted after the lookup above.
|
||||
if db.IsForeignKeyConstraintError(fanErr) {
|
||||
return 0, "", noSuchChannel
|
||||
return "", noSuchChannel
|
||||
}
|
||||
|
||||
if fanErr != nil {
|
||||
return 0, "", fanErr
|
||||
return "", fanErr
|
||||
}
|
||||
|
||||
return dbID, uuid, nil
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// SendDirectMessage validates the target and sends a
|
||||
// direct message, returning the message UUID and any away
|
||||
// message set on the target.
|
||||
// direct message to the target and the sender's session,
|
||||
// skipping client skipClientID as FanOut does, and returns
|
||||
// the message UUID and any away message set on the target.
|
||||
func (s *Service) SendDirectMessage(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, command, target string,
|
||||
body, meta json.RawMessage,
|
||||
) (*DirectMsgResult, error) {
|
||||
@@ -506,7 +495,7 @@ func (s *Service) SendDirectMessage(
|
||||
|
||||
_, uuid, fanErr := s.FanOut(
|
||||
ctx, command, nick, target, targetSID, 0,
|
||||
nil, body, meta, recipients,
|
||||
nil, body, meta, recipients, skipClientID,
|
||||
)
|
||||
// The target's session was deleted after the lookup above.
|
||||
if db.IsForeignKeyConstraintError(fanErr) {
|
||||
@@ -522,11 +511,12 @@ func (s *Service) SendDirectMessage(
|
||||
|
||||
// JoinChannel creates or joins a channel, making the
|
||||
// first joiner the operator. Fans out the JOIN to all
|
||||
// channel members. A channel name that validChannelRe
|
||||
// does not match gets ERR_NOSUCHCHANNEL.
|
||||
// channel members, skipping client skipClientID as FanOut
|
||||
// does. A channel name that validChannelRe does not match
|
||||
// gets ERR_NOSUCHCHANNEL.
|
||||
func (s *Service) JoinChannel(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, channel, suppliedKey string,
|
||||
) (*JoinResult, error) {
|
||||
if !validChannelRe.MatchString(channel) {
|
||||
@@ -549,7 +539,7 @@ func (s *Service) JoinChannel(
|
||||
|
||||
_, _, _ = s.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, irc.CmdJoin, nick, channel, 0, chID,
|
||||
nil, body, nil, memberIDs,
|
||||
nil, body, nil, memberIDs, skipClientID,
|
||||
)
|
||||
|
||||
return &JoinResult{
|
||||
@@ -558,12 +548,12 @@ func (s *Service) JoinChannel(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PartChannel validates membership, broadcasts PART to
|
||||
// remaining members, removes the user, and cleans up empty
|
||||
// channels.
|
||||
// PartChannel validates membership, broadcasts PART to all
|
||||
// channel members, skipping client skipClientID as FanOut
|
||||
// does, removes the user, and cleans up empty channels.
|
||||
func (s *Service) PartChannel(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, channel, reason string,
|
||||
) error {
|
||||
chID, err := s.db.GetChannelByName(ctx, channel)
|
||||
@@ -587,12 +577,11 @@ func (s *Service) PartChannel(
|
||||
}
|
||||
|
||||
memberIDs, _ := s.db.GetChannelMemberIDs(ctx, chID)
|
||||
recipients := excludeSession(memberIDs, sessionID)
|
||||
body, _ := json.Marshal([]string{reason}) //nolint:errchkjson
|
||||
|
||||
_, _, _ = s.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, irc.CmdPart, nick, channel, 0, chID,
|
||||
nil, body, nil, recipients,
|
||||
nil, body, nil, memberIDs, skipClientID,
|
||||
)
|
||||
|
||||
s.db.PartChannelAndDeleteIfEmpty(ctx, chID, sessionID) //nolint:errcheck,gosec
|
||||
@@ -601,10 +590,11 @@ func (s *Service) PartChannel(
|
||||
}
|
||||
|
||||
// SetTopic validates membership and topic-lock, sets the
|
||||
// topic, and broadcasts the change.
|
||||
// topic, and broadcasts the change to all channel members,
|
||||
// skipping client skipClientID as FanOut does.
|
||||
func (s *Service) SetTopic(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, channel, topic string,
|
||||
) error {
|
||||
chID, err := s.db.GetChannelByName(ctx, channel)
|
||||
@@ -653,18 +643,19 @@ func (s *Service) SetTopic(
|
||||
|
||||
_, _, _ = s.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, irc.CmdTopic, nick, channel, 0, chID,
|
||||
nil, body, nil, memberIDs,
|
||||
nil, body, nil, memberIDs, skipClientID,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KickUser validates operator status and target
|
||||
// membership, broadcasts the KICK, removes the target,
|
||||
// and cleans up empty channels.
|
||||
// membership, broadcasts the KICK to all channel members,
|
||||
// skipping client skipClientID as FanOut does, removes the
|
||||
// target, and cleans up empty channels.
|
||||
func (s *Service) KickUser(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
nick, channel, targetNick, reason string,
|
||||
) error {
|
||||
chID, err := s.db.GetChannelByName(ctx, channel)
|
||||
@@ -717,7 +708,7 @@ func (s *Service) KickUser(
|
||||
|
||||
_, _, _ = s.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, irc.CmdKick, nick, channel, 0, chID,
|
||||
params, body, nil, memberIDs,
|
||||
params, body, nil, memberIDs, skipClientID,
|
||||
)
|
||||
|
||||
s.db.PartChannelAndDeleteIfEmpty(ctx, chID, targetSID) //nolint:errcheck,gosec
|
||||
@@ -798,10 +789,11 @@ func (s *Service) InviteUser(
|
||||
}
|
||||
|
||||
// ChangeNick changes a user's nickname and broadcasts the
|
||||
// change to all users sharing channels.
|
||||
// change to the user's own session and all users sharing
|
||||
// channels, skipping client skipClientID as FanOut does.
|
||||
func (s *Service) ChangeNick(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
oldNick, newNick string,
|
||||
) error {
|
||||
err := s.db.ChangeNick(ctx, sessionID, newNick)
|
||||
@@ -822,7 +814,9 @@ func (s *Service) ChangeNick(
|
||||
}
|
||||
}
|
||||
|
||||
s.broadcastNickChange(ctx, sessionID, oldNick, newNick)
|
||||
s.broadcastNickChange(
|
||||
ctx, sessionID, skipClientID, oldNick, newNick,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1042,9 +1036,10 @@ func (s *Service) SetChannelFlag(
|
||||
}
|
||||
|
||||
// BroadcastMode fans out a MODE change to all channel
|
||||
// members.
|
||||
// members, skipping client skipClientID as FanOut does.
|
||||
func (s *Service) BroadcastMode(
|
||||
ctx context.Context,
|
||||
skipClientID int64,
|
||||
nick, channel string,
|
||||
chID int64,
|
||||
modeText string,
|
||||
@@ -1054,7 +1049,7 @@ func (s *Service) BroadcastMode(
|
||||
|
||||
_, _, _ = s.FanOut( //nolint:dogsled // fire-and-forget broadcast
|
||||
ctx, irc.CmdMode, nick, channel, 0, chID,
|
||||
nil, body, nil, memberIDs,
|
||||
nil, body, nil, memberIDs, skipClientID,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1306,11 +1301,12 @@ func (s *Service) checkChannelHashcash(
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcastNickChange notifies channel peers of a nick
|
||||
// broadcastNickChange notifies the user's own session,
|
||||
// except client skipClientID, and channel peers of a nick
|
||||
// change.
|
||||
func (s *Service) broadcastNickChange(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
sessionID, skipClientID int64,
|
||||
oldNick, newNick string,
|
||||
) {
|
||||
channels, err := s.db.GetSessionChannels(
|
||||
@@ -1332,7 +1328,7 @@ func (s *Service) broadcastNickChange(
|
||||
}
|
||||
|
||||
// Notify the user themselves (for multi-client sync).
|
||||
_ = s.db.EnqueueToSession(ctx, sessionID, dbID)
|
||||
_ = s.db.EnqueueToSession(ctx, sessionID, dbID, skipClientID)
|
||||
s.broker.Notify(sessionID)
|
||||
notified[sessionID] = true
|
||||
|
||||
@@ -1351,7 +1347,7 @@ func (s *Service) broadcastNickChange(
|
||||
|
||||
notified[mid] = true
|
||||
|
||||
_ = s.db.EnqueueToSession(ctx, mid, dbID)
|
||||
_ = s.db.EnqueueToSession(ctx, mid, dbID, 0)
|
||||
s.broker.Notify(mid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ func TestFanOut(t *testing.T) {
|
||||
dbID, uuid, err := env.svc.FanOut(
|
||||
ctx, irc.CmdPrivmsg, "alice", "#test", 0, 0,
|
||||
nil, body, nil,
|
||||
[]int64{sid1, sid2},
|
||||
[]int64{sid1, sid2}, 0,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("FanOut: %v", err)
|
||||
@@ -190,7 +190,7 @@ func TestJoinChannel(t *testing.T) {
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
result, err := env.svc.JoinChannel(
|
||||
ctx, sid, "alice", "#general", "",
|
||||
ctx, sid, 0, "alice", "#general", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("JoinChannel: %v", err)
|
||||
@@ -208,7 +208,7 @@ func TestJoinChannel(t *testing.T) {
|
||||
sid2 := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
result2, err := env.svc.JoinChannel(
|
||||
ctx, sid2, "bob", "#general", "",
|
||||
ctx, sid2, 0, "bob", "#general", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("JoinChannel bob: %v", err)
|
||||
@@ -230,14 +230,14 @@ func TestPartChannel(t *testing.T) {
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
_, err := env.svc.JoinChannel(
|
||||
ctx, sid, "alice", "#general", "",
|
||||
ctx, sid, 0, "alice", "#general", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("JoinChannel: %v", err)
|
||||
}
|
||||
|
||||
err = env.svc.PartChannel(
|
||||
ctx, sid, "alice", "#general", "bye",
|
||||
ctx, sid, 0, "alice", "#general", "bye",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("PartChannel: %v", err)
|
||||
@@ -245,7 +245,7 @@ func TestPartChannel(t *testing.T) {
|
||||
|
||||
// Parting a non-existent channel returns error.
|
||||
err = env.svc.PartChannel(
|
||||
ctx, sid, "alice", "#nonexistent", "",
|
||||
ctx, sid, 0, "alice", "#nonexistent", "",
|
||||
)
|
||||
if err == nil {
|
||||
t.Error("expected error for non-existent channel")
|
||||
@@ -309,7 +309,7 @@ func joinConcurrently(
|
||||
|
||||
joins[i] = func() error {
|
||||
result, err := env.svc.JoinChannel(
|
||||
ctx, sessionID, nick, channel, "",
|
||||
ctx, sessionID, 0, nick, channel, "",
|
||||
)
|
||||
results[i] = result
|
||||
|
||||
@@ -335,7 +335,7 @@ func TestConcurrentJoinsRespectUserLimit(t *testing.T) {
|
||||
opSID := createSession(ctx, t, env.db, opNick)
|
||||
|
||||
created, err := env.svc.JoinChannel(
|
||||
ctx, opSID, opNick, channel, "",
|
||||
ctx, opSID, 0, opNick, channel, "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", opNick, err)
|
||||
@@ -448,7 +448,7 @@ func TestJoinConcurrentWithLastPart(t *testing.T) {
|
||||
joinerSID := createSession(ctx, t, env.db, joinerNick)
|
||||
|
||||
_, err := env.svc.JoinChannel(
|
||||
ctx, leaverSID, leaverNick, channel, "",
|
||||
ctx, leaverSID, 0, leaverNick, channel, "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", leaverNick, err)
|
||||
@@ -457,12 +457,12 @@ func TestJoinConcurrentWithLastPart(t *testing.T) {
|
||||
errs := runConcurrently(
|
||||
func() error {
|
||||
return env.svc.PartChannel(
|
||||
ctx, leaverSID, leaverNick, channel, "",
|
||||
ctx, leaverSID, 0, leaverNick, channel, "",
|
||||
)
|
||||
},
|
||||
func() error {
|
||||
_, joinErr := env.svc.JoinChannel(
|
||||
ctx, joinerSID, joinerNick, channel, "",
|
||||
ctx, joinerSID, 0, joinerNick, channel, "",
|
||||
)
|
||||
|
||||
return joinErr
|
||||
@@ -513,7 +513,7 @@ func TestJoinConcurrentWithLastQuit(t *testing.T) {
|
||||
joinerSID := createSession(ctx, t, env.db, joinerNick)
|
||||
|
||||
created, err := env.svc.JoinChannel(
|
||||
ctx, leaverSID, leaverNick, channel, "",
|
||||
ctx, leaverSID, 0, leaverNick, channel, "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", leaverNick, err)
|
||||
@@ -545,7 +545,7 @@ func TestJoinConcurrentWithLastQuit(t *testing.T) {
|
||||
)
|
||||
|
||||
joined, joinErr = env.svc.JoinChannel(
|
||||
ctx, joinerSID, joinerNick, channel, "",
|
||||
ctx, joinerSID, 0, joinerNick, channel, "",
|
||||
)
|
||||
if !errors.As(joinErr, &ircErr) ||
|
||||
ircErr.Code != irc.ErrBadChannelKey {
|
||||
@@ -592,7 +592,7 @@ func TestQuitQueuedAheadOfNewNickHoldersJoin(t *testing.T) {
|
||||
quitterSID := createSession(ctx, t, env.db, nick)
|
||||
|
||||
for _, channel := range []string{crowd, shared} {
|
||||
_, err := env.svc.JoinChannel(ctx, quitterSID, nick, channel, "")
|
||||
_, err := env.svc.JoinChannel(ctx, quitterSID, 0, nick, channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", channel, err)
|
||||
}
|
||||
@@ -602,7 +602,7 @@ func TestQuitQueuedAheadOfNewNickHoldersJoin(t *testing.T) {
|
||||
memberNick := fmt.Sprintf("crowd%dx%d", trial, member)
|
||||
memberSID := createSession(ctx, t, env.db, memberNick)
|
||||
|
||||
_, err := env.svc.JoinChannel(ctx, memberSID, memberNick, crowd, "")
|
||||
_, err := env.svc.JoinChannel(ctx, memberSID, 0, memberNick, crowd, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", memberNick, err)
|
||||
}
|
||||
@@ -617,7 +617,7 @@ func TestQuitQueuedAheadOfNewNickHoldersJoin(t *testing.T) {
|
||||
t.Fatalf("create session %s: %v", watcherNick, err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, watcherSID, watcherNick, shared, "")
|
||||
_, err = env.svc.JoinChannel(ctx, watcherSID, 0, watcherNick, shared, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join %s: %v", watcherNick, err)
|
||||
}
|
||||
@@ -678,7 +678,7 @@ func takeNickAndJoin(
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, sessionID, nick, channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, sessionID, 0, nick, channel, "")
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -710,7 +710,7 @@ func TestJoinUsesUpInvite(t *testing.T) {
|
||||
opSID := createSession(ctx, t, env.db, "op")
|
||||
guestSID := createSession(ctx, t, env.db, "guest")
|
||||
|
||||
created, err := env.svc.JoinChannel(ctx, opSID, "op", channel, "")
|
||||
created, err := env.svc.JoinChannel(ctx, opSID, 0, "op", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join op: %v", err)
|
||||
}
|
||||
@@ -725,17 +725,17 @@ func TestJoinUsesUpInvite(t *testing.T) {
|
||||
t.Fatalf("invite guest: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, "guest", channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, 0, "guest", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("invited join: %v", err)
|
||||
}
|
||||
|
||||
err = env.svc.PartChannel(ctx, guestSID, "guest", channel, "")
|
||||
err = env.svc.PartChannel(ctx, guestSID, 0, "guest", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("part guest: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, "guest", channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, 0, "guest", channel, "")
|
||||
|
||||
var ircErr *service.IRCError
|
||||
if !errors.As(err, &ircErr) || ircErr.Code != irc.ErrInviteOnlyChan {
|
||||
@@ -754,7 +754,7 @@ func TestJoinRefusedByHostmaskBan(t *testing.T) {
|
||||
|
||||
opSID := createSession(ctx, t, env.db, "op")
|
||||
|
||||
created, err := env.svc.JoinChannel(ctx, opSID, "op", channel, "")
|
||||
created, err := env.svc.JoinChannel(ctx, opSID, 0, "op", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join op: %v", err)
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func TestJoinRefusedByHostmaskBan(t *testing.T) {
|
||||
t.Fatalf("create session guest: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, "guest", channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, guestSID, 0, "guest", channel, "")
|
||||
|
||||
var ircErr *service.IRCError
|
||||
if !errors.As(err, &ircErr) || ircErr.Code != irc.ErrBannedFromChan {
|
||||
@@ -793,17 +793,17 @@ func TestPartDeletesChannelOnlyWhenEmpty(t *testing.T) {
|
||||
aliceSID := createSession(ctx, t, env.db, "alice")
|
||||
bobSID := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
_, err := env.svc.JoinChannel(ctx, aliceSID, "alice", channel, "")
|
||||
_, err := env.svc.JoinChannel(ctx, aliceSID, 0, "alice", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, bobSID, "bob", channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, bobSID, 0, "bob", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join bob: %v", err)
|
||||
}
|
||||
|
||||
err = env.svc.PartChannel(ctx, aliceSID, "alice", channel, "")
|
||||
err = env.svc.PartChannel(ctx, aliceSID, 0, "alice", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("part alice: %v", err)
|
||||
}
|
||||
@@ -818,7 +818,7 @@ func TestPartDeletesChannelOnlyWhenEmpty(t *testing.T) {
|
||||
t.Fatal("bob is not on the channel after alice parted")
|
||||
}
|
||||
|
||||
err = env.svc.PartChannel(ctx, bobSID, "bob", channel, "")
|
||||
err = env.svc.PartChannel(ctx, bobSID, 0, "bob", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("part bob: %v", err)
|
||||
}
|
||||
@@ -839,12 +839,12 @@ func TestKickOfLastMemberDeletesChannel(t *testing.T) {
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
_, err := env.svc.JoinChannel(ctx, sid, "alice", channel, "")
|
||||
_, err := env.svc.JoinChannel(ctx, sid, 0, "alice", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
err = env.svc.KickUser(ctx, sid, "alice", channel, "alice", "")
|
||||
err = env.svc.KickUser(ctx, sid, 0, "alice", channel, "alice", "")
|
||||
if err != nil {
|
||||
t.Fatalf("kick alice: %v", err)
|
||||
}
|
||||
@@ -863,14 +863,14 @@ func TestSendChannelMessage(t *testing.T) {
|
||||
sid2 := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
_, err := env.svc.JoinChannel(
|
||||
ctx, sid1, "alice", "#chat", "",
|
||||
ctx, sid1, 0, "alice", "#chat", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(
|
||||
ctx, sid2, "bob", "#chat", "",
|
||||
ctx, sid2, 0, "bob", "#chat", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join bob: %v", err)
|
||||
@@ -878,18 +878,14 @@ func TestSendChannelMessage(t *testing.T) {
|
||||
|
||||
body, _ := json.Marshal([]string{"hello world"}) //nolint:errchkjson
|
||||
|
||||
dbID, uuid, err := env.svc.SendChannelMessage(
|
||||
ctx, sid1, "alice",
|
||||
uuid, err := env.svc.SendChannelMessage(
|
||||
ctx, sid1, 0, "alice",
|
||||
irc.CmdPrivmsg, "#chat", body, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("SendChannelMessage: %v", err)
|
||||
}
|
||||
|
||||
if dbID == 0 {
|
||||
t.Error("expected non-zero dbID")
|
||||
}
|
||||
|
||||
if uuid == "" {
|
||||
t.Error("expected non-empty UUID")
|
||||
}
|
||||
@@ -897,8 +893,8 @@ func TestSendChannelMessage(t *testing.T) {
|
||||
// Non-member cannot send.
|
||||
sid3 := createSession(ctx, t, env.db, "charlie")
|
||||
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, sid3, "charlie",
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, sid3, 0, "charlie",
|
||||
irc.CmdPrivmsg, "#chat", body, nil,
|
||||
)
|
||||
if err == nil {
|
||||
@@ -914,14 +910,14 @@ func TestBroadcastQuit(t *testing.T) {
|
||||
sid2 := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
_, err := env.svc.JoinChannel(
|
||||
ctx, sid1, "alice", "#room", "",
|
||||
ctx, sid1, 0, "alice", "#room", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(
|
||||
ctx, sid2, "bob", "#room", "",
|
||||
ctx, sid2, 0, "bob", "#room", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join bob: %v", err)
|
||||
@@ -963,7 +959,7 @@ func TestSendDirectMessageTargetQuits(t *testing.T) {
|
||||
body, _ := json.Marshal([]string{"hi"}) //nolint:errchkjson
|
||||
|
||||
_, err = env.svc.SendDirectMessage(
|
||||
ctx, sid, "alice", irc.CmdPrivmsg, "bob", body, nil,
|
||||
ctx, sid, 0, "alice", irc.CmdPrivmsg, "bob", body, nil,
|
||||
)
|
||||
|
||||
var ircErr *service.IRCError
|
||||
@@ -983,7 +979,7 @@ func TestSendChannelMessageChannelDeleted(t *testing.T) {
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
_, err := env.svc.JoinChannel(ctx, sid, "alice", "#gone", "")
|
||||
_, err := env.svc.JoinChannel(ctx, sid, 0, "alice", "#gone", "")
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
@@ -1000,8 +996,8 @@ func TestSendChannelMessageChannelDeleted(t *testing.T) {
|
||||
|
||||
body, _ := json.Marshal([]string{"hi"}) //nolint:errchkjson
|
||||
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, sid, "alice", irc.CmdPrivmsg, "#gone", body, nil,
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, sid, 0, "alice", irc.CmdPrivmsg, "#gone", body, nil,
|
||||
)
|
||||
|
||||
var ircErr *service.IRCError
|
||||
@@ -1019,14 +1015,14 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
|
||||
sid2 := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
result, err := env.svc.JoinChannel(
|
||||
ctx, sid1, "alice", "#modchat", "",
|
||||
ctx, sid1, 0, "alice", "#modchat", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(
|
||||
ctx, sid2, "bob", "#modchat", "",
|
||||
ctx, sid2, 0, "bob", "#modchat", "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("join bob: %v", err)
|
||||
@@ -1039,8 +1035,8 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
|
||||
body, _ := json.Marshal([]string{"test"}) //nolint:errchkjson
|
||||
|
||||
// Bob (non-op, non-voiced) should fail to send.
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, sid2, "bob",
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, sid2, 0, "bob",
|
||||
irc.CmdPrivmsg, "#modchat", body, nil,
|
||||
)
|
||||
if err == nil {
|
||||
@@ -1048,8 +1044,8 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
|
||||
}
|
||||
|
||||
// Alice (operator) should succeed.
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, sid1, "alice",
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, sid1, 0, "alice",
|
||||
irc.CmdPrivmsg, "#modchat", body, nil,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1074,7 +1070,7 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
|
||||
refuse func(ctx context.Context, env *testEnv, chID, bobSID int64) error
|
||||
}{
|
||||
{"not a member", func(ctx context.Context, env *testEnv, _, bobSID int64) error {
|
||||
return env.svc.PartChannel(ctx, bobSID, "bob", channel, "")
|
||||
return env.svc.PartChannel(ctx, bobSID, 0, "bob", channel, "")
|
||||
}},
|
||||
{"banned", func(ctx context.Context, env *testEnv, chID, _ int64) error {
|
||||
return env.db.AddChannelBan(ctx, chID, "bob!*@*", "alice")
|
||||
@@ -1094,12 +1090,12 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
|
||||
aliceSID := createSession(ctx, t, env.db, "alice")
|
||||
bobSID := createSession(ctx, t, env.db, "bob")
|
||||
|
||||
joined, err := env.svc.JoinChannel(ctx, aliceSID, "alice", channel, "")
|
||||
joined, err := env.svc.JoinChannel(ctx, aliceSID, 0, "alice", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join alice: %v", err)
|
||||
}
|
||||
|
||||
_, err = env.svc.JoinChannel(ctx, bobSID, "bob", channel, "")
|
||||
_, err = env.svc.JoinChannel(ctx, bobSID, 0, "bob", channel, "")
|
||||
if err != nil {
|
||||
t.Fatalf("join bob: %v", err)
|
||||
}
|
||||
@@ -1119,8 +1115,8 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
|
||||
)
|
||||
meta, _ := json.Marshal(map[string]string{"hashcash": stamp})
|
||||
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, bobSID, "bob", irc.CmdPrivmsg, channel, body, meta,
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, bobSID, 0, "bob", irc.CmdPrivmsg, channel, body, meta,
|
||||
)
|
||||
|
||||
var ircErr *service.IRCError
|
||||
@@ -1129,8 +1125,8 @@ func TestRefusedChannelMessageLeavesStampUnspent(t *testing.T) {
|
||||
t.Fatalf("bob: want ERR_CANNOTSENDTOCHAN, got %v", err)
|
||||
}
|
||||
|
||||
_, _, err = env.svc.SendChannelMessage(
|
||||
ctx, aliceSID, "alice", irc.CmdPrivmsg, channel, body, meta,
|
||||
_, err = env.svc.SendChannelMessage(
|
||||
ctx, aliceSID, 0, "alice", irc.CmdPrivmsg, channel, body, meta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("alice with the same stamp: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user