Skip to content

Commit

Permalink
message: make username part of the sender
Browse files Browse the repository at this point in the history
This allows us to eliminate a user's display name from contexts where
we don't want to provide it, particularly learning.
  • Loading branch information
zephyrtronium committed Nov 24, 2024
1 parent 0cf77ac commit 78c28b0
Show file tree
Hide file tree
Showing 11 changed files with 37 additions and 34 deletions.
2 changes: 0 additions & 2 deletions brain/learn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ func TestRecall(t *testing.T) {
ID: "1",
To: "#bocchi",
Sender: userhash.Hash{1},
Name: "bocchi",
Text: "bocchi the rock!",
Timestamp: 1,
},
Expand All @@ -122,7 +121,6 @@ func TestRecall(t *testing.T) {
ID: "1",
To: "#bocchi",
Sender: userhash.Hash{1},
Name: "bocchi",
Text: "bocchi the rock!",
Timestamp: 1,
},
Expand Down
2 changes: 1 addition & 1 deletion channel/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Channel struct {
// History is a list of recent messages seen in the channel.
// Note that messages which are forgotten due to moderation are not removed
// from this list in general.
History History[*message.Received[string]]
History History[*message.Received[message.User]]
// Memery is the meme detector for the channel.
Memery *MemeDetector
// Emotes is the distribution of emotes.
Expand Down
2 changes: 1 addition & 1 deletion command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Invocation struct {
// Message is the message which triggered the invocation with the platform
// user ID as the type argument.
// It is always non-nil, but not all fields are guaranteed to be populated.
Message *message.Received[string]
Message *message.Received[message.User]
// Args is the parsed arguments to the command.
Args map[string]string
}
Expand Down
14 changes: 7 additions & 7 deletions command/marriage.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
"github.com/zephyrtronium/robot/message"
)

func score(log *slog.Logger, h *channel.History[*message.Received[string]], user string) (x float64, c, f, l, n int) {
func score(log *slog.Logger, h *channel.History[*message.Received[message.User]], user string) (x float64, c, f, l, n int) {
mine := make(map[string]map[string]struct{})
for m := range h.All() {
who, text := m.Sender, m.Text
who, text := m.Sender.ID, m.Text
n++
// Count the number of distinct other users who said each of your messages
// after you said it.
Expand Down Expand Up @@ -77,12 +77,12 @@ var affections = pick.New([]pick.Case[string]{
// Affection describes the caller's affection MMR.
// No arguments.
func Affection(ctx context.Context, robo *Robot, call *Invocation) {
x, c, f, l, n := score(robo.Log, &call.Channel.History, call.Message.Sender)
x, c, f, l, n := score(robo.Log, &call.Channel.History, call.Message.Sender.ID)
// Anything we do will require an emote.
e := call.Channel.Emotes.Pick(rand.Uint32())
if x == 0 {
// Check for the broadcaster. They get special treatment.
if strings.EqualFold(call.Message.Name, strings.TrimPrefix(call.Channel.Name, "#")) {
if strings.EqualFold(call.Message.Sender.Name, strings.TrimPrefix(call.Channel.Name, "#")) {
if _, ok := call.Channel.Extra.LoadOrStore(broadcasterAffectionKey{}, struct{}{}); ok {
call.Channel.Message(ctx, message.Format("", "Don't make me repeat myself, it's embarrassing! %s", e).AsReply(call.Message.ID))
return
Expand Down Expand Up @@ -111,14 +111,14 @@ type partner struct {
// Marry proposes to the robo.
// - partnership: Type of partnership requested, e.g. "wife", "waifu", "daddy". Optional.
func Marry(ctx context.Context, robo *Robot, call *Invocation) {
x, _, _, _, _ := score(robo.Log, &call.Channel.History, call.Message.Sender)
x, _, _, _, _ := score(robo.Log, &call.Channel.History, call.Message.Sender.ID)
e := call.Channel.Emotes.Pick(rand.Uint32())
broadcaster := strings.EqualFold(call.Message.Name, strings.TrimPrefix(call.Channel.Name, "#")) && x == 0
broadcaster := strings.EqualFold(call.Message.Sender.Name, strings.TrimPrefix(call.Channel.Name, "#")) && x == 0
if x < 10 && !broadcaster {
call.Channel.Message(ctx, message.Format("", "no %s", e).AsReply(call.Message.ID))
return
}
me := &partner{who: call.Message.Sender, until: call.Message.Time().Add(time.Hour)}
me := &partner{who: call.Message.Sender.ID, until: call.Message.Time().Add(time.Hour)}
for {
l, ok := call.Channel.Extra.LoadOrStore(partnerKey{}, me)
if !ok {
Expand Down
4 changes: 2 additions & 2 deletions command/privacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func Private(ctx context.Context, robo *Robot, call *Invocation) {
err := robo.Privacy.Add(ctx, call.Message.Sender)
err := robo.Privacy.Add(ctx, call.Message.Sender.ID)
if err != nil {
robo.Log.ErrorContext(ctx, "privacy add failed", slog.Any("err", err), slog.String("channel", call.Channel.Name))
call.Channel.Message(ctx, message.Format("", "Something went wrong while trying to add you to the privacy list. Try again. Sorry!").AsReply(call.Message.ID))
Expand All @@ -20,7 +20,7 @@ func Private(ctx context.Context, robo *Robot, call *Invocation) {
}

func Unprivate(ctx context.Context, robo *Robot, call *Invocation) {
err := robo.Privacy.Remove(ctx, call.Message.Sender)
err := robo.Privacy.Remove(ctx, call.Message.Sender.ID)
if err != nil {
robo.Log.ErrorContext(ctx, "privacy remove failed", slog.Any("err", err), slog.String("channel", call.Channel.Name))
call.Channel.Message(ctx, message.Format("", "Something went wrong while trying to add you to the privacy list. Try again. Sorry!").AsReply(call.Message.ID))
Expand Down
1 change: 0 additions & 1 deletion command/talk.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func speakCmd(ctx context.Context, robo *Robot, call *Invocation, effect string)
if ngPrompt.MatchString(call.Args["prompt"]) {
robo.Log.WarnContext(ctx, "nasty prompt",
slog.String("in", call.Channel.Name),
slog.String("from", call.Message.Name),
slog.String("prompt", call.Args["prompt"]),
)
e := call.Channel.Emotes.Pick(rand.Uint32())
Expand Down
7 changes: 3 additions & 4 deletions message/irc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import (
)

// FromTMI adapts a TMI IRC message.
func FromTMI(m *tmi.Message) *Received[string] {
func FromTMI(m *tmi.Message) *Received[User] {
id, _ := m.Tag("id")
sender, _ := m.Tag("user-id")
ts, _ := m.Tag("tmi-sent-ts")
u, _ := strconv.ParseInt(ts, 10, 64)
r := Received[string]{
r := Received[User]{
ID: id,
To: m.To(),
Sender: sender,
Name: m.DisplayName(),
Sender: User{ID: sender, Name: m.DisplayName()},
Text: m.Trailing,
Timestamp: u,
IsModerator: moderator(m),
Expand Down
7 changes: 4 additions & 3 deletions message/irc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"testing"
"time"

"github.com/zephyrtronium/robot/message"
"gitlab.com/zephyrtronium/tmi"

"github.com/zephyrtronium/robot/message"
)

func TestFromIRC(t *testing.T) {
Expand Down Expand Up @@ -74,10 +75,10 @@ func TestFromIRC(t *testing.T) {
if got := msg.To; got != c.to {
t.Errorf("wrong to: want %q, got %q", c.to, got)
}
if got := msg.Sender; got != c.sender {
if got := msg.Sender.ID; got != c.sender {
t.Errorf("wrong sender: want %q, got %q", c.sender, got)
}
if got := msg.Name; got != c.disp {
if got := msg.Sender.Name; got != c.disp {
t.Errorf("wrong display name: want %q, got %q", c.disp, got)
}
if got := msg.Text; got != c.text {
Expand Down
11 changes: 9 additions & 2 deletions message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ type Received[U comparable] struct {
// Whether it remains constant for a given sender depends on the semantics
// of the type argument.
Sender U
// Name is the display name of the message sender.
Name string
// Text is the text of the message.
Text string
// Timestamp is the timestamp of the message as milliseconds since the
Expand All @@ -37,6 +35,15 @@ func (m *Received[U]) Time() time.Time {
return time.UnixMilli(m.Timestamp)
}

// User is a user's ID and display name.
type User struct {
// ID is a user's ID.
// It must remain constant for a given user even across name changes.
ID string
// Name is a user's display name.
Name string
}

// Sent is a message to be sent to a service.
type Sent struct {
// Reply is a message to reply to. If empty, the message is not interpreted
Expand Down
19 changes: 9 additions & 10 deletions privmsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
log := slog.With(slog.String("trace", m.ID), slog.String("in", ch.Name))
log.InfoContext(ctx, "privmsg", slog.Duration("bias", time.Since(m.Time())))
defer log.InfoContext(ctx, "end")
if ch.Ignore[m.Sender] {
if ch.Ignore[m.Sender.ID] {
log.InfoContext(ctx, "message from ignored user")
return
}
Expand All @@ -42,7 +42,7 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
return
}
if cmd, ok := parseCommand(robo.tmi.name, m.Text); ok {
robo.command(ctx, log, ch, m, m.Sender, cmd)
robo.command(ctx, log, ch, m, cmd)
return
}
ch.History.Add(m.Time(), m)
Expand All @@ -56,7 +56,7 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
m.Text = t
}
robo.learn(ctx, log, ch, robo.hashes(), m)
switch err := ch.Memery.Check(m.Time(), m.Sender, m.Text); err {
switch err := ch.Memery.Check(m.Time(), m.Sender.ID, m.Text); err {
case channel.ErrNotCopypasta: // do nothing
case nil:
// Meme detected. Copypasta.
Expand Down Expand Up @@ -142,20 +142,20 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
robo.sendTMI(ctx, send, out)
}

func (robo *Robot) command(ctx context.Context, log *slog.Logger, ch *channel.Channel, m *message.Received[string], from, cmd string) {
func (robo *Robot) command(ctx context.Context, log *slog.Logger, ch *channel.Channel, m *message.Received[message.User], cmd string) {
robo.Metrics.TMICommandCount.Observe(1)
var c *twitchCommand
var args map[string]string
level := "any"
switch {
case from == robo.tmi.owner:
case m.Sender.ID == robo.tmi.owner:
c, args = findTwitch(twitchOwner, cmd)
if c != nil {
level = "owner"
break
}
fallthrough
case ch.Mod[from], m.IsModerator:
case ch.Mod[m.Sender.ID], m.IsModerator:
c, args = findTwitch(twitchMod, cmd)
if c != nil {
level = "mod"
Expand Down Expand Up @@ -192,12 +192,12 @@ func (robo *Robot) command(ctx context.Context, log *slog.Logger, ch *channel.Ch
}

// learn learns a given message's text if it passes ch's filters.
func (robo *Robot) learn(ctx context.Context, log *slog.Logger, ch *channel.Channel, hasher userhash.Hasher, msg *message.Received[string]) {
func (robo *Robot) learn(ctx context.Context, log *slog.Logger, ch *channel.Channel, hasher userhash.Hasher, msg *message.Received[message.User]) {
if !ch.Enabled.Load() {
log.DebugContext(ctx, "not learning in disabled channel")
return
}
switch err := robo.privacy.Check(ctx, msg.Sender); err {
switch err := robo.privacy.Check(ctx, msg.Sender.ID); err {
case nil: // do nothing
case privacy.ErrPrivate:
log.DebugContext(ctx, "private sender")
Expand All @@ -214,12 +214,11 @@ func (robo *Robot) learn(ctx context.Context, log *slog.Logger, ch *channel.Chan
log.DebugContext(ctx, "no learn tag")
return
}
user := hasher.Hash(msg.Sender, msg.To, msg.Time())
user := hasher.Hash(msg.Sender.ID, msg.To, msg.Time())
m := brain.Message{
ID: msg.ID,
To: msg.To,
Sender: user,
Name: msg.Name,
Text: msg.Text,
Timestamp: msg.Timestamp,
IsModerator: msg.IsModerator,
Expand Down
2 changes: 1 addition & 1 deletion tmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (robo *Robot) clearchat(ctx context.Context, msg *tmi.Message) {
default:
// Delete from user.
for m := range ch.History.All() {
if m.Sender != t {
if m.Sender.ID != t {
continue
}
slog.DebugContext(ctx, "forget from user", slog.String("channel", msg.To()), slog.String("id", m.ID))
Expand Down

0 comments on commit 78c28b0

Please sign in to comment.