diff --git a/channel/channel.go b/channel/channel.go index 6caee56..156caed 100644 --- a/channel/channel.go +++ b/channel/channel.go @@ -1,16 +1,18 @@ package channel import ( + "context" "regexp" - "golang.org/x/time/rate" - "gitlab.com/zephyrtronium/pick" + "golang.org/x/time/rate" ) type Channel struct { // Name is the name of the channel. Name string + // Message sends a message to the channel with an optional reply message ID. + Message func(ctx context.Context, reply, text string) // Learn and Send are the channel tags. Learn, Send string // Block is a regex that matches messages which should not be used for diff --git a/config.go b/config.go index ef61802..18a7239 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,7 @@ import ( "github.com/BurntSushi/toml" "gitlab.com/zephyrtronium/pick" "gitlab.com/zephyrtronium/sq" + "gitlab.com/zephyrtronium/tmi" "golang.org/x/crypto/hkdf" "golang.org/x/crypto/sha3" "golang.org/x/oauth2" @@ -22,6 +23,7 @@ import ( "github.com/zephyrtronium/robot/auth" "github.com/zephyrtronium/robot/brain/sqlbrain" "github.com/zephyrtronium/robot/channel" + "github.com/zephyrtronium/robot/message" "github.com/zephyrtronium/robot/privacy" ) @@ -76,7 +78,9 @@ func (robo *Robot) SetSources(ctx context.Context, brain, priv *sq.DB) error { // It must be called after SetSecrets. func (robo *Robot) SetTMI(ctx context.Context, cfg ClientCfg) error { cfg.endpoint = twitch.Endpoint - tmi, err := loadClient(ctx, cfg, *robo.secrets.twitch, "chat:read", "chat:edit") + send := make(chan *tmi.Message, 1) + recv := make(chan *tmi.Message, 8) // 8 is enough for on-connect msgs + tmi, err := loadClient(ctx, cfg, send, recv, *robo.secrets.twitch, "chat:read", "chat:edit") if err != nil { return fmt.Errorf("couldn't load TMI client: %w", err) } @@ -113,7 +117,7 @@ func (robo *Robot) SetTwitchChannels(ctx context.Context, global Global, channel } } for _, p := range ch.Channels { - v := channel.Channel{ + v := &channel.Channel{ Name: p, Learn: ch.Learn, Send: ch.Send, @@ -125,7 +129,11 @@ func (robo *Robot) SetTwitchChannels(ctx context.Context, global Global, channel Memery: channel.NewMemeDetector(ch.Copypasta.Need, fseconds(ch.Copypasta.Within)), Emotes: emotes, } - robo.channels[p] = &v + v.Message = func(ctx context.Context, reply, text string) { + msg := message.Format(reply, v.Name, "%s", text) + robo.sendTMI(ctx, robo.tmi.send, msg) + } + robo.channels[p] = v } } return nil @@ -171,7 +179,7 @@ func fseconds(s float64) time.Duration { } // loadClient loads client configuration from unmarshaled TOML. -func loadClient(ctx context.Context, t ClientCfg, key [auth.KeySize]byte, scopes ...string) (*client, error) { +func loadClient[Send, Receive any](ctx context.Context, t ClientCfg, send chan Send, recv chan Receive, key [auth.KeySize]byte, scopes ...string) (*client[Send, Receive], error) { secret, err := os.ReadFile(t.SecretFile) if err != nil { return nil, fmt.Errorf("couldn't read client secret: %w", err) @@ -197,7 +205,9 @@ func loadClient(ctx context.Context, t ClientCfg, key [auth.KeySize]byte, scopes if err != nil { return nil, fmt.Errorf("couldn't create token: %w", err) } - return &client{ + return &client[Send, Receive]{ + send: send, + recv: recv, me: t.User, owner: t.Owner, rate: rate.NewLimiter(rate.Every(fseconds(t.Rate.Every)), t.Rate.Num), diff --git a/robot.go b/robot.go index c9089e2..e288d82 100644 --- a/robot.go +++ b/robot.go @@ -36,11 +36,16 @@ type Robot struct { ownerContact string // tmi contains the bot's Twitch OAuth2 settings. It may be nil if there is // no Twitch configuration. - tmi *client + tmi *client[*tmi.Message, *tmi.Message] } // client is the settings for OAuth2 and related elements. -type client struct { +// The type parameter is the type of messages sent TO the service. +type client[Send, Receive any] struct { + // send is the channel on which messages are sent. + send chan Send + // recv is the channel on which received messages are communicated. + recv chan Receive // 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 @@ -90,11 +95,9 @@ func (robo *Robot) twitch(ctx context.Context, group *errgroup.Group) error { Capabilities: []string{"twitch.tv/commands", "twitch.tv/tags"}, Timeout: 300 * time.Second, } - send := make(chan *tmi.Message, 1) - recv := make(chan *tmi.Message, 8) // 8 is enough for on-connect msgs // TODO(zeph): could run several instances of loop - go robo.tmiLoop(ctx, send, recv) - tmi.Connect(ctx, cfg, tmi.Log(log.Default(), false), send, recv) + go robo.tmiLoop(ctx, robo.tmi.send, robo.tmi.recv) + tmi.Connect(ctx, cfg, tmi.Log(log.Default(), false), robo.tmi.send, robo.tmi.recv) return ctx.Err() }