Started v1

This commit is contained in:
tyler 2024-02-23 12:10:39 -05:00
parent 65cb86ad04
commit 7d3c6e34ec
1429 changed files with 241 additions and 55680 deletions

7
.gitignore vendored
View file

@ -1,8 +1 @@
build/bin/
node_modules
frontend/dist
frontend/wailsjs
.prettierignore .prettierignore
config.json

563
app.go
View file

@ -1,563 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"time"
"github.com/tylertravisty/rum-goggles/internal/api"
"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 {
username string
password string
url string
}
// App struct
type App struct {
ctx context.Context
cfg *config.App
cfgMu sync.Mutex
api *api.Api
apiMu sync.Mutex
cb *chatbot.ChatBot
cbMu sync.Mutex
logError *log.Logger
logInfo *log.Logger
}
// NewApp creates a new App application struct
func NewApp() *App {
app := &App{}
err := app.initLog()
if err != nil {
log.Fatal("error initializing log:", err)
}
app.api = api.NewApi(app.logError, app.logInfo)
return app
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.api.Startup(ctx)
err := a.loadConfig()
if err != nil {
a.logError.Fatal("error loading config: ", err)
}
}
func (a *App) initLog() error {
f, err := config.LogFile()
if err != nil {
return fmt.Errorf("error opening log file: %v", err)
}
a.logInfo = log.New(f, "[info]", log.LstdFlags|log.Lshortfile)
a.logError = log.New(f, "[error]", log.LstdFlags|log.Lshortfile)
return nil
}
func (a *App) loadConfig() error {
cfg, err := config.Load()
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("error loading config: %v", err)
}
return a.newConfig()
}
a.cfg = cfg
return nil
}
func (a *App) newConfig() error {
cfg := &config.App{Channels: map[string]config.Channel{}}
err := cfg.Save()
if err != nil {
return fmt.Errorf("error saving new config: %v", err)
}
a.cfg = cfg
return nil
}
func (a *App) Config() *config.App {
return a.cfg
}
func (a *App) AddChannel(url string) (*config.App, error) {
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
a.logError.Println("error executing api request:", err)
return nil, fmt.Errorf("Error querying API. Verify key and try again.")
}
name := resp.Username
if resp.ChannelName != "" {
name = resp.ChannelName
}
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
_, err = a.cfg.NewChannel(url, name)
if err != nil {
a.logError.Println("error creating new channel:", err)
return nil, fmt.Errorf("Error creating new channel. Try again.")
}
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return nil, fmt.Errorf("Error saving channel information. Try again.")
}
return a.cfg, nil
}
func (a *App) ChatBotMessages(cid string) (map[string]config.ChatMessage, error) {
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
channel, exists := a.cfg.Channels[cid]
if !exists {
a.logError.Println("channel does not exist:", cid)
return nil, fmt.Errorf("Cannot find channel. Try reloading.")
}
return channel.ChatBot.Messages, nil
}
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, cm)
if err != nil {
a.logError.Println("error creating new chat:", err)
return nil, fmt.Errorf("Error creating new chat message. Try again.")
}
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return nil, fmt.Errorf("Error saving chat message information. Try again.")
}
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
return a.cfg.Channels[cid].ChatBot.Messages, nil
}
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(cm.ID)
if err != nil {
a.logError.Println("error stopping chat bot message:", err)
return nil, fmt.Errorf("Error stopping message. Try Again.")
}
}
a.cbMu.Unlock()
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
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.")
}
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return nil, fmt.Errorf("Error saving chat message information. Try again.")
}
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
return a.cfg.Channels[cid].ChatBot.Messages, nil
}
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(cid, cm)
if err != nil {
a.logError.Println("error updating chat message:", err)
return nil, fmt.Errorf("Error updating chat message. Try again.")
}
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return nil, fmt.Errorf("Error saving chat message information. Try again.")
}
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
return a.cfg.Channels[cid].ChatBot.Messages, nil
}
type NewChatBotResponse struct {
LoggedIn bool `json:"logged_in"`
StreamUrl string `json:"stream_url"`
Username string `json:"username"`
}
func (a *App) GetChatBot(cid string) (NewChatBotResponse, error) {
if a.cb == nil {
return NewChatBotResponse{}, fmt.Errorf("Chat bot not initalized.")
}
loggedIn, err := a.cb.LoggedIn()
if err != nil {
a.logError.Println("error checking if chat bot is logged in:", err)
return NewChatBotResponse{}, fmt.Errorf("Error checking if chat bot is logged in. Try again.")
}
return NewChatBotResponse{loggedIn, a.cb.Cfg.Session.Client.StreamUrl, a.cb.Cfg.Session.Username}, nil
}
func (a *App) NewChatBot(cid string) (NewChatBotResponse, error) {
a.cbMu.Lock()
defer a.cbMu.Unlock()
if a.cb != nil {
err := a.resetChatBot()
if err != nil {
a.logError.Println("error resetting chat bot:", err)
return NewChatBotResponse{}, fmt.Errorf("Error creating chat bot. Try Again.")
}
}
channel, exists := a.cfg.Channels[cid]
if !exists {
a.logError.Println("channel does not exist:", cid)
return NewChatBotResponse{}, fmt.Errorf("Channel does not exist.")
}
if channel.ChatBot.Session.Client.StreamUrl == "" {
return NewChatBotResponse{}, nil
}
var err error
a.cb, err = chatbot.NewChatBot(a.ctx, channel.ChatBot, a.logError)
if err != nil {
a.logError.Println("error creating new chat bot:", err)
return NewChatBotResponse{}, fmt.Errorf("Error creating new chat bot. Try again.")
}
loggedIn, err := a.cb.LoggedIn()
if err != nil {
a.logError.Println("error checking if chat bot is logged in:", err)
return NewChatBotResponse{}, fmt.Errorf("Error checking if chat bot is logged in. Try again.")
}
if loggedIn {
err = a.cb.StartChatStream()
if err != nil {
a.logError.Println("error starting chat stream:", err)
return NewChatBotResponse{}, fmt.Errorf("Error connecting to chat. Try again.")
}
}
return NewChatBotResponse{loggedIn, channel.ChatBot.Session.Client.StreamUrl, channel.ChatBot.Session.Username}, nil
}
func (a *App) LoginChatBot(cid string, username string, password string, streamUrl string) error {
a.cbMu.Lock()
defer a.cbMu.Unlock()
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
if a.cb != nil {
err := a.resetChatBot()
if err != nil {
a.logError.Println("error resetting chat bot:", err)
return fmt.Errorf("Error creating chat bot. Try Again.")
}
}
channel, exists := a.cfg.Channels[cid]
if !exists {
a.logError.Println("channel does not exist:", cid)
return fmt.Errorf("Channel does not exist.")
}
channel.ChatBot.Session.Client.StreamUrl = streamUrl
var err error
a.cb, err = chatbot.NewChatBot(a.ctx, channel.ChatBot, a.logError)
if err != nil {
a.logError.Println("error creating new chat bot:", err)
return fmt.Errorf("Error creating new chat bot. Try again.")
}
cookies, 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.")
}
channel.ChatBot.Session = config.ChatBotSession{
Client: rumblelivestreamlib.NewClientOptions{
Cookies: cookies,
StreamUrl: streamUrl,
},
Username: username,
}
a.cfg.Channels[cid] = channel
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return fmt.Errorf("Error saving session information. Try again.")
}
a.cb.Cfg.Session = channel.ChatBot.Session
err = a.cb.StartChatStream()
if err != nil {
a.logError.Println("error starting chat stream:", err)
return fmt.Errorf("Error connecting to chat. Try again.")
}
return nil
}
func (a *App) StopAllChatBot(cid string) error {
if a.cb == nil {
return fmt.Errorf("Chat bot not initialized.")
}
err := a.cb.StopAllMessages()
if err != nil {
a.logError.Println("error stopping all chat bot messages:", err)
return fmt.Errorf("Error stopping messages.")
}
return nil
}
func (a *App) StartAllChatBot(cid string) error {
if a.cb == nil {
return fmt.Errorf("Chat bot not initialized.")
}
err := a.cb.StartAllMessages()
if err != nil {
a.logError.Println("error starting all chat bot messages:", err)
return fmt.Errorf("Error starting messages.")
}
return nil
}
func (a *App) UpdateChatBotUrl(cid string, streamUrl string) error {
a.cbMu.Lock()
defer a.cbMu.Unlock()
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
if a.cb == nil {
return fmt.Errorf("Chat bot not initialized.")
}
err := a.resetChatBot()
if err != nil {
a.logError.Println("error resetting chat bot:", err)
return fmt.Errorf("Error creating chat bot. Try Again.")
}
channel, exists := a.cfg.Channels[cid]
if !exists {
a.logError.Println("channel does not exist:", cid)
return fmt.Errorf("Channel does not exist.")
}
channel.ChatBot.Session.Client.StreamUrl = streamUrl
a.cb, err = chatbot.NewChatBot(a.ctx, channel.ChatBot, a.logError)
if err != nil {
a.logError.Println("error creating new chat bot:", err)
return fmt.Errorf("Error creating new chat bot. Try again.")
}
a.cfg.Channels[cid] = channel
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return fmt.Errorf("Error saving session information. Try again.")
}
a.cb.Cfg.Session.Client.StreamUrl = streamUrl
err = a.cb.StartChatStream()
if err != nil {
a.logError.Println("error starting chat stream:", err)
return fmt.Errorf("Error connecting to chat. Try again.")
}
return nil
}
func (a *App) ResetChatBot(cid string, logout bool) error {
a.cbMu.Lock()
defer a.cbMu.Unlock()
a.cfgMu.Lock()
defer a.cfgMu.Unlock()
if a.cb == nil {
return nil
}
err := a.cb.StopAllMessages()
if err != nil {
return fmt.Errorf("error stopping all chat bot messages: %v", err)
}
if logout {
err := a.cb.Logout()
if err != nil {
return fmt.Errorf("error logging out of chat bot: %v", err)
}
//TODO: reset session in config
channel, exists := a.cfg.Channels[cid]
if !exists {
a.logError.Println("channel does not exist:", cid)
return fmt.Errorf("Channel does not exist.")
}
channel.ChatBot.Session = config.ChatBotSession{}
a.cfg.Channels[cid] = channel
err = a.cfg.Save()
if err != nil {
a.logError.Println("error saving config:", err)
return fmt.Errorf("Error saving session information. Try again.")
}
}
err = a.resetChatBot()
if err != nil {
a.logError.Println("error resetting chat bot:", err)
return fmt.Errorf("Error resetting chat bot. Try Again.")
}
return nil
}
func (a *App) resetChatBot() error {
if a.cb == nil {
// return fmt.Errorf("chat bot is nil")
return nil
}
err := a.cb.StopAllMessages()
if err != nil {
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)
}
a.cb = nil
return nil
}
func (a *App) StartChatBotMessage(mid string) error {
a.cbMu.Lock()
defer a.cbMu.Unlock()
if a.cb == nil {
return fmt.Errorf("Chat bot not initialized.")
}
err := a.cb.StartMessage(mid)
if err != nil {
a.logError.Println("error starting chat bot message:", err)
return fmt.Errorf("Error starting message. Try Again.")
}
return nil
}
func (a *App) StopChatBotMessage(mid string) error {
a.cbMu.Lock()
defer a.cbMu.Unlock()
// If chat bot not initialized, then stop does nothing
if a.cb == nil {
return nil
}
err := a.cb.StopMessage(mid)
if err != nil {
a.logError.Println("error stopping chat bot message:", err)
return fmt.Errorf("Error stopping message. Try Again.")
}
return nil
}
func (a *App) StartApi(cid string) error {
channel, found := a.cfg.Channels[cid]
if !found {
a.logError.Println("could not find channel CID:", cid)
return fmt.Errorf("channel CID not found")
}
err := a.api.Start(channel.ApiUrl, channel.Interval*time.Second)
if err != nil {
a.logError.Println("error starting api:", err)
return fmt.Errorf("error starting API")
}
return nil
}
func (a *App) StopApi() {
a.api.Stop()
}
func (a *App) updateChatBotConfig(cfg config.ChatBot) {
a.cbMu.Lock()
defer a.cbMu.Unlock()
if a.cb != nil {
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)
}

View file

@ -1,21 +0,0 @@
import { useEffect, useState } from 'react';
import { MemoryRouter as Router, Route, Routes, Link } from 'react-router-dom';
import './App.css';
import { NavSignIn, NavDashboard } from './screens/Navigation';
import Dashboard from './screens/Dashboard';
import SignIn from './screens/SignIn';
function App() {
return (
<Router>
<Routes>
<Route path={NavSignIn} element={<SignIn />}></Route>
<Route path={NavDashboard} element={<Dashboard />}></Route>
</Routes>
</Router>
);
}
export default App;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,31 +0,0 @@
import eye from './eye.png';
import eye_slash from './eye-slash.png';
import gear from './gear.png';
import gear_fill from './gear-fill.png';
import heart from './heart-fill.png';
import house from './house.png';
import pause from './pause-fill.png';
import play from './play-fill.png';
import play_green from './play-fill-green.png';
import plus_circle from './plus-circle-fill.png';
import star from './star-fill.png';
import stop from './stop-fill.png';
import thumbs_down from './hand-thumbs-down.png';
import thumbs_up from './hand-thumbs-up.png';
import x_lg from './x-lg.png';
export const Eye = eye;
export const EyeSlash = eye_slash;
export const Gear = gear;
export const GearFill = gear_fill;
export const Heart = heart;
export const House = house;
export const Pause = pause;
export const Play = play;
export const PlayGreen = play_green;
export const PlusCircle = plus_circle;
export const Star = star;
export const Stop = stop;
export const ThumbsDown = thumbs_down;
export const ThumbsUp = thumbs_up;
export const XLg = x_lg;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -1,66 +0,0 @@
.channel-list {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
width: 100%;
}
.channel-list-title {
color: #85c742;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
padding: 5px;
}
.channels {
background-color: white;
border: 1px solid #D6E0EA;
border-radius: 5px;
height: 100%;
overflow: auto;
width: 100%;
}
.channel {
align-items: center;
/* border-top: 1px solid #D6E0EA; */
display: flex;
}
.channel-add {
background-color: #f3f5f8;
border: none;
padding: 10px;
}
.channel-add:hover {
cursor: pointer;
}
.channel-add-icon {
height: 36px;
width: 36px;
}
.channel-button {
background-color: white;
border: none;
border-radius: 5px;
color: #061726;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
overflow: hidden;
padding: 10px 10px;
text-align: left;
white-space: nowrap;
width: 100%;
}
.channel-button:hover {
background-color: #85c742;
cursor: pointer;
}

View file

@ -1,39 +0,0 @@
import { PlusCircle } from '../assets/icons';
import './ChannelList.css';
function ChannelList(props) {
const sortChannelsAlpha = () => {
let keys = Object.keys(props.channels);
// let sorted = [...props.channels].sort((a, b) =>
// a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
// );
let sorted = [...keys].sort((a, b) =>
props.channels[a].name.toLowerCase() > props.channels[b].name.toLowerCase() ? 1 : -1
);
return sorted;
};
return (
<div className='channel-list'>
<span className='channel-list-title'>Channels</span>
<div className='channels'>
{sortChannelsAlpha().map((channel, index) => (
<div className='channel' style={index === 0 ? { borderTop: 'none' } : {}}>
<button
className='channel-button'
onClick={() => props.openStreamDashboard(props.channels[channel].id)}
>
{props.channels[channel].name}
</button>
</div>
))}
</div>
{/* <button className='channel-add'>
<img className='channel-add-icon' src={PlusCircle} />
</button> */}
</div>
);
}
export default ChannelList;

View file

@ -1,64 +0,0 @@
.chat-bot-error {
border: 1px solid red;
box-sizing: border-box;
color: red;
font-family: monospace;
font-size: 16px;
padding: 5px;
text-align: center;
width: 100%;
}
.chat-bot-modal {
align-items: left;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
width: 100%;
}
.chat-bot-setting {
align-items: start;
display: flex;
flex-direction: column;
padding-top: 10px;
width: 100%;
}
.chat-bot-setting-label {
color: white;
font-family: sans-serif;
font-size: 20px;
padding-bottom: 5px;
width: 100%;
}
.chat-bot-setting-input {
border: none;
border-radius: 5px;
box-sizing: border-box;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 100%;
}
.chat-bot-description {
align-items: center;
display: flex;
flex-direction: row;
justify-content: start;
padding-top: 10px;
width: 100%;
}
.chat-bot-description-label {
color: white;
font-family: sans-serif;
font-size: 20px;
padding-bottom: 5px;
padding-right: 5px;
}

View file

@ -1,174 +0,0 @@
import { useEffect, useState } from 'react';
import { Modal, SmallModal } from './Modal';
import { LoginChatBot, UpdateChatBotUrl } from '../../wailsjs/go/main/App';
import './ChatBot.css';
export function ChatBotModal(props) {
const [error, setError] = useState('');
const [loggedIn, setLoggedIn] = useState(props.loggedIn);
const [password, setPassword] = useState('');
const [saving, setSaving] = useState(false);
const updatePassword = (event) => setPassword(event.target.value);
const [url, setUrl] = useState(props.streamUrl);
const updateUrl = (event) => setUrl(event.target.value);
const [username, setUsername] = useState(props.username);
const updateUsername = (event) => setUsername(event.target.value);
useEffect(() => {
if (saving) {
// let user = username;
// let p = password;
// let u = url;
// props.onSubmit(user, p, u);
// NewChatBot(props.cid, username, password, url)
if (loggedIn) {
UpdateChatBotUrl(props.cid, url)
.then(() => {
reset();
props.onUpdate(url);
})
.catch((error) => {
setSaving(false);
setError(error);
console.log('Error updating chat bot:', error);
});
} else {
LoginChatBot(props.cid, username, password, url)
.then(() => {
reset();
props.onLogin();
})
.catch((error) => {
setSaving(false);
setError(error);
console.log('Error creating new chat bot:', error);
});
}
}
}, [saving]);
const reset = () => {
setError('');
setLoggedIn(false);
setPassword('');
setSaving(false);
setUrl('');
setUsername('');
};
const close = () => {
reset();
props.onClose();
};
const logout = () => {
reset();
props.onLogout();
};
const submit = () => {
if (username === '') {
setError('Add username');
return;
}
if (password === '' && !loggedIn) {
setError('Add password');
return;
}
if (url === '') {
setError('Add stream URL');
return;
}
setSaving(true);
// let user = username;
// let p = password;
// let u = url;
// reset();
// props.onSubmit(user, p, u);
};
return (
<>
<Modal
onClose={close}
show={props.show}
style={{ minWidth: '300px', maxWidth: '400px' }}
cancelButton={loggedIn ? '' : 'Cancel'}
onCancel={close}
deleteButton={loggedIn ? 'Logout' : ''}
onDelete={logout}
submitButton={saving ? 'Saving' : 'Save'}
onSubmit={
saving
? () => {
console.log('Saving');
}
: submit
}
title={'Chat Bot'}
>
<div className='chat-bot-modal'>
{loggedIn ? (
<div className='chat-bot-description'>
<span className='chat-bot-description-label'>Logged in:</span>
<span
className='chat-bot-description-label'
style={{ fontWeight: 'bold' }}
>
{username}
</span>
</div>
) : (
<div className='chat-bot-setting'>
<span className='chat-bot-setting-label'>Username</span>
<input
className='chat-bot-setting-input'
onChange={updateUsername}
placeholder='Username'
type='text'
value={username}
/>
</div>
)}
{!loggedIn && (
<div className='chat-bot-setting'>
<span className='chat-bot-setting-label'>Password</span>
<input
className='chat-bot-setting-input'
onChange={updatePassword}
placeholder='Password'
type='password'
value={password}
/>
</div>
)}
<div className='chat-bot-setting'>
<span className='chat-bot-setting-label'>Stream URL</span>
<input
className='chat-bot-setting-input'
onChange={updateUrl}
placeholder='https://'
type='text'
value={url}
/>
</div>
</div>
</Modal>
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '300px', maxHeight: '100px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
</>
);
}
export function StreamChatMessageItem() {}

View file

@ -1,44 +0,0 @@
.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;
}

View file

@ -1,28 +0,0 @@
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;

View file

@ -1,24 +0,0 @@
.highlight {
align-items: start;
color: white;
display: flex;
background-color: #75a54b;
border-radius: 0.5rem;
flex-direction: column;
font-family: sans-serif;
font-weight: bold;
height: 40px;
justify-content: center;
min-width: 90px;
padding: 5px 10px;
width: 75px;
}
.highlight-value {
font-family: monospace;
font-size: 20px;
}
.highlight-description {
font-size: 12px;
}

View file

@ -1,72 +0,0 @@
import './Highlight.css';
function Highlight(props) {
const countString = () => {
switch (true) {
case props.value <= 0:
return '-';
case props.value < 1000:
return props.value;
case props.value < 1000000:
return (props.value / 1000).toFixed(3).slice(0, -2) + 'K';
case props.value < 1000000000:
return (props.value / 1000000).toFixed(6).slice(0, -5) + 'M';
default:
return 'Inf';
}
};
const stopwatchString = () => {
if (isNaN(Date.parse(props.value))) {
return '--:--';
}
let now = new Date();
let date = new Date(props.value);
let diff = now - date;
let msMinute = 1000 * 60;
let msHour = msMinute * 60;
let msDay = msHour * 24;
let days = Math.floor(diff / msDay);
let hours = Math.floor((diff - days * msDay) / msHour);
let minutes = Math.floor((diff - days * msDay - hours * msHour) / msMinute);
if (diff >= 100 * msDay) {
return days + 'd';
}
if (diff >= msDay) {
return days + 'd ' + hours + 'h';
}
if (hours < 10) {
hours = '0' + hours;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
return hours + ':' + minutes;
};
const valueString = () => {
switch (props.type) {
case 'count':
return countString();
case 'stopwatch':
return stopwatchString();
default:
return props.value;
}
};
return (
<div className='highlight'>
<span className='highlight-value'>{valueString()}</span>
<span className='highlight-description'>{props.description}</span>
</div>
);
}
export default Highlight;

View file

@ -1,176 +0,0 @@
.modal-background {
align-items: center;
background-color: transparent;
display: flex;
height: 100vh;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100vw;
}
.modal-body {
align-items: center;
display: flex;
height: 80%;
justify-content: center;
width: 100%;
}
.modal-button {
background-color: #85c742;
border: none;
border-radius: 5px;
color: #061726;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
/* width: 20%; */
width: 70px;
}
.modal-button-cancel {
background-color: transparent;
border: 1px solid #495a6a;
border-radius: 5px;
color: #495a6a;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
/* width: 20%; */
width: 70px;
}
.modal-button-delete {
background-color: transparent;
border: 1px solid red;
border-radius: 5px;
color: red;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
/* width: 20%; */
width: 70px;
}
.modal-close {
align-items: center;
background-color: transparent;
border: none;
display: flex;
flex-direction: center;
padding: 0px;
}
.modal-close:hover {
cursor: pointer;
}
.modal-close-icon {
height: 24px;
padding: 0px;
width: 24px;
}
.modal-container {
align-items: center;
background-color: rgba(6,23,38,1);
border: 1px solid #495a6a;
border-radius: 15px;
color: black;
display: flex;
flex-direction: column;
height: 50%;
justify-content: space-between;
opacity: 1;
padding: 10px 20px;
width: 50%;
}
.modal-footer {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 10%;
width: 100%;
}
.modal-header {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 10%;
width: 100%;
}
.modal-title {
color: white;
font-family: sans-serif;
font-size: 24px;
}
.small-modal-button-delete {
background-color: red;
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
/* width: 20%; */
width: 70px;
}
.small-modal-container {
align-items: center;
/* background-color: rgba(6,23,38,1); */
background-color: white;
border: 1px solid #495a6a;
/* border: 1px solid black; */
border-radius: 15px;
color: black;
display: flex;
flex-direction: column;
height: 50%;
justify-content: space-between;
opacity: 1;
padding: 10px 20px;
width: 50%;
}
.small-modal-header {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 10%;
width: 100%;
}
.small-modal-footer {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 20%;
width: 100%;
}
.small-modal-message {
font-family: sans-serif;
font-size: 18px;
}
.small-modal-title {
color: black;
font-family: sans-serif;
font-size: 24px;
}

View file

@ -1,86 +0,0 @@
import { XLg } from '../assets/icons';
import './Modal.css';
export function Modal(props) {
return (
<div
className='modal-background'
onClick={props.onClose}
style={{ zIndex: props.show ? 10 : -10 }}
>
<div
className='modal-container'
onClick={(event) => event.stopPropagation()}
style={props.style}
>
<div className='modal-header'>
<span className='modal-title'>{props.title}</span>
<button className='modal-close' onClick={props.onClose}>
<img className='modal-close-icon' src={XLg} />
</button>
</div>
<div className='modal-body'>{props.children}</div>
<div className='modal-footer'>
{props.cancelButton && (
<button className='modal-button-cancel' onClick={props.onCancel}>
{props.cancelButton}
</button>
)}
{props.deleteButton && (
<button className='modal-button-delete' onClick={props.onDelete}>
{props.deleteButton}
</button>
)}
{props.submitButton && (
<button className='modal-button' onClick={props.onSubmit}>
{props.submitButton}
</button>
)}
</div>
</div>
</div>
);
}
export function SmallModal(props) {
return (
<div
className='modal-background'
onClick={props.onClose}
style={{ zIndex: props.show ? 10 : -10 }}
>
<div
className='small-modal-container'
onClick={(event) => event.stopPropagation()}
style={props.style}
>
<div className='small-modal-header'>
<span className='small-modal-title'>{props.title}</span>
<button className='modal-close' onClick={props.onClose}>
<img className='modal-close-icon' src={XLg} />
</button>
</div>
<div className='modal-body'>
<span className='small-modal-message'>{props.message}</span>
</div>
<div className='small-modal-footer'>
{props.cancelButton && (
<button className='modal-button-cancel' onClick={props.onCancel}>
{props.cancelButton}
</button>
)}
{props.deleteButton && (
<button className='small-modal-button-delete' onClick={props.onDelete}>
{props.deleteButton}
</button>
)}
{props.submitButton && (
<button className='modal-button' onClick={props.onSubmit}>
{props.submitButton}
</button>
)}
</div>
</div>
</div>
);
}

View file

@ -1,24 +0,0 @@
.stream-activity {
width: 100%;
height: 100%;
}
.stream-activity-header {
text-align: left;
background-color: rgba(6,23,38,1);
border-bottom: 1px solid #495a6a;
height: 19px;
padding: 10px 20px;
}
.stream-activity-title {
color: white;
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
}
.stream-activity-list {
overflow-y: auto;
height: calc(100vh - 84px - 40px - 179px);
}

View file

@ -1,20 +0,0 @@
import StreamEvent from './StreamEvent';
import './StreamActivity.css';
function StreamActivity(props) {
return (
<div className='stream-activity'>
<div className='stream-activity-header'>
<span className='stream-activity-title'>{props.title}</span>
</div>
<div className='stream-activity-list'>
{props.events.map((event, index) => (
<StreamEvent event={event} />
))}
</div>
</div>
);
}
export default StreamActivity;

View file

@ -1,28 +0,0 @@
.stream-chat {
width: 100%;
height: 100%;
}
.stream-chat-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-chat-list {
overflow-y: auto;
height: calc(100vh - 84px - 40px - 179px);
}
.stream-chat-title {
color: white;
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
}

View file

@ -1,38 +0,0 @@
import { useState } from 'react';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import ChatMessage from './ChatMessage';
import './StreamChat.css';
function StreamChat(props) {
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',
},
]);
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>
<div className='stream-chat-list'>
{messages.map((message, index) => (
<ChatMessage message={message} />
))}
</div>
</div>
);
}
export default StreamChat;

View file

@ -1,104 +0,0 @@
.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;
}

View file

@ -1,237 +0,0 @@
import { useEffect, useState } from 'react';
import { FilepathBase, StartChatBotMessage, StopChatBotMessage } from '../../wailsjs/go/main/App';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import { GearFill, Pause, Play, PlayGreen, PlusCircle, Stop } 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.onPlayAll}
>
<img className='stream-chatbot-icon' src={PlayGreen} />
</button>
<button
className='stream-chatbot-button stream-chatbot-button-title'
onClick={props.onStopAll}
>
<img className='stream-chatbot-icon' src={Stop} />
</button>
</div>
<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
activateMessage={props.activateMessage}
chat={props.chats[chat]}
isMessageActive={props.isMessageActive}
onItemClick={props.onEdit}
/>
))}
</div>
</div>
);
}
export default StreamChatBot;
function StreamChatItem(props) {
// const [active, setActive] = useState(props.isMessageActive(props.chat.id));
const [active, setActive] = useState(false);
const [error, setError] = useState('');
const [filename, setFilename] = useState(props.chat.text_file);
useEffect(() => {
if (props.chat.text_file !== '') {
FilepathBase(props.chat.text_file).then((name) => {
setFilename(name);
});
}
// setActive(props.isMessageActive(props.chat.id));
}, [props]);
const changeActive = (bool) => {
// console.log('ChangeActive:', bool);
// props.chat.active = bool;
// props.activateMessage(props.chat.id, 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({
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 = () => {
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>
</>
);
}

View file

@ -1,358 +0,0 @@
/* .modal-chat {
align-items: center;
background-color: red;
color: black;
display: flex;
height: 50%;
justify-content: center;
opacity: 1;
width: 50%;
}
.modal-container {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100vw;
} */
.chat-toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding-top: 10px;
width: 100%;
}
.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: 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-rant-amount {
border: none;
/* border-radius: 34px; */
box-sizing: border-box;
font-family: monospace;
font-size: 16px;
outline: none;
/* 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;
flex-direction: row;
justify-content: space-between;
padding-top: 10px;
width: 100%;
}
.chat-interval-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;
}
.chat-interval-input-zero::placeholder {
text-align: center;
}
.chat-interval-input-value::placeholder {
color: black;
opacity: 1;
text-align: center;
}
.chat-interval-label {
color: white;
font-family: sans-serif;
padding-right: 10px;
}
.chat-options {
display: flex;
flex-direction: column;
width: 100%;
}
.stream-chat-message {
align-items: center;
color: white;
display: flex;
flex-direction: column;
font-family: sans-serif;
justify-content: start;
width: 100%;
}
.stream-chat-message-error {
border: 1px solid red;
box-sizing: border-box;
color: red;
font-family: monospace;
font-size: 16px;
padding: 5px;
text-align: center;
width: 100%;
}
.stream-chat-message-label {
padding: 5px 0px;
/* width: 50%; */
}
.stream-chat-message-modal {
align-items: left;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
width: 100%;
}
.stream-chat-message-textarea {
border: none;
border-radius: 5px;
box-sizing: border-box;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 100%;
}
.stream-chat-message-title {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.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-toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.chat-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #495a6a;
-webkit-transition: .4s;
transition: .4s;
}
.chat-toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .chat-toggle-slider {
background-color: #85c742;
}
input:checked + .chat-toggle-slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.chat-toggle-slider.round {
border-radius: 34px;
}
.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 {
color: white;
font-family: sans-serif;
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;
}

View file

@ -1,404 +0,0 @@
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 [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);
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);
setOnCommandFollower(props.onCommandFollower);
setOnCommandSubscriber(props.onCommandSubscriber);
setOnCommandRantAmount(props.onCommandRantAmount);
setError('');
setReadFromFile(props.textFile !== '');
setText(props.text);
setTextFile(props.textFile);
setTimer(props.interval);
}, []);
const reset = () => {
setAsChannel(false);
setChatCommand(false);
setError('');
setReadFromFile(false);
setText('');
setTextFile('');
setOnCommand(false);
setOnCommandFollower(false);
setOnCommandSubscriber(false);
setOnCommandRantAmount(0);
setTimer('');
};
const close = () => {
reset();
props.onClose();
};
const submit = () => {
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 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 = () => {
if (props.chatID === '') {
close();
return;
}
setOpenDelete(true);
};
const confirmDelete = () => {
reset();
setOpenDelete(false);
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;
}
if (e.keyCode === 8) {
setTimer(timer.substring(0, timer.length - 1));
}
};
const updateTimer = (e) => {
let nums = '0123456789';
let digit = e.target.value;
if (!nums.includes(digit)) {
return;
}
if (timer.length === 6) {
return;
}
if (timer.length === 0 && digit === '0') {
return;
}
setTimer(timer + digit);
};
const timerToInterval = () => {
let prefix = '0'.repeat(6 - timer.length);
let t = prefix + timer;
let hours = parseInt(t.substring(0, 2));
let minutes = parseInt(t.substring(2, 4));
let seconds = parseInt(t.substring(4, 6));
return hours * 3600 + minutes * 60 + seconds;
};
const printTimer = () => {
if (timer === '') {
return '00:00:00';
}
let prefix = '0'.repeat(6 - timer.length);
let t = prefix + timer;
return t.substring(0, 2) + ':' + t.substring(2, 4) + ':' + t.substring(4, 6);
};
const checkChannelToggle = (e) => {
setAsChannel(e.target.checked);
};
const checkCommandToggle = (e) => {
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) {
setTextFile('');
}
};
const chooseFile = () => {
OpenFileDialog()
.then((filepath) => {
if (filepath !== '') {
setTextFile(filepath);
}
})
.catch((error) => setError(error));
};
return (
<>
<Modal
onClose={close}
show={props.show}
style={{ minHeight: '500px', minWidth: '300px', maxWidth: '400px' }}
cancelButton={props.chatID === '' ? 'Cancel' : ''}
onCancel={deleteMessage}
deleteButton={props.chatID === '' ? '' : 'Delete'}
onDelete={deleteMessage}
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>} */}
<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>
{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'>
{onCommand ? 'Command timeout' : 'Chat interval'}
</span>
<input
className={
timer === ''
? 'chat-interval-input chat-interval-input-zero'
: 'chat-interval-input chat-interval-input-value'
}
onKeyDown={updateTimerBackspace}
onInput={updateTimer}
placeholder={printTimer()}
size='8'
type='text'
value={''}
/>
</div>
<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>
<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'>
<span className='chat-command-label'>{'\u00A0'}</span>
</div>
)}
</div>
</div>
</Modal>
<SmallModal
onClose={() => setOpenDelete(false)}
show={openDelete}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
cancelButton={'Cancel'}
onCancel={() => setOpenDelete(false)}
deleteButton={'Delete'}
message={
'Are you sure you want to delete this message? You cannot undo this action.'
}
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('')}
/>
</>
);
}
export function StreamChatMessageItem() {}

View file

@ -1,42 +0,0 @@
.stream-event {
border-bottom: 1px solid #82b1ff;
color: white;
display: flex;
flex-direction: row;
font-family: sans-serif;
justify-content: space-between;
padding: 10px 20px;
}
.stream-event-left {
align-items: center;
display: flex;
flex-direction: row;
}
.stream-event-icon {
width: 20px;
height: 20px;
padding-right: 10px;
}
.stream-event-left-text {
display: flex;
flex-direction: column;
}
.stream-event-username {
font-size: 14px;
font-weight: bold;
}
.stream-event-description {
color: #88a0b8;
font-size: 14px;
}
.stream-event-date {
align-items: center;
display: flex;
font-family: monospace;
}

View file

@ -1,111 +0,0 @@
import { Heart, Star } from '../assets/icons';
import './StreamEvent.css';
function StreamEvent(props) {
const dateDate = (date) => {
const options = { month: 'short' };
let month = new Intl.DateTimeFormat('en-US', options).format(date);
let day = date.getDate();
return month + ' ' + day;
};
const dateDay = (date) => {
let now = new Date();
let today = now.getDay();
switch (date.getDay()) {
case 0:
return 'Sunday';
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
}
};
const dateTime = (date) => {
let now = new Date();
let today = now.getDay();
let day = date.getDay();
if (today !== day) {
return dateDay(date);
}
let hours24 = date.getHours();
let hours = hours24 % 12 || 12;
let minutes = date.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let mer = 'pm';
if (hours24 < 12) {
mer = 'am';
}
return hours + ':' + minutes + ' ' + mer;
};
const dateString = (d) => {
if (isNaN(Date.parse(d))) {
return 'Who knows?';
}
let now = new Date();
let date = new Date(d);
// Fix Rumble's timezone problem
date.setHours(date.getHours() - 4);
let diff = now - date;
switch (true) {
case diff < 0:
return 'In the future!?';
case diff < 60000:
return 'Now';
case diff < 3600000:
let minutes = Math.floor(diff / 1000 / 60);
let postfix = ' minutes ago';
if (minutes == 1) {
postfix = ' minute ago';
}
return minutes + postfix;
case diff < 86400000:
return dateTime(date);
case diff < 604800000:
return dateDay(date);
default:
return dateDate(date);
}
};
return (
<div className='stream-event'>
<div className='stream-event-left'>
{props.event.followed_on && <img className='stream-event-icon' src={Heart}></img>}
{props.event.subscribed_on && <img className='stream-event-icon' src={Star}></img>}
<div className='stream-event-left-text'>
<span className='stream-event-username'>{props.event.username}</span>
<span className='stream-event-description'>
{props.event.followed_on && 'Followed you'}
{props.event.subscribed_on && 'Subscribed'}
</span>
</div>
</div>
<span className='stream-event-date'>
{props.event.followed_on && dateString(props.event.followed_on)}
{props.event.subscribed_on && dateString(props.event.subscribed_on)}
</span>
</div>
);
}
export default StreamEvent;

View file

@ -1,120 +0,0 @@
.stream-info {
display: flex;
flex-direction: column;
/* padding: 20px 0px; */
width: 100%;
}
.stream-info-title {
color: white;
font-family: sans-serif;
font-size: 20px;
font-weight: bold;
}
.stream-info-categories {
padding: 5px 0px;
margin-right: 50px;
}
.stream-info-category {
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 30px;
color: white;
font-family: sans-serif;
margin-right: 5px;
padding: 1px 7px;
}
.stream-info-channel {
color: white;
font-family: sans-serif;
font-size: 16px;
font-weight: bold;
padding: 5px 20px;
}
.stream-info-controls {
align-items: center;
border: 1px solid white;
border-radius: 5px;
display: flex;
justify-content: center;
margin: 5px 0px 20px 0px;
}
.stream-info-control {
height: 32px;
padding: 5px;
width: 32px;
}
.stream-info-control-button {
background-color: #000312;
border-radius: 5px;
cursor: pointer;
border: none;
padding: 0px;
}
.stream-info-control-button:hover {
background-color: rgba(6,23,38,1);
}
.stream-info-footer {
align-items: center;
display: flex;
justify-content: center;
}
.stream-info-likes {
align-items: center;
display: flex;
flex-direction: row;
padding: 5px 0px;
}
.stream-info-likes-count {
color: white;
font-family: monospace;
font-size: 16px;
padding: 0px 5px;
}
.stream-info-likes-left {
align-items: center;
display: flex;
flex-direction: row;
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 10rem 0rem 0rem 10rem;
margin-right: 1px;
}
.stream-info-likes-right {
align-items: center;
display: flex;
flex-direction: row;
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 0rem 10rem 10rem 0rem;
}
.stream-info-likes-icon {
align-items: center;
display: flex;
flex-direction: row;
height: 16px;
padding: 5px;
width: 16px;
}
.stream-info-live {
padding: 10px 20px 5px 20px;
}
.stream-info-subtitle {
align-items: center;
display: flex;
flex-direction: row;
}

View file

@ -1,77 +0,0 @@
import { Gear, House, Pause, Play, ThumbsDown, ThumbsUp } from '../assets/icons';
import './StreamInfo.css';
function StreamInfo(props) {
const likesString = (likes) => {
switch (true) {
case likes <= 0:
return '0';
case likes < 1000:
return likes;
case likes < 1000000:
return (likes / 1000).toFixed(3).slice(0, -2) + 'K';
case likes < 1000000000:
return (likes / 1000000).toFixed(6).slice(0, -5) + 'M';
default:
return 'Inf';
}
};
return (
<div className='stream-info'>
<div className='stream-info-live'>
<div className='stream-info-title'>
<span>{props.live ? props.title : '-'}</span>
</div>
<div className='stream-info-subtitle'>
<div className='stream-info-categories'>
<span className='stream-info-category'>
{props.live ? props.categories.primary.title : 'primary'}
</span>
<span className='stream-info-category'>
{props.live ? props.categories.secondary.title : 'secondary'}
</span>
</div>
<div className='stream-info-likes'>
<div className='stream-info-likes-left'>
<img className='stream-info-likes-icon' src={ThumbsUp} />
<span className='stream-info-likes-count'>
{props.live ? likesString(props.likes) : '-'}
</span>
</div>
<div className='stream-info-likes-right'>
<img className='stream-info-likes-icon' src={ThumbsDown} />
<span className='stream-info-likes-count'>
{props.live ? likesString(props.dislikes) : '-'}
</span>
</div>
</div>
</div>
</div>
<div className='stream-info-channel'>
<span>Channel: {props.channel}</span>
</div>
<div className='stream-info-footer'>
<div></div>
<div className='stream-info-controls'>
<button className='stream-info-control-button' onClick={props.home}>
<img className='stream-info-control' src={House} />
</button>
<button className='stream-info-control-button'>
<img
className='stream-info-control'
onClick={props.active ? props.pause : props.play}
src={props.active ? Pause : Play}
/>
</button>
<button className='stream-info-control-button' onClick={props.settings}>
<img className='stream-info-control' src={Gear} />
</button>
</div>
<div></div>
</div>
</div>
);
}
export default StreamInfo;

View file

@ -1,72 +0,0 @@
#Dashboard {
align-items: center;
background-color: #000312;
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
}
.header {
align-items: center;
display: flex;
flex-direction: row;
height: 62px;
justify-content: center;
padding: 10px 0px;
width: 100%;
}
.header-left {
width: 20%;
}
.header-right {
width: 20%;
}
.main {
border-bottom: 1px solid #495a6a;
border-top: 1px solid #495a6a;
display: flex;
flex-direction: row;
height: calc(100vh - 83px - 179px);
justify-content: space-between;
width: 100%;
}
.main-left {
border-right: 1px solid #495a6a;
width: 33%;
height: 100%;
}
.main-middle {
border-right: 1px solid #495a6a;
width: 33%;
height: 100%;
}
.main-right {
width: 67%;
height: 100%;
}
.modal {
background-color: white;
color: red;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
}
.highlights {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-evenly;
height: 50px;
width: 60%;
}

View file

@ -1,449 +0,0 @@
import { useEffect, useState } from 'react';
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
import {
AddChatMessage,
ChatBotMessages,
DeleteChatMessage,
GetChatBot,
NewChatBot,
ResetChatBot,
StartAllChatBot,
StartApi,
StopAllChatBot,
StopApi,
StopChatBotMessage,
UpdateChatBotUrl,
UpdateChatMessage,
} from '../../wailsjs/go/main/App';
import './Dashboard.css';
import { EventsEmit, EventsOn } from '../../wailsjs/runtime/runtime';
import { Heart, Star } from '../assets/icons';
import { ChatBotModal } from '../components/ChatBot';
import Highlight from '../components/Highlight';
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 { StreamChatMessageModal } from '../components/StreamChatMessage';
function Dashboard() {
const location = useLocation();
const navigate = useNavigate();
const [error, setError] = useState('');
const [refresh, setRefresh] = useState(false);
const [active, setActive] = useState(false);
const [openChatBot, setOpenChatBot] = useState(false);
const [chatBotMessages, setChatBotMessages] = useState({});
const [chatBotMessagesActive, setChatBotMessagesActive] = useState({});
const [chatBotSessionLoggedIn, setChatBotSessionLoggedIn] = useState(false);
const [chatBotSessionStreamUrl, setChatBotSessionStreamUrl] = useState('');
const [chatBotSessionUsername, setChatBotSessionUsername] = useState('');
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('');
const [chatTextFile, setChatTextFile] = useState('');
const [openChat, setOpenChat] = useState(false);
const [cid, setCID] = useState(location.state.cid);
const [username, setUsername] = useState('');
const [channelName, setChannelName] = useState('');
const [followers, setFollowers] = useState({});
const [totalFollowers, setTotalFollowers] = useState(0);
const [channelFollowers, setChannelFollowers] = useState(0);
const [recentFollowers, setRecentFollowers] = useState([]);
const [subscribers, setSubscribers] = useState({});
const [subscriberCount, setSubscriberCount] = useState(0);
const [recentSubscribers, setRecentSubscribers] = useState([]);
const [streamCategories, setStreamCategories] = useState({
primary: { title: '' },
secondary: { title: '' },
});
const [streamLikes, setStreamLikes] = useState(0);
const [streamLive, setStreamLive] = useState(false);
const [streamDislikes, setStreamDislikes] = useState(0);
const [streamTitle, setStreamTitle] = useState('');
const [watchingNow, setWatchingNow] = useState(0);
const [createdOn, setCreatedOn] = useState('');
useEffect(() => {
console.log('use effect start');
// TODO: catch error
StartApi(cid);
setActive(true);
ChatBotMessages(cid).then((messages) => {
console.log(messages);
setChatBotMessages(messages);
});
NewChatBot(cid).then((response) => {
setChatBotSessionLoggedIn(response.logged_in);
setChatBotSessionStreamUrl(response.stream_url);
setChatBotSessionUsername(response.username);
});
EventsOn('QueryResponse', (response) => {
// console.log('query response received');
setRefresh(!refresh);
setActive(true);
setUsername(response.username);
setChannelName(response.channel_name);
setFollowers(response.followers);
setChannelFollowers(response.followers.num_followers);
setTotalFollowers(response.followers.num_followers_total);
setRecentFollowers(response.followers.recent_followers);
setSubscribers(response.subscribers);
setSubscriberCount(response.subscribers.num_subscribers);
setRecentSubscribers(response.subscribers.recent_subscribers);
if (response.livestreams.length > 0) {
setStreamLive(true);
setStreamCategories(response.livestreams[0].categories);
setStreamLikes(response.livestreams[0].likes);
setStreamDislikes(response.livestreams[0].dislikes);
setStreamTitle(response.livestreams[0].title);
setCreatedOn(response.livestreams[0].created_on);
setWatchingNow(response.livestreams[0].watching_now);
} else {
setStreamLive(false);
}
});
EventsOn('QueryResponseError', (error) => {
setError(error);
// console.log('Query response error:', error);
setActive(false);
});
EventsOn('ChatBotChatStreamError', (error) => {
setError(error);
});
}, []);
const home = () => {
StopApi()
.then(() => setActive(false))
.then(() => {
ResetChatBot(cid, false);
})
.then(() => {
navigate(NavSignIn);
})
.catch((error) => {
setError(error);
console.log('Stop error:', error);
});
};
const startQuery = () => {
console.log('start');
StartApi(cid)
.then(() => {
setActive(true);
})
.catch((error) => {
setError(error);
console.log('Start error:', error);
});
};
const stopQuery = () => {
console.log('stop');
StopApi().then(() => {
setActive(false);
});
};
const activityDate = (activity) => {
if (activity.followed_on) {
return activity.followed_on;
}
if (activity.subscribed_on) {
return activity.subscribed_on;
}
};
const activityEvents = () => {
let sorted = [...recentFollowers, ...recentSubscribers].sort((a, b) =>
activityDate(a) < activityDate(b) ? 1 : -1
);
return sorted;
};
const newChat = () => {
setChatAsChannel(false);
setChatCommand('');
setChatID('');
setChatInterval('');
setChatText('');
setChatTextFile('');
setChatOnCommand(false);
setChatOnCommandFollower(false);
setChatOnCommandRantAmount(0);
setChatOnCommandSubscriber(false);
setOpenChat(true);
};
// 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);
};
const deleteChat = (id) => {
setOpenChat(false);
if (id === '') {
return;
}
let message = { id: id };
StopChatBotMessage(id)
.then(() => {
// DeleteChatMessage(id, cid)
DeleteChatMessage(cid, message)
.then((messages) => {
setChatBotMessages(messages);
})
.catch((error) => {
setError(error);
// console.log('Error deleting message:', error);
});
})
.catch((error) => {
setError(error);
// console.log('Error stopping message:', error);
});
};
// const saveChat = (id, asChannel, command, interval, onCommand, text, textFile) => {
const saveChat = (message) => {
setOpenChat(false);
if (message.id === '') {
// AddChatMessage(cid, asChannel, command, interval, onCommand, text, textFile)
AddChatMessage(cid, message)
.then((messages) => {
setChatBotMessages(messages);
})
.catch((error) => {
setError(error);
console.log('Error saving chat:', error);
});
return;
}
// UpdateChatMessage(id, cid, asChannel, command, interval, onCommand, text, textFile)
UpdateChatMessage(cid, message)
.then((messages) => {
console.log(messages);
setChatBotMessages(messages);
})
.catch((error) => {
setError(error);
console.log('Error saving chat:', error);
});
};
// TODO: this never gets called - delete
const saveChatBot = (username, password, url) => {
NewChatBot(cid, username, password, url)
.then(() => {
setOpenChatBot(false);
})
.catch((error) => console.log('Error creating new chat bot:', error));
};
const updateChatBot = (url) => {
setChatBotSessionStreamUrl(url);
setOpenChatBot(false);
};
const loginChatBot = () => {
GetChatBot(cid)
.then((response) => {
setChatBotSessionLoggedIn(response.logged_in);
setChatBotSessionStreamUrl(response.stream_url);
setChatBotSessionUsername(response.username);
})
.catch((error) => {
setError(error);
console.log('Error getting chat bot:', error);
})
.finally(() => {
setOpenChatBot(false);
});
};
const logoutChatBot = () => {
ResetChatBot(cid, true)
.then(() => {
NewChatBot(cid).then((response) => {
console.log('NewChatBot response:', response);
setChatBotSessionLoggedIn(response.logged_in);
setChatBotSessionStreamUrl(response.stream_url);
setChatBotSessionUsername(response.username);
});
})
.catch((error) => {
setError(error);
console.log('Error resetting chat bot:', error);
})
.finally(() => {
setOpenChatBot(false);
});
};
const chatBotStartAll = () => {
StartAllChatBot(cid).catch((error) => {
setError(error);
console.log('Error starting all chat bot messages:', error);
});
};
const chatBotStopAll = () => {
StopAllChatBot(cid)
.then(() => {
setChatBotMessagesActive({});
})
.catch((error) => {
setError(error);
console.log('Error stopping all chat bot messages:', error);
});
};
const activateMessage = (id, active) => {
// console.log('Dashboard activateMessage:', id, active);
chatBotMessagesActive[id] = active;
};
const isMessageActive = (id) => {
// console.log('is Message active start', id, chatBotMessagesActive[id]);
if (chatBotMessagesActive[id] === undefined) {
chatBotMessagesActive[id] = false;
}
// console.log('is Message active after', id, chatBotMessagesActive[id]);
return chatBotMessagesActive[id];
};
return (
<>
{openChat && (
<StreamChatMessageModal
chatID={chatID}
asChannel={chatAsChannel}
chatCommand={chatCommand}
onCommand={chatOnCommand}
onCommandFollower={chatOnCommandFollower}
onCommandRantAmount={chatOnCommandRantAmount}
onCommandSubscriber={chatOnCommandSubscriber}
interval={chatInterval}
onClose={() => setOpenChat(false)}
onDelete={deleteChat}
onSubmit={saveChat}
show={openChat}
text={chatText}
textFile={chatTextFile}
/>
)}
{openChatBot && (
<ChatBotModal
cid={cid}
loggedIn={chatBotSessionLoggedIn}
onClose={() => setOpenChatBot(false)}
onLogin={loginChatBot}
onLogout={logoutChatBot}
onSubmit={saveChatBot}
onUpdate={updateChatBot}
show={openChatBot}
streamUrl={chatBotSessionStreamUrl}
username={chatBotSessionUsername}
/>
)}
<div id='Dashboard'>
<div className='header'>
<div className='header-left'></div>
<div className='highlights'>
{/* <Highlight description={'Session'} type={'stopwatch'} value={createdOn} /> */}
<Highlight description={'Viewers'} type={'count'} value={watchingNow} />
<Highlight
description={'Followers'}
type={'count'}
value={channelFollowers}
/>
<Highlight
description={'Subscribers'}
type={'count'}
value={subscriberCount}
/>
</div>
<div className='header-right'></div>
</div>
<div className='main'>
<div className='main-left'>
<StreamActivity title={'Stream Activity'} events={activityEvents()} />
</div>
{/* <div className='main-middle'>
<StreamChat title={'Stream Chat'} />
</div> */}
<div className='main-right'>
<StreamChatBot
activateMessage={activateMessage}
chats={chatBotMessages}
onAdd={newChat}
onEdit={editChat}
onPlayAll={chatBotStartAll}
onSettings={() => setOpenChatBot(true)}
onStopAll={chatBotStopAll}
title={'Chat Bot'}
isMessageActive={isMessageActive}
/>
</div>
</div>
<StreamInfo
active={active}
channel={channelName !== '' ? channelName : username}
title={streamTitle}
categories={streamCategories}
likes={streamLikes}
live={streamLive}
dislikes={streamDislikes}
home={home}
play={startQuery}
pause={stopQuery}
// settings={openModal}
/>
</div>
{error !== '' && (
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
)}
</>
);
}
export default Dashboard;

View file

@ -1,2 +0,0 @@
export const NavSignIn = '/';
export const NavDashboard = '/dashboard';

View file

@ -1,144 +0,0 @@
#SignIn {
align-items: center;
background-color: #f3f5f8;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100vh;
}
.add-channel-description {
font-family: sans-serif;
font-size: 12px;
padding-bottom: 5px;
}
.add-channel-error {
color: red;
font-family: sans-serif;
font-size: 12px;
padding-top: 5px;
}
.signin-input-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px 0px;
width: 50%;
}
.signin-input-button {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.signin-input {
border-bottom: 2px solid #D6E0EA;
border-left: 2px solid #D6E0EA;
border-right: none;
border-top: 2px solid #D6E0EA;
border-radius: 10rem 0rem 0rem 10rem;
background-color: white;
color: #061726;
outline: none;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 0;
padding-top: 0.5rem;
width: 70%;
}
.signin-button {
background-color: #85c742;
border: none;
border-radius: 0rem 10rem 10rem 0rem;
color: #061726;
cursor: pointer;
font-weight: bold;
text-decoration: none;
width: 20%;
}
.signin-button:hover {
background-color: #77b23b;
}
.signin-center {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
height: 50%;
width: 50%;
}
.signin-show {
display: flex;
align-items: center;
justify-content: center;
background-color: white;
border-bottom: 2px solid #D6E0EA;
border-left: none;
border-right: 2px solid #D6E0EA;
border-top: 2px solid #D6E0EA;
color: #061726;
cursor: pointer;
font-weight: bold;
text-decoration: none;
width: 10%;
}
.signin-show-icon {
height: 16px;
width: 16px;
}
.signin-label {
color: #061726;
display: flex;
font-family: sans-serif;
font-weight: bold;
justify-content: center;
padding: 5px;
text-transform: uppercase;
width: 100%;
}
.signin-header {
align-items: center;
color: #061726;
display: flex;
flex-direction: column;
font-family: sans-serif;
font-weight: bold;
height: 10%;
justify-content: center;
margin: 20px;
text-align: center;
}
.signin-footer {
align-items: center;
color: #061726;
display: flex;
flex-direction: column;
font-family: sans-serif;
font-weight: bold;
height: 10%;
justify-content: center;
margin: 20px;
text-align: center;
}
.signin-title-text {
font-size: 20px;
margin: 0;
}
.signin-title-subtext {
font-size: 12px;
margin: 0;
}

View file

@ -1,107 +0,0 @@
import { useEffect, useState } from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
import { NavDashboard } from './Navigation';
import { AddChannel, Config } from '../../wailsjs/go/main/App';
import { Eye, EyeSlash } from '../assets/icons';
import './SignIn.css';
import ChannelList from '../components/ChannelList';
import { SmallModal } from '../components/Modal';
function SignIn() {
const [error, setError] = useState('');
const navigate = useNavigate();
const [config, setConfig] = useState({ channels: {} });
const [addChannelError, setAddChannelError] = useState('');
const [streamKey, setStreamKey] = useState('');
const updateStreamKey = (event) => setStreamKey(event.target.value);
const [showStreamKey, setShowStreamKey] = useState(false);
const updateShowStreamKey = () => setShowStreamKey(!showStreamKey);
useEffect(() => {
Config()
.then((response) => {
setConfig(response);
})
.catch((error) => {
// TODO: display error to user
setError('Error loading config: ' + error);
console.log('error getting config', error);
});
}, []);
const saveStreamKey = () => {
AddChannel(streamKey)
.then((response) => {
console.log(response);
setConfig(response);
setStreamKey('');
})
.catch((error) => {
console.log('error adding channel', error);
setAddChannelError(error);
});
};
const openStreamDashboard = (cid) => {
navigate(NavDashboard, { state: { cid: cid } });
};
return (
<>
{error !== '' && (
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '300px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
)}
<div id='SignIn'>
<div className='signin-header'>
<span className='signin-title-text'>Rum Goggles</span>
<span className='signin-title-subtext'>Rumble Stream Dashboard</span>
</div>
<div className='signin-center'>
<ChannelList
channels={config.channels}
openStreamDashboard={openStreamDashboard}
/>
</div>
<div className='signin-input-box'>
<label className='signin-label'>Add Channel</label>
<span className='add-channel-description'>
Copy your API key from your Rumble account
</span>
<div className='signin-input-button'>
<input
id='StreamKey'
className='signin-input'
onChange={updateStreamKey}
placeholder='Stream Key'
type={showStreamKey ? 'text' : 'password'}
value={streamKey}
/>
<button className='signin-show' onClick={updateShowStreamKey}>
<img
className='signin-show-icon'
src={showStreamKey ? EyeSlash : Eye}
></img>
</button>
<button className='signin-button' onClick={saveStreamKey}>
Save
</button>
</div>
<span className='add-channel-error'>
{addChannelError ? addChannelError : '\u00A0'}
</span>
</div>
<div className='signin-footer'></div>
</div>
</>
);
}
export default SignIn;

View file

@ -1,10 +0,0 @@
html {
}
body {
margin: 0;
}
#app {
/* height: 100vh; */
}

View file

@ -1,107 +0,0 @@
package api
import (
"context"
"fmt"
"log"
"sync"
"time"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type Api struct {
ctx context.Context
cancel context.CancelFunc
cancelMu sync.Mutex
logError *log.Logger
logInfo *log.Logger
querying bool
queryingMu sync.Mutex
}
func NewApi(logError *log.Logger, logInfo *log.Logger) *Api {
return &Api{logError: logError, logInfo: logInfo}
}
func (a *Api) Startup(ctx context.Context) {
a.ctx = ctx
}
func (a *Api) Start(url string, interval time.Duration) error {
a.logInfo.Println("Api.Start")
if url == "" {
return fmt.Errorf("empty stream key")
}
a.queryingMu.Lock()
start := !a.querying
a.querying = true
a.queryingMu.Unlock()
if start {
a.logInfo.Println("Start querying")
ctx, cancel := context.WithCancel(context.Background())
a.cancelMu.Lock()
a.cancel = cancel
a.cancelMu.Unlock()
go a.start(ctx, url, interval)
} else {
a.logInfo.Println("Querying already started")
}
return nil
}
func (a *Api) Stop() {
a.logInfo.Println("Stop querying")
a.cancelMu.Lock()
if a.cancel != nil {
a.cancel()
}
a.cancelMu.Unlock()
}
func (a *Api) start(ctx context.Context, url string, interval time.Duration) {
for {
a.query(url)
timer := time.NewTimer(interval)
select {
case <-ctx.Done():
a.queryingMu.Lock()
a.querying = false
a.queryingMu.Unlock()
timer.Stop()
return
case <-timer.C:
}
}
}
func (a *Api) query(url string) {
// a.logInfo.Println("QueryAPI")
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
a.logError.Println("api: error executing client request:", err)
a.Stop()
runtime.EventsEmit(a.ctx, "QueryResponseError", "Failed to query API")
return
}
// resp := &rumblelivestreamlib.LivestreamResponse{}
// resp.Followers.RecentFollowers = append(resp.Followers.RecentFollowers, rumblelivestreamlib.Follower{"tyler-follow", "2023-12-12T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-12-14T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-12-13T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-11-13T21:53:34-04:00"})
// resp.Livestreams = []rumblelivestreamlib.Livestream{
// {
// CreatedOn: "2023-12-16T16:13:30+00:00",
// WatchingNow: 4},
// }
runtime.EventsEmit(a.ctx, "QueryResponse", &resp)
}
// TODO: if start errors, send event

View file

@ -1,490 +0,0 @@
package chatbot
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"fmt"
"html/template"
"log"
"math/big"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/tylertravisty/rum-goggles/internal/config"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type ChatBot struct {
ctx context.Context
cancelChatStream context.CancelFunc
cancelChatStreamMu sync.Mutex
client *rumblelivestreamlib.Client
commands map[string]chan rumblelivestreamlib.ChatView
commandsMu sync.Mutex
Cfg config.ChatBot
logError *log.Logger
messages map[string]*message
messagesMu sync.Mutex
}
type message struct {
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, cfg config.ChatBot, logError *log.Logger) (*ChatBot, error) {
// client, err := rumblelivestreamlib.NewClient("", validUrl(streamUrl))
client, err := rumblelivestreamlib.NewClient(cfg.Session.Client)
if err != nil {
return nil, fmt.Errorf("chatbot: error creating new client: %v", err)
}
return &ChatBot{ctx: ctx, client: client, Cfg: cfg, commands: map[string]chan rumblelivestreamlib.ChatView{}, logError: logError, messages: map[string]*message{}}, nil
}
func validUrl(url string) string {
valid := url
if !strings.HasPrefix(valid, "https://") {
valid = "https://" + valid
}
return valid
}
func (cb *ChatBot) StartMessage(id string) error {
msg, exists := cb.Cfg.Messages[id]
if !exists {
return fmt.Errorf("chatbot: message does not exist")
}
cb.messagesMu.Lock()
defer cb.messagesMu.Unlock()
m, exists := cb.messages[id]
if exists {
m.stop()
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,
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())
m.cancelMu.Lock()
m.cancel = cancel
m.cancelMu.Unlock()
if msg.OnCommand {
go cb.startCommand(ctx, m)
} else {
go cb.startMessage(ctx, m)
}
cb.messages[id] = m
return nil
}
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 {
runtime.EventsEmit(cb.ctx, "ChatBotCommandActive-"+m.id, m.id)
// TODO: if error, emit error to user, stop loop?
select {
case <-ctx.Done():
runtime.EventsEmit(cb.ctx, "ChatBotMessageError-"+m.id, m.id)
return
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.chatCommand(m, cv)
if err != nil {
cb.logError.Println("error sending chat:", err)
cb.StopMessage(m.id)
runtime.EventsEmit(cb.ctx, "ChatBotCommandError-"+m.id, m.id)
return
} 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?
err := cb.chat(m)
if err != nil {
cb.logError.Println("error sending chat:", err)
cb.StopMessage(m.id)
runtime.EventsEmit(cb.ctx, "ChatBotMessageError-"+m.id, m.id)
// TODO: stop this loop?
} else {
runtime.EventsEmit(cb.ctx, "ChatBotMessageActive-"+m.id, m.id)
}
timer := time.NewTimer(m.interval * time.Second)
select {
case <-ctx.Done():
timer.Stop()
runtime.EventsEmit(cb.ctx, "ChatBotMessageError-"+m.id, m.id)
return
case <-timer.C:
}
}
}
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")
}
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)
}
return nil
}
func (cb *ChatBot) StartAllMessages() error {
for _, msg := range cb.Cfg.Messages {
err := cb.StartMessage(msg.ID)
if err != nil {
return fmt.Errorf("error starting message: %v", err)
}
}
return nil
}
func (cb *ChatBot) StopAllMessages() error {
cb.messagesMu.Lock()
defer cb.messagesMu.Unlock()
for id, m := range cb.messages {
m.stop()
delete(cb.messages, id)
if m.command != "" && m.onCommand {
cb.commandsMu.Lock()
ch, exists := cb.commands[m.command]
if exists {
close(ch)
delete(cb.commands, m.command)
}
cb.commandsMu.Unlock()
}
}
return nil
}
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
}
func (m *message) stop() {
m.cancelMu.Lock()
if m.cancel != nil {
m.cancel()
}
m.cancelMu.Unlock()
}
func (cb *ChatBot) LoggedIn() (bool, error) {
if cb.client == nil {
return false, fmt.Errorf("chatbot: client is nil")
}
loggedIn, err := cb.client.LoggedIn()
if err != nil {
return false, fmt.Errorf("chatbot: error checking if chat bot is logged in: %v", err)
}
return loggedIn, nil
}
func (cb *ChatBot) Login(username string, password string) ([]*http.Cookie, error) {
if cb.client == nil {
return nil, fmt.Errorf("chatbot: client is nil")
}
cookies, err := cb.client.Login(username, password)
if err != nil {
return nil, fmt.Errorf("chatbot: error logging in: %v", err)
}
return cookies, nil
}
func (cb *ChatBot) Logout() error {
if cb.client == nil {
return fmt.Errorf("chatbot: client is nil")
}
err := cb.client.Logout()
if err != nil {
return fmt.Errorf("chatbot: error logging out: %v", err)
}
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)
}
ctx, cancel := context.WithCancel(context.Background())
cb.cancelChatStreamMu.Lock()
cb.cancelChatStream = cancel
cb.cancelChatStreamMu.Unlock()
go cb.startChatStream(ctx)
// 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) startChatStream(ctx context.Context) {
for {
err := cb.client.StartChatStream(cb.handleChat, cb.handleError)
if err != nil {
cb.logError.Println("error starting chat stream:", err)
runtime.EventsEmit(cb.ctx, "ChatBotChatStreamError", "Error starting chat stream.")
return
}
select {
case <-time.After(90 * time.Minute):
cb.client.StopChatStream()
break
case <-ctx.Done():
cb.client.StopChatStream()
return
}
}
}
func (cb *ChatBot) StopChatStream() error {
if cb.client == nil {
return fmt.Errorf("chatbot: client is nil")
}
// TODO: should a panic be caught here?
cb.cancelChatStreamMu.Lock()
if cb.cancelChatStream != nil {
cb.cancelChatStream()
} else {
cb.client.StopChatStream()
}
cb.cancelChatStreamMu.Unlock()
return nil
}
func (cb *ChatBot) RestartChatStream() error {
if cb.client == nil {
return fmt.Errorf("chatbot: client is nil")
}
cb.client.StopChatStream()
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) 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)
}

View file

@ -1,241 +0,0 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/tylertravisty/go-utils/random"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
)
const (
CIDLen = 8
DefaultInterval = 10
configDir = ".rum-goggles"
configDirWin = "RumGoggles"
configFile = "config.json"
logFile = "logs.txt"
)
func LogFile() (*os.File, error) {
dir, err := buildConfigDir()
if err != nil {
return nil, fmt.Errorf("config: error getting config directory: %v", err)
}
err = os.MkdirAll(dir, 0750)
if err != nil {
return nil, fmt.Errorf("config: error making config directory: %v", err)
}
fp := filepath.Join(dir, logFile)
f, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return nil, fmt.Errorf("config: error opening log file: %v", err)
}
return f, nil
}
func buildConfigDir() (string, error) {
userDir, err := userDir()
if err != nil {
return "", fmt.Errorf("error getting user directory: %v", err)
}
var dir string
switch runtime.GOOS {
case "windows":
dir = filepath.Join(userDir, configDirWin)
default:
dir = filepath.Join(userDir, configDir)
}
return dir, nil
}
func userDir() (string, error) {
var dir string
var err error
switch runtime.GOOS {
case "windows":
dir, err = os.UserCacheDir()
default:
dir, err = os.UserHomeDir()
}
return dir, err
}
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"`
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 ChatBotSession struct {
Client rumblelivestreamlib.NewClientOptions `json:"client"`
Username string `json:"username"`
}
type ChatBot struct {
Messages map[string]ChatMessage `json:"messages"`
Session ChatBotSession `json:"session"`
// Commands []ChatCommand
}
type Channel struct {
ID string `json:"id"`
ApiUrl string `json:"api_url"`
Name string `json:"name"`
Interval time.Duration `json:"interval"`
ChatBot ChatBot `json:"chat_bot"`
}
func (a *App) NewChannel(url string, name string) (string, error) {
for {
id, err := random.String(CIDLen)
if err != nil {
return "", fmt.Errorf("config: error generating ID: %v", err)
}
if _, exists := a.Channels[id]; !exists {
a.Channels[id] = Channel{id, url, name, DefaultInterval, ChatBot{Messages: map[string]ChatMessage{}}}
return id, nil
}
}
}
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[cm.ID]
if !exists {
return fmt.Errorf("config: message does not exist")
}
delete(channel.ChatBot.Messages, cm.ID)
return nil
}
func (a *App) NewChatMessage(cid string, cm ChatMessage) (string, error) {
if _, exists := a.Channels[cid]; !exists {
return "", fmt.Errorf("config: channel does not exist")
}
for {
id, err := random.String(CIDLen)
if err != nil {
return "", fmt.Errorf("config: error generating ID: %v", err)
}
_, 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(cid string, cm ChatMessage) (string, error) {
channel, exists := a.Channels[cid]
if !exists {
return "", fmt.Errorf("config: channel does not exist")
}
_, exists = channel.ChatBot.Messages[cm.ID]
if !exists {
return "", fmt.Errorf("config: message does not exist")
}
channel.ChatBot.Messages[cm.ID] = cm
return cm.ID, nil
}
type App struct {
Channels map[string]Channel `json:"channels"`
}
func Load() (*App, error) {
dir, err := buildConfigDir()
if err != nil {
return nil, fmt.Errorf("config: error getting config directory: %v", err)
}
fp := filepath.Join(dir, configFile)
app, err := load(fp)
if err != nil {
return nil, fmt.Errorf("config: error loading config: %w", err)
}
return app, nil
}
func load(filepath string) (*App, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("error opening file: %w", err)
}
var app App
decoder := json.NewDecoder(f)
err = decoder.Decode(&app)
if err != nil {
return nil, fmt.Errorf("error decoding file into json: %v", err)
}
return &app, nil
}
func (a *App) Save() error {
dir, err := buildConfigDir()
if err != nil {
return fmt.Errorf("config: error getting config directory: %v", err)
}
err = os.MkdirAll(dir, 0750)
if err != nil {
return fmt.Errorf("config: error making config directory: %v", err)
}
fp := filepath.Join(dir, configFile)
err = a.save(fp)
if err != nil {
return fmt.Errorf("config: error saving config: %v", err)
}
return nil
}
func (app *App) save(filepath string) error {
b, err := json.MarshalIndent(app, "", "\t")
if err != nil {
return fmt.Errorf("error encoding config into json: %v", err)
}
err = os.WriteFile(filepath, b, 0666)
if err != nil {
return fmt.Errorf("error writing config file: %v", err)
}
return nil
}

185
package-lock.json generated
View file

@ -1,185 +0,0 @@
{
"name": "rum-goggles",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"devDependencies": {
"react-router-dom": "^6.20.1"
}
},
"node_modules/@remix-run/router": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
"integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==",
"dev": true,
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"peer": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/react-router": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
"integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==",
"dev": true,
"dependencies": {
"@remix-run/router": "1.13.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz",
"integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==",
"dev": true,
"dependencies": {
"@remix-run/router": "1.13.1",
"react-router": "6.20.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
}
},
"dependencies": {
"@remix-run/router": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
"integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"peer": true
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"peer": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dev": true,
"peer": true,
"requires": {
"loose-envify": "^1.1.0"
}
},
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"dev": true,
"peer": true,
"requires": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
}
},
"react-router": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
"integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==",
"dev": true,
"requires": {
"@remix-run/router": "1.13.1"
}
},
"react-router-dom": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz",
"integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==",
"dev": true,
"requires": {
"@remix-run/router": "1.13.1",
"react-router": "6.20.1"
}
},
"scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dev": true,
"peer": true,
"requires": {
"loose-envify": "^1.1.0"
}
}
}
}

View file

@ -1,5 +0,0 @@
{
"devDependencies": {
"react-router-dom": "^6.20.1"
}
}

6
v1/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
build/bin
build/darwin
build/windows
node_modules
frontend/dist
frontend/wailsjs

19
v1/README.md Normal file
View file

@ -0,0 +1,19 @@
# README
## About
This is the official Wails React template.
You can configure the project by editing `wails.json`. More information about the project settings can be found
here: https://wails.io/docs/reference/project-config
## Live Development
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.
## Building
To build a redistributable, production mode package, use `wails build`.

21
v1/app.go Normal file
View file

@ -0,0 +1,21 @@
package main
import (
"context"
)
// App struct
type App struct {
ctx context.Context
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}

View file

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Rum Goggles</title> <title>v1</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -54,9 +54,9 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
@ -64,11 +64,11 @@
"@babel/generator": "^7.23.6", "@babel/generator": "^7.23.6",
"@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3", "@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.23.6", "@babel/helpers": "^7.23.9",
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.9",
"@babel/template": "^7.22.15", "@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.6", "@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.6", "@babel/types": "^7.23.9",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@ -252,14 +252,14 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.22.15", "@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.6", "@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.6" "@babel/types": "^7.23.9"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -280,9 +280,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
"dev": true, "dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -371,23 +371,23 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.22.15", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.22.15", "@babel/parser": "^7.23.9",
"@babel/types": "^7.22.15" "@babel/types": "^7.23.9"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz",
"integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.23.5", "@babel/code-frame": "^7.23.5",
@ -396,8 +396,8 @@
"@babel/helper-function-name": "^7.23.0", "@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.9",
"@babel/types": "^7.23.6", "@babel/types": "^7.23.9",
"debug": "^4.3.1", "debug": "^4.3.1",
"globals": "^11.1.0" "globals": "^11.1.0"
}, },
@ -406,9 +406,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.23.4", "@babel/helper-string-parser": "^7.23.4",
@ -466,9 +466,9 @@
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@ -490,9 +490,9 @@
"dev": true "dev": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.20", "version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@ -506,9 +506,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.45", "version": "18.2.58",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.58.tgz",
"integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", "integrity": "sha512-TaGvMNhxvG2Q0K0aYxiKfNDS5m5ZsoIBBbtfUorxdH4NGSXIlYvZxLJI+9Dd3KjeB3780bciLyAb7ylO8pLhPw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@ -517,9 +517,9 @@
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "18.2.17", "version": "18.2.19",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
"integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
@ -565,9 +565,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.22.2", "version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -584,8 +584,8 @@
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001565", "caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.601", "electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14", "node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13" "update-browserslist-db": "^1.0.13"
}, },
@ -597,9 +597,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001570", "version": "1.0.30001589",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
"integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -675,9 +675,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.611", "version": "1.4.680",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.611.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz",
"integrity": "sha512-ZtRpDxrjHapOwxtv+nuth5ByB8clyn8crVynmRNGO3wG3LOp8RTcyZDqwaI6Ng6y8FCK2hVZmJoqwCskKbNMaw==", "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A==",
"dev": true "dev": true
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
@ -1038,9 +1038,9 @@
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -1106,9 +1106,9 @@
} }
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@ -1233,9 +1233,9 @@
"dev": true "dev": true
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.32", "version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1421,9 +1421,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "3.2.7", "version": "3.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==", "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.15.9", "esbuild": "^0.15.9",
@ -1504,9 +1504,9 @@
"dev": true "dev": true
}, },
"@babel/core": { "@babel/core": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
@ -1514,11 +1514,11 @@
"@babel/generator": "^7.23.6", "@babel/generator": "^7.23.6",
"@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3", "@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.23.6", "@babel/helpers": "^7.23.9",
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.9",
"@babel/template": "^7.22.15", "@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.6", "@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.6", "@babel/types": "^7.23.9",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@ -1650,14 +1650,14 @@
"dev": true "dev": true
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/template": "^7.22.15", "@babel/template": "^7.23.9",
"@babel/traverse": "^7.23.6", "@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.6" "@babel/types": "^7.23.9"
} }
}, },
"@babel/highlight": { "@babel/highlight": {
@ -1672,9 +1672,9 @@
} }
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
"dev": true "dev": true
}, },
"@babel/plugin-syntax-jsx": { "@babel/plugin-syntax-jsx": {
@ -1727,20 +1727,20 @@
} }
}, },
"@babel/template": { "@babel/template": {
"version": "7.22.15", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.22.15", "@babel/parser": "^7.23.9",
"@babel/types": "^7.22.15" "@babel/types": "^7.23.9"
} }
}, },
"@babel/traverse": { "@babel/traverse": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz",
"integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.23.5", "@babel/code-frame": "^7.23.5",
@ -1749,16 +1749,16 @@
"@babel/helper-function-name": "^7.23.0", "@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.9",
"@babel/types": "^7.23.6", "@babel/types": "^7.23.9",
"debug": "^4.3.1", "debug": "^4.3.1",
"globals": "^11.1.0" "globals": "^11.1.0"
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-string-parser": "^7.23.4", "@babel/helper-string-parser": "^7.23.4",
@ -1792,9 +1792,9 @@
} }
}, },
"@jridgewell/resolve-uri": { "@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true "dev": true
}, },
"@jridgewell/set-array": { "@jridgewell/set-array": {
@ -1810,9 +1810,9 @@
"dev": true "dev": true
}, },
"@jridgewell/trace-mapping": { "@jridgewell/trace-mapping": {
"version": "0.3.20", "version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@ -1826,9 +1826,9 @@
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "18.2.45", "version": "18.2.58",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.58.tgz",
"integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", "integrity": "sha512-TaGvMNhxvG2Q0K0aYxiKfNDS5m5ZsoIBBbtfUorxdH4NGSXIlYvZxLJI+9Dd3KjeB3780bciLyAb7ylO8pLhPw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
@ -1837,9 +1837,9 @@
} }
}, },
"@types/react-dom": { "@types/react-dom": {
"version": "18.2.17", "version": "18.2.19",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
"integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
@ -1876,21 +1876,21 @@
} }
}, },
"browserslist": { "browserslist": {
"version": "4.22.2", "version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-lite": "^1.0.30001565", "caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.601", "electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14", "node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13" "update-browserslist-db": "^1.0.13"
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001570", "version": "1.0.30001589",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
"integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
"dev": true "dev": true
}, },
"chalk": { "chalk": {
@ -1941,9 +1941,9 @@
} }
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.611", "version": "1.4.680",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.611.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz",
"integrity": "sha512-ZtRpDxrjHapOwxtv+nuth5ByB8clyn8crVynmRNGO3wG3LOp8RTcyZDqwaI6Ng6y8FCK2hVZmJoqwCskKbNMaw==", "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A==",
"dev": true "dev": true
}, },
"esbuild": { "esbuild": {
@ -2117,9 +2117,9 @@
"optional": true "optional": true
}, },
"escalade": { "escalade": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true "dev": true
}, },
"escape-string-regexp": { "escape-string-regexp": {
@ -2160,9 +2160,9 @@
"dev": true "dev": true
}, },
"hasown": { "hasown": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dev": true, "dev": true,
"requires": { "requires": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@ -2251,9 +2251,9 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "8.4.32", "version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true, "dev": true,
"requires": { "requires": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
@ -2362,9 +2362,9 @@
} }
}, },
"vite": { "vite": {
"version": "3.2.7", "version": "3.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==", "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
"dev": true, "dev": true,
"requires": { "requires": {
"esbuild": "^0.15.9", "esbuild": "^0.15.9",

View file

@ -1,3 +1,3 @@
#App { #app {
height: 100vh; height: 100vh;
} }

8
v1/frontend/src/App.jsx Normal file
View file

@ -0,0 +1,8 @@
import { useState } from 'react';
import './App.css';
function App() {
return <div id='App'></div>;
}
export default App;

View file

@ -0,0 +1,3 @@
body {
margin: 0;
}

View file

@ -1,12 +1,10 @@
module github.com/tylertravisty/rum-goggles module github.com/tylertravisty/rum-goggles/v1
go 1.19 go 1.21
require ( toolchain go1.22.0
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4 require github.com/wailsapp/wails/v2 v2.8.0
github.com/wailsapp/wails/v2 v2.7.1
)
require ( require (
github.com/bep/debounce v1.2.1 // indirect github.com/bep/debounce v1.2.1 // indirect
@ -24,9 +22,7 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // 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/rivo/uniseg v0.4.4 // indirect
github.com/robertkrimen/otto v0.3.0 // indirect
github.com/samber/lo v1.38.1 // indirect github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
@ -38,6 +34,6 @@ require (
golang.org/x/net v0.20.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.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
) )
// replace github.com/wailsapp/wails/v2 v2.8.0 => /home/tyler/dev/go/pkg/mod

View file

@ -41,24 +41,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE=
github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= 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.3.4 h1:VPKelrC3hesJlbqdByMkUhbEubFx80T5FNC60JKrEfw=
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4/go.mod h1:rUET5uInouMfB4ekqdGiYeoN5ibOdzU9cCgRE0i57Wg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -68,18 +61,15 @@ github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhy
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= 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.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4=
github.com/wailsapp/wails/v2 v2.7.1/go.mod h1:oIJVwwso5fdOgprBYWXBBqtx6PaSvxg8/KTQHNGkadc= github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
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 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 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 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 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.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= 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/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-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-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -93,16 +83,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 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/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/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.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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= 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/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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

Some files were not shown because too many files have changed in this diff Show more