diff --git a/app.go b/app.go index 4f0a0df..36b3c64 100644 --- a/app.go +++ b/app.go @@ -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.") diff --git a/frontend/src/components/StreamChatBot.jsx b/frontend/src/components/StreamChatBot.jsx index e321f7d..65827ee 100644 --- a/frontend/src/components/StreamChatBot.jsx +++ b/frontend/src/components/StreamChatBot.jsx @@ -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 = () => { diff --git a/frontend/src/components/StreamChatMessage.css b/frontend/src/components/StreamChatMessage.css index 02038e9..edfcc30 100644 --- a/frontend/src/components/StreamChatMessage.css +++ b/frontend/src/components/StreamChatMessage.css @@ -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; } diff --git a/frontend/src/components/StreamChatMessage.jsx b/frontend/src/components/StreamChatMessage.jsx index 770d9af..774b0b6 100644 --- a/frontend/src/components/StreamChatMessage.jsx +++ b/frontend/src/components/StreamChatMessage.jsx @@ -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) { {onCommand ? ( -
- +
+
+ +
+
+
+ Followers only + +
+
+ Subscribers only + +
+
+ + Minimum rant amount + +
+ + $ + + +
+
+
) : (
diff --git a/frontend/src/screens/Dashboard.jsx b/frontend/src/screens/Dashboard.jsx index 0a4086a..4821550 100644 --- a/frontend/src/screens/Dashboard.jsx +++ b/frontend/src/screens/Dashboard.jsx @@ -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} diff --git a/go.mod b/go.mod index 17eacfa..fe6cf7b 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 7ea00c3..5e37610 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/chatbot/chatbot.go b/internal/chatbot/chatbot.go index 7d42f18..f343091 100644 --- a/internal/chatbot/chatbot.go +++ b/internal/chatbot/chatbot.go @@ -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") diff --git a/internal/config/config.go b/internal/config/config.go index 920386d..e09150a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 {