diff --git a/jfapi/jfapi.go b/jfapi/jfapi.go index 27c5586..c75615d 100644 --- a/jfapi/jfapi.go +++ b/jfapi/jfapi.go @@ -57,7 +57,7 @@ func NewJellyfin(server, client, version, device, deviceID string, timeoutHandle jf.deviceID = deviceID jf.useragent = fmt.Sprintf("%s/%s", client, version) jf.timeoutHandler = timeoutHandler - jf.auth = fmt.Sprintf("MediaBrowser Client=%s, Device=%s, DeviceId=%s, Version=%s", client, device, deviceID, version) + jf.auth = fmt.Sprintf("MediaBrowser Client=\"%s\", Device=\"%s\", DeviceId=\"%s\", Version=\"%s\"", client, device, deviceID, version) jf.header = map[string]string{ "Accept": "application/json", "Content-type": "application/json; charset=UTF-8", diff --git a/main.go b/main.go index 78da16a..7c538ae 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "os/signal" "path/filepath" "runtime" + "strconv" "strings" "time" @@ -460,6 +461,47 @@ func start(asDaemon, firstCall bool) { app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status) } app.info.Printf("Authenticated with %s", server) + // from 10.7.0, jellyfin hyphenates user IDs. This checks if the version is equal or higher. + checkVersion := func(version string) int { + numberStrings := strings.Split(version, ".") + n := 0 + for _, s := range numberStrings { + num, err := strconv.Atoi(s) + if err == nil { + n += num + } + } + return n + } + if checkVersion(app.jf.ServerInfo.Version) >= checkVersion("10.7.0") { + noHyphens := true + for id := range app.storage.emails { + if strings.Contains(id, "-") { + noHyphens = false + break + } + } + if noHyphens { + app.info.Println(aurora.Yellow("From Jellyfin 10.7.0 onwards, user IDs are hyphenated.\nYour emails.json file will be modified to match this new format.\nA backup will be placed next to the file.\n")) + time.Sleep(time.Second * time.Duration(3)) + newEmails, status, err := app.upgradeEmailStorage(app.storage.emails) + if status != 200 || err != nil { + app.err.Printf("Failed to get users from Jellyfin: Code %d", status) + app.debug.Printf("Error: %s", err) + app.err.Fatalf("Couldn't upgrade emails.json") + } + bakFile := app.storage.emails_path + ".bak" + err = storeJSON(bakFile, app.storage.emails) + if err != nil { + app.err.Fatalf("couldn't store emails.json backup: %s", err) + } + app.storage.emails = newEmails + err = app.storage.storeEmails() + if err != nil { + app.err.Fatalf("couldn't store emails.json: %s", err) + } + } + } app.authJf, _ = jfapi.NewJellyfin(server, "jfa-go", app.version, "auth", "auth", common.NewTimeoutHandler("Jellyfin", server, true), cacheTimeout) app.loadStrftime() diff --git a/scss/bs4/bs4-jf.scss b/scss/bs4/bs4-jf.scss index 73146ac..a9d470c 100644 --- a/scss/bs4/bs4-jf.scss +++ b/scss/bs4/bs4-jf.scss @@ -1,145 +1,4 @@ -$jf-blue: rgb(0, 164, 220); -$jf-blue-hover: rgba(0, 164, 220, 0.2); -$jf-blue-focus: rgb(12, 176, 232); -$jf-blue-light: #4bb3dd; - -$jf-red: rgb(204, 0, 0); -$jf-red-light: #e12026; -$jf-yellower: #ffc107; -$jf-yellow: #e1b222; -$jf-orange: #ff870f; -$jf-green: #6fbd45; -$jf-green-dark: #008040; - - -$jf-black: #101010; // 16 16 16 -$jf-gray-90: #202020; // 32 32 32 -$jf-gray-80: #242424; // jf-card 36 36 36 -$jf-gray-70: #292929; // jf-input 41 41 41 -$jf-gray-60: #303030; // jf-button 48 48 48 -$jf-gray-50: #383838; // jf-button-focus 56 56 56 -$jf-text-bold: rgba(255, 255, 255, 0.87); -$jf-text-primary: rgba(255, 255, 255, 0.8); -$jf-text-secondary: rgb(153, 153, 153); - -$primary: $jf-blue; -$secondary: $jf-gray-50; -$success: $jf-green-dark; -$danger: $jf-red-light; -$light: $jf-text-primary; -$dark: $jf-gray-90; -$info: $jf-yellow; -$warning: $jf-yellower; - - - -$enable-gradients: false; -$enable-shadows: false; - -$enable-rounded: false; -$body-bg: $jf-black; -$body-color: $jf-text-primary; -$border-color: $jf-gray-60; -$component-active-color: $jf-text-bold; -$component-active-bg: $jf-blue-focus; -$text-muted: $jf-text-secondary; -$link-color: $jf-blue-focus; -$btn-link-disabled-color: $jf-text-secondary; -$input-bg: $jf-gray-90; -$input-color: $jf-text-primary; -$input-focus-bg: $jf-gray-60; -$input-focus-border-color: $jf-blue-focus; -$input-disabled-bg: $jf-gray-70; -input:disabled { - color: $text-muted; -} -$input-border-color: $jf-gray-60; -$input-placeholder-color: $text-muted; - -$form-check-input-bg: $jf-gray-60; -$form-check-input-border: $jf-gray-50; -$form-check-input-checked-color: $jf-blue-focus; -$form-check-input-checked-bg-color: $jf-blue-hover; - -$input-group-addon-bg: $input-bg; - -$form-select-disabled-color: $jf-text-secondary; -$form-select-disabled-bg: $input-disabled-bg; -$form-select-indicator-color: $jf-gray-50; - -$card-bg: $jf-gray-80; -$card-border-color: null; - -$tooltip-color: $jf-text-bold; -$tooltip-bg: $jf-gray-50; - -$modal-content-bg: $jf-gray-80; -$modal-content-border-color: $jf-gray-50; -$modal-header-border-color: null; -$modal-footer-border-color: null; - -$list-group-bg: $card-bg; -$list-group-border-color: $jf-gray-50; -$list-group-hover-bg: $jf-blue-hover; -$list-group-active-bg: $jf-blue-focus; -$list-group-action-color: $jf-text-primary; -$list-group-action-hover-color: $jf-text-bold; -$list-group-action-active-color: $jf-text-bold; -$list-group-action-active-bg: $jf-blue-focus; - -// idk why but i had to put these above and below the import -.list-group-item-danger { - color: $jf-text-bold; - background-color: $danger; -} - -.list-group-item-success { - color: $jf-text-bold; - background-color: $success; -} - +@import "../jf-pre.scss"; @import "../../node_modules/bootstrap4/scss/bootstrap"; - -.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active { - color: $jf-text-bold; -} - -.close { - color: $jf-text-secondary; -} - -.close:hover, .close:active { - color: $jf-text-primary; -} - -.icon-button { - color: $text-muted; -} - -.icon-button:hover { - color: $jf-text-bold; -} - -.icon-button { - color: $text-muted; -} - -.text-bright { - color: $jf-text-bold; -} - -.list-group-item-danger { - color: $jf-text-bold; - background-color: $danger; -} - -.list-group-item-success { - color: $jf-text-bold; - background-color: $success; -} - -.nav-link:hover { - background-color: $jf-blue-hover; -} - +@import "../jf-post.scss"; @import "../base.scss"; diff --git a/scss/bs5/bs5-jf.scss b/scss/bs5/bs5-jf.scss index ebcafde..6b4a353 100644 --- a/scss/bs5/bs5-jf.scss +++ b/scss/bs5/bs5-jf.scss @@ -1,149 +1,7 @@ -$jf-blue: rgb(0, 164, 220); -$jf-blue-hover: rgba(0, 164, 220, 0.2); -$jf-blue-focus: rgb(12, 176, 232); -$jf-blue-light: #4bb3dd; - -$jf-red: rgb(204, 0, 0); -$jf-red-light: #e12026; -$jf-yellower: #ffc107; -$jf-yellow: #e1b222; -$jf-orange: #ff870f; -$jf-green: #6fbd45; -$jf-green-dark: #008040; - - -$jf-black: #101010; // 16 16 16 -$jf-gray-90: #202020; // 32 32 32 -$jf-gray-80: #242424; // jf-card 36 36 36 -$jf-gray-70: #292929; // jf-input 41 41 41 -$jf-gray-60: #303030; // jf-button 48 48 48 -$jf-gray-50: #383838; // jf-button-focus 56 56 56 -$jf-text-bold: rgba(255, 255, 255, 0.87); -$jf-text-primary: rgba(255, 255, 255, 0.8); -$jf-text-secondary: rgb(153, 153, 153); - -$primary: $jf-blue; -$secondary: $jf-gray-50; -$success: $jf-green-dark; -$danger: $jf-red-light; -$light: $jf-text-primary; -$dark: $jf-gray-90; -$info: $jf-yellow; -$warning: $jf-yellower; - - - -$enable-gradients: false; -$enable-shadows: false; - -$enable-rounded: false; -$body-bg: $jf-black; -$body-color: $jf-text-primary; -$border-color: $jf-gray-60; -$component-active-color: $jf-text-bold; -$component-active-bg: $jf-blue-focus; -$text-muted: $jf-text-secondary; -$link-color: $jf-blue-focus; -$btn-link-disabled-color: $jf-text-secondary; -$input-bg: $jf-gray-90; -$input-color: $jf-text-primary; -$input-focus-bg: $jf-gray-60; -$input-focus-border-color: $jf-blue-focus; -$input-disabled-bg: $jf-gray-70; -input:disabled { - color: $text-muted; -} -$input-border-color: $jf-gray-60; -$input-placeholder-color: $text-muted; - -$form-check-input-bg: $jf-gray-60; -$form-check-input-border: $jf-gray-50; -$form-check-input-checked-color: $jf-blue-focus; -$form-check-input-checked-bg-color: $jf-blue-hover; - -$input-group-addon-bg: $input-bg; - -$form-select-disabled-color: $jf-text-secondary; -$form-select-disabled-bg: $input-disabled-bg; -$form-select-indicator-color: $jf-gray-50; - -$card-bg: $jf-gray-80; -$card-border-color: null; - -$tooltip-color: $jf-text-bold; -$tooltip-bg: $jf-gray-50; - -$modal-content-bg: $jf-gray-80; -$modal-content-border-color: $jf-gray-50; -$modal-header-border-color: null; -$modal-footer-border-color: null; - -$list-group-bg: $card-bg; -$list-group-border-color: $jf-gray-50; -$list-group-hover-bg: $jf-blue-hover; -$list-group-active-bg: $jf-blue-focus; -$list-group-action-color: $jf-text-primary; -$list-group-action-hover-color: $jf-text-bold; -$list-group-action-active-color: $jf-text-bold; -$list-group-action-active-bg: $jf-blue-focus; - -// idk why but i had to put these above and below the import -.list-group-item-danger { - color: $jf-text-bold; - background-color: $danger; -} - -.list-group-item-success { - color: $jf-text-bold; - background-color: $success; -} - -@import "../../node_modules/bootstrap/scss/bootstrap"; - -.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active { - color: $jf-text-bold; -} - -.close { - color: $jf-text-secondary; -} - -.close:hover, .close:active { - color: $jf-text-primary; -} - -.icon-button { - color: $text-muted; -} - -.icon-button:hover { - color: $jf-text-bold; -} - -.icon-button:active { - color: $text-muted; -} - -.text-bright { - color: $jf-text-bold; -} - -.list-group-item-danger { - color: $jf-text-bold; - background-color: $danger; -} - -.list-group-item-success { - color: $jf-text-bold; - background-color: $success; -} - -.nav-link:hover { - background-color: $jf-blue-hover; -} - .btn-close { filter: invert(80%); } - +@import "../jf-pre.scss"; +@import "../../node_modules/bootstrap/scss/bootstrap"; +@import "../jf-post.scss"; @import "../base.scss"; diff --git a/scss/jf-post.scss b/scss/jf-post.scss new file mode 100644 index 0000000..5a82ff1 --- /dev/null +++ b/scss/jf-post.scss @@ -0,0 +1,41 @@ +.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active { + color: $jf-text-bold; +} + +.close { + color: $jf-text-secondary; +} + +.close:hover, .close:active { + color: $jf-text-primary; +} + +.icon-button { + color: $text-muted; +} + +.icon-button:hover { + color: $jf-text-bold; +} + +.icon-button:active { + color: $text-muted; +} + +.text-bright { + color: $jf-text-bold; +} + +.list-group-item-danger { + color: $jf-text-bold; + background-color: $danger; +} + +.list-group-item-success { + color: $jf-text-bold; + background-color: $success; +} + +.nav-link:hover { + background-color: $jf-blue-hover; +} diff --git a/scss/jf-pre.scss b/scss/jf-pre.scss new file mode 100644 index 0000000..205e77d --- /dev/null +++ b/scss/jf-pre.scss @@ -0,0 +1,101 @@ +$jf-blue: rgb(0, 164, 220); +$jf-blue-hover: rgba(0, 164, 220, 0.2); +$jf-blue-focus: rgb(12, 176, 232); +$jf-blue-light: #4bb3dd; + +$jf-red: rgb(204, 0, 0); +$jf-red-light: #e12026; +$jf-yellower: #ffc107; +$jf-yellow: #e1b222; +$jf-orange: #ff870f; +$jf-green: #6fbd45; +$jf-green-dark: #008040; + + +$jf-black: #101010; // 16 16 16 +$jf-gray-90: #202020; // 32 32 32 +$jf-gray-80: #242424; // jf-card 36 36 36 +$jf-gray-70: #292929; // jf-input 41 41 41 +$jf-gray-60: #303030; // jf-button 48 48 48 +$jf-gray-50: #383838; // jf-button-focus 56 56 56 +$jf-text-bold: rgba(255, 255, 255, 0.87); +$jf-text-primary: rgba(255, 255, 255, 0.8); +$jf-text-secondary: rgb(153, 153, 153); + +$primary: $jf-blue; +$secondary: $jf-gray-50; +$success: $jf-green-dark; +$danger: $jf-red-light; +$light: $jf-text-primary; +$dark: $jf-gray-90; +$info: $jf-yellow; +$warning: $jf-yellower; + + + +$enable-gradients: false; +$enable-shadows: false; + +$enable-rounded: false; +$body-bg: $jf-black; +$body-color: $jf-text-primary; +$border-color: $jf-gray-60; +$component-active-color: $jf-text-bold; +$component-active-bg: $jf-blue-focus; +$text-muted: $jf-text-secondary; +$link-color: $jf-blue-focus; +$btn-link-disabled-color: $jf-text-secondary; +$input-bg: $jf-gray-90; +$input-color: $jf-text-primary; +$input-focus-bg: $jf-gray-60; +$input-focus-border-color: $jf-blue-focus; +$input-disabled-bg: $jf-gray-70; +input:disabled { + color: $text-muted; +} +$input-border-color: $jf-gray-60; +$input-placeholder-color: $text-muted; + +$form-check-input-bg: $jf-gray-60; +$form-check-input-border: $jf-gray-50; +$form-check-input-checked-color: $jf-blue-focus; +$form-check-input-checked-bg-color: $jf-blue-hover; + +$input-group-addon-bg: $input-bg; + +$form-select-disabled-color: $jf-text-secondary; +$form-select-disabled-bg: $input-disabled-bg; +$form-select-indicator-color: $jf-gray-50; + +$card-bg: $jf-gray-80; +$card-border-color: null; + +$tooltip-color: $jf-text-bold; +$tooltip-bg: $jf-gray-50; + +$modal-content-bg: $jf-gray-80; +$modal-content-border-color: $jf-gray-50; +$modal-header-border-color: null; +$modal-footer-border-color: null; + +$list-group-bg: $card-bg; +$list-group-border-color: $jf-gray-50; +$list-group-hover-bg: $jf-blue-hover; +$list-group-active-bg: $jf-blue-focus; +$list-group-action-color: $jf-text-primary; +$list-group-action-hover-color: $jf-text-bold; +$list-group-action-active-color: $jf-text-bold; +$list-group-action-active-bg: $jf-blue-focus; + +// idk why but i had to put these above and below the import +.list-group-item-danger { + color: $jf-text-bold; + background-color: $danger; +} + +.list-group-item-success { + color: $jf-text-bold; + background-color: $success; +} + +// @import "../../node_modules/bootstrap/scss/bootstrap"; diff --git a/storage.go b/storage.go index b8aab62..b21e705 100644 --- a/storage.go +++ b/storage.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "log" "strconv" + "strings" "time" ) @@ -190,3 +191,23 @@ func storeJSON(path string, obj interface{}) error { } return err } + +// JF 10.7.0 added hyphens to user IDs like this and we need to upgrade email storage to match it: +// [8 chars]-[4]-[4]-[4]-[12] +// This seems consistent, but we'll grab IDs from jellyfin just in case theres some variation. +func (app *appContext) upgradeEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) { + jfUsers, status, err := app.jf.GetUsers(false) + if status != 200 || err != nil { + return nil, status, err + } + newEmails := map[string]interface{}{} + for _, user := range jfUsers { + unstripped := user["Id"].(string) + stripped := strings.ReplaceAll(unstripped, "-", "") + email, ok := old[stripped] + if ok { + newEmails[unstripped] = email + } + } + return newEmails, status, err +}