diff --git a/command/talk.go b/command/talk.go index f398a48..2089f78 100644 --- a/command/talk.go +++ b/command/talk.go @@ -4,12 +4,24 @@ import ( "context" "log/slog" "math/rand/v2" + "regexp" "time" "github.com/zephyrtronium/robot/brain" ) func speakCmd(ctx context.Context, robo *Robot, call *Invocation, effect string) string { + // Don't continue prompts that look like they start with TMI commands + // (even though those don't do anything anymore). + 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()) + return "no " + e + } m, trace, err := brain.Speak(ctx, robo.Brain, call.Channel.Send, call.Args["prompt"]) if err != nil { robo.Log.ErrorContext(ctx, "couldn't speak", "err", err.Error()) @@ -44,6 +56,10 @@ func speakCmd(ctx context.Context, robo *Robot, call *Invocation, effect string) return m + " " + e } +var ngPrompt = regexp.MustCompile(`^/|^.\w`) + +// Speak generates a message. +// - prompt: Start of the message to use. Optional. func Speak(ctx context.Context, robo *Robot, call *Invocation) { u := speakCmd(ctx, robo, call, "") if u == "" { @@ -53,6 +69,8 @@ func Speak(ctx context.Context, robo *Robot, call *Invocation) { call.Channel.Message(ctx, "", u) } +// OwO genyewates an uwu message. +// - prompt: Start of the message to use. Optional. func OwO(ctx context.Context, robo *Robot, call *Invocation) { u := speakCmd(ctx, robo, call, "OwO") if u == "" { @@ -62,7 +80,13 @@ func OwO(ctx context.Context, robo *Robot, call *Invocation) { call.Channel.Message(ctx, "", u) } +// AAAAA AAAAAAAAA A AAAAAAA. func AAAAA(ctx context.Context, robo *Robot, call *Invocation) { + // Never use a prompt for this one. + // But also look before we delete in case the arg isn't given. + if call.Args["prompt"] != "" { + delete(call.Args, "prompt") + } u := speakCmd(ctx, robo, call, "AAAAA") if u == "" { return @@ -71,8 +95,30 @@ func AAAAA(ctx context.Context, robo *Robot, call *Invocation) { call.Channel.Message(ctx, "", u) } -var ( - _ Func = Speak - _ Func = OwO - _ Func = AAAAA -) +func Rawr(ctx context.Context, robo *Robot, call *Invocation) { + e := call.Channel.Emotes.Pick(rand.Uint32()) + if e == "" { + e = ":3" + } + t := time.Now() + r := call.Channel.Rate.ReserveN(t, 1) + if d := r.DelayFrom(t); d > 0 { + robo.Log.InfoContext(ctx, "won't rawr; rate limited", + slog.String("action", "rawr"), + slog.String("in", call.Channel.Name), + slog.String("delay", d.String()), + ) + r.CancelAt(t) + return + } + call.Channel.Message(ctx, call.Message.ID, "rawr "+e) +} + +func Source(ctx context.Context, robo *Robot, call *Invocation) { + const srcMessage = `My source code is at https://github.com/zephyrtronium/robot – ` + + `I'm written in Go, and I'm free, open-source software licensed ` + + `under the GNU General Public License, Version 3.` + call.Channel.Message(ctx, call.Message.ID, srcMessage) +} + +// TODO(zeph): DescribePrivacy diff --git a/privmsg.go b/privmsg.go index 4ff04d0..ce86e24 100644 --- a/privmsg.go +++ b/privmsg.go @@ -311,7 +311,7 @@ func findTwitch(cmds []twitchCommand, text string) (*twitchCommand, map[string]s var twitchOwner = []twitchCommand{ { - parse: regexp.MustCompile(`^(?i:in\s+(?#\S+)\s+echo)\s+(?.*)`), + parse: regexp.MustCompile(`^(?i:in\s+(?#\S+)[,:]?\s+echo)\s+(?.*)`), fn: command.EchoIn, name: "echo-in", }, @@ -336,6 +336,16 @@ var twitchAny = []twitchCommand{ fn: command.AAAAA, name: "AAAAA", }, + { + parse: regexp.MustCompile(`^(?i:r+o+a+r+|r+a+w+r+)`), + fn: command.Rawr, + name: "rawr", + }, + { + parse: regexp.MustCompile(`^(?i:where(?:'?s|\s+is)?\s+y?o?u'?re?\s+so?u?rce?(?:\s*code)?)`), + fn: command.Source, + name: "source", + }, { // NOTE(zeph): This command MUST be last, because it swallows all // invocations for the prompt.