Skip to content

Commit

Permalink
message: add service-agnostic type for messages we send
Browse files Browse the repository at this point in the history
  • Loading branch information
zephyrtronium committed Feb 15, 2024
1 parent d97d795 commit 2cf336f
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 32 deletions.
13 changes: 0 additions & 13 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,6 @@ func fseconds(s float64) time.Duration {
return time.Duration(s * float64(time.Second))
}

// client is the settings for OAuth2 and related elements.
type client struct {
// me is the bot's username. The interpretation of this is domain-specific.
me string
// owner is the user ID of the owner. The interpretation of this is
// domain-specific.
owner string
// rate is the global rate limiter for this client.
rate *rate.Limiter
// token is the OAuth2 token.
token *auth.Token
}

// loadClient loads client configuration from unmarshaled TOML.
func loadClient(ctx context.Context, t ClientCfg, key [auth.KeySize]byte, scopes ...string) (*client, error) {
secret, err := os.ReadFile(t.SecretFile)
Expand Down
8 changes: 4 additions & 4 deletions message/irc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ func elevated(m *tmi.Message) bool {

// ToTMI creates a message to send to TMI. If reply is not empty, then the
// result is a reply to the message with that ID.
func ToTMI(reply, to, text string) *tmi.Message {
r := tmi.Privmsg(to, text)
if reply != "" {
r.Tags = "reply-parent-msg-id=" + reply
func ToTMI(msg Sent) *tmi.Message {
r := tmi.Privmsg(msg.To, msg.Text)
if msg.Reply != "" {
r.Tags = "reply-parent-msg-id=" + msg.Reply
}
return r
}
26 changes: 26 additions & 0 deletions message/message.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package message

import (
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -32,3 +34,27 @@ type Received struct {
func (m *Received) Time() time.Time {
return time.UnixMilli(m.Timestamp)
}

// 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
// as a reply.
Reply string
// To is the channel to whom the message is sent.
To string
// Text is the message text.
Text string
}

// 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(reply, to string, f formatString, args ...any) Sent {
return Sent{
Reply: reply,
To: to,
Text: strings.TrimSpace(fmt.Sprintf(string(f), args...)),
}
}
39 changes: 24 additions & 15 deletions privmsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
if ch.Ignore[from] {
return
}
if ch.Block.MatchString(m.Text) {
return
}
if cmd, ok := parseCommand(robo.tmi.me, m.Text); ok {
if from == robo.tmi.owner {
// TODO(zeph): check owner and moderator commands
Expand All @@ -45,15 +48,21 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
return
}
robo.learn(ctx, ch, userhash.New(robo.secrets.userhash), m)
// TODO(zeph): this should be asking for a reservation
if !ch.Rate.Allow() {
return
}
if err := ch.Memery.Check(m.Time(), from, m.Text); err == nil {
// NOTE(zeph): inverted error check
robo.sendTMI(ctx, send, ch, m.Text)
switch err := ch.Memery.Check(m.Time(), from, m.Text); err {
case channel.ErrNotCopypasta: // do nothing
case nil:
// Meme detected. Copypasta.
text := m.Text
// TODO(zeph): effects; once we apply them, we also need to check block
msg := message.Format("", ch.Name, "%s", text)
robo.sendTMI(ctx, send, msg)
return
} else if err != channel.ErrNotCopypasta {
log.Println("copypasta error:", err)
default:
log.Println("copypasta check error:", err)
}
if rand.Float64() < ch.Responses {
s, err := brain.Speak(ctx, robo.brain, ch.Send, "")
Expand All @@ -64,7 +73,13 @@ func (robo *Robot) tmiMessage(ctx context.Context, send chan<- *tmi.Message, msg
e := ch.Emotes.Pick(rand.Uint32())
s = strings.TrimSpace(s + " " + e)
// TODO(zeph): effect
robo.sendTMI(ctx, send, ch, s)
if ch.Block.MatchString(s) {
// Don't send messages we wouldn't learn from.
// TODO(zeph): log?
return
}
msg := message.Format("", ch.Name, "%s", s)
robo.sendTMI(ctx, send, msg)
}
}
robo.enqueue(ctx, work)
Expand Down Expand Up @@ -140,18 +155,12 @@ func (robo *Robot) learn(ctx context.Context, ch *channel.Channel, hasher userha
}

// sendTMI sends a message to TMI after waiting for the global rate limit.
func (robo *Robot) sendTMI(ctx context.Context, send chan<- *tmi.Message, ch *channel.Channel, s string) {
if ch.Block.MatchString(s) {
return
}
// The caller should verify that it is safe to send the message.
func (robo *Robot) sendTMI(ctx context.Context, send chan<- *tmi.Message, msg message.Sent) {
if err := robo.tmi.rate.Wait(ctx); err != nil {
return
}
resp := &tmi.Message{
Command: "PRIVMSG",
Params: []string{ch.Name},
Trailing: s,
}
resp := message.ToTMI(msg)
select {
case <-ctx.Done():
return
Expand Down
15 changes: 15 additions & 0 deletions robot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (

"gitlab.com/zephyrtronium/tmi"
"golang.org/x/sync/errgroup"
"golang.org/x/time/rate"

"github.com/zephyrtronium/robot/auth"
"github.com/zephyrtronium/robot/brain/sqlbrain"
"github.com/zephyrtronium/robot/channel"
"github.com/zephyrtronium/robot/privacy"
Expand All @@ -37,6 +39,19 @@ type Robot struct {
tmi *client
}

// client is the settings for OAuth2 and related elements.
type client struct {
// me is the bot's username. The interpretation of this is domain-specific.
me string
// owner is the user ID of the owner. The interpretation of this is
// domain-specific.
owner string
// rate is the global rate limiter for this client.
rate *rate.Limiter
// token is the OAuth2 token.
token *auth.Token
}

// New creates a new robot instance. Use SetOwner, SetSecrets, &c. as needed
// to initialize the robot.
func New(poolSize int) *Robot {
Expand Down

0 comments on commit 2cf336f

Please sign in to comment.