2021-05-02 12:29:43 +00:00
package main
import (
"embed"
"flag"
"fmt"
"io/fs"
"net/http"
2021-05-02 19:33:37 +00:00
"runtime"
2021-05-02 12:29:43 +00:00
"time"
2021-05-16 15:55:40 +00:00
kv "github.com/strimertul/kilovolt/v4"
2021-05-02 19:33:37 +00:00
2021-05-10 21:09:15 +00:00
"github.com/strimertul/strimertul/database"
2021-05-02 12:29:43 +00:00
"github.com/strimertul/strimertul/modules"
"github.com/strimertul/strimertul/modules/loyalty"
2021-05-14 14:37:54 +00:00
"github.com/strimertul/strimertul/modules/stulbe"
2021-05-14 11:15:38 +00:00
"github.com/strimertul/strimertul/modules/twitch"
2021-05-02 12:29:43 +00:00
"github.com/dgraph-io/badger/v3"
"github.com/pkg/browser"
2021-05-02 19:33:37 +00:00
"github.com/mattn/go-colorable"
"github.com/sirupsen/logrus"
2021-05-02 12:29:43 +00:00
)
const AppTitle = "strimertül"
const AppHeader = `
_ _ _ O O _
__ | | _ _ _ ( _ ) _ __ ___ _ _ | | _ _ _ | |
( _ - < _ | ' _ | | ' \ / - _ ) ' _ | _ | || | |
/ __ / \ __ | _ | | _ | _ | _ | _ \ ___ | _ | \ __ | \ _ , _ | _ | `
const DefaultBind = "localhost:4337"
//go:embed frontend/dist/*
var frontend embed . FS
2021-05-02 19:33:37 +00:00
var log = logrus . New ( )
func wrapLogger ( module string ) logrus . FieldLogger {
return log . WithField ( "module" , module )
}
func parseLogLevel ( level string ) logrus . Level {
switch level {
case "error" :
return logrus . ErrorLevel
case "warn" , "warning" :
return logrus . WarnLevel
case "info" , "notice" :
return logrus . InfoLevel
case "debug" :
return logrus . DebugLevel
case "trace" :
return logrus . TraceLevel
default :
return logrus . InfoLevel
2021-05-02 12:29:43 +00:00
}
}
func main ( ) {
2021-05-02 19:33:37 +00:00
// Get cmd line parameters
2021-05-10 21:09:15 +00:00
dbdir := flag . String ( "dbdir" , "data" , "Path to strimertul database dir" )
2021-05-02 19:33:37 +00:00
loglevel := flag . String ( "loglevel" , "info" , "Logging level (debug, info, warn, error)" )
2021-05-02 12:29:43 +00:00
flag . Parse ( )
2021-05-02 19:33:37 +00:00
log . SetLevel ( parseLogLevel ( * loglevel ) )
// Ok this is dumb but listen, I like colors.
if runtime . GOOS == "windows" {
log . SetFormatter ( & logrus . TextFormatter { ForceColors : true } )
log . SetOutput ( colorable . NewColorableStdout ( ) )
}
// Print the app header :D
2021-05-02 12:29:43 +00:00
fmt . Println ( AppHeader )
// Loading routine
2021-05-16 16:10:51 +00:00
dblogger := wrapLogger ( "db" )
db , err := database . Open ( badger . DefaultOptions ( * dbdir ) . WithLogger ( dblogger ) , dblogger )
2021-05-02 12:29:43 +00:00
failOnError ( err , "Could not open DB" )
defer db . Close ( )
// Check if onboarding was completed
var moduleConfig modules . ModuleConfig
2021-05-10 21:09:15 +00:00
err = db . GetJSON ( modules . ModuleConfigKey , & moduleConfig )
2021-05-02 12:29:43 +00:00
if err != nil {
if err == badger . ErrKeyNotFound {
moduleConfig = modules . ModuleConfig { CompletedOnboarding : false }
} else {
fatalError ( err , "Could not read from DB" )
}
}
if ! moduleConfig . CompletedOnboarding {
// Initialize DB as empty and default endpoint
2021-05-10 21:09:15 +00:00
failOnError ( db . PutJSON ( modules . HTTPServerConfigKey , modules . HTTPServerConfig {
Bind : DefaultBind ,
} ) , "could not save http config" )
failOnError ( db . PutJSON ( modules . ModuleConfigKey , modules . ModuleConfig {
EnableKV : true ,
EnableStaticServer : false ,
2021-05-14 11:15:38 +00:00
EnableTwitch : false ,
2021-05-10 21:09:15 +00:00
EnableStulbe : false ,
CompletedOnboarding : true ,
} ) , "could not save onboarding config" )
2021-05-02 12:29:43 +00:00
fmt . Printf ( "It appears this is your first time running %s! Please go to http://%s and make sure to configure anything you want!\n\n" , AppTitle , DefaultBind )
}
// Initialize KV (required)
2021-05-16 15:55:40 +00:00
hub , err := kv . NewHub ( db . Client ( ) , wrapLogger ( "kv" ) )
failOnError ( err , "Could not initialize kilovolt hub" )
2021-05-02 12:29:43 +00:00
go hub . Run ( )
// Get HTTP config
var httpConfig modules . HTTPServerConfig
2021-05-10 21:09:15 +00:00
failOnError ( db . GetJSON ( modules . HTTPServerConfigKey , & httpConfig ) , "Could not retrieve HTTP server config" )
2021-05-02 12:29:43 +00:00
// Get Stulbe config, if enabled
2021-05-11 11:12:00 +00:00
var stulbeManager * stulbe . Manager = nil
2021-05-02 12:29:43 +00:00
if moduleConfig . EnableStulbe {
2021-05-18 11:30:27 +00:00
stulbeLogger := wrapLogger ( "stulbe" )
stulbeManager , err = stulbe . Initialize ( db , stulbeLogger )
2021-05-07 16:36:23 +00:00
if err != nil {
log . WithError ( err ) . Error ( "Stulbe initialization failed! Module was temporarely disabled" )
moduleConfig . EnableStulbe = false
2021-05-18 13:53:17 +00:00
} else {
defer stulbeManager . Close ( )
go func ( ) {
err := stulbeManager . ReceiveEvents ( )
stulbeLogger . WithError ( err ) . Error ( "Stulbe subscription died unexpectedly!" )
} ( )
2021-05-07 16:36:23 +00:00
}
2021-05-02 12:29:43 +00:00
}
var loyaltyManager * loyalty . Manager
loyaltyLogger := wrapLogger ( "loyalty" )
if moduleConfig . EnableLoyalty {
2021-05-16 15:55:40 +00:00
loyaltyManager , err = loyalty . NewManager ( db , loyaltyLogger )
2021-05-02 12:29:43 +00:00
if err != nil {
2021-05-14 11:15:38 +00:00
log . WithError ( err ) . Error ( "Loyalty initialization failed! Module was temporarily disabled" )
2021-05-07 16:36:23 +00:00
moduleConfig . EnableLoyalty = false
2021-05-02 12:29:43 +00:00
}
2021-05-14 14:37:54 +00:00
if stulbeManager != nil {
go stulbeManager . ReplicateKey ( loyalty . ConfigKey )
go stulbeManager . ReplicateKey ( loyalty . RewardsKey )
go stulbeManager . ReplicateKey ( loyalty . GoalsKey )
2021-05-16 15:55:40 +00:00
go stulbeManager . ReplicateKey ( loyalty . PointsPrefix )
2021-05-14 14:37:54 +00:00
}
2021-05-02 12:29:43 +00:00
}
//TODO Refactor this to something sane
2021-05-14 11:15:38 +00:00
if moduleConfig . EnableTwitch {
2021-05-02 12:29:43 +00:00
// Create logger
2021-05-14 11:15:38 +00:00
twitchLogger := wrapLogger ( "twitch" )
// Get Twitch config
var twitchConfig twitch . Config
failOnError ( db . GetJSON ( twitch . ConfigKey , & twitchConfig ) , "Could not retrieve twitch config" )
// Create Twitch client
twitchClient , err := twitch . NewClient ( twitchConfig , twitchLogger )
if err == nil {
// Get Twitchbot config
var twitchBotConfig twitch . BotConfig
failOnError ( db . GetJSON ( twitch . BotConfigKey , & twitchBotConfig ) , "Could not retrieve twitch bot config" )
// Create and run IRC bot
bot := twitch . NewBot ( twitchClient , twitchBotConfig )
if moduleConfig . EnableLoyalty {
bot . SetupLoyalty ( loyaltyManager )
}
go func ( ) {
failOnError ( bot . Connect ( ) , "connection failed" )
} ( )
} else {
log . WithError ( err ) . Error ( "Twitch initialization failed! Module was temporarily disabled" )
moduleConfig . EnableTwitch = false
2021-05-02 12:29:43 +00:00
}
}
// Create logger and endpoints
httpLogger := wrapLogger ( "http" )
fedir , _ := fs . Sub ( frontend , "frontend/dist" )
http . Handle ( "/ui/" , http . StripPrefix ( "/ui/" , FileServerWithDefault ( http . FS ( fedir ) ) ) )
http . HandleFunc ( "/ws" , func ( w http . ResponseWriter , r * http . Request ) {
kv . ServeWs ( hub , w , r )
} )
if moduleConfig . EnableStaticServer {
http . Handle ( "/static/" , http . StripPrefix ( "/static/" , http . FileServer ( http . Dir ( httpConfig . Path ) ) ) )
2021-05-02 19:33:37 +00:00
httpLogger . WithField ( "path" , httpConfig . Path ) . Info ( "serving %s" )
2021-05-02 12:29:43 +00:00
}
go func ( ) {
time . Sleep ( time . Second ) // THIS IS STUPID
browser . OpenURL ( fmt . Sprintf ( "http://%s/ui" , httpConfig . Bind ) )
} ( )
// Start HTTP server
fatalError ( http . ListenAndServe ( httpConfig . Bind , nil ) , "HTTP server died unexepectedly" )
}
func failOnError ( err error , text string ) {
if err != nil {
fatalError ( err , text )
}
}
func fatalError ( err error , text string ) {
log . Fatalf ( "FATAL ERROR OCCURRED: %s\n\n%s" , text , err . Error ( ) )
}