twitch-bot/internal/actors/vip/vip.go
Knut Ahlers f9716d6591
[vip/unvip] Implement actors and chat commands
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2022-12-05 19:20:30 +01:00

196 lines
5.8 KiB
Go

package vip
import (
"context"
"strings"
"github.com/go-irc/irc"
"github.com/pkg/errors"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
var (
formatMessage plugins.MsgFormatter
permCheckFn plugins.ChannelPermissionCheckFunc
tcGetter func(string) (*twitch.Client, error)
ptrStringEmpty = func(s string) *string { return &s }("")
)
func Register(args plugins.RegistrationArguments) error {
formatMessage = args.FormatMessage
permCheckFn = args.HasPermissionForChannel
tcGetter = args.GetTwitchClientForChannel
args.RegisterActor("vip", func() plugins.Actor { return &vipActor{} })
args.RegisterActor("unvip", func() plugins.Actor { return &unvipActor{} })
args.RegisterActorDocumentation(plugins.ActionDocumentation{
Description: "Add VIP for the given channel",
Name: "Add VIP",
Type: "vip",
Fields: []plugins.ActionDocumentationField{
{
Default: "",
Description: "Channel to add the VIP to",
Key: "channel",
Name: "Channel",
Optional: false,
SupportTemplate: true,
Type: plugins.ActionDocumentationFieldTypeString,
},
{
Default: "",
Description: "User to add as VIP",
Key: "user",
Name: "User",
Optional: false,
SupportTemplate: true,
Type: plugins.ActionDocumentationFieldTypeString,
},
},
})
args.RegisterActorDocumentation(plugins.ActionDocumentation{
Description: "Remove VIP for the given channel",
Name: "Remove VIP",
Type: "unvip",
Fields: []plugins.ActionDocumentationField{
{
Default: "",
Description: "Channel to remove the VIP from",
Key: "channel",
Name: "Channel",
Optional: false,
SupportTemplate: true,
Type: plugins.ActionDocumentationFieldTypeString,
},
{
Default: "",
Description: "User to remove as VIP",
Key: "user",
Name: "User",
Optional: false,
SupportTemplate: true,
Type: plugins.ActionDocumentationFieldTypeString,
},
},
})
args.RegisterMessageModFunc("/vip", handleAddVIP)
args.RegisterMessageModFunc("/unvip", handleRemoveVIP)
return nil
}
// Actor
type (
actor struct{}
unvipActor struct{ actor }
vipActor struct{ actor }
)
func (actor) IsAsync() bool { return false }
func (a actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
for _, field := range []string{"channel", "user"} {
if v, err := attrs.String(field); err != nil || v == "" {
return errors.Errorf("%s must be non-empty string", field)
}
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
}
return nil
}
func (a actor) getParams(m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (channel, user string, err error) {
if channel, err = formatMessage(attrs.MustString("channel", nil), m, r, eventData); err != nil {
return "", "", errors.Wrap(err, "parsing channel")
}
if user, err = formatMessage(attrs.MustString("user", nil), m, r, eventData); err != nil {
return "", "", errors.Wrap(err, "parsing user")
}
return strings.TrimLeft(channel, "#"), user, nil
}
func (u unvipActor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
channel, user, err := u.getParams(m, r, eventData, attrs)
if err != nil {
return false, errors.Wrap(err, "getting parameters")
}
return false, errors.Wrap(
executeModVIP(channel, func(tc *twitch.Client) error { return tc.RemoveChannelVIP(context.Background(), channel, user) }),
"removing VIP",
)
}
func (unvipActor) Name() string { return "unvip" }
func (v vipActor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
channel, user, err := v.getParams(m, r, eventData, attrs)
if err != nil {
return false, errors.Wrap(err, "getting parameters")
}
return false, errors.Wrap(
executeModVIP(channel, func(tc *twitch.Client) error { return tc.AddChannelVIP(context.Background(), channel, user) }),
"adding VIP",
)
}
func (vipActor) Name() string { return "vip" }
// Generic helper
func executeModVIP(channel string, modFn func(tc *twitch.Client) error) error {
ok, err := permCheckFn(channel, twitch.ScopeChannelManageVIPS)
if err != nil {
return errors.Wrap(err, "checking for channel permissions")
}
if !ok {
return errors.Errorf("channel %q is missing permission %s", channel, twitch.ScopeChannelManageVIPS)
}
tc, err := tcGetter(channel)
if err != nil {
return errors.Wrap(err, "getting channel twitch-client")
}
return modFn(tc)
}
// Chat-Commands
func handleAddVIP(m *irc.Message) error {
return handleModVIP(m, func(tc *twitch.Client, channel, user string) error {
return errors.Wrap(tc.AddChannelVIP(context.Background(), channel, user), "adding VIP")
})
}
func handleModVIP(m *irc.Message, modFn func(tc *twitch.Client, channel, user string) error) error {
channel := strings.TrimLeft(plugins.DeriveChannel(m, nil), "#")
parts := strings.Split(m.Trailing(), " ")
if len(parts) != 2 { //nolint:gomnd // Just a count, makes no sense as a constant
return errors.Errorf("wrong command usage, must consist of 2 words")
}
return executeModVIP(channel, func(tc *twitch.Client) error { return modFn(tc, channel, parts[1]) })
}
func handleRemoveVIP(m *irc.Message) error {
return handleModVIP(m, func(tc *twitch.Client, channel, user string) error {
return errors.Wrap(tc.RemoveChannelVIP(context.Background(), channel, user), "removing VIP")
})
}