1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: add twitch/bot/@send-message for sending messages with options

This commit is contained in:
Ash Keel 2023-06-13 10:50:20 +02:00
parent e027170907
commit d40d90fbe4
No known key found for this signature in database
GPG key ID: 53A9E9A6035DD109
4 changed files with 99 additions and 7 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Custom chat commands can now be sent as replies, whispers and announcements. Due to some API shenanigans yet to be solved, the latter two will always be sent from your main account, not the bot account (if they are different) - Custom chat commands can now be sent as replies, whispers and announcements. Due to some API shenanigans yet to be solved, the latter two will always be sent from your main account, not the bot account (if they are different)
- Added a structured RPC `twitch/bot/@send-message` for sending messages as replies, announcements and whispers.
### Changed ### Changed

View file

@ -6,6 +6,8 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/nicklaw5/helix/v2"
"git.sr.ht/~hamcha/containers/sync" "git.sr.ht/~hamcha/containers/sync"
irc "github.com/gempir/go-twitch-irc/v4" irc "github.com/gempir/go-twitch-irc/v4"
"go.uber.org/zap" "go.uber.org/zap"
@ -14,8 +16,23 @@ import (
"github.com/strimertul/strimertul/utils" "github.com/strimertul/strimertul/utils"
) )
type IRCBot interface {
Join(channel ...string)
Connect() error
Disconnect() error
Say(channel, message string)
Reply(channel, messageID, message string)
OnConnect(handler func())
OnPrivateMessage(handler func(irc.PrivateMessage))
OnUserJoinMessage(handler func(message irc.UserJoinMessage))
OnUserPartMessage(handler func(message irc.UserPartMessage))
}
type Bot struct { type Bot struct {
Client *irc.Client Client IRCBot
Config BotConfig Config BotConfig
api *Client api *Client
@ -32,8 +49,9 @@ type Bot struct {
OnConnect *utils.SyncList[BotConnectHandler] OnConnect *utils.SyncList[BotConnectHandler]
OnMessage *utils.SyncList[BotMessageHandler] OnMessage *utils.SyncList[BotMessageHandler]
cancelUpdateSub database.CancelFunc cancelUpdateSub database.CancelFunc
cancelWriteRPCSub database.CancelFunc cancelWritePlainRPCSub database.CancelFunc
cancelWriteRPCSub database.CancelFunc
// Module specific vars // Module specific vars
Timers *BotTimerModule Timers *BotTimerModule
@ -61,6 +79,10 @@ func newBot(api *Client, config BotConfig) *Bot {
// Create client // Create client
client := irc.NewClient(config.Username, config.Token) client := irc.NewClient(config.Username, config.Token)
return newBotWithClient(client, api, config)
}
func newBotWithClient(client IRCBot, api *Client, config BotConfig) *Bot {
bot := &Bot{ bot := &Bot{
Client: client, Client: client,
Config: config, Config: config,
@ -110,6 +132,10 @@ func newBot(api *Client, config BotConfig) *Bot {
if err != nil { if err != nil {
bot.logger.Error("Could not set-up bot command reload subscription", zap.Error(err)) bot.logger.Error("Could not set-up bot command reload subscription", zap.Error(err))
} }
err, bot.cancelWritePlainRPCSub = api.db.SubscribeKey(WritePlainMessageRPC, bot.handleWritePlainMessageRPC)
if err != nil {
bot.logger.Error("Could not set-up bot command reload subscription", zap.Error(err))
}
err, bot.cancelWriteRPCSub = api.db.SubscribeKey(WriteMessageRPC, bot.handleWriteMessageRPC) err, bot.cancelWriteRPCSub = api.db.SubscribeKey(WriteMessageRPC, bot.handleWriteMessageRPC)
if err != nil { if err != nil {
bot.logger.Error("Could not set-up bot command reload subscription", zap.Error(err)) bot.logger.Error("Could not set-up bot command reload subscription", zap.Error(err))
@ -221,6 +247,9 @@ func (b *Bot) Close() error {
if b.cancelWriteRPCSub != nil { if b.cancelWriteRPCSub != nil {
b.cancelWriteRPCSub() b.cancelWriteRPCSub()
} }
if b.cancelWritePlainRPCSub != nil {
b.cancelWritePlainRPCSub()
}
if b.Timers != nil { if b.Timers != nil {
b.Timers.Close() b.Timers.Close()
} }
@ -243,10 +272,54 @@ func (b *Bot) updateCommands(value string) {
} }
} }
func (b *Bot) handleWriteMessageRPC(value string) { func (b *Bot) handleWritePlainMessageRPC(value string) {
b.Client.Say(b.Config.Channel, value) b.Client.Say(b.Config.Channel, value)
} }
func (b *Bot) handleWriteMessageRPC(value string) {
var request WriteMessageRequest
err := json.Unmarshal([]byte(value), &request)
if err != nil {
b.logger.Warn("Failed to decode write message request", zap.Error(err))
return
}
if request.ReplyTo != nil && *request.ReplyTo != "" {
b.Client.Reply(b.Config.Channel, *request.ReplyTo, request.Message)
return
}
if request.WhisperTo != nil && *request.WhisperTo != "" {
client, err := b.api.GetUserClient(false)
reply, err := client.SendUserWhisper(&helix.SendUserWhisperParams{
FromUserID: b.api.User.ID,
ToUserID: *request.WhisperTo,
Message: request.Message,
})
if reply.Error != "" {
b.logger.Error("Failed to send whisper", zap.String("code", reply.Error), zap.String("message", reply.ErrorMessage))
}
if err != nil {
b.logger.Error("Failed to send whisper", zap.Error(err))
}
return
}
if request.Announce {
client, err := b.api.GetUserClient(false)
reply, err := client.SendChatAnnouncement(&helix.SendChatAnnouncementParams{
BroadcasterID: b.api.User.ID,
ModeratorID: b.api.User.ID,
Message: request.Message,
})
if reply.Error != "" {
b.logger.Error("Failed to send announcement", zap.String("code", reply.Error), zap.String("message", reply.ErrorMessage))
}
if err != nil {
b.logger.Error("Failed to send announcement", zap.Error(err))
}
return
}
b.Client.Say(b.Config.Channel, request.Message)
}
func (b *Bot) updateTemplates() error { func (b *Bot) updateTemplates() error {
b.customTemplates.Set(make(map[string]*template.Template)) b.customTemplates.Set(make(map[string]*template.Template))
for cmd, tmpl := range b.customCommands.Copy() { for cmd, tmpl := range b.customCommands.Copy() {

View file

@ -77,7 +77,20 @@ type BotCustomCommand struct {
const CustomCommandsKey = "twitch/bot-custom-commands" const CustomCommandsKey = "twitch/bot-custom-commands"
const WriteMessageRPC = "twitch/@send-chat-message" const (
// WritePlainMessageRPC is the old send command, will be renamed someday
WritePlainMessageRPC = "twitch/@send-chat-message"
WriteMessageRPC = "twitch/bot/@send-message"
)
// WriteMessageRequest is an RPC to send a chat message with extra options
type WriteMessageRequest struct {
Message string `json:"message" desc:"Chat message to send"`
ReplyTo *string `json:"reply_to" desc:"If specified, send as reply to a message ID"`
WhisperTo *string `json:"whisper_to" desc:"If specified, send as whisper to user ID"`
Announce bool `json:"announce" desc:"If true, send as announcement"`
}
const BotCounterPrefix = "twitch/bot-counters/" const BotCounterPrefix = "twitch/bot-counters/"

View file

@ -63,11 +63,16 @@ var Keys = interfaces.KeyMap{
Description: "Configuration of chat bot timers", Description: "Configuration of chat bot timers",
Type: reflect.TypeOf(BotTimersConfig{}), Type: reflect.TypeOf(BotTimersConfig{}),
}, },
WriteMessageRPC: interfaces.KeyDef{ WritePlainMessageRPC: interfaces.KeyDef{
Description: "Send plain text chat message", Description: "Send plain text chat message (this will be deprecated or renamed someday, please use the other one!)",
Type: reflect.TypeOf(""), Type: reflect.TypeOf(""),
Tags: []interfaces.KeyTag{interfaces.TagRPC}, Tags: []interfaces.KeyTag{interfaces.TagRPC},
}, },
WriteMessageRPC: interfaces.KeyDef{
Description: "Send chat message with extra options (as reply, whisper, etc)",
Type: reflect.TypeOf(WriteMessageRequest{}),
Tags: []interfaces.KeyTag{interfaces.TagRPC},
},
} }
var Enums = interfaces.EnumMap{ var Enums = interfaces.EnumMap{