Skip to content

Commit bfd5ea7

Browse files
committed
robot: allow messages to be copypasta but not learned
Fixes #72.
1 parent ab29f51 commit bfd5ea7

File tree

5 files changed

+44
-17
lines changed

5 files changed

+44
-17
lines changed

channel/channel.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ type Channel struct {
1818
// Learn and Send are the channel tags.
1919
Learn, Send string
2020
// Block is a regex that matches messages which should not be used for
21-
// learning.
21+
// learning or copypasta.
2222
Block *regexp.Regexp
23+
// Meme is a regex that matches messages which bypass Block only for copypasta.
24+
Meme *regexp.Regexp
2325
// Responses is the probability that a received message will trigger a
2426
// random response.
2527
Responses float64

config.go

+27-12
Original file line numberDiff line numberDiff line change
@@ -262,27 +262,35 @@ func (robo *Robot) InitTwitchUsers(ctx context.Context, owner *Privilege, global
262262
return nil
263263
}
264264

265+
func mergere(global, ch string) (*regexp.Regexp, error) {
266+
var re string
267+
switch {
268+
case global != "" && ch != "":
269+
re = "(" + global + ")|(" + ch + ")"
270+
case global != "":
271+
re = global
272+
case ch != "":
273+
re = ch
274+
default:
275+
re = "$^"
276+
}
277+
return regexp.Compile(re)
278+
}
279+
265280
// SetTwitchChannels initializes Twitch channel configuration.
266281
// It must be called after SetTMI.
267282
func (robo *Robot) SetTwitchChannels(ctx context.Context, global Global, channels map[string]*ChannelCfg) error {
268283
// TODO(zeph): we can convert this to a SetChannels, where it just adds the
269284
// channels for any given service
270285
for nm, ch := range channels {
271-
var re string
272-
switch {
273-
case global.Block != "" && ch.Block != "":
274-
re = "(" + global.Block + ")|(" + ch.Block + ")"
275-
case global.Block != "":
276-
re = global.Block
277-
case ch.Block != "":
278-
re = ch.Block
279-
default:
280-
re = "$^"
281-
}
282-
blk, err := regexp.Compile(re)
286+
blk, err := mergere(global.Block, ch.Block)
283287
if err != nil {
284288
return fmt.Errorf("bad global or channel block expression for twitch.%s: %w", nm, err)
285289
}
290+
meme, err := mergere(global.Meme, ch.Meme)
291+
if err != nil {
292+
return fmt.Errorf("bad global or channel meme expression for twitch.%s: %w", nm, err)
293+
}
286294
emotes := pick.New(pick.FromMap(mergemaps(global.Emotes, ch.Emotes)))
287295
effects := pick.New(pick.FromMap(mergemaps(global.Effects, ch.Effects)))
288296
ign, mod := make(map[string]bool), make(map[string]bool)
@@ -308,6 +316,7 @@ func (robo *Robot) SetTwitchChannels(ctx context.Context, global Global, channel
308316
Learn: ch.Learn,
309317
Send: ch.Send,
310318
Block: blk,
319+
Meme: meme,
311320
Responses: ch.Responses,
312321
Rate: rate.NewLimiter(rate.Every(fseconds(ch.Rate.Every)), ch.Rate.Num),
313322
Ignore: ign,
@@ -493,6 +502,9 @@ type ChannelCfg struct {
493502
Rate Rate `toml:"rate"`
494503
// Copypasta is the configuration for copypasta.
495504
Copypasta Copypasta `toml:"copypasta"`
505+
// Meme is a regular expression of messages to allow to be copypasta even
506+
// if matched by this channel's or the global Block.
507+
Meme string `toml:"meme"`
496508
// Emotes is the emotes and their weights for the channel.
497509
Emotes map[string]int `toml:"emotes"`
498510
// Effects is the effects and their weights for the channel.
@@ -505,6 +517,9 @@ type ChannelCfg struct {
505517
type Global struct {
506518
// Block is a regular expression of messages to ignore everywhere.
507519
Block string `toml:"block"`
520+
// Meme is a regular expression of messages to allow to be copypasta even
521+
// if matched by Block.
522+
Meme string `toml:"meme"`
508523
// Emotes is the emotes and their weights to use everywhere.
509524
Emotes map[string]int `toml:"emotes"`
510525
// Effects is the effects and their weights to use everywhere.

config_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func TestExampleConfig(t *testing.T) {
5555
eqcase(t, "Twitch[`bocchi`].Rate.Num", cfg.Twitch[`bocchi`].Rate.Num, 2)
5656
eqcase(t, "Twitch[`bocchi`].Copypasta.Need", cfg.Twitch[`bocchi`].Copypasta.Need, 2)
5757
eqcase(t, "Twitch[`bocchi`].Copypasta.Within", cfg.Twitch[`bocchi`].Copypasta.Within, 30)
58+
eqcase(t, "Twitch[`bocchi`].Meme", cfg.Twitch[`bocchi`].Meme, `^\S*$`)
5859
eqcase(t, "Twitch[`bocchi`].Privileges[0].Name", cfg.Twitch[`bocchi`].Privileges[0].Name, `zephyrtronium`)
5960
eqcase(t, "Twitch[`bocchi`].Privileges[0].Level", cfg.Twitch[`bocchi`].Privileges[0].Level, `moderator`)
6061
eqcase(t, "Twitch[`bocchi`].Emotes[`btw`]", cfg.Twitch[`bocchi`].Emotes[`btw make sure to stretch, hydrate, and take care of yourself <3`], 1)

example.toml

+8-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ listen = ':4959'
4343

4444
# global includes chat settings that apply to all channels.
4545
[global]
46-
# block is a regex that blocks messages from being learned in any channel.
46+
# block is a regex that blocks messages from being learned or copypastad in any channel.
4747
# Unlike most string options, it is not expanded with environment variables.
4848
block = '(?i)bad\s+stuff[^$x]'
49+
# meme is a regex that overrides block for copypasta only.
50+
meme = '^\S*$'
4951

5052
# global.emotes is a table of emotes to use in every channel along with their
5153
# relative weights.
@@ -100,8 +102,9 @@ learn = 'bocchi'
100102
send = 'bocchi'
101103
# block is a regex that blocks messages from being learned in this channel. Any
102104
# message containing text matching this or the global block regex is not used
103-
# for learning. Unlike most string options, it is not expanded with environment
104-
# variables.
105+
# for learning. block also prevents a message from contributing to copypasta
106+
# unless the message additionally matches meme, below.
107+
# Unlike most string options, it is not expanded with environment variables.
105108
block = '(?i)cucumber[^$x]'
106109
# responses is the probability of generating a random message when a
107110
# non-command message is received.
@@ -110,6 +113,8 @@ responses = 0.02
110113
rate = { every = 10.1, num = 2 }
111114
# copypasta is the configuration of copypastaing.
112115
copypasta = { need = 2, within = 30 }
116+
# meme overrides block for copypasta only.
117+
meme = '^\S*$'
113118
# Access levels for users.
114119
# Each entry must have a name or ID and a level. If both a name and ID are
115120
# given, the name is ignored.

privmsg.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ func (robo *Robot) tmiMessage(ctx context.Context, group *errgroup.Group, send c
3939
log.InfoContext(ctx, "message from ignored user")
4040
return
4141
}
42+
if ch.Block.MatchString(m.Text) && !ch.Meme.MatchString(m.Text) {
43+
log.InfoContext(ctx, "blocked message", slog.String("text", m.Text), slog.Bool("meme", false))
44+
return
45+
}
4246
if cmd, ok := parseCommand(robo.tmi.name, m.Text); ok {
4347
robo.command(ctx, log, ch, m, from, cmd)
4448
return
@@ -239,7 +243,7 @@ func (robo *Robot) learn(ctx context.Context, log *slog.Logger, ch *channel.Chan
239243
return
240244
}
241245
if ch.Block.MatchString(msg.Text) {
242-
log.InfoContext(ctx, "blocked message", slog.String("text", msg.Text))
246+
log.InfoContext(ctx, "blocked message", slog.String("text", msg.Text), slog.Bool("meme", true))
243247
return
244248
}
245249
if ch.Learn == "" {

0 commit comments

Comments
 (0)