Added chat commands
This commit is contained in:
parent
f40ba29179
commit
60b8dd7bab
16
NOTES.md
16
NOTES.md
|
@ -1,10 +1,16 @@
|
|||
# Doing
|
||||
|
||||
New chat message modal:
|
||||
- submit button in modal component (check for button on click and button text)
|
||||
- primary button (save)
|
||||
- secondary button (delete)
|
||||
- on close: reset values (in stream chat message component)
|
||||
Show error when choosing file "chooseFile"
|
||||
Show filename in chat bot list
|
||||
Add styling to choose file button
|
||||
|
||||
Commands
|
||||
- specify for follower/subscriber/locals only/rants
|
||||
- check badges for subscriber and locals
|
||||
|
||||
Update
|
||||
- github.com/rhysd/go-github-selfupdate
|
||||
- github.com/inconshreveable/go-update
|
||||
|
||||
Create loading indicator before API is called
|
||||
|
||||
|
|
42
app.go
42
app.go
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/tylertravisty/rum-goggles/internal/chatbot"
|
||||
"github.com/tylertravisty/rum-goggles/internal/config"
|
||||
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
type chat struct {
|
||||
|
@ -141,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, interval time.Duration, message string) (map[string]config.ChatMessage, error) {
|
||||
func (a *App) AddChatMessage(cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (map[string]config.ChatMessage, error) {
|
||||
var err error
|
||||
a.cfgMu.Lock()
|
||||
defer a.cfgMu.Unlock()
|
||||
_, err = a.cfg.NewChatMessage(cid, asChannel, interval, message)
|
||||
_, err = a.cfg.NewChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
|
||||
if err != nil {
|
||||
a.logError.Println("error creating new chat:", err)
|
||||
return nil, fmt.Errorf("Error creating new chat message. Try again.")
|
||||
|
@ -192,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, interval time.Duration, message string) (map[string]config.ChatMessage, error) {
|
||||
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) {
|
||||
var err error
|
||||
a.cfgMu.Lock()
|
||||
defer a.cfgMu.Unlock()
|
||||
_, err = a.cfg.UpdateChatMessage(id, cid, asChannel, interval, message)
|
||||
_, err = a.cfg.UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
|
||||
if err != nil {
|
||||
a.logError.Println("error updating chat message:", err)
|
||||
return nil, fmt.Errorf("Error updating chat message. Try again.")
|
||||
|
@ -240,7 +242,13 @@ func (a *App) NewChatBot(cid string, username string, password string, streamUrl
|
|||
err = a.cb.Login(username, password)
|
||||
if err != nil {
|
||||
a.logError.Println("error logging into chat bot:", err)
|
||||
return fmt.Errorf("Error logging in. Try Again.")
|
||||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
|
||||
err = a.cb.StartChatStream()
|
||||
if err != nil {
|
||||
a.logError.Println("error starting chat stream:", err)
|
||||
return fmt.Errorf("Error connecting to chat. Try again.")
|
||||
}
|
||||
|
||||
// a.cb = cb
|
||||
|
@ -271,6 +279,11 @@ func (a *App) resetChatBot() error {
|
|||
return fmt.Errorf("error stopping all chat bot messages: %v", err)
|
||||
}
|
||||
|
||||
err = a.cb.StopChatStream()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stopping chat stream: %v", err)
|
||||
}
|
||||
|
||||
err = a.cb.Logout()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error logging out of chat bot: %v", err)
|
||||
|
@ -343,3 +356,22 @@ func (a *App) updateChatBotConfig(cfg config.ChatBot) {
|
|||
a.cb.Cfg = cfg
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) OpenFileDialog() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting home directory:", err)
|
||||
return "", fmt.Errorf("Error opening file explorer. Try again.")
|
||||
}
|
||||
filepath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{DefaultDirectory: home})
|
||||
if err != nil {
|
||||
a.logError.Println("error opening file dialog:", err)
|
||||
return "", fmt.Errorf("Error opening file explorer. Try again.")
|
||||
}
|
||||
|
||||
return filepath, err
|
||||
}
|
||||
|
||||
func (a *App) FilepathBase(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
|
44
frontend/src/components/ChatMessage.css
Normal file
44
frontend/src/components/ChatMessage.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
.chat-message {
|
||||
align-items: start;
|
||||
background-color: rgba(6,23,38,1);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.chat-message-user-image {
|
||||
border-radius: 50%;
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.chat-message-user-initial {
|
||||
align-items: center;
|
||||
background-color: #37c;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50%;
|
||||
color: #eee;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
height: 22px;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.chat-message-username {
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.chat-message-text {
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
28
frontend/src/components/ChatMessage.jsx
Normal file
28
frontend/src/components/ChatMessage.jsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import './ChatMessage.css';
|
||||
|
||||
function ChatMessage(props) {
|
||||
const upperInitial = () => {
|
||||
return props.message.username[0].toUpperCase();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='chat-message'>
|
||||
{props.message.image === '' || props.message.image === undefined ? (
|
||||
<span className='chat-message-user-initial'>{upperInitial()}</span>
|
||||
) : (
|
||||
<img className='chat-message-user-image' src={props.message.image} />
|
||||
)}
|
||||
<div>
|
||||
<span
|
||||
className='chat-message-username'
|
||||
style={props.message.color && { color: props.message.color }}
|
||||
>
|
||||
{props.message.username}
|
||||
</span>
|
||||
<span className='chat-message-text'>{props.message.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatMessage;
|
|
@ -17,13 +17,11 @@ function Highlight(props) {
|
|||
};
|
||||
|
||||
const stopwatchString = () => {
|
||||
console.log(props.value);
|
||||
if (isNaN(Date.parse(props.value))) {
|
||||
return '--:--';
|
||||
}
|
||||
let now = new Date();
|
||||
let date = new Date(props.value);
|
||||
console.log(date);
|
||||
let diff = now - date;
|
||||
|
||||
let msMinute = 1000 * 60;
|
||||
|
|
|
@ -3,43 +3,6 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.stream-chat-button {
|
||||
align-items: center;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.stream-chat-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stream-chat-button-title {
|
||||
background-color: rgba(6,23,38,1);
|
||||
}
|
||||
|
||||
.stream-chat-button-chat {
|
||||
align-items: center;
|
||||
background-color: #000312;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.stream-chat-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.stream-chat-controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.stream-chat-header {
|
||||
align-items: center;
|
||||
background-color: rgba(6,23,38,1);
|
||||
|
@ -52,45 +15,6 @@
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.stream-chat-item {
|
||||
border-bottom: 1px solid #82b1ff;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: sans-serif;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stream-chat-item-sender {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding-left: 10px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.stream-chat-item-interval {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding-left: 10px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.stream-chat-item-message {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.stream-chat-list {
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 84px - 40px - 179px);
|
||||
|
|
|
@ -1,42 +1,34 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { StartChatBotMessage, StopChatBotMessage } from '../../wailsjs/go/main/App';
|
||||
import { useState } from 'react';
|
||||
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||
import { GearFill, Pause, Play, PlusCircle } from '../assets/icons';
|
||||
import ChatMessage from './ChatMessage';
|
||||
import './StreamChat.css';
|
||||
import { SmallModal } from './Modal';
|
||||
|
||||
function StreamChat(props) {
|
||||
const sortChatsAlpha = () => {
|
||||
let keys = Object.keys(props.chats);
|
||||
const [messages, setMessages] = useState([
|
||||
{
|
||||
color: '#ec131f',
|
||||
image: 'https://ak2.rmbl.ws/z0/V/m/v/E/VmvEe.asF.4-18osof-s35kf7.jpeg',
|
||||
username: 'tylertravisty',
|
||||
text: 'Hello, world this is si s a a sdf asd f',
|
||||
},
|
||||
{
|
||||
username: 'tylertravisty',
|
||||
text: 'Another chat message',
|
||||
},
|
||||
]);
|
||||
|
||||
let sorted = [...keys].sort((a, b) =>
|
||||
props.chats[a].text.toLowerCase() > props.chats[b].text.toLowerCase() ? 1 : -1
|
||||
);
|
||||
return sorted;
|
||||
};
|
||||
EventsOn('ChatMessage', (msg) => {
|
||||
setMessages(...messages, msg);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='stream-chat'>
|
||||
<div className='stream-chat-header'>
|
||||
<span className='stream-chat-title'>{props.title}</span>
|
||||
<div className='stream-chat-controls'>
|
||||
<button
|
||||
className='stream-chat-button stream-chat-button-title'
|
||||
onClick={props.onAdd}
|
||||
>
|
||||
<img className='stream-chat-icon' src={PlusCircle} />
|
||||
</button>
|
||||
<button
|
||||
className='stream-chat-button stream-chat-button-title'
|
||||
onClick={props.onSettings}
|
||||
>
|
||||
<img className='stream-chat-icon' src={GearFill} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='stream-chat-list'>
|
||||
{sortChatsAlpha().map((chat, index) => (
|
||||
<StreamChatItem chat={props.chats[chat]} onItemClick={props.onEdit} />
|
||||
{messages.map((message, index) => (
|
||||
<ChatMessage message={message} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,139 +36,3 @@ function StreamChat(props) {
|
|||
}
|
||||
|
||||
export default StreamChat;
|
||||
|
||||
function StreamChatItem(props) {
|
||||
const [active, setActive] = useState(props.chat.active);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const changeActive = (bool) => {
|
||||
console.log('ChangeActive:', bool);
|
||||
props.chat.active = bool;
|
||||
setActive(bool);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
EventsOn('ChatBotMessageActive-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotMessageActive', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(true);
|
||||
}
|
||||
});
|
||||
|
||||
EventsOn('ChatBotMessageError-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotMessageError', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const prependZero = (value) => {
|
||||
if (value < 10) {
|
||||
return '0' + value;
|
||||
}
|
||||
|
||||
return '' + value;
|
||||
};
|
||||
|
||||
const printInterval = (interval) => {
|
||||
let hours = Math.floor(interval / 3600);
|
||||
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||
|
||||
// hours = prependZero(hours);
|
||||
// minutes = prependZero(minutes);
|
||||
// seconds = prependZero(seconds);
|
||||
// return hours + ':' + minutes + ':' + seconds;
|
||||
|
||||
return hours + 'h ' + minutes + 'm ' + seconds + 's';
|
||||
};
|
||||
|
||||
const intervalToTimer = (interval) => {
|
||||
let hours = Math.floor(interval / 3600);
|
||||
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||
|
||||
if (minutes !== 0) {
|
||||
seconds = prependZero(seconds);
|
||||
}
|
||||
if (hours !== 0) {
|
||||
minutes = prependZero(minutes);
|
||||
}
|
||||
if (hours === 0) {
|
||||
hours = '';
|
||||
if (minutes === 0) {
|
||||
minutes = '';
|
||||
if (seconds === 0) {
|
||||
seconds = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hours + minutes + seconds;
|
||||
};
|
||||
|
||||
const openChat = () => {
|
||||
props.onItemClick(
|
||||
props.chat.id,
|
||||
props.chat.as_channel,
|
||||
intervalToTimer(props.chat.interval),
|
||||
props.chat.text
|
||||
);
|
||||
};
|
||||
|
||||
const startMessage = () => {
|
||||
StartChatBotMessage(props.chat.id)
|
||||
.then(() => {
|
||||
changeActive(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
const stopMessage = () => {
|
||||
StopChatBotMessage(props.chat.id).then(() => {
|
||||
changeActive(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
<div className='stream-chat-item' onClick={() => openChat()}>
|
||||
<span className='stream-chat-item-message'>{props.chat.text}</span>
|
||||
<span className='stream-chat-item-interval'>
|
||||
{printInterval(props.chat.interval)}
|
||||
</span>
|
||||
<span className='stream-chat-item-sender'>
|
||||
{props.chat.as_channel ? 'Channel' : 'User'}
|
||||
</span>
|
||||
<button
|
||||
className='stream-chat-button stream-chat-button-chat'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('message ID:', props.chat.id);
|
||||
if (active) {
|
||||
console.log('Stop message');
|
||||
stopMessage();
|
||||
} else {
|
||||
console.log('Start message');
|
||||
startMessage();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img className='stream-chat-icon' src={active ? Pause : Play} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
104
frontend/src/components/StreamChatBot.css
Normal file
104
frontend/src/components/StreamChatBot.css
Normal file
|
@ -0,0 +1,104 @@
|
|||
.stream-chatbot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stream-chatbot-button {
|
||||
align-items: center;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.stream-chatbot-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stream-chatbot-button-title {
|
||||
background-color: rgba(6,23,38,1);
|
||||
}
|
||||
|
||||
.stream-chatbot-button-chat {
|
||||
align-items: center;
|
||||
background-color: #000312;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.stream-chatbot-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.stream-chatbot-controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.stream-chatbot-header {
|
||||
align-items: center;
|
||||
background-color: rgba(6,23,38,1);
|
||||
border-bottom: 1px solid #495a6a;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
height: 19px;
|
||||
padding: 10px 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.stream-chatbot-item {
|
||||
border-bottom: 1px solid #82b1ff;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: sans-serif;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stream-chatbot-item-sender {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding-left: 10px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.stream-chatbot-item-interval {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding-left: 10px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.stream-chatbot-item-message {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.stream-chatbot-list {
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 84px - 40px - 179px);
|
||||
}
|
||||
|
||||
.stream-chatbot-title {
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
212
frontend/src/components/StreamChatBot.jsx
Normal file
212
frontend/src/components/StreamChatBot.jsx
Normal file
|
@ -0,0 +1,212 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { FilepathBase, StartChatBotMessage, StopChatBotMessage } from '../../wailsjs/go/main/App';
|
||||
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||
import { GearFill, Pause, Play, PlusCircle } from '../assets/icons';
|
||||
import './StreamChatBot.css';
|
||||
import { SmallModal } from './Modal';
|
||||
|
||||
function StreamChatBot(props) {
|
||||
const sortChatsAlpha = () => {
|
||||
let keys = Object.keys(props.chats);
|
||||
|
||||
let sorted = [...keys].sort((a, b) =>
|
||||
props.chats[a].text.toLowerCase() > props.chats[b].text.toLowerCase() ? 1 : -1
|
||||
);
|
||||
return sorted;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='stream-chatbot'>
|
||||
<div className='stream-chatbot-header'>
|
||||
<span className='stream-chatbot-title'>{props.title}</span>
|
||||
<div className='stream-chatbot-controls'>
|
||||
<button
|
||||
className='stream-chatbot-button stream-chatbot-button-title'
|
||||
onClick={props.onAdd}
|
||||
>
|
||||
<img className='stream-chatbot-icon' src={PlusCircle} />
|
||||
</button>
|
||||
<button
|
||||
className='stream-chatbot-button stream-chatbot-button-title'
|
||||
onClick={props.onSettings}
|
||||
>
|
||||
<img className='stream-chatbot-icon' src={GearFill} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='stream-chatbot-list'>
|
||||
{sortChatsAlpha().map((chat, index) => (
|
||||
<StreamChatItem chat={props.chats[chat]} onItemClick={props.onEdit} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StreamChatBot;
|
||||
|
||||
function StreamChatItem(props) {
|
||||
const [active, setActive] = useState(props.chat.active);
|
||||
const [error, setError] = useState('');
|
||||
const [filename, setFilename] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (props.chat.text_file !== '') {
|
||||
FilepathBase(props.chat.text_file).then((name) => {
|
||||
setFilename(name);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const changeActive = (bool) => {
|
||||
console.log('ChangeActive:', bool);
|
||||
props.chat.active = bool;
|
||||
setActive(bool);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
EventsOn('ChatBotCommandActive-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotCommandActive', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(true);
|
||||
}
|
||||
});
|
||||
|
||||
EventsOn('ChatBotCommandError-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotCommandError', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(false);
|
||||
}
|
||||
});
|
||||
|
||||
EventsOn('ChatBotMessageActive-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotMessageActive', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(true);
|
||||
}
|
||||
});
|
||||
|
||||
EventsOn('ChatBotMessageError-' + props.chat.id, (mid) => {
|
||||
console.log('ChatBotMessageError', props.chat.id, mid);
|
||||
if (mid === props.chat.id) {
|
||||
changeActive(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const prependZero = (value) => {
|
||||
if (value < 10) {
|
||||
return '0' + value;
|
||||
}
|
||||
|
||||
return '' + value;
|
||||
};
|
||||
|
||||
const printInterval = (interval) => {
|
||||
let hours = Math.floor(interval / 3600);
|
||||
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||
|
||||
// hours = prependZero(hours);
|
||||
// minutes = prependZero(minutes);
|
||||
// seconds = prependZero(seconds);
|
||||
// return hours + ':' + minutes + ':' + seconds;
|
||||
|
||||
return hours + 'h ' + minutes + 'm ' + seconds + 's';
|
||||
};
|
||||
|
||||
const intervalToTimer = (interval) => {
|
||||
let hours = Math.floor(interval / 3600);
|
||||
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||
|
||||
if (minutes !== 0) {
|
||||
seconds = prependZero(seconds);
|
||||
}
|
||||
if (hours !== 0) {
|
||||
minutes = prependZero(minutes);
|
||||
}
|
||||
if (hours === 0) {
|
||||
hours = '';
|
||||
if (minutes === 0) {
|
||||
minutes = '';
|
||||
if (seconds === 0) {
|
||||
seconds = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hours + minutes + seconds;
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
};
|
||||
|
||||
const startMessage = () => {
|
||||
StartChatBotMessage(props.chat.id)
|
||||
.then(() => {
|
||||
changeActive(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
const stopMessage = () => {
|
||||
StopChatBotMessage(props.chat.id).then(() => {
|
||||
changeActive(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
<div className='stream-chatbot-item' onClick={() => openChat()}>
|
||||
<span className='stream-chatbot-item-message'>
|
||||
{props.chat.text_file !== '' ? filename : props.chat.text}
|
||||
</span>
|
||||
<span className='stream-chatbot-item-interval'>
|
||||
{props.chat.on_command
|
||||
? props.chat.command
|
||||
: printInterval(props.chat.interval)}
|
||||
</span>
|
||||
<span className='stream-chatbot-item-sender'>
|
||||
{props.chat.as_channel ? 'Channel' : 'User'}
|
||||
</span>
|
||||
<button
|
||||
className='stream-chatbot-button stream-chatbot-button-chat'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('message ID:', props.chat.id);
|
||||
if (active) {
|
||||
console.log('Stop message');
|
||||
stopMessage();
|
||||
} else {
|
||||
console.log('Start message');
|
||||
startMessage();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img className='stream-chatbot-icon' src={active ? Pause : Play} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
width: 100vw;
|
||||
} */
|
||||
|
||||
.chat-as-channel {
|
||||
.chat-toggle {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -28,12 +28,49 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-as-channel-label {
|
||||
.chat-toggle-label {
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.chat-command {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-command-input {
|
||||
border: none;
|
||||
border-radius: 34px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
padding: 5px 10px 5px 10px;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-command-label {
|
||||
color: white;
|
||||
height: 29px;
|
||||
}
|
||||
|
||||
.chat-command-input {
|
||||
border: none;
|
||||
border-radius: 34px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
padding: 5px 10px 5px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chat-interval {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -127,24 +164,31 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-as-channel-switch {
|
||||
.stream-chat-message-title-right {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.chat-as-channel-switch input {
|
||||
.chat-toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.chat-as-channel-slider {
|
||||
.chat-toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
|
@ -156,7 +200,7 @@
|
|||
transition: .4s;
|
||||
}
|
||||
|
||||
.chat-as-channel-slider:before {
|
||||
.chat-toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
|
@ -168,20 +212,117 @@
|
|||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .chat-as-channel-slider {
|
||||
input:checked + .chat-toggle-slider {
|
||||
background-color: #85c742;
|
||||
}
|
||||
|
||||
input:checked + .chat-as-channel-slider:before {
|
||||
input:checked + .chat-toggle-slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
/* Rounded sliders */
|
||||
.chat-as-channel-slider.round {
|
||||
.chat-toggle-slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.chat-as-channel-slider.round:before {
|
||||
.chat-toggle-slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
margin-bottom: 15px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.chat-toggle-check {
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-color: #495a6a;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container:hover input ~ .chat-toggle-check {
|
||||
background-color: #495a6a;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container input:checked ~ .chat-toggle-check {
|
||||
background-color: #85c742;
|
||||
}
|
||||
|
||||
.chat-toggle-check:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container input:checked ~ .chat-toggle-check:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chat-toggle-check-container .chat-toggle-check:after {
|
||||
left: 4px;
|
||||
top: 1px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.chat-toggle-check-label {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.choose-file {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choose-file-button-box {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.choose-file-button {
|
||||
background-color: #85c742;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: #061726;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
/* width: 200px; */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choose-file-path {
|
||||
overflow: scroll;
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,28 +2,41 @@ import { useEffect, useState } from 'react';
|
|||
|
||||
import { Modal, SmallModal } from './Modal';
|
||||
|
||||
import { OpenFileDialog } from '../../wailsjs/go/main/App';
|
||||
|
||||
import './StreamChatMessage.css';
|
||||
|
||||
export function StreamChatMessageModal(props) {
|
||||
const [asChannel, setAsChannel] = useState(props.asChannel);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const [chatCommand, setChatCommand] = useState(props.chatCommand);
|
||||
const [error, setError] = useState('');
|
||||
const [message, setMessage] = useState(props.message);
|
||||
const updateMessage = (event) => setMessage(event.target.value);
|
||||
const [onCommand, setOnCommand] = useState(props.onCommand);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const [readFromFile, setReadFromFile] = useState(false);
|
||||
const [text, setText] = useState(props.text);
|
||||
const [textFile, setTextFile] = useState(props.textFile);
|
||||
const updateText = (event) => setText(event.target.value);
|
||||
const [timer, setTimer] = useState(props.interval);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('update chat');
|
||||
setAsChannel(props.asChannel);
|
||||
setOnCommand(props.onCommand);
|
||||
setError('');
|
||||
setMessage(props.message);
|
||||
setReadFromFile(props.textFile !== '');
|
||||
setText(props.text);
|
||||
setTextFile(props.textFile);
|
||||
setTimer(props.interval);
|
||||
}, []);
|
||||
|
||||
const reset = () => {
|
||||
setAsChannel(false);
|
||||
setChatCommand(false);
|
||||
setError('');
|
||||
setMessage('');
|
||||
setReadFromFile(false);
|
||||
setText('');
|
||||
setTextFile('');
|
||||
setOnCommand(false);
|
||||
setTimer('');
|
||||
};
|
||||
|
||||
|
@ -33,21 +46,34 @@ export function StreamChatMessageModal(props) {
|
|||
};
|
||||
|
||||
const submit = () => {
|
||||
if (message === '') {
|
||||
if (!readFromFile && text === '') {
|
||||
setError('Add message');
|
||||
return;
|
||||
}
|
||||
|
||||
if (readFromFile && textFile === '') {
|
||||
setError('Select file containing messages');
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer === '') {
|
||||
setError('Set timer');
|
||||
return;
|
||||
}
|
||||
|
||||
if (onCommand && chatCommand === '') {
|
||||
setError('Add command');
|
||||
return;
|
||||
}
|
||||
|
||||
let ac = asChannel;
|
||||
let msg = message;
|
||||
let oc = onCommand;
|
||||
let cmd = chatCommand;
|
||||
let int = timerToInterval();
|
||||
let txt = text;
|
||||
let txtfile = textFile;
|
||||
reset();
|
||||
props.onSubmit(props.chatID, ac, int, msg);
|
||||
props.onSubmit(props.chatID, ac, cmd, int, oc, txt, txtfile);
|
||||
};
|
||||
|
||||
const deleteMessage = () => {
|
||||
|
@ -65,6 +91,24 @@ export function StreamChatMessageModal(props) {
|
|||
props.onDelete(props.chatID);
|
||||
};
|
||||
|
||||
const updateChatCommand = (e) => {
|
||||
let command = e.target.value;
|
||||
|
||||
if (command.length === 1) {
|
||||
if (command !== '!') {
|
||||
command = '!' + command;
|
||||
}
|
||||
}
|
||||
command = command.toLowerCase();
|
||||
let postfix = command.replace('!', '');
|
||||
|
||||
if (postfix !== '' && !/^[a-z0-9]+$/gi.test(postfix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setChatCommand(command);
|
||||
};
|
||||
|
||||
const updateTimerBackspace = (e) => {
|
||||
if (timer.length === 0) {
|
||||
return;
|
||||
|
@ -116,10 +160,31 @@ export function StreamChatMessageModal(props) {
|
|||
return t.substring(0, 2) + ':' + t.substring(2, 4) + ':' + t.substring(4, 6);
|
||||
};
|
||||
|
||||
const checkToggle = (e) => {
|
||||
const checkChannelToggle = (e) => {
|
||||
setAsChannel(e.target.checked);
|
||||
};
|
||||
|
||||
const checkCommandToggle = (e) => {
|
||||
setOnCommand(e.target.checked);
|
||||
};
|
||||
|
||||
const checkReadFromFile = (e) => {
|
||||
setReadFromFile(e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
setTextFile('');
|
||||
}
|
||||
};
|
||||
|
||||
const chooseFile = () => {
|
||||
OpenFileDialog()
|
||||
.then((filepath) => {
|
||||
if (filepath !== '') {
|
||||
setTextFile(filepath);
|
||||
}
|
||||
})
|
||||
.catch((error) => setError(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
|
@ -130,27 +195,52 @@ export function StreamChatMessageModal(props) {
|
|||
onCancel={deleteMessage}
|
||||
deleteButton={props.chatID === '' ? '' : 'Delete'}
|
||||
onDelete={deleteMessage}
|
||||
style={{ minHeight: '450px', maxWidth: '400px' }}
|
||||
submitButton={'Save'}
|
||||
onSubmit={submit}
|
||||
title={'Chat Message'}
|
||||
>
|
||||
<div className='stream-chat-message-modal'>
|
||||
<div className='stream-chat-message'>
|
||||
{error && <span className='stream-chat-message-error'>{error}</span>}
|
||||
{/* {error && <span className='stream-chat-message-error'>{error}</span>} */}
|
||||
<div className='stream-chat-message-title'>
|
||||
<span className='stream-chat-message-label'>Message</span>
|
||||
<div className='stream-chat-message-title-right'>
|
||||
<span className='chat-toggle-check-label'>Read from file</span>
|
||||
<label className='chat-toggle-check-container'>
|
||||
<input
|
||||
checked={readFromFile}
|
||||
onChange={checkReadFromFile}
|
||||
type='checkbox'
|
||||
/>
|
||||
<span className='chat-toggle-check'></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
className='stream-chat-message-textarea'
|
||||
cols='25'
|
||||
onChange={updateMessage}
|
||||
rows='4'
|
||||
value={message}
|
||||
/>
|
||||
{readFromFile ? (
|
||||
<div className='choose-file'>
|
||||
<div className='choose-file-button-box'>
|
||||
<button className='choose-file-button' onClick={chooseFile}>
|
||||
Choose file
|
||||
</button>
|
||||
</div>
|
||||
<span className='choose-file-path'>{textFile}</span>
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
className='stream-chat-message-textarea'
|
||||
cols='25'
|
||||
onChange={updateText}
|
||||
rows='4'
|
||||
value={text}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='chat-options'>
|
||||
<div className='chat-interval'>
|
||||
<span className='chat-interval-label'>Chat interval</span>
|
||||
<span className='chat-interval-label'>
|
||||
{onCommand ? 'Command timeout' : 'Chat interval'}
|
||||
</span>
|
||||
<input
|
||||
className={
|
||||
timer === ''
|
||||
|
@ -165,13 +255,44 @@ export function StreamChatMessageModal(props) {
|
|||
value={''}
|
||||
/>
|
||||
</div>
|
||||
<div className='chat-as-channel'>
|
||||
<span className='chat-as-channel-label'>Chat as channel</span>
|
||||
<label className='chat-as-channel-switch'>
|
||||
<input onChange={checkToggle} type='checkbox' checked={asChannel} />
|
||||
<span className='chat-as-channel-slider round'></span>
|
||||
<div className='chat-toggle'>
|
||||
<span className='chat-toggle-label'>Chat as channel</span>
|
||||
<label className='chat-toggle-switch'>
|
||||
<input
|
||||
onChange={checkChannelToggle}
|
||||
type='checkbox'
|
||||
checked={asChannel}
|
||||
/>
|
||||
<span className='chat-toggle-slider round'></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className='chat-toggle'>
|
||||
<span className='chat-toggle-label'>Chat on command</span>
|
||||
<label className='chat-toggle-switch'>
|
||||
<input
|
||||
onChange={checkCommandToggle}
|
||||
type='checkbox'
|
||||
checked={onCommand}
|
||||
/>
|
||||
<span className='chat-toggle-slider round'></span>
|
||||
</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'>
|
||||
<span className='chat-command-label'>{'\u00A0'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -188,6 +309,15 @@ export function StreamChatMessageModal(props) {
|
|||
onDelete={confirmDelete}
|
||||
title={'Delete Message'}
|
||||
/>
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '300px', maxHeight: '100px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,12 +37,18 @@
|
|||
|
||||
.main-left {
|
||||
border-right: 1px solid #495a6a;
|
||||
width: 30%;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-middle {
|
||||
border-right: 1px solid #495a6a;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-right {
|
||||
width: 70%;
|
||||
width: 67%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,10 @@ import { SmallModal } from '../components/Modal';
|
|||
import StreamEvent from '../components/StreamEvent';
|
||||
import StreamActivity from '../components/StreamActivity';
|
||||
import StreamChat from '../components/StreamChat';
|
||||
import StreamChatBot from '../components/StreamChatBot';
|
||||
import StreamInfo from '../components/StreamInfo';
|
||||
import { NavSignIn } from './Navigation';
|
||||
import { StreamChatMessageItem, StreamChatMessageModal } from '../components/StreamChatMessage';
|
||||
import { StreamChatMessageModal } from '../components/StreamChatMessage';
|
||||
|
||||
function Dashboard() {
|
||||
const location = useLocation();
|
||||
|
@ -34,9 +35,12 @@ function Dashboard() {
|
|||
const [openChatBot, setOpenChatBot] = useState(false);
|
||||
const [chatBotMessages, setChatBotMessages] = useState({});
|
||||
const [chatAsChannel, setChatAsChannel] = useState(false);
|
||||
const [chatCommand, setChatCommand] = useState('');
|
||||
const [chatOnCommand, setChatOnCommand] = useState(false);
|
||||
const [chatID, setChatID] = useState('');
|
||||
const [chatInterval, setChatInterval] = useState('');
|
||||
const [chatMessage, setChatMessage] = useState('');
|
||||
const [chatText, setChatText] = useState('');
|
||||
const [chatTextFile, setChatTextFile] = useState('');
|
||||
const [openChat, setOpenChat] = useState(false);
|
||||
const [cid, setCID] = useState(location.state.cid);
|
||||
const [username, setUsername] = useState('');
|
||||
|
@ -66,6 +70,7 @@ function Dashboard() {
|
|||
setActive(true);
|
||||
|
||||
ChatBotMessages(cid).then((messages) => {
|
||||
console.log(messages);
|
||||
setChatBotMessages(messages);
|
||||
});
|
||||
|
||||
|
@ -154,17 +159,23 @@ function Dashboard() {
|
|||
|
||||
const newChat = () => {
|
||||
setChatAsChannel(false);
|
||||
setChatCommand('');
|
||||
setChatID('');
|
||||
setChatInterval('');
|
||||
setChatMessage('');
|
||||
setChatText('');
|
||||
setChatTextFile('');
|
||||
setChatOnCommand(false);
|
||||
setOpenChat(true);
|
||||
};
|
||||
|
||||
const editChat = (id, asChannel, interval, message) => {
|
||||
const editChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
|
||||
setChatAsChannel(asChannel);
|
||||
setChatInterval(interval);
|
||||
setChatMessage(message);
|
||||
setChatCommand(command);
|
||||
setChatID(id);
|
||||
setChatInterval(interval);
|
||||
setChatOnCommand(onCommand);
|
||||
setChatText(text);
|
||||
setChatTextFile(textFile);
|
||||
setOpenChat(true);
|
||||
};
|
||||
|
||||
|
@ -191,10 +202,11 @@ function Dashboard() {
|
|||
});
|
||||
};
|
||||
|
||||
const saveChat = (id, asChannel, interval, message) => {
|
||||
const saveChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
|
||||
console.log('save chat textfile:', textFile);
|
||||
setOpenChat(false);
|
||||
if (id === '') {
|
||||
AddChatMessage(cid, asChannel, interval, message)
|
||||
AddChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
|
||||
.then((messages) => {
|
||||
setChatBotMessages(messages);
|
||||
})
|
||||
|
@ -206,7 +218,7 @@ function Dashboard() {
|
|||
return;
|
||||
}
|
||||
|
||||
UpdateChatMessage(id, cid, asChannel, interval, message)
|
||||
UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
|
||||
.then((messages) => {
|
||||
console.log(messages);
|
||||
setChatBotMessages(messages);
|
||||
|
@ -231,12 +243,15 @@ function Dashboard() {
|
|||
<StreamChatMessageModal
|
||||
chatID={chatID}
|
||||
asChannel={chatAsChannel}
|
||||
chatCommand={chatCommand}
|
||||
onCommand={chatOnCommand}
|
||||
interval={chatInterval}
|
||||
message={chatMessage}
|
||||
onClose={() => setOpenChat(false)}
|
||||
onDelete={deleteChat}
|
||||
onSubmit={saveChat}
|
||||
show={openChat}
|
||||
text={chatText}
|
||||
textFile={chatTextFile}
|
||||
/>
|
||||
)}
|
||||
{openChatBot && (
|
||||
|
@ -270,16 +285,18 @@ function Dashboard() {
|
|||
<div className='main-left'>
|
||||
<StreamActivity title={'Stream Activity'} events={activityEvents()} />
|
||||
</div>
|
||||
{/* <div className='main-middle'>
|
||||
<StreamChat title={'Stream Chat'} />
|
||||
</div> */}
|
||||
<div className='main-right'>
|
||||
<StreamChat
|
||||
<StreamChatBot
|
||||
chats={chatBotMessages}
|
||||
onAdd={newChat}
|
||||
onEdit={editChat}
|
||||
onSettings={() => setOpenChatBot(true)}
|
||||
title={'Stream Chat'}
|
||||
title={'Chat Bot'}
|
||||
/>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<StreamInfo
|
||||
active={active}
|
||||
|
|
|
@ -52,7 +52,7 @@ function SignIn() {
|
|||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
style={{ minWidth: '300px', maxWidth: '300px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
|
|
10
go.mod
10
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.1.0
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.2.0
|
||||
github.com/wailsapp/wails/v2 v2.7.1
|
||||
)
|
||||
|
||||
|
@ -24,6 +24,7 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/r3labs/sse/v2 v2.10.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/robertkrimen/otto v0.3.0 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
|
@ -32,10 +33,11 @@ require (
|
|||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.10 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -41,6 +41,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=
|
||||
github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
|
@ -55,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.1.0 h1:HQxuRkkA0iN2XyfK3KfoeBFonWCHZt8cy4GVKAiIIeM=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.1.0/go.mod h1:YrfW5N6xVozOzubzfNNsy+v0MIL2GPi9Kx3mTZ/Q9zI=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.2.0 h1:sOXTZKBeB9PN3xfVSVYiILhMQdBQ2OiLMq+k70x5yb0=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.2.0/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=
|
||||
|
@ -68,13 +70,16 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
|
|||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.7.1 h1:HAzp2c5ODOzsLC6ZMDVtNOB72ozM7/SJecJPB2Ur+UU=
|
||||
github.com/wailsapp/wails/v2 v2.7.1/go.mod h1:oIJVwwso5fdOgprBYWXBBqtx6PaSvxg8/KTQHNGkadc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -85,13 +90,16 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||
|
|
|
@ -80,7 +80,7 @@ func (a *Api) start(ctx context.Context, url string, interval time.Duration) {
|
|||
}
|
||||
|
||||
func (a *Api) query(url string) {
|
||||
a.logInfo.Println("QueryAPI")
|
||||
// a.logInfo.Println("QueryAPI")
|
||||
client := rumblelivestreamlib.Client{StreamKey: url}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package chatbot
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -15,6 +20,8 @@ import (
|
|||
type ChatBot struct {
|
||||
ctx context.Context
|
||||
client *rumblelivestreamlib.Client
|
||||
commands map[string]chan rumblelivestreamlib.ChatView
|
||||
commandsMu sync.Mutex
|
||||
Cfg config.ChatBot
|
||||
logError *log.Logger
|
||||
messages map[string]*message
|
||||
|
@ -22,12 +29,15 @@ type ChatBot struct {
|
|||
}
|
||||
|
||||
type message struct {
|
||||
cancel context.CancelFunc
|
||||
cancelMu sync.Mutex
|
||||
asChannel bool
|
||||
id string
|
||||
interval time.Duration
|
||||
text string
|
||||
cancel context.CancelFunc
|
||||
cancelMu sync.Mutex
|
||||
asChannel bool
|
||||
command string
|
||||
id string
|
||||
interval time.Duration
|
||||
onCommand bool
|
||||
text string
|
||||
textFromFile []string
|
||||
}
|
||||
|
||||
func NewChatBot(ctx context.Context, streamUrl string, cfg config.ChatBot, logError *log.Logger) (*ChatBot, error) {
|
||||
|
@ -36,7 +46,7 @@ func NewChatBot(ctx context.Context, streamUrl string, cfg config.ChatBot, logEr
|
|||
return nil, fmt.Errorf("chatbot: error creating new client: %v", err)
|
||||
}
|
||||
|
||||
return &ChatBot{ctx: ctx, client: client, Cfg: cfg, logError: logError, messages: map[string]*message{}}, nil
|
||||
return &ChatBot{ctx: ctx, client: client, Cfg: cfg, commands: map[string]chan rumblelivestreamlib.ChatView{}, logError: logError, messages: map[string]*message{}}, nil
|
||||
}
|
||||
|
||||
func (cb *ChatBot) StartMessage(id string) error {
|
||||
|
@ -53,24 +63,82 @@ func (cb *ChatBot) StartMessage(id string) error {
|
|||
delete(cb.messages, id)
|
||||
}
|
||||
|
||||
textFromFile := []string{}
|
||||
if msg.TextFile != "" {
|
||||
file, err := os.Open(msg.TextFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("chatbot: error opening file with responses: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
textFromFile = append(textFromFile, line)
|
||||
}
|
||||
}
|
||||
|
||||
m = &message{
|
||||
asChannel: msg.AsChannel,
|
||||
id: msg.ID,
|
||||
interval: msg.Interval,
|
||||
text: msg.Text,
|
||||
asChannel: msg.AsChannel,
|
||||
command: msg.Command,
|
||||
id: msg.ID,
|
||||
interval: msg.Interval,
|
||||
onCommand: msg.OnCommand,
|
||||
text: msg.Text,
|
||||
textFromFile: textFromFile,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m.cancelMu.Lock()
|
||||
m.cancel = cancel
|
||||
m.cancelMu.Unlock()
|
||||
go cb.startMessage(ctx, m)
|
||||
if msg.OnCommand {
|
||||
go cb.startCommand(ctx, m)
|
||||
} else {
|
||||
go cb.startMessage(ctx, m)
|
||||
}
|
||||
|
||||
cb.messages[id] = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: lock commands map, update commands map with channel, unlock commands map
|
||||
func (cb *ChatBot) startCommand(ctx context.Context, m *message) {
|
||||
cb.commandsMu.Lock()
|
||||
ch := make(chan rumblelivestreamlib.ChatView)
|
||||
cb.commands[m.command] = ch
|
||||
cb.commandsMu.Unlock()
|
||||
|
||||
var prev time.Time
|
||||
for {
|
||||
// TODO: if error, emit error to user, stop loop?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ch:
|
||||
// TODO: parse !command
|
||||
now := time.Now()
|
||||
if now.Sub(prev) < m.interval*time.Second {
|
||||
break
|
||||
}
|
||||
|
||||
err := cb.chat(m)
|
||||
if err != nil {
|
||||
cb.logError.Println("error sending chat:", err)
|
||||
cb.StopMessage(m.id)
|
||||
runtime.EventsEmit(cb.ctx, "ChatBotCommandError-"+m.id, m.id)
|
||||
} else {
|
||||
prev = now
|
||||
runtime.EventsEmit(cb.ctx, "ChatBotCommandActive-"+m.id, m.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cb *ChatBot) startMessage(ctx context.Context, m *message) {
|
||||
for {
|
||||
// TODO: if error, emit error to user, stop loop?
|
||||
|
@ -99,7 +167,17 @@ func (cb *ChatBot) chat(m *message) error {
|
|||
return fmt.Errorf("client is nil")
|
||||
}
|
||||
|
||||
err := cb.client.Chat(m.asChannel, m.text)
|
||||
text := 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)
|
||||
}
|
||||
|
||||
text = m.textFromFile[n.Int64()]
|
||||
}
|
||||
|
||||
err := cb.client.Chat(m.asChannel, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending chat: %v", err)
|
||||
}
|
||||
|
@ -114,6 +192,16 @@ func (cb *ChatBot) StopAllMessages() error {
|
|||
for id, m := range cb.messages {
|
||||
m.stop()
|
||||
delete(cb.messages, id)
|
||||
|
||||
if m.command != "" && m.onCommand {
|
||||
cb.commandsMu.Lock()
|
||||
defer cb.commandsMu.Unlock()
|
||||
ch, exists := cb.commands[m.command]
|
||||
if exists {
|
||||
close(ch)
|
||||
delete(cb.commands, m.command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -122,10 +210,21 @@ func (cb *ChatBot) StopAllMessages() error {
|
|||
func (cb *ChatBot) StopMessage(id string) error {
|
||||
cb.messagesMu.Lock()
|
||||
defer cb.messagesMu.Unlock()
|
||||
|
||||
m, exists := cb.messages[id]
|
||||
if exists {
|
||||
m.stop()
|
||||
delete(cb.messages, id)
|
||||
|
||||
if m.command != "" && m.onCommand {
|
||||
cb.commandsMu.Lock()
|
||||
defer cb.commandsMu.Unlock()
|
||||
ch, exists := cb.commands[m.command]
|
||||
if exists {
|
||||
close(ch)
|
||||
delete(cb.commands, m.command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -164,3 +263,64 @@ func (cb *ChatBot) Logout() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *ChatBot) StartChatStream() error {
|
||||
if cb.client == nil {
|
||||
return fmt.Errorf("chatbot: client is nil")
|
||||
}
|
||||
|
||||
err := cb.client.ChatInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("chatbot: error getting chat info: %v", err)
|
||||
}
|
||||
|
||||
err = cb.client.StartChatStream(cb.handleChat, cb.handleError)
|
||||
if err != nil {
|
||||
return fmt.Errorf("chatbot: error starting chat stream: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *ChatBot) StopChatStream() error {
|
||||
if cb.client == nil {
|
||||
return fmt.Errorf("chatbot: client is nil")
|
||||
}
|
||||
|
||||
// TODO: should a panic be caught here?
|
||||
cb.client.StopChatStream()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *ChatBot) handleChat(cv rumblelivestreamlib.ChatView) {
|
||||
// runtime.EventsEmit(cb.ctx, "ChatMessageReceived", cv)
|
||||
|
||||
if cv.Type != "init" {
|
||||
cb.handleCommand(cv)
|
||||
}
|
||||
}
|
||||
|
||||
func (cb *ChatBot) handleCommand(cv rumblelivestreamlib.ChatView) {
|
||||
cb.commandsMu.Lock()
|
||||
defer cb.commandsMu.Unlock()
|
||||
|
||||
words := strings.Split(cv.Text, " ")
|
||||
first := words[0]
|
||||
cmd, exists := cb.commands[first]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cmd <- cv:
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (cb *ChatBot) handleError(err error) {
|
||||
cb.logError.Println("error handling chat message:", err)
|
||||
// runtime.EventsEmit(cb.ctx, "ChatError", err)
|
||||
}
|
||||
|
|
|
@ -75,8 +75,11 @@ 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"`
|
||||
}
|
||||
|
||||
type ChatBot struct {
|
||||
|
@ -122,7 +125,7 @@ func (a *App) DeleteChatMessage(id string, cid string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) NewChatMessage(cid string, asChannel bool, interval time.Duration, message string) (string, error) {
|
||||
func (a *App) NewChatMessage(cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (string, error) {
|
||||
if _, exists := a.Channels[cid]; !exists {
|
||||
return "", fmt.Errorf("config: channel does not exist")
|
||||
}
|
||||
|
@ -134,27 +137,38 @@ func (a *App) NewChatMessage(cid string, asChannel bool, interval time.Duration,
|
|||
}
|
||||
|
||||
if _, exists := a.Channels[cid].ChatBot.Messages[id]; !exists {
|
||||
a.Channels[cid].ChatBot.Messages[id] = ChatMessage{id, asChannel, interval, message}
|
||||
a.Channels[cid].ChatBot.Messages[id] = ChatMessage{
|
||||
ID: id,
|
||||
AsChannel: asChannel,
|
||||
Command: command,
|
||||
Interval: interval,
|
||||
OnCommand: onCommand,
|
||||
Text: text,
|
||||
TextFile: textFile,
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, interval time.Duration, text string) (string, error) {
|
||||
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, command string, interval time.Duration, onCommand bool, text string, textFile string) (string, error) {
|
||||
channel, exists := a.Channels[cid]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("config: channel does not exist")
|
||||
}
|
||||
|
||||
message, exists := channel.ChatBot.Messages[id]
|
||||
cm, exists := channel.ChatBot.Messages[id]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("config: message does not exist")
|
||||
}
|
||||
|
||||
message.AsChannel = asChannel
|
||||
message.Interval = interval
|
||||
message.Text = text
|
||||
channel.ChatBot.Messages[id] = message
|
||||
cm.AsChannel = asChannel
|
||||
cm.Command = command
|
||||
cm.Interval = interval
|
||||
cm.OnCommand = onCommand
|
||||
cm.Text = text
|
||||
cm.TextFile = textFile
|
||||
channel.ChatBot.Messages[id] = cm
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
|
21
licenses/github.com/twbs/icons/LICENSE
Normal file
21
licenses/github.com/twbs/icons/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2021 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Loading…
Reference in a new issue