Moved config to its own directory
This commit is contained in:
parent
d329c36653
commit
5ddf50a152
87
app.go
87
app.go
|
@ -14,22 +14,34 @@ import (
|
|||
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||
)
|
||||
|
||||
const (
|
||||
configFilepath = "./config.json"
|
||||
)
|
||||
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
|
||||
ctx context.Context
|
||||
cfg *config.App
|
||||
cfgMu sync.Mutex
|
||||
api *api.Api
|
||||
apiMu sync.Mutex
|
||||
logError *log.Logger
|
||||
logInfo *log.Logger
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App {
|
||||
return &App{api: api.NewApi()}
|
||||
app := &App{}
|
||||
err := app.initLog()
|
||||
if err != nil {
|
||||
log.Fatal("error initializing log")
|
||||
}
|
||||
|
||||
app.api = api.NewApi(app.logError, app.logInfo)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// startup is called when the app starts. The context is saved
|
||||
|
@ -37,15 +49,31 @@ func NewApp() *App {
|
|||
func (a *App) startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
a.api.Startup(ctx)
|
||||
|
||||
err := a.loadConfig()
|
||||
if err != nil {
|
||||
// TODO: handle error better on startup
|
||||
log.Fatal("error loading config: ", err)
|
||||
a.logError.Fatal("error loading config: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) initLog() error {
|
||||
fp, err := config.LogFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting filepath for log file")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening log file")
|
||||
}
|
||||
|
||||
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(configFilepath)
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("error loading config: %v", err)
|
||||
|
@ -60,7 +88,7 @@ func (a *App) loadConfig() error {
|
|||
|
||||
func (a *App) newConfig() error {
|
||||
cfg := &config.App{Channels: map[string]config.Channel{}}
|
||||
err := cfg.Save(configFilepath)
|
||||
err := cfg.Save()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving new config: %v", err)
|
||||
}
|
||||
|
@ -73,23 +101,12 @@ func (a *App) Config() *config.App {
|
|||
return a.cfg
|
||||
}
|
||||
|
||||
func (a *App) SaveConfig() error {
|
||||
err := a.cfg.Save(configFilepath)
|
||||
if err != nil {
|
||||
// TODO: log error; return user error
|
||||
return fmt.Errorf("Error saving config")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) AddChannel(url string) (*config.App, error) {
|
||||
client := rumblelivestreamlib.Client{StreamKey: url}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
fmt.Println("error requesting api:", err)
|
||||
return nil, fmt.Errorf("error querying API")
|
||||
a.logError.Println("error executing api request:", err)
|
||||
return nil, fmt.Errorf("Error querying API. Verify key and try again.")
|
||||
}
|
||||
|
||||
name := resp.Username
|
||||
|
@ -101,16 +118,14 @@ func (a *App) AddChannel(url string) (*config.App, error) {
|
|||
defer a.cfgMu.Unlock()
|
||||
_, err = a.cfg.NewChannel(url, name)
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
fmt.Println("error creating new channel:", err)
|
||||
return nil, fmt.Errorf("error creating new channel")
|
||||
a.logError.Println("error creating new channel:", err)
|
||||
return nil, fmt.Errorf("Error creating new channel. Try again.")
|
||||
}
|
||||
|
||||
err = a.cfg.Save(configFilepath)
|
||||
err = a.cfg.Save()
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
fmt.Println("error saving config:", err)
|
||||
return nil, fmt.Errorf("error saving new channel")
|
||||
a.logError.Println("error saving config:", err)
|
||||
return nil, fmt.Errorf("Error saving channel information. Try again.")
|
||||
}
|
||||
|
||||
return a.cfg, nil
|
||||
|
@ -119,15 +134,13 @@ func (a *App) AddChannel(url string) (*config.App, error) {
|
|||
func (a *App) StartApi(cid string) error {
|
||||
channel, found := a.cfg.Channels[cid]
|
||||
if !found {
|
||||
// TODO: log error
|
||||
fmt.Println("could not find channel CID:", cid)
|
||||
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 {
|
||||
// TODO: log error
|
||||
fmt.Println("error starting api:", err)
|
||||
a.logError.Println("error starting api:", err)
|
||||
return fmt.Errorf("error starting API")
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,34 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.stream-chat-add-button {
|
||||
align-items: center;
|
||||
background-color: rgba(6,23,38,1);
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.stream-chat-add-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stream-chat-add-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.stream-chat-header {
|
||||
text-align: left;
|
||||
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-title {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { PlusCircle } from '../assets/icons';
|
||||
import './StreamChat.css';
|
||||
|
||||
function StreamChat(props) {
|
||||
|
@ -5,6 +6,12 @@ function StreamChat(props) {
|
|||
<div className='stream-chat'>
|
||||
<div className='stream-chat-header'>
|
||||
<span className='stream-chat-title'>{props.title}</span>
|
||||
<button
|
||||
onClick={() => console.log('Add chat bot')}
|
||||
className='stream-chat-add-button'
|
||||
>
|
||||
<img className='stream-chat-add-icon' src={PlusCircle} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
21
frontend/src/components/StreamChatMessage.css
Normal file
21
frontend/src/components/StreamChatMessage.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
.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;
|
||||
}
|
13
frontend/src/components/StreamChatMessage.jsx
Normal file
13
frontend/src/components/StreamChatMessage.jsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import './StreamChatMessage.css';
|
||||
|
||||
export function StreamChatMessageModal() {
|
||||
return (
|
||||
<div className='modal-container'>
|
||||
<div className='modal-chat'>
|
||||
<span>hello world</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function StreamChatMessageItem() {}
|
|
@ -11,6 +11,7 @@ import StreamActivity from '../components/StreamActivity';
|
|||
import StreamChat from '../components/StreamChat';
|
||||
import StreamInfo from '../components/StreamInfo';
|
||||
import { NavSignIn } from './Navigation';
|
||||
import { StreamChatMessageItem, StreamChatMessageModal } from '../components/StreamChatMessage';
|
||||
|
||||
function Dashboard() {
|
||||
const location = useLocation();
|
||||
|
@ -131,6 +132,7 @@ function Dashboard() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<StreamChatMessageModal />
|
||||
<div className='modal' style={{ zIndex: modalZ ? 10 : -10 }}>
|
||||
<span>show this instead</span>
|
||||
<button onClick={closeModal}>close</button>
|
||||
|
|
|
@ -7,6 +7,19 @@
|
|||
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;
|
||||
|
|
|
@ -9,6 +9,7 @@ import ChannelList from '../components/ChannelList';
|
|||
function SignIn() {
|
||||
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);
|
||||
|
@ -34,6 +35,7 @@ function SignIn() {
|
|||
})
|
||||
.catch((err) => {
|
||||
console.log('error adding channel', err);
|
||||
setAddChannelError(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -52,6 +54,9 @@ function SignIn() {
|
|||
</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'
|
||||
|
@ -71,6 +76,9 @@ function SignIn() {
|
|||
Save
|
||||
</button>
|
||||
</div>
|
||||
<span className='add-channel-error'>
|
||||
{addChannelError ? addChannelError : '\u00A0'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='signin-footer'></div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -14,12 +15,14 @@ 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() *Api {
|
||||
return &Api{}
|
||||
func NewApi(logError *log.Logger, logInfo *log.Logger) *Api {
|
||||
return &Api{logError: logError, logInfo: logInfo}
|
||||
}
|
||||
|
||||
func (a *Api) Startup(ctx context.Context) {
|
||||
|
@ -27,7 +30,7 @@ func (a *Api) Startup(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (a *Api) Start(url string, interval time.Duration) error {
|
||||
fmt.Println("Api.Start")
|
||||
a.logInfo.Println("Api.Start")
|
||||
if url == "" {
|
||||
return fmt.Errorf("empty stream key")
|
||||
}
|
||||
|
@ -38,21 +41,21 @@ func (a *Api) Start(url string, interval time.Duration) error {
|
|||
a.queryingMu.Unlock()
|
||||
|
||||
if start {
|
||||
fmt.Println("Starting querying")
|
||||
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 {
|
||||
fmt.Println("Querying already started")
|
||||
a.logInfo.Println("Querying already started")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Api) Stop() {
|
||||
fmt.Println("stop querying")
|
||||
a.logInfo.Println("Stop querying")
|
||||
a.cancelMu.Lock()
|
||||
if a.cancel != nil {
|
||||
a.cancel()
|
||||
|
@ -77,12 +80,12 @@ func (a *Api) start(ctx context.Context, url string, interval time.Duration) {
|
|||
}
|
||||
|
||||
func (a *Api) query(url string) {
|
||||
fmt.Println("QueryAPI")
|
||||
a.logInfo.Println("QueryAPI")
|
||||
client := rumblelivestreamlib.Client{StreamKey: url}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
fmt.Println("client.Request err:", err)
|
||||
a.logError.Println("api: error executing client request:", err)
|
||||
a.Stop()
|
||||
runtime.EventsEmit(a.ctx, "QueryResponseError", "Failed to query API")
|
||||
return
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/tylertravisty/go-utils/random"
|
||||
|
@ -12,13 +14,69 @@ import (
|
|||
const (
|
||||
CIDLen = 8
|
||||
DefaultInterval = 10
|
||||
|
||||
configDir = ".rum-goggles"
|
||||
configDirWin = "RumGoggles"
|
||||
configFile = "config.json"
|
||||
logFile = "logs.txt"
|
||||
)
|
||||
|
||||
func LogFile() (string, error) {
|
||||
dir, err := buildConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("config: error getting config directory: %v", err)
|
||||
}
|
||||
|
||||
return filepath.Join(dir, logFile), 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 {
|
||||
AsChannel bool `json:"as_channel"`
|
||||
Text string `json:"text"`
|
||||
Interval time.Duration `json:"interval"`
|
||||
}
|
||||
|
||||
type ChatBot struct {
|
||||
Messages []ChatMessage `json:"messages"`
|
||||
// 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) {
|
||||
|
@ -29,7 +87,7 @@ func (a *App) NewChannel(url string, name string) (string, error) {
|
|||
}
|
||||
|
||||
if _, exists := a.Channels[id]; !exists {
|
||||
a.Channels[id] = Channel{id, url, name, DefaultInterval}
|
||||
a.Channels[id] = Channel{id, url, name, DefaultInterval, ChatBot{[]ChatMessage{}}}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
@ -39,31 +97,66 @@ type App struct {
|
|||
Channels map[string]Channel `json:"channels"`
|
||||
}
|
||||
|
||||
func Load(filepath string) (*App, error) {
|
||||
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("config: error opening file: %w", err)
|
||||
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("config: error decoding file into json: %v", err)
|
||||
return nil, fmt.Errorf("error decoding file into json: %v", err)
|
||||
}
|
||||
|
||||
return &app, nil
|
||||
}
|
||||
|
||||
func (app *App) Save(filepath string) error {
|
||||
b, err := json.MarshalIndent(app, "", "\t")
|
||||
func (a *App) Save() error {
|
||||
dir, err := buildConfigDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: error encoding config into json: %v", err)
|
||||
return fmt.Errorf("config: error getting config directory: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath, b, 0666)
|
||||
err = os.MkdirAll(dir, 0750)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: error writing config file: %v", err)
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue