From 1b60287266897ccc396b15178c8fb2516d523220 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Sat, 22 Feb 2025 11:48:02 -0500 Subject: [PATCH] message: don't require target to format --- command/marriage.go | 24 ++++++++++++------------ command/moderate.go | 14 +++++++------- command/privacy.go | 10 +++++----- command/talk.go | 30 +++++++++++++++--------------- command/tamagotchi.go | 18 +++++++++--------- message/message.go | 10 +++++++--- privmsg.go | 4 ++-- 7 files changed, 57 insertions(+), 53 deletions(-) diff --git a/command/marriage.go b/command/marriage.go index 45a1a47..98cbaf9 100644 --- a/command/marriage.go +++ b/command/marriage.go @@ -88,15 +88,15 @@ func Affection(ctx context.Context, robo *Robot, call *Invocation) { // Check for the broadcaster. They get special treatment. 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)) + call.Channel.Message(ctx, message.Format("Don't make me repeat myself, it's embarrassing! %s", e).AsReply(call.Message.ID)) return } const funnyMessage = `It's a bit awkward to think of you like that, streamer... But, well, it's so fun to be here, and I have you to thank for that! So I'd say a whole bunch! %s` - call.Channel.Message(ctx, message.Format("", funnyMessage, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(funnyMessage, e).AsReply(call.Message.ID)) return } // possible! - call.Channel.Message(ctx, message.Format("", "literally zero %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("literally zero %s", e).AsReply(call.Message.ID)) return } s := affections.Pick(rand.Uint32()) @@ -123,7 +123,7 @@ func Marry(ctx context.Context, robo *Robot, call *Invocation) { e := call.Channel.Emotes.Pick(rand.Uint32()) 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)) + call.Channel.Message(ctx, message.Format("no %s", e).AsReply(call.Message.ID)) return } me := &partner{who: call.Message.Sender.ID, until: call.Message.Time().Add(time.Hour)} @@ -131,7 +131,7 @@ func Marry(ctx context.Context, robo *Robot, call *Invocation) { l, ok := call.Channel.Extra.LoadOrStore(partnerKey{}, me) if !ok { // No competition. We're a shoo-in. - call.Channel.Message(ctx, message.Format("", "sure why not %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("sure why not %s", e).AsReply(call.Message.ID)) return } cur := l.(*partner) @@ -143,19 +143,19 @@ func Marry(ctx context.Context, robo *Robot, call *Invocation) { // but start over anyway. continue } - call.Channel.Message(ctx, message.Format("", "How could you forget we're already together? I hate you! Unsubbed, unfollowed, unloved! %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("How could you forget we're already together? I hate you! Unsubbed, unfollowed, unloved! %s", e).AsReply(call.Message.ID)) return } - call.Channel.Message(ctx, message.Format("", "We're already together, silly! You're so funny and cute haha. %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("We're already together, silly! You're so funny and cute haha. %s", e).AsReply(call.Message.ID)) return } if call.Message.Time().Before(cur.until) { - call.Channel.Message(ctx, message.Format("", "My heart yet belongs to another... %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("My heart yet belongs to another... %s", e).AsReply(call.Message.ID)) return } y, _, _, _, _ := score(robo.Log, &call.Channel.History, cur.who) if x < y && !broadcaster { - call.Channel.Message(ctx, message.Format("", "I'm touched, but I must decline. I'm in love with someone else. %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("I'm touched, but I must decline. I'm in love with someone else. %s", e).AsReply(call.Message.ID)) return } if !call.Channel.Extra.CompareAndSwap(partnerKey{}, cur, me) { @@ -165,9 +165,9 @@ func Marry(ctx context.Context, robo *Robot, call *Invocation) { // We win. Now just decide which message to send. // TODO(zeph): since pick.Dist exists now, we could randomize if call.Args["partnership"] != "" { - call.Channel.Message(ctx, message.Format("", "Yes! I'll be your %s! %s", call.Args["partnership"], e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("Yes! I'll be your %s! %s", call.Args["partnership"], e).AsReply(call.Message.ID)) } else { - call.Channel.Message(ctx, message.Format("", "Yes! I'll marry you! %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("Yes! I'll marry you! %s", e).AsReply(call.Message.ID)) } return } @@ -177,7 +177,7 @@ func Marry(ctx context.Context, robo *Robot, call *Invocation) { // No args. func DescribeMarriage(ctx context.Context, robo *Robot, call *Invocation) { if t := call.Channel.SilentTime(); call.Message.Time().Before(t) { - call.Channel.Message(ctx, message.Format("", "I'm being quiet for the next %v, so the marriage system is disabled until then.", t.Sub(call.Message.Time())).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("I'm being quiet for the next %v, so the marriage system is disabled until then.", t.Sub(call.Message.Time())).AsReply(call.Message.ID)) return } const s = `I am looking for a long series of short-term relationships and am holding a ranked competitive how-much-I-like-you tournament to decide my suitors! Politely ask me to marry you (or become your partner) and I'll evaluate your score. I like copypasta, memes, and long walks in the chat.` diff --git a/command/moderate.go b/command/moderate.go index c57026d..9997b91 100644 --- a/command/moderate.go +++ b/command/moderate.go @@ -39,11 +39,11 @@ func Forget(ctx context.Context, robo *Robot, call *Invocation) { var r message.Sent switch n { case 0: - r = message.Format("", "No messages contained %q.", term) + r = message.Format("No messages contained %q.", term) case 1: - r = message.Format("", "Forgot 1 message.") + r = message.Format("Forgot 1 message.") default: - r = message.Format("", "Forgot %d messages.", n) + r = message.Format("Forgot %d messages.", n) } call.Channel.Message(ctx, r.AsReply(call.Message.ID)) } @@ -75,7 +75,7 @@ func Quiet(ctx context.Context, robo *Robot, call *Invocation) { n, err := strconv.Atoi(m[1]) if err != nil { // Should be impossible. - call.Channel.Message(ctx, message.Format("", `sorry? (%v)`, err).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`sorry? (%v)`, err).AsReply(call.Message.ID)) return } switch m[2][0] { @@ -89,7 +89,7 @@ func Quiet(ctx context.Context, robo *Robot, call *Invocation) { var err error dur, err = time.ParseDuration(call.Args["dur"]) if err != nil { - call.Channel.Message(ctx, message.Format("", `sorry? (%v)`, err).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`sorry? (%v)`, err).AsReply(call.Message.ID)) return } } @@ -102,7 +102,7 @@ func Quiet(ctx context.Context, robo *Robot, call *Invocation) { // Only do the spiel if the timer isn't very short. // Otherwise it's likely just clearing an existing silent time. if dur > 5*time.Second { - call.Channel.Message(ctx, message.Format("", `I won't talk or learn for %v. Some commands relating to moderation and privacy will still make me talk. I'll mention when quiet time is up.`, dur).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`I won't talk or learn for %v. Some commands relating to moderation and privacy will still make me talk. I'll mention when quiet time is up.`, dur).AsReply(call.Message.ID)) } t := time.NewTimer(dur) defer t.Stop() @@ -116,7 +116,7 @@ func Quiet(ctx context.Context, robo *Robot, call *Invocation) { if call.Channel.Silent.Load() != n { return } - call.Channel.Message(ctx, message.Format("", `@%s My quiet time has ended.`, call.Message.Sender.Name)) + call.Channel.Message(ctx, message.Format(`@%s My quiet time has ended.`, call.Message.Sender.Name)) } } diff --git a/command/privacy.go b/command/privacy.go index 1d3d195..403ead0 100644 --- a/command/privacy.go +++ b/command/privacy.go @@ -12,25 +12,25 @@ func Private(ctx context.Context, robo *Robot, call *Invocation) { 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)) + 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)) return } e := call.Channel.Emotes.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", `Sure, I won't learn from your messages. Most of my functionality will still work for you. If you'd like to have me learn from you again, just tell me, "learn from me again." %s`, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`Sure, I won't learn from your messages. Most of my functionality will still work for you. If you'd like to have me learn from you again, just tell me, "learn from me again." %s`, e).AsReply(call.Message.ID)) } func Unprivate(ctx context.Context, robo *Robot, call *Invocation) { 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)) + 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)) return } e := call.Channel.Emotes.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", `Sure, I'll learn from you again! %s`, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`Sure, I'll learn from you again! %s`, e).AsReply(call.Message.ID)) } func DescribePrivacy(ctx context.Context, robo *Robot, call *Invocation) { // TODO(zeph): describe privacy - call.Channel.Message(ctx, message.Format("", `See here for a description of what information I collect, and how to opt out of all collection: https://github.com/zephyrtronium/robot#what-data-does-robot-store`).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(`See here for a description of what information I collect, and how to opt out of all collection: https://github.com/zephyrtronium/robot#what-data-does-robot-store`).AsReply(call.Message.ID)) } diff --git a/command/talk.go b/command/talk.go index 51d26bd..0fc23eb 100644 --- a/command/talk.go +++ b/command/talk.go @@ -142,7 +142,7 @@ func Rawr(ctx context.Context, robo *Robot, call *Invocation) { r.CancelAt(t) return } - call.Channel.Message(ctx, message.Format("", "rawr %s", e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("rawr %s", e).AsReply(call.Message.ID)) } // HappyBirthdayToYou wishes the robot a happy birthday. @@ -169,32 +169,32 @@ func HappyBirthdayToYou(ctx context.Context, robo *Robot, call *Invocation) { var m message.Sent switch t.Month() { case time.January: - m = message.Format("", "No no no my birthday is next month. %s", e) + m = message.Format("No no no my birthday is next month. %s", e) case time.February: switch t.Day() { case 1, 2, 3, 4, 5: - m = message.Format("", "Oh, but my birthday is later this month. %s", e) + m = message.Format("Oh, but my birthday is later this month. %s", e) case 6: - m = message.Format("", "My birthday is just a week away! I am so excited about this information. %s", e) + m = message.Format("My birthday is just a week away! I am so excited about this information. %s", e) case 7, 8, 9, 10: - m = message.Format("", "My birthday is still less than a week away. %s", e) + m = message.Format("My birthday is still less than a week away. %s", e) case 11: - m = message.Format("", "Two days away...! %s", e) + m = message.Format("Two days away...! %s", e) case 12: - m = message.Format("", "My birthday is tomorrow! At least in my timezone. %s", e) + m = message.Format("My birthday is tomorrow! At least in my timezone. %s", e) case 13: - m = message.Format("", "Thank you! Happy my birthday to you, too! %s", e) + m = message.Format("Thank you! Happy my birthday to you, too! %s", e) case 14: - m = message.Format("", "You missed it. My birthday was yesterday. You are disqualified from being my valentine. %s", e) + m = message.Format("You missed it. My birthday was yesterday. You are disqualified from being my valentine. %s", e) case 15, 16, 17, 18, 19, 20: - m = message.Format("", "My birthday was the other day, actually, but I appreciate the sentiment. %s", e) + m = message.Format("My birthday was the other day, actually, but I appreciate the sentiment. %s", e) default: - m = message.Format("", "My birthday was earlier this month, actually, but I appreciate the sentiment. %s", e) + m = message.Format("My birthday was earlier this month, actually, but I appreciate the sentiment. %s", e) } case time.March: - m = message.Format("", "No no no my birthday was last month. %s", e) + m = message.Format("No no no my birthday was last month. %s", e) default: - m = message.Format("", "My birthday is in February, silly %s", e) + m = message.Format("My birthday is in February, silly %s", e) } call.Channel.Message(ctx, m.AsReply(call.Message.ID)) } @@ -211,11 +211,11 @@ func Source(ctx context.Context, robo *Robot, call *Invocation) { func Who(ctx context.Context, robo *Robot, call *Invocation) { const whoMessage = `I'm a Markov chain bot! I learn from things people say in chat, then spew vaguely intelligible memes back. More info at: https://github.com/zephyrtronium/robot#how-robot-works %s` e := call.Channel.Emotes.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", whoMessage, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format(whoMessage, e).AsReply(call.Message.ID)) } // Contact gives information on how to contact the bot owner. func Contact(ctx context.Context, robo *Robot, call *Invocation) { e := call.Channel.Emotes.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", "My operator is %[1]s. %[2]s is the best way to contact %[1]s. %[3]s", robo.Owner, robo.Contact, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("My operator is %[1]s. %[2]s is the best way to contact %[1]s. %[3]s", robo.Owner, robo.Contact, e).AsReply(call.Message.ID)) } diff --git a/command/tamagotchi.go b/command/tamagotchi.go index 6bb0fb8..73bed47 100644 --- a/command/tamagotchi.go +++ b/command/tamagotchi.go @@ -66,7 +66,7 @@ func Tamagotchi(ctx context.Context, robo *Robot, call *Invocation) { e := call.Channel.Emotes.Pick(rand.Uint32()) sat := robo.Pet.Satisfaction(call.Message.Time()) _, m := satmsg(sat) - call.Channel.Message(ctx, message.Format("", "%s %s", m, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("%s %s", m, e).AsReply(call.Message.ID)) } type dinner struct { @@ -154,12 +154,12 @@ func Eat(ctx context.Context, robo *Robot, call *Invocation) { ) if !ok { s := fullmsgs.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", "%s %s", s, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("%s %s", s, e).AsReply(call.Message.ID)) return } c, m := satmsg(sat) chew := chewmsgs.Pick(rand.Uint32()) - call.Channel.Message(ctx, message.Format("", "%s %s %s %s %s%s %s %s", chew[0], menu[0].name, menu[1].name, menu[2].name, chew[1], c, m, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("%s %s %s %s %s%s %s %s", chew[0], menu[0].name, menu[1].name, menu[2].name, chew[1], c, m, e).AsReply(call.Message.ID)) } var cleancounts = pick.New([]pick.Case[int]{ @@ -208,15 +208,15 @@ func Clean(ctx context.Context, robo *Robot, call *Invocation) { var msg message.Sent switch len(rooms) { case 0: - msg = message.Format("", "Everything's already clean! %s %s", m, e) + msg = message.Format("Everything's already clean! %s %s", m, e) case 1: - msg = message.Format("", "%s %s%s Now %s %s", clean[0], rooms[0], clean[1], m, e) + msg = message.Format("%s %s%s Now %s %s", clean[0], rooms[0], clean[1], m, e) case 2: - msg = message.Format("", "%s %s and %s%s Now %s %s", clean[0], rooms[0], rooms[1], clean[1], m, e) + msg = message.Format("%s %s and %s%s Now %s %s", clean[0], rooms[0], rooms[1], clean[1], m, e) case 3: - msg = message.Format("", "%s %s, %s, and %s%s Now %s %s", clean[0], rooms[0], rooms[1], rooms[2], clean[1], m, e) + msg = message.Format("%s %s, %s, and %s%s Now %s %s", clean[0], rooms[0], rooms[1], rooms[2], clean[1], m, e) case 4: - msg = message.Format("", "%s whole home%s Now %s %s", clean[0], clean[1], m, e) + msg = message.Format("%s whole home%s Now %s %s", clean[0], clean[1], m, e) } call.Channel.Message(ctx, msg.AsReply(call.Message.ID)) } @@ -280,5 +280,5 @@ func Pat(ctx context.Context, robo *Robot, call *Invocation) { ) sat := robo.Pet.Pat(call.Message.Time(), pat.love) _, m := satmsg(sat) - call.Channel.Message(ctx, message.Format("", "%s %s %s", pat.where, m, e).AsReply(call.Message.ID)) + call.Channel.Message(ctx, message.Format("%s %s %s", pat.where, m, e).AsReply(call.Message.ID)) } diff --git a/message/message.go b/message/message.go index eec346c..cdf82b8 100644 --- a/message/message.go +++ b/message/message.go @@ -49,7 +49,7 @@ type Sent struct { // Reply is a message to reply to. If empty, the message is not interpreted // as a reply. Reply string - // To is the channel to whom the message is sent. + // To is the channel to which the message is sent. To string // Text is the message text. Text string @@ -60,14 +60,18 @@ func (m Sent) AsReply(reply string) Sent { return m } +func (m Sent) SendTo(to string) Sent { + m.To = to + return m +} + // formatString is a type to prevent misuse of format strings passed to [Format]. type formatString string // Format constructs a message to send from a format string literal and // formatting arguments. -func Format(to string, f formatString, args ...any) Sent { +func Format(f formatString, args ...any) Sent { return Sent{ - To: to, Text: strings.TrimSpace(fmt.Sprintf(string(f), args...)), } } diff --git a/privmsg.go b/privmsg.go index 520996a..d6cdc87 100644 --- a/privmsg.go +++ b/privmsg.go @@ -107,7 +107,7 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg slog.String("text", s), slog.String("effect", f), ) - msg := message.Format(ch.Name, "%s", s) + msg := message.Format("%s", s).SendTo(ch.Name) robo.sendTMI(ctx, send, msg) return default: @@ -165,7 +165,7 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg r.CancelAt(t) return } - out := message.Format(ch.Name, "%s", sef) + out := message.Format("%s", sef).SendTo(ch.Name) robo.sendTMI(ctx, send, out) }