Added chat bot message command options

This commit is contained in:
tyler 2024-02-06 14:57:56 -05:00
parent adacddb97a
commit a3ba8aa721
9 changed files with 304 additions and 108 deletions

14
app.go
View file

@ -143,11 +143,11 @@ func (a *App) ChatBotMessages(cid string) (map[string]config.ChatMessage, error)
return channel.ChatBot.Messages, nil
}
func (a *App) AddChatMessage(cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (map[string]config.ChatMessage, error) {
func (a *App) AddChatMessage(cid string, cm config.ChatMessage) (map[string]config.ChatMessage, error) {
var err error
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
_, err = a.cfg.NewChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
_, err = a.cfg.NewChatMessage(cid, cm)
if err != nil {
a.logError.Println("error creating new chat:", err)
return nil, fmt.Errorf("Error creating new chat message. Try again.")
@ -164,10 +164,10 @@ func (a *App) AddChatMessage(cid string, asChannel bool, command string, interva
return a.cfg.Channels[cid].ChatBot.Messages, nil
}
func (a *App) DeleteChatMessage(mid string, cid string) (map[string]config.ChatMessage, error) {
func (a *App) DeleteChatMessage(cid string, cm config.ChatMessage) (map[string]config.ChatMessage, error) {
a.cbMu.Lock()
if a.cb != nil {
err := a.cb.StopMessage(mid)
err := a.cb.StopMessage(cm.ID)
if err != nil {
a.logError.Println("error stopping chat bot message:", err)
return nil, fmt.Errorf("Error stopping message. Try Again.")
@ -177,7 +177,7 @@ func (a *App) DeleteChatMessage(mid string, cid string) (map[string]config.ChatM
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
err := a.cfg.DeleteChatMessage(mid, cid)
err := a.cfg.DeleteChatMessage(cid, cm)
if err != nil {
a.logError.Println("error deleting chat message:", err)
return nil, fmt.Errorf("Error deleting chat message. Try again.")
@ -194,11 +194,11 @@ func (a *App) DeleteChatMessage(mid string, cid string) (map[string]config.ChatM
return a.cfg.Channels[cid].ChatBot.Messages, nil
}
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (map[string]config.ChatMessage, error) {
func (a *App) UpdateChatMessage(cid string, cm config.ChatMessage) (map[string]config.ChatMessage, error) {
var err error
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
_, err = a.cfg.UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
_, err = a.cfg.UpdateChatMessage(cid, cm)
if err != nil {
a.logError.Println("error updating chat message:", err)
return nil, fmt.Errorf("Error updating chat message. Try again.")

View file

@ -147,15 +147,18 @@ function StreamChatItem(props) {
};
const openChat = () => {
props.onItemClick(
props.chat.id,
props.chat.as_channel,
props.chat.command,
intervalToTimer(props.chat.interval),
props.chat.on_command,
props.chat.text,
props.chat.text_file
);
props.onItemClick({
id: props.chat.id,
as_channel: props.chat.as_channel,
command: props.chat.command,
interval: intervalToTimer(props.chat.interval),
on_command: props.chat.on_command,
on_command_follower: props.chat.on_command_follower,
on_command_rant_amount: props.chat.on_command_rant_amount,
on_command_subscriber: props.chat.on_command_subscriber,
text: props.chat.text,
text_file: props.chat.text_file,
});
};
const startMessage = () => {

View file

@ -51,26 +51,54 @@
font-size: 16px;
outline: none;
padding: 5px 10px 5px 10px;
text-align: right;
text-align: center;
width: 100%;
}
.chat-command-option {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
}
.chat-command-options {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.chat-command-label {
color: white;
height: 29px;
}
.chat-command-input {
.chat-command-rant-amount {
border: none;
border-radius: 34px;
/* border-radius: 34px; */
box-sizing: border-box;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 5px 10px 5px 10px;
/* padding: 5px 10px 5px 10px; */
padding: 5px;
text-align: center;
}
.chat-command-rant-amount-label {
color: white;
font-family: sans-serif;
padding-right: 10px;
}
.chat-command-rant-amount-symbol {
color: white;
font-family: sans-serif;
font-size: 20px;
padding-right: 1px;
}
.chat-interval {
align-items: center;
display: flex;
@ -292,6 +320,8 @@ input:checked + .chat-toggle-slider:before {
}
.chat-toggle-check-label {
color: white;
font-family: sans-serif;
padding-right: 5px;
}

View file

@ -11,6 +11,9 @@ export function StreamChatMessageModal(props) {
const [chatCommand, setChatCommand] = useState(props.chatCommand);
const [error, setError] = useState('');
const [onCommand, setOnCommand] = useState(props.onCommand);
const [onCommandFollower, setOnCommandFollower] = useState(props.onCommandFollower);
const [onCommandRantAmount, setOnCommandRantAmount] = useState(props.onCommandRantAmount);
const [onCommandSubscriber, setOnCommandSubscriber] = useState(props.onCommandSubscriber);
const [openDelete, setOpenDelete] = useState(false);
const [readFromFile, setReadFromFile] = useState(false);
const [text, setText] = useState(props.text);
@ -22,6 +25,9 @@ export function StreamChatMessageModal(props) {
console.log('update chat');
setAsChannel(props.asChannel);
setOnCommand(props.onCommand);
setOnCommandFollower(props.onCommandFollower);
setOnCommandSubscriber(props.onCommandSubscriber);
setOnCommandRantAmount(props.onCommandRantAmount);
setError('');
setReadFromFile(props.textFile !== '');
setText(props.text);
@ -37,6 +43,9 @@ export function StreamChatMessageModal(props) {
setText('');
setTextFile('');
setOnCommand(false);
setOnCommandFollower(false);
setOnCommandSubscriber(false);
setOnCommandRantAmount(0);
setTimer('');
};
@ -66,14 +75,20 @@ export function StreamChatMessageModal(props) {
return;
}
let ac = asChannel;
let oc = onCommand;
let cmd = chatCommand;
let int = timerToInterval();
let txt = text;
let txtfile = textFile;
reset();
props.onSubmit(props.chatID, ac, cmd, int, oc, txt, txtfile);
let message = {
id: props.chatID,
as_channel: asChannel,
command: chatCommand,
interval: timerToInterval(),
on_command: onCommand,
on_command_follower: onCommandFollower,
on_command_rant_amount: onCommandRantAmount,
on_command_subscriber: onCommandSubscriber,
text: text,
text_file: textFile,
};
props.onSubmit(message);
};
const deleteMessage = () => {
@ -168,6 +183,23 @@ export function StreamChatMessageModal(props) {
setOnCommand(e.target.checked);
};
const checkCommandFollower = (e) => {
setOnCommandFollower(e.target.checked);
};
const checkCommandSubscriber = (e) => {
setOnCommandSubscriber(e.target.checked);
};
const updateRantAmount = (e) => {
let amount = parseInt(e.target.value);
if (isNaN(amount)) {
amount = 0;
}
setOnCommandRantAmount(amount);
};
const checkReadFromFile = (e) => {
setReadFromFile(e.target.checked);
if (!e.target.checked) {
@ -190,12 +222,11 @@ export function StreamChatMessageModal(props) {
<Modal
onClose={close}
show={props.show}
style={{ minWidth: '300px', maxWidth: '400px' }}
style={{ minHeight: '500px', minWidth: '300px', maxWidth: '400px' }}
cancelButton={props.chatID === '' ? 'Cancel' : ''}
onCancel={deleteMessage}
deleteButton={props.chatID === '' ? '' : 'Delete'}
onDelete={deleteMessage}
style={{ minHeight: '450px', maxWidth: '400px' }}
submitButton={'Save'}
onSubmit={submit}
title={'Chat Message'}
@ -278,15 +309,63 @@ export function StreamChatMessageModal(props) {
</label>
</div>
{onCommand ? (
<div className='chat-command'>
<input
className='chat-command-input'
onInput={updateChatCommand}
placeholder={'!command'}
size='8'
type='text'
value={chatCommand}
/>
<div>
<div className='chat-command'>
<input
className='chat-command-input'
onInput={updateChatCommand}
placeholder={'!command'}
size='8'
type='text'
value={chatCommand}
/>
</div>
<div className='chat-command-options'>
<div className='chat-toggle'>
<span className='chat-toggle-label'>Followers only</span>
<label className='chat-toggle-switch'>
<input
onChange={checkCommandFollower}
type='checkbox'
checked={onCommandFollower}
/>
<span className='chat-toggle-slider round'></span>
</label>
</div>
<div className='chat-toggle'>
<span className='chat-toggle-label'>Subscribers only</span>
<label className='chat-toggle-switch'>
<input
onChange={checkCommandSubscriber}
type='checkbox'
checked={onCommandSubscriber}
/>
<span className='chat-toggle-slider round'></span>
</label>
</div>
<div className='chat-interval'>
<span className='chat-command-rant-amount-label'>
Minimum rant amount
</span>
<div>
<span className='chat-command-rant-amount-symbol'>
$
</span>
<input
className='chat-command-rant-amount'
onChange={updateRantAmount}
placeholder='0'
size='4'
type='text'
value={
onCommandRantAmount === 0
? ''
: onCommandRantAmount
}
/>
</div>
</div>
</div>
</div>
) : (
<div className='chat-command'>

View file

@ -38,6 +38,9 @@ function Dashboard() {
const [chatAsChannel, setChatAsChannel] = useState(false);
const [chatCommand, setChatCommand] = useState('');
const [chatOnCommand, setChatOnCommand] = useState(false);
const [chatOnCommandFollower, setChatOnCommandFollower] = useState(false);
const [chatOnCommandRantAmount, setChatOnCommandRantAmount] = useState(0);
const [chatOnCommandSubscriber, setChatOnCommandSubscriber] = useState(false);
const [chatID, setChatID] = useState('');
const [chatInterval, setChatInterval] = useState('');
const [chatText, setChatText] = useState('');
@ -166,17 +169,24 @@ function Dashboard() {
setChatText('');
setChatTextFile('');
setChatOnCommand(false);
setChatOnCommandFollower(false);
setChatOnCommandRantAmount(0);
setChatOnCommandSubscriber(false);
setOpenChat(true);
};
const editChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
setChatAsChannel(asChannel);
setChatCommand(command);
setChatID(id);
setChatInterval(interval);
setChatOnCommand(onCommand);
setChatText(text);
setChatTextFile(textFile);
// const editChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
const editChat = (message) => {
setChatAsChannel(message.as_channel);
setChatCommand(message.command);
setChatID(message.id);
setChatInterval(message.interval);
setChatOnCommand(message.on_command);
setChatOnCommandFollower(message.on_command_follower);
setChatOnCommandRantAmount(message.on_command_rant_amount);
setChatOnCommandSubscriber(message.on_command_subscriber);
setChatText(message.text);
setChatTextFile(message.text_file);
setOpenChat(true);
};
@ -186,28 +196,31 @@ function Dashboard() {
return;
}
StopChatBotMessage(id, cid)
let message = { id: id };
StopChatBotMessage(id)
.then(() => {
DeleteChatMessage(id, cid)
// DeleteChatMessage(id, cid)
DeleteChatMessage(cid, message)
.then((messages) => {
setChatBotMessages(messages);
})
.catch((error) => {
setError(error);
console.log('Error deleting message:', error);
// console.log('Error deleting message:', error);
});
})
.catch((error) => {
setError(error);
console.log('Error stopping message:', error);
// console.log('Error stopping message:', error);
});
};
const saveChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
console.log('save chat textfile:', textFile);
// const saveChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
const saveChat = (message) => {
setOpenChat(false);
if (id === '') {
AddChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
if (message.id === '') {
// AddChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
AddChatMessage(cid, message)
.then((messages) => {
setChatBotMessages(messages);
})
@ -219,7 +232,8 @@ function Dashboard() {
return;
}
UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
// UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
UpdateChatMessage(cid, message)
.then((messages) => {
console.log(messages);
setChatBotMessages(messages);
@ -261,6 +275,9 @@ function Dashboard() {
asChannel={chatAsChannel}
chatCommand={chatCommand}
onCommand={chatOnCommand}
onCommandFollower={chatOnCommandFollower}
onCommandRantAmount={chatOnCommandRantAmount}
onCommandSubscriber={chatOnCommandSubscriber}
interval={chatInterval}
onClose={() => setOpenChat(false)}
onDelete={deleteChat}

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.19
require (
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
github.com/tylertravisty/rumble-livestream-lib-go v0.2.1
github.com/tylertravisty/rumble-livestream-lib-go v0.2.2
github.com/wailsapp/wails/v2 v2.7.1
)

4
go.sum
View file

@ -57,8 +57,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
github.com/tylertravisty/rumble-livestream-lib-go v0.2.1 h1:VtdzuMIBePVTc26ZQeHmk3g2wtrPoAOiSjYvf8s2GTY=
github.com/tylertravisty/rumble-livestream-lib-go v0.2.1/go.mod h1:CACpHQV9xQqBKB7C13tUkL7O8Neb35+dJzRV1N211s4=
github.com/tylertravisty/rumble-livestream-lib-go v0.2.2 h1:gWmw44gSNOK37UQIEWS0GVxFJABt/8np9ArhFSSZj1o=
github.com/tylertravisty/rumble-livestream-lib-go v0.2.2/go.mod h1:CACpHQV9xQqBKB7C13tUkL7O8Neb35+dJzRV1N211s4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=

View file

@ -2,9 +2,11 @@ package chatbot
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"fmt"
"html/template"
"log"
"math/big"
"os"
@ -29,15 +31,18 @@ type ChatBot struct {
}
type message struct {
cancel context.CancelFunc
cancelMu sync.Mutex
asChannel bool
command string
id string
interval time.Duration
onCommand bool
text string
textFromFile []string
cancel context.CancelFunc
cancelMu sync.Mutex
asChannel bool
command string
id string
interval time.Duration
onCommand bool
onCommandFollower bool
onCommandRantAmount int
OnCommandSubscriber bool
text string
textFromFile []string
}
func NewChatBot(ctx context.Context, streamUrl string, cfg config.ChatBot, logError *log.Logger) (*ChatBot, error) {
@ -91,13 +96,16 @@ func (cb *ChatBot) StartMessage(id string) error {
}
m = &message{
asChannel: msg.AsChannel,
command: msg.Command,
id: msg.ID,
interval: msg.Interval,
onCommand: msg.OnCommand,
text: msg.Text,
textFromFile: textFromFile,
asChannel: msg.AsChannel,
command: msg.Command,
id: msg.ID,
interval: msg.Interval,
onCommand: msg.OnCommand,
onCommandFollower: msg.OnCommandFollower,
onCommandRantAmount: msg.OnCommandRantAmount,
OnCommandSubscriber: msg.OnCommandSubscriber,
text: msg.Text,
textFromFile: textFromFile,
}
ctx, cancel := context.WithCancel(context.Background())
@ -127,14 +135,37 @@ func (cb *ChatBot) startCommand(ctx context.Context, m *message) {
select {
case <-ctx.Done():
return
case <-ch:
case cv := <-ch:
if m.onCommandFollower && !cv.IsFollower {
break
}
subscriber := false
for _, badge := range cv.Badges {
if badge == "recurring_subscription" || badge == "locals_supporter" {
subscriber = true
}
}
if m.OnCommandSubscriber && !subscriber {
break
}
// if m.onCommandRantAmount > 0 && cv.Rant < m.onCommandRantAmount * 100 {
// break
// }
if cv.Rant < m.onCommandRantAmount*100 {
break
}
// TODO: parse !command
now := time.Now()
if now.Sub(prev) < m.interval*time.Second {
break
}
err := cb.chat(m)
err := cb.chatCommand(m, cv)
if err != nil {
cb.logError.Println("error sending chat:", err)
cb.StopMessage(m.id)
@ -171,6 +202,51 @@ func (cb *ChatBot) startMessage(ctx context.Context, m *message) {
}
}
func (cb *ChatBot) chatCommand(m *message, cv rumblelivestreamlib.ChatView) error {
if cb.client == nil {
return fmt.Errorf("client is nil")
}
msgText := m.text
if len(m.textFromFile) > 0 {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(m.textFromFile))))
if err != nil {
return fmt.Errorf("error generating random number: %v", err)
}
msgText = m.textFromFile[n.Int64()]
}
tmpl, err := template.New("chat").Parse(msgText)
if err != nil {
return fmt.Errorf("error creating template: %v", err)
}
fields := struct {
ChannelName string
Username string
Rant int
}{
ChannelName: cv.ChannelName,
Username: cv.Username,
Rant: cv.Rant / 100,
}
var textB bytes.Buffer
err = tmpl.Execute(&textB, fields)
if err != nil {
return fmt.Errorf("error executing template: %v", err)
}
text := textB.String()
err = cb.client.Chat(m.asChannel, text)
if err != nil {
return fmt.Errorf("error sending chat: %v", err)
}
return nil
}
func (cb *ChatBot) chat(m *message) error {
if cb.client == nil {
return fmt.Errorf("client is nil")

View file

@ -73,13 +73,16 @@ func userDir() (string, error) {
}
type ChatMessage struct {
ID string `json:"id"`
AsChannel bool `json:"as_channel"`
Command string `json:"command"`
Interval time.Duration `json:"interval"`
OnCommand bool `json:"on_command"`
Text string `json:"text"`
TextFile string `json:"text_file"`
ID string `json:"id"`
AsChannel bool `json:"as_channel"`
Command string `json:"command"`
Interval time.Duration `json:"interval"`
OnCommand bool `json:"on_command"`
OnCommandFollower bool `json:"on_command_follower"`
OnCommandRantAmount int `json:"on_command_rant_amount"`
OnCommandSubscriber bool `json:"on_command_subscriber"`
Text string `json:"text"`
TextFile string `json:"text_file"`
}
type ChatBot struct {
@ -109,23 +112,23 @@ func (a *App) NewChannel(url string, name string) (string, error) {
}
}
func (a *App) DeleteChatMessage(id string, cid string) error {
func (a *App) DeleteChatMessage(cid string, cm ChatMessage) error {
channel, exists := a.Channels[cid]
if !exists {
return fmt.Errorf("config: channel does not exist")
}
_, exists = channel.ChatBot.Messages[id]
_, exists = channel.ChatBot.Messages[cm.ID]
if !exists {
return fmt.Errorf("config: message does not exist")
}
delete(channel.ChatBot.Messages, id)
delete(channel.ChatBot.Messages, cm.ID)
return nil
}
func (a *App) NewChatMessage(cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (string, error) {
func (a *App) NewChatMessage(cid string, cm ChatMessage) (string, error) {
if _, exists := a.Channels[cid]; !exists {
return "", fmt.Errorf("config: channel does not exist")
}
@ -136,41 +139,29 @@ func (a *App) NewChatMessage(cid string, asChannel bool, command string, interva
return "", fmt.Errorf("config: error generating ID: %v", err)
}
if _, exists := a.Channels[cid].ChatBot.Messages[id]; !exists {
a.Channels[cid].ChatBot.Messages[id] = ChatMessage{
ID: id,
AsChannel: asChannel,
Command: command,
Interval: interval,
OnCommand: onCommand,
Text: text,
TextFile: textFile,
}
_, exists := a.Channels[cid].ChatBot.Messages[id]
if !exists {
cm.ID = id
a.Channels[cid].ChatBot.Messages[id] = cm
return id, nil
}
}
}
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (string, error) {
func (a *App) UpdateChatMessage(cid string, cm ChatMessage) (string, error) {
channel, exists := a.Channels[cid]
if !exists {
return "", fmt.Errorf("config: channel does not exist")
}
cm, exists := channel.ChatBot.Messages[id]
_, exists = channel.ChatBot.Messages[cm.ID]
if !exists {
return "", fmt.Errorf("config: message does not exist")
}
cm.AsChannel = asChannel
cm.Command = command
cm.Interval = interval
cm.OnCommand = onCommand
cm.Text = text
cm.TextFile = textFile
channel.ChatBot.Messages[id] = cm
channel.ChatBot.Messages[cm.ID] = cm
return id, nil
return cm.ID, nil
}
type App struct {