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:
parent
e027170907
commit
d40d90fbe4
4 changed files with 99 additions and 7 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -33,6 +50,7 @@ type Bot struct {
|
||||||
OnMessage *utils.SyncList[BotMessageHandler]
|
OnMessage *utils.SyncList[BotMessageHandler]
|
||||||
|
|
||||||
cancelUpdateSub database.CancelFunc
|
cancelUpdateSub database.CancelFunc
|
||||||
|
cancelWritePlainRPCSub database.CancelFunc
|
||||||
cancelWriteRPCSub database.CancelFunc
|
cancelWriteRPCSub database.CancelFunc
|
||||||
|
|
||||||
// Module specific vars
|
// Module specific vars
|
||||||
|
@ -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() {
|
||||||
|
|
|
@ -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/"
|
||||||
|
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in a new issue