diff --git a/controller/config.go b/controller/config.go index fdfa756..1d3cbbb 100644 --- a/controller/config.go +++ b/controller/config.go @@ -77,9 +77,5 @@ func handleConfigOverrides(config *model.Config) error { if env, has := os.LookupEnv("ARIMELODY_DISCORD_CLIENT_ID"); has { config.Discord.ClientID = env } if env, has := os.LookupEnv("ARIMELODY_DISCORD_SECRET"); has { config.Discord.Secret = env } - if env, has := os.LookupEnv("ARIMELODY_TWITCH_BROADCASTER"); has { config.Twitch.Broadcaster = env } - if env, has := os.LookupEnv("ARIMELODY_TWITCH_CLIENT_ID"); has { config.Twitch.ClientID = env } - if env, has := os.LookupEnv("ARIMELODY_TWITCH_SECRET"); has { config.Twitch.Secret = env } - return nil } diff --git a/controller/twitch.go b/controller/twitch.go deleted file mode 100644 index f7dc509..0000000 --- a/controller/twitch.go +++ /dev/null @@ -1,97 +0,0 @@ -package controller - -import ( - "arimelody-web/model" - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "time" -) - -const TWITCH_API_BASE = "https://api.twitch.tv/helix/" - -func TwitchSetup(app *model.AppState) error { - app.Twitch = &model.TwitchState{} - err := RefreshTwitchToken(app) - return err -} - -func RefreshTwitchToken(app *model.AppState) error { - if app.Twitch != nil && app.Twitch.Token != nil && time.Now().UTC().After(app.Twitch.Token.ExpiresAt) { - return nil - } - - requestUrl, _ := url.Parse("https://id.twitch.tv/oauth2/token") - req, _ := http.NewRequest(http.MethodPost, requestUrl.String(), bytes.NewBuffer([]byte(url.Values{ - "client_id": []string{ app.Config.Twitch.ClientID }, - "client_secret": []string{ app.Config.Twitch.Secret }, - "grant_type": []string{ "client_credentials" }, - }.Encode()))) - - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - - type TwitchOAuthToken struct { - AccessToken string `json:"access_token"` - ExpiresIn int `json:"expires_in"` - TokenType string `json:"token_type"` - } - oauthResponse := TwitchOAuthToken{} - err = json.NewDecoder(res.Body).Decode(&oauthResponse) - if err != nil { - return err - } - - app.Twitch.Token = &model.TwitchOAuthToken{ - AccessToken: oauthResponse.AccessToken, - ExpiresAt: time.Now().UTC().Add(time.Second * time.Duration(oauthResponse.ExpiresIn)).UTC(), - TokenType: oauthResponse.TokenType, - } - - return nil -} - -var lastStreamState *model.TwitchStreamInfo -var lastStreamStateAt time.Time - -func GetTwitchStatus(app *model.AppState, broadcaster string) (*model.TwitchStreamInfo, error) { - if lastStreamState != nil && time.Now().UTC().Before(lastStreamStateAt.Add(time.Minute)) { - return lastStreamState, nil - } - - fmt.Print("MAKING COSTLY REQUEST TO TWITCH.TV API...\n") - - requestUrl, _ := url.Parse(TWITCH_API_BASE + "streams") - requestUrl.RawQuery = url.Values{ - "user_login": []string{ broadcaster }, - }.Encode() - req, _ := http.NewRequest(http.MethodGet, requestUrl.String(), nil) - req.Header.Set("Client-Id", app.Config.Twitch.ClientID) - req.Header.Set("Authorization", "Bearer " + app.Twitch.Token.AccessToken) - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - type StreamsResponse struct { - Data []model.TwitchStreamInfo `json:"data"` - } - streamInfo := StreamsResponse{} - err = json.NewDecoder(res.Body).Decode(&streamInfo) - if err != nil { - return nil, err - } - - if len(streamInfo.Data) == 0 { - return nil, nil - } - - lastStreamState = &streamInfo.Data[0] - lastStreamStateAt = time.Now().UTC() - return lastStreamState, nil -} diff --git a/main.go b/main.go index 8f37639..29539ac 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "arimelody-web/cursor" "arimelody-web/log" "arimelody-web/model" + "arimelody-web/templates" "arimelody-web/view" "github.com/jmoiron/sqlx" @@ -39,7 +40,6 @@ func main() { app := model.AppState{ Config: controller.GetConfig(), - Twitch: nil, } // initialise database connection @@ -460,11 +460,6 @@ func main() { // handle DB migrations controller.CheckDBVersionAndMigrate(app.DB) - err = controller.TwitchSetup(&app) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to set up Twitch integration: %v\n", err) - } - // initial invite code accountsCount := 0 err = app.DB.Get(&accountsCount, "SELECT count(*) FROM account") @@ -516,13 +511,49 @@ func createServeMux(app *model.AppState) *http.ServeMux { mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app))) mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app))) mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app))) - mux.Handle("/uploads/", http.StripPrefix("/uploads", view.StaticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) + mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) mux.Handle("/cursor-ws", cursor.Handler(app)) - mux.Handle("/", view.IndexHandler(app)) + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodHead { + w.WriteHeader(http.StatusOK) + return + } + + if r.URL.Path == "/" || r.URL.Path == "/index.html" { + err := templates.IndexTemplate.Execute(w, nil) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return + } + staticHandler("public").ServeHTTP(w, r) + })) return mux } +func staticHandler(directory string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path))) + + // does the file exist? + if err != nil { + if errors.Is(err, os.ErrNotExist) { + http.NotFound(w, r) + return + } + } + + // is thjs a directory? (forbidden) + if info.IsDir() { + http.NotFound(w, r) + return + } + + http.FileServer(http.Dir(directory)).ServeHTTP(w, r) + }) +} + var PoweredByStrings = []string{ "nerd rage", "estrogen", diff --git a/model/appstate.go b/model/appstate.go index 861a991..a910a29 100644 --- a/model/appstate.go +++ b/model/appstate.go @@ -21,12 +21,6 @@ type ( Secret string `toml:"secret"` } - TwitchConfig struct { - Broadcaster string `toml:"broadcaster"` - ClientID string `toml:"client_id"` - Secret string `toml:"secret"` - } - Config struct { BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` Host string `toml:"host"` @@ -35,13 +29,11 @@ type ( TrustedProxies []string `toml:"trusted_proxies"` DB DBConfig `toml:"db"` Discord DiscordConfig `toml:"discord"` - Twitch TwitchConfig `toml:"twitch"` } AppState struct { DB *sqlx.DB Config Config Log log.Logger - Twitch *TwitchState } ) diff --git a/model/twitch.go b/model/twitch.go deleted file mode 100644 index 6bca17d..0000000 --- a/model/twitch.go +++ /dev/null @@ -1,43 +0,0 @@ -package model - -import ( - "fmt" - "strings" - "time" -) - -type ( - TwitchOAuthToken struct { - AccessToken string - ExpiresAt time.Time - TokenType string - } - - TwitchState struct { - Token *TwitchOAuthToken - } - - TwitchStreamInfo struct { - ID string `json:"id"` - UserID string `json:"user_id"` - UserLogin string `json:"user_login"` - UserName string `json:"user_name"` - GameID string `json:"game_id"` - GameName string `json:"game_name"` - Type string `json:"type"` - Title string `json:"title"` - ViewerCount int `json:"viewer_count"` - StartedAt string `json:"started_at"` - Language string `json:"language"` - ThumbnailURL string `json:"thumbnail_url"` - TagIDs []string `json:"tag_ids"` - Tags []string `json:"tags"` - IsMature bool `json:"is_mature"` - } -) - -func (info *TwitchStreamInfo) Thumbnail(width int, height int) string { - res := strings.Replace(info.ThumbnailURL, "{width}", fmt.Sprintf("%d", width), 1) - res = strings.Replace(res, "{height}", fmt.Sprintf("%d", height), 1) - return res -} diff --git a/public/style/colours.css b/public/style/colours.css index de63198..2bf607d 100644 --- a/public/style/colours.css +++ b/public/style/colours.css @@ -6,7 +6,6 @@ --secondary: #f8e05b; --tertiary: #f788fe; --links: #5eb2ff; - --live: #fd3737; } @media (prefers-color-scheme: light) { diff --git a/public/style/header.css b/public/style/header.css index f399d4d..531649a 100644 --- a/public/style/header.css +++ b/public/style/header.css @@ -25,6 +25,7 @@ nav { flex-grow: 1; display: flex; gap: .5em; + cursor: pointer; } img#header-icon { @@ -35,19 +36,19 @@ img#header-icon { } #header-text { + width: 11em; display: flex; flex-direction: column; justify-content: center; + flex-grow: 1; } #header-text h1 { - width: fit-content; margin: 0; font-size: 1em; } #header-text h2 { - width: fit-content; height: 1.2em; line-height: 1.2em; margin: 0; diff --git a/public/style/index.css b/public/style/index.css index f60fdb7..6edb362 100644 --- a/public/style/index.css +++ b/public/style/index.css @@ -222,84 +222,3 @@ div#web-buttons { transform: translate(-2px, -2px); box-shadow: 1px 1px 0 #eee, 2px 2px 0 #eee; } - -#live-banner { - margin: 1em 0 2em 0; - padding: 1em; - border-radius: 4px; - border: 1px solid var(--primary); - box-shadow: 0 0 8px var(--primary); -} - -#live-banner h2 { - margin: 0 0 .4em 0; - color: var(--on-background); -} - -#live-banner p { - margin: 0; -} - -.live-highlight { - color: var(--primary); -} - -.live-preview { - display: flex; - flex-direction: row; - justify-content: center; - gap: 1em; -} - -.live-preview div:first-of-type { - text-align: center; -} - -.live-thumbnail { - border-radius: 4px; -} - -.live-button { - margin: .2em; - padding: .4em .5em; - display: inline-block; - color: var(--primary); - border: 1px solid var(--primary); - border-radius: 4px; - transition: color .1s linear, background-color .1s linear, box-shadow .1s linear; -} - -.live-button:hover { - color: var(--background); - background-color: var(--primary); - box-shadow: 0 0 8px var(--primary); - text-decoration: none; -} - -.live-info { - display: flex; - flex-direction: column; - gap: .3em; - overflow-x: hidden; -} - -.live-game { - overflow: hidden; - text-wrap: nowrap; - text-overflow: ellipsis; -} - -.live-game .live-game-prefix { - opacity: .8; -} - -.live-title { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.live-viewers { - opacity: .5; -} diff --git a/view/index.go b/view/index.go deleted file mode 100644 index 995de44..0000000 --- a/view/index.go +++ /dev/null @@ -1,45 +0,0 @@ -package view - -import ( - "arimelody-web/controller" - "arimelody-web/model" - "arimelody-web/templates" - "fmt" - "net/http" - "os" -) - -func IndexHandler(app *model.AppState) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodHead { - w.WriteHeader(http.StatusOK) - return - } - - type IndexData struct { - TwitchStatus *model.TwitchStreamInfo - } - - var err error - var twitchStatus *model.TwitchStreamInfo = nil - if len(app.Config.Twitch.Broadcaster) > 0 { - twitchStatus, err = controller.GetTwitchStatus(app, app.Config.Twitch.Broadcaster) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to get Twitch status for %s: %v\n", app.Config.Twitch.Broadcaster, err) - } - } - - if r.URL.Path == "/" || r.URL.Path == "/index.html" { - err := templates.IndexTemplate.Execute(w, IndexData{ - TwitchStatus: twitchStatus, - }) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to render index page: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - return - } - - StaticHandler("public").ServeHTTP(w, r) - }) -} diff --git a/view/static.go b/view/static.go deleted file mode 100644 index 52263a2..0000000 --- a/view/static.go +++ /dev/null @@ -1,31 +0,0 @@ -package view - -import ( - "errors" - "net/http" - "os" - "path/filepath" -) - -func StaticHandler(directory string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path))) - - // does the file exist? - if err != nil { - if errors.Is(err, os.ErrNotExist) { - http.NotFound(w, r) - return - } - } - - // is thjs a directory? (forbidden) - if info.IsDir() { - http.NotFound(w, r) - return - } - - http.FileServer(http.Dir(directory)).ServeHTTP(w, r) - }) -} - diff --git a/views/footer.html b/views/footer.html index eccf125..217c4b5 100644 --- a/views/footer.html +++ b/views/footer.html @@ -2,12 +2,7 @@ diff --git a/views/header.html b/views/header.html index 03a8384..291b8ac 100644 --- a/views/header.html +++ b/views/header.html @@ -23,6 +23,9 @@