diff --git a/api.go b/api.go index 2e20b2e..cff593f 100644 --- a/api.go +++ b/api.go @@ -108,31 +108,33 @@ func (app *appContext) checkInvites() { changed := false for code, data := range app.storage.invites { expiry := data.ValidTill - if current_time.After(expiry) { - app.debug.Printf("Housekeeping: Deleting old invite %s", code) - notify := data.Notify - if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { - app.debug.Printf("%s: Expiry notification", code) - for address, settings := range notify { - if settings["notify-expiry"] { - go func() { - msg, err := app.email.constructExpiry(code, data, app) - if err != nil { - app.err.Printf("%s: Failed to construct expiry notification", code) - app.debug.Printf("Error: %s", err) - } else if err := app.email.send(address, msg); err != nil { - app.err.Printf("%s: Failed to send expiry notification", code) - app.debug.Printf("Error: %s", err) - } else { - app.info.Printf("Sent expiry notification to %s", address) - } - }() - } + if !current_time.After(expiry) { + continue + } + app.debug.Printf("Housekeeping: Deleting old invite %s", code) + notify := data.Notify + if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { + app.debug.Printf("%s: Expiry notification", code) + for address, settings := range notify { + if !settings["notify-expiry"] { + continue } + go func() { + msg, err := app.email.constructExpiry(code, data, app) + if err != nil { + app.err.Printf("%s: Failed to construct expiry notification", code) + app.debug.Printf("Error: %s", err) + } else if err := app.email.send(address, msg); err != nil { + app.err.Printf("%s: Failed to send expiry notification", code) + app.debug.Printf("Error: %s", err) + } else { + app.info.Printf("Sent expiry notification to %s", address) + } + }() } - changed = true - delete(app.storage.invites, code) } + changed = true + delete(app.storage.invites, code) } if changed { app.storage.storeInvites() @@ -143,63 +145,64 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool current_time := time.Now() app.storage.loadInvites() changed := false - if inv, match := app.storage.invites[code]; match { - expiry := inv.ValidTill - if current_time.After(expiry) { - app.debug.Printf("Housekeeping: Deleting old invite %s", code) - notify := inv.Notify - if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { - app.debug.Printf("%s: Expiry notification", code) - for address, settings := range notify { - if settings["notify-expiry"] { - go func() { - msg, err := app.email.constructExpiry(code, inv, app) - if err != nil { - app.err.Printf("%s: Failed to construct expiry notification", code) - app.debug.Printf("Error: %s", err) - } else if err := app.email.send(address, msg); err != nil { - app.err.Printf("%s: Failed to send expiry notification", code) - app.debug.Printf("Error: %s", err) - } else { - app.info.Printf("Sent expiry notification to %s", address) - } - }() - } + inv, match := app.storage.invites[code] + if !match { + return false + } + expiry := inv.ValidTill + if current_time.After(expiry) { + app.debug.Printf("Housekeeping: Deleting old invite %s", code) + notify := inv.Notify + if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { + app.debug.Printf("%s: Expiry notification", code) + for address, settings := range notify { + if settings["notify-expiry"] { + go func() { + msg, err := app.email.constructExpiry(code, inv, app) + if err != nil { + app.err.Printf("%s: Failed to construct expiry notification", code) + app.debug.Printf("Error: %s", err) + } else if err := app.email.send(address, msg); err != nil { + app.err.Printf("%s: Failed to send expiry notification", code) + app.debug.Printf("Error: %s", err) + } else { + app.info.Printf("Sent expiry notification to %s", address) + } + }() } } - changed = true - match = false + } + changed = true + match = false + delete(app.storage.invites, code) + } else if used { + changed = true + del := false + newInv := inv + if newInv.RemainingUses == 1 { + del = true delete(app.storage.invites, code) - } else if used { - changed = true - del := false - newInv := inv - if newInv.RemainingUses == 1 { - del = true - delete(app.storage.invites, code) - } else if newInv.RemainingUses != 0 { - // 0 means infinite i guess? - newInv.RemainingUses -= 1 - } - newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(current_time)}) - if !del { - app.storage.invites[code] = newInv - } + } else if newInv.RemainingUses != 0 { + // 0 means infinite i guess? + newInv.RemainingUses -= 1 } - if changed { - app.storage.storeInvites() + newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(current_time)}) + if !del { + app.storage.invites[code] = newInv } - return match } - return false + if changed { + app.storage.storeInvites() + } + return match } func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) { - ombiUsers, code, err := app.ombi.getUsers() + ombiUsers, code, err := app.ombi.GetUsers() if err != nil || code != 200 { return nil, code, err } - jfUser, code, err := app.jf.userById(jfID, false) + jfUser, code, err := app.jf.UserByID(jfID, false) if err != nil || code != 200 { return nil, code, err } @@ -229,14 +232,14 @@ func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, er func (app *appContext) NewUserAdmin(gc *gin.Context) { var req newUserDTO gc.BindJSON(&req) - existingUser, _, _ := app.jf.userByName(req.Username, false) + existingUser, _, _ := app.jf.UserByName(req.Username, false) if existingUser != nil { msg := fmt.Sprintf("User already exists named %s", req.Username) app.info.Printf("%s New user failed: %s", req.Username, msg) respond(401, msg, gc) return } - user, status, err := app.jf.newUser(req.Username, req.Password) + user, status, err := app.jf.NewUser(req.Username, req.Password) if !(status == 200 || status == 204) || err != nil { app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Username, status) respond(401, "Unknown error", gc) @@ -247,15 +250,15 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { id = user["Id"].(string) } if len(app.storage.policy) != 0 { - status, err = app.jf.setPolicy(id, app.storage.policy) + status, err = app.jf.SetPolicy(id, app.storage.policy) if !(status == 200 || status == 204) { app.err.Printf("%s: Failed to set user policy: Code %d", req.Username, status) } } if len(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 { - status, err = app.jf.setConfiguration(id, app.storage.configuration) + status, err = app.jf.SetConfiguration(id, app.storage.configuration) if (status == 200 || status == 204) && err == nil { - status, err = app.jf.setDisplayPreferences(id, app.storage.displayprefs) + status, err = app.jf.SetDisplayPreferences(id, app.storage.displayprefs) } else { app.err.Printf("%s: Failed to set configuration template: Code %d", req.Username, status) } @@ -267,7 +270,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { if app.config.Section("ombi").Key("enabled").MustBool(false) { app.storage.loadOmbiTemplate() if len(app.storage.ombi_template) != 0 { - errors, code, err := app.ombi.newUser(req.Username, req.Password, req.Email, app.storage.ombi_template) + errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, app.storage.ombi_template) if err != nil || code != 200 { app.info.Printf("Failed to create Ombi user (%d): %s", code, err) app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) @@ -276,7 +279,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { } } } - app.jf.cacheExpiry = time.Now() + app.jf.CacheExpiry = time.Now() } // @Summary Creates a new Jellyfin user via invite code @@ -309,14 +312,14 @@ func (app *appContext) NewUser(gc *gin.Context) { gc.Abort() return } - existingUser, _, _ := app.jf.userByName(req.Username, false) + existingUser, _, _ := app.jf.UserByName(req.Username, false) if existingUser != nil { msg := fmt.Sprintf("User already exists named %s", req.Username) app.info.Printf("%s New user failed: %s", req.Code, msg) respond(401, msg, gc) return } - user, status, err := app.jf.newUser(req.Username, req.Password) + user, status, err := app.jf.NewUser(req.Username, req.Password) if !(status == 200 || status == 204) || err != nil { app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Code, status) respond(401, "Unknown error", gc) @@ -355,16 +358,16 @@ func (app *appContext) NewUser(gc *gin.Context) { } if len(profile.Policy) != 0 { app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile) - status, err = app.jf.setPolicy(id, profile.Policy) + status, err = app.jf.SetPolicy(id, profile.Policy) if !(status == 200 || status == 204) { app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status) } } if len(profile.Configuration) != 0 && len(profile.Displayprefs) != 0 { app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile) - status, err = app.jf.setConfiguration(id, profile.Configuration) + status, err = app.jf.SetConfiguration(id, profile.Configuration) if (status == 200 || status == 204) && err == nil { - status, err = app.jf.setDisplayPreferences(id, profile.Displayprefs) + status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs) } else { app.err.Printf("%s: Failed to set configuration template: Code %d", req.Code, status) } @@ -377,7 +380,7 @@ func (app *appContext) NewUser(gc *gin.Context) { if app.config.Section("ombi").Key("enabled").MustBool(false) { app.storage.loadOmbiTemplate() if len(app.storage.ombi_template) != 0 { - errors, code, err := app.ombi.newUser(req.Username, req.Password, req.Email, app.storage.ombi_template) + errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, app.storage.ombi_template) if err != nil || code != 200 { app.info.Printf("Failed to create Ombi user (%d): %s", code, err) app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) @@ -414,7 +417,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) { ombiUser, code, err := app.getOmbiUser(userID) if code == 200 && err == nil { if id, ok := ombiUser["id"]; ok { - status, err := app.ombi.deleteUser(id.(string)) + status, err := app.ombi.DeleteUser(id.(string)) if err != nil || status != 200 { app.err.Printf("Failed to delete ombi user: %d %s", status, err) errors[userID] = fmt.Sprintf("Ombi: %d %s, ", status, err) @@ -422,7 +425,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) { } } } - status, err := app.jf.deleteUser(userID) + status, err := app.jf.DeleteUser(userID) if !(status == 200 || status == 204) || err != nil { msg := fmt.Sprintf("%d: %s", status, err) if _, ok := errors[userID]; !ok { @@ -449,7 +452,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) { } } } - app.jf.cacheExpiry = time.Now() + app.jf.CacheExpiry = time.Now() if len(errors) == len(req.Users) { respondBool(500, false, gc) app.err.Printf("Account deletion failed: %s", errors[req.Users[0]]) @@ -612,7 +615,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) { app.info.Println("Profile creation requested") var req newProfileDTO gc.BindJSON(&req) - user, status, err := app.jf.userById(req.ID, false) + user, status, err := app.jf.UserByID(req.ID, false) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get user from Jellyfin: Code %d", status) app.debug.Printf("Error: %s", err) @@ -626,7 +629,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) { app.debug.Printf("Creating profile from user \"%s\"", user["Name"].(string)) if req.Homescreen { profile.Configuration = user["Configuration"].(map[string]interface{}) - profile.Displayprefs, status, err = app.jf.getDisplayPreferences(req.ID) + profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get DisplayPrefs: Code %d", status) app.debug.Printf("Error: %s", err) @@ -844,7 +847,7 @@ func (app *appContext) GetUsers(gc *gin.Context) { app.debug.Println("Users requested") var resp getUsersDTO resp.UserList = []respUser{} - users, status, err := app.jf.getUsers(false) + users, status, err := app.jf.GetUsers(false) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.debug.Printf("Error: %s", err) @@ -879,7 +882,7 @@ func (app *appContext) GetUsers(gc *gin.Context) { // @tags Ombi func (app *appContext) OmbiUsers(gc *gin.Context) { app.debug.Println("Ombi users requested") - users, status, err := app.ombi.getUsers() + users, status, err := app.ombi.GetUsers() if err != nil || status != 200 { app.err.Printf("Failed to get users from Ombi: Code %d", status) app.debug.Printf("Error: %s", err) @@ -907,7 +910,7 @@ func (app *appContext) OmbiUsers(gc *gin.Context) { func (app *appContext) SetOmbiDefaults(gc *gin.Context) { var req ombiUser gc.BindJSON(&req) - template, code, err := app.ombi.templateByID(req.ID) + template, code, err := app.ombi.TemplateByID(req.ID) if err != nil || code != 200 || len(template) == 0 { app.err.Printf("Couldn't get user from Ombi: %d %s", code, err) respond(500, "Couldn't get user", gc) @@ -930,7 +933,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { var req modifyEmailsDTO gc.BindJSON(&req) app.debug.Println("Email modification requested") - users, status, err := app.jf.getUsers(false) + users, status, err := app.jf.GetUsers(false) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.debug.Printf("Error: %s", err) @@ -946,7 +949,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { ombiUser, code, err := app.getOmbiUser(id) if code == 200 && err == nil { ombiUser["emailAddress"] = address - code, err = app.ombi.modifyUser(ombiUser) + code, err = app.ombi.ModifyUser(ombiUser) if code != 200 || err != nil { app.err.Printf("%s: Failed to change ombi email address: %d %s", ombiUser["userName"].(string), code, err) } @@ -959,42 +962,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { respondBool(200, true, gc) } -/*func (app *appContext) SetDefaults(gc *gin.Context) { - var req defaultsReq - gc.BindJSON(&req) - userID := req.ID - user, status, err := app.jf.userById(userID, false) - if !(status == 200 || status == 204) || err != nil { - app.err.Printf("Failed to get user from Jellyfin: Code %d", status) - app.debug.Printf("Error: %s", err) - respond(500, "Couldn't get user", gc) - return - } - app.info.Printf("Getting user defaults from \"%s\"", user["Name"].(string)) - policy := user["Policy"].(map[string]interface{}) - app.storage.policy = policy - app.storage.storePolicy() - app.debug.Println("User policy template stored") - if req.Homescreen { - configuration := user["Configuration"].(map[string]interface{}) - var displayprefs map[string]interface{} - displayprefs, status, err = app.jf.getDisplayPreferences(userID) - if !(status == 200 || status == 204) || err != nil { - app.err.Printf("Failed to get DisplayPrefs: Code %d", status) - app.debug.Printf("Error: %s", err) - respond(500, "Couldn't get displayprefs", gc) - return - } - app.storage.configuration = configuration - app.storage.displayprefs = displayprefs - app.storage.storeConfiguration() - app.debug.Println("Configuration template stored") - app.storage.storeDisplayprefs() - app.debug.Println("DisplayPrefs template stored") - } - gc.JSON(200, map[string]bool{"success": true}) -}*/ - // @Summary Apply settings to a list of users, either from a profile or from another user. // @Produce json // @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings" @@ -1028,7 +995,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) { policy = app.storage.profiles[req.Profile].Policy } else if req.From == "user" { applyingFrom = "user" - user, status, err := app.jf.userById(req.ID, false) + user, status, err := app.jf.UserByID(req.ID, false) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get user from Jellyfin: Code %d", status) app.debug.Printf("Error: %s", err) @@ -1038,7 +1005,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) { applyingFrom = "\"" + user["Name"].(string) + "\"" policy = user["Policy"].(map[string]interface{}) if req.Homescreen { - displayprefs, status, err = app.jf.getDisplayPreferences(req.ID) + displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get DisplayPrefs: Code %d", status) app.debug.Printf("Error: %s", err) @@ -1054,17 +1021,17 @@ func (app *appContext) ApplySettings(gc *gin.Context) { "homescreen": map[string]string{}, } for _, id := range req.ApplyTo { - status, err := app.jf.setPolicy(id, policy) + status, err := app.jf.SetPolicy(id, policy) if !(status == 200 || status == 204) || err != nil { errors["policy"][id] = fmt.Sprintf("%d: %s", status, err) } if req.Homescreen { - status, err = app.jf.setConfiguration(id, configuration) + status, err = app.jf.SetConfiguration(id, configuration) errorString := "" if !(status == 200 || status == 204) || err != nil { errorString += fmt.Sprintf("Configuration %d: %s ", status, err) } else { - status, err = app.jf.setDisplayPreferences(id, displayprefs) + status, err = app.jf.SetDisplayPreferences(id, displayprefs) if !(status == 200 || status == 204) || err != nil { errorString += fmt.Sprintf("Displayprefs %d: %s ", status, err) } diff --git a/auth.go b/auth.go index a68dba0..47691f3 100644 --- a/auth.go +++ b/auth.go @@ -146,7 +146,7 @@ func (app *appContext) getToken(gc *gin.Context) { var status int var err error var user map[string]interface{} - user, status, err = app.authJf.authenticate(creds[0], creds[1]) + user, status, err = app.authJf.Authenticate(creds[0], creds[1]) if status != 200 || err != nil { if status == 401 || status == 400 { app.info.Println("Auth denied: Invalid username/password (Jellyfin)") diff --git a/common/common.go b/common/common.go new file mode 100644 index 0000000..43b09c3 --- /dev/null +++ b/common/common.go @@ -0,0 +1,21 @@ +package common + +import ( + "fmt" + "log" +) + +// TimeoutHandler recovers from an http timeout. +type TimeoutHandler func() + +// NewTimeoutHandler returns a new Timeout handler. +func NewTimeoutHandler(name, addr string, noFail bool) TimeoutHandler { + return func() { + out := fmt.Sprintf("Failed to authenticate with %s @ %s: Timed out", name, addr) + if noFail { + log.Print(out) + } else { + log.Fatalf(out) + } + } +} diff --git a/common/go.mod b/common/go.mod new file mode 100644 index 0000000..ec0c50a --- /dev/null +++ b/common/go.mod @@ -0,0 +1,3 @@ +module github.com/hrfee/jfa-go/common + +go 1.15 diff --git a/go.mod b/go.mod index 2adf8eb..dbee776 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,12 @@ go 1.14 replace github.com/hrfee/jfa-go/docs => ./docs +replace github.com/hrfee/jfa-go/jfapi => ./jfapi + +replace github.com/hrfee/jfa-go/common => ./common + +replace github.com/hrfee/jfa-go/ombi => ./ombi + require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible @@ -17,7 +23,10 @@ require ( github.com/go-playground/validator/v10 v10.4.0 // indirect github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.2 // indirect + github.com/hrfee/jfa-go/common v0.0.0-00010101000000-000000000000 github.com/hrfee/jfa-go/docs v0.0.0-20200927200337-7628e5d71da8 + github.com/hrfee/jfa-go/jfapi v0.0.0-00010101000000-000000000000 + github.com/hrfee/jfa-go/ombi v0.0.0-00010101000000-000000000000 github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible github.com/json-iterator/go v1.1.10 // indirect github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e diff --git a/jfapi/go.mod b/jfapi/go.mod new file mode 100644 index 0000000..df09909 --- /dev/null +++ b/jfapi/go.mod @@ -0,0 +1,7 @@ +module github.com/hrfee/jfa-go/jfapi + +go 1.15 + +replace github.com/hrfee/jfa-go/common => ../common + +require github.com/hrfee/jfa-go/common v0.0.0-00010101000000-000000000000 diff --git a/jfapi.go b/jfapi/jfapi.go similarity index 54% rename from jfapi.go rename to jfapi/jfapi.go index 236b113..5e160a0 100644 --- a/jfapi.go +++ b/jfapi/jfapi.go @@ -1,4 +1,4 @@ -package main +package jfapi import ( "bytes" @@ -7,63 +7,57 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "strings" "time" + + "github.com/hrfee/jfa-go/common" ) -type ServerInfo struct { +type serverInfo struct { LocalAddress string `json:"LocalAddress"` Name string `json:"ServerName"` Version string `json:"Version"` - Os string `json:"OperatingSystem"` - Id string `json:"Id"` + OS string `json:"OperatingSystem"` + ID string `json:"Id"` } +// Jellyfin represents a running Jellyfin instance. type Jellyfin struct { - server string - client string - version string - device string - deviceId string - useragent string - auth string - header map[string]string - serverInfo ServerInfo - username string - password string - authenticated bool - accessToken string - userId string - httpClient *http.Client - loginParams map[string]string - userCache []map[string]interface{} - cacheExpiry time.Time - cacheLength int - noFail bool -} - -func timeoutHandler(name, addr string, noFail bool) { - if r := recover(); r != nil { - out := fmt.Sprintf("Failed to authenticate with %s @ %s: Timed out", name, addr) - if noFail { - log.Printf(out) - } else { - log.Fatalf(out) - } - } + Server string + client string + version string + device string + deviceID string + useragent string + auth string + header map[string]string + ServerInfo serverInfo + Username string + password string + Authenticated bool + AccessToken string + userID string + httpClient *http.Client + loginParams map[string]string + userCache []map[string]interface{} + CacheExpiry time.Time + cacheLength int + noFail bool + timeoutHandler common.TimeoutHandler } -func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, error) { +// NewJellyfin returns a new Jellyfin object. +func NewJellyfin(server, client, version, device, deviceID string, timeoutHandler common.TimeoutHandler) (*Jellyfin, error) { jf := &Jellyfin{} - jf.server = server + jf.Server = server jf.client = client jf.version = version jf.device = device - jf.deviceId = deviceId + jf.deviceID = deviceID jf.useragent = fmt.Sprintf("%s/%s", client, version) - jf.auth = fmt.Sprintf("MediaBrowser Client=%s, Device=%s, DeviceId=%s, Version=%s", client, device, deviceId, version) + jf.timeoutHandler = timeoutHandler + 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", @@ -76,21 +70,22 @@ func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, e jf.httpClient = &http.Client{ Timeout: 10 * time.Second, } - infoUrl := fmt.Sprintf("%s/System/Info/Public", server) - req, _ := http.NewRequest("GET", infoUrl, nil) + infoURL := fmt.Sprintf("%s/System/Info/Public", server) + req, _ := http.NewRequest("GET", infoURL, nil) resp, err := jf.httpClient.Do(req) - defer timeoutHandler("Jellyfin", jf.server, jf.noFail) + defer jf.timeoutHandler() if err == nil { data, _ := ioutil.ReadAll(resp.Body) - json.Unmarshal(data, &jf.serverInfo) + json.Unmarshal(data, &jf.ServerInfo) } jf.cacheLength = 30 - jf.cacheExpiry = time.Now() + jf.CacheExpiry = time.Now() return jf, nil } -func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) { - jf.username = username +// Authenticate attempts to authenticate using a username & password +func (jf *Jellyfin) Authenticate(username, password string) (map[string]interface{}, int, error) { + jf.Username = username jf.password = password jf.loginParams = map[string]string{ "Username": username, @@ -105,9 +100,9 @@ func (jf *Jellyfin) authenticate(username, password string) (map[string]interfac return nil, 0, err } // loginParams, _ := json.Marshal(jf.loginParams) - url := fmt.Sprintf("%s/Users/authenticatebyname", jf.server) + url := fmt.Sprintf("%s/Users/authenticatebyname", jf.Server) req, err := http.NewRequest("POST", url, buffer) - defer timeoutHandler("Jellyfin", jf.server, jf.noFail) + defer jf.timeoutHandler() if err != nil { return nil, 0, err } @@ -128,16 +123,16 @@ func (jf *Jellyfin) authenticate(username, password string) (map[string]interfac } var respData map[string]interface{} json.NewDecoder(data).Decode(&respData) - jf.accessToken = respData["AccessToken"].(string) + jf.AccessToken = respData["AccessToken"].(string) user := respData["User"].(map[string]interface{}) - jf.userId = respData["User"].(map[string]interface{})["Id"].(string) - jf.auth = fmt.Sprintf("MediaBrowser Client=\"%s\", Device=\"%s\", DeviceId=\"%s\", Version=\"%s\", Token=\"%s\"", jf.client, jf.device, jf.deviceId, jf.version, jf.accessToken) + jf.userID = respData["User"].(map[string]interface{})["Id"].(string) + jf.auth = fmt.Sprintf("MediaBrowser Client=\"%s\", Device=\"%s\", DeviceId=\"%s\", Version=\"%s\", Token=\"%s\"", jf.client, jf.device, jf.deviceID, jf.version, jf.AccessToken) jf.header["X-Emby-Authorization"] = jf.auth - jf.authenticated = true + jf.Authenticated = true return user, resp.StatusCode, nil } -func (jf *Jellyfin) _get(url string, params map[string]string) (string, int, error) { +func (jf *Jellyfin) get(url string, params map[string]string) (string, int, error) { var req *http.Request if params != nil { jsonParams, _ := json.Marshal(params) @@ -149,13 +144,13 @@ func (jf *Jellyfin) _get(url string, params map[string]string) (string, int, err req.Header.Add(name, value) } resp, err := jf.httpClient.Do(req) - defer timeoutHandler("Jellyfin", jf.server, jf.noFail) + defer jf.timeoutHandler() if err != nil || resp.StatusCode != 200 { - if resp.StatusCode == 401 && jf.authenticated { - jf.authenticated = false - _, _, authErr := jf.authenticate(jf.username, jf.password) + if resp.StatusCode == 401 && jf.Authenticated { + jf.Authenticated = false + _, _, authErr := jf.Authenticate(jf.Username, jf.password) if authErr == nil { - v1, v2, v3 := jf._get(url, params) + v1, v2, v3 := jf.get(url, params) return v1, v2, v3 } } @@ -164,9 +159,6 @@ func (jf *Jellyfin) _get(url string, params map[string]string) (string, int, err defer resp.Body.Close() var data io.Reader encoding := resp.Header.Get("Content-Encoding") - if TEST { - fmt.Println("response encoding:", encoding) - } switch encoding { case "gzip": data, _ = gzip.NewReader(resp.Body) @@ -180,20 +172,20 @@ func (jf *Jellyfin) _get(url string, params map[string]string) (string, int, err return buf.String(), resp.StatusCode, nil } -func (jf *Jellyfin) _post(url string, data map[string]interface{}, response bool) (string, int, error) { +func (jf *Jellyfin) post(url string, data map[string]interface{}, response bool) (string, int, error) { params, _ := json.Marshal(data) req, _ := http.NewRequest("POST", url, bytes.NewBuffer(params)) for name, value := range jf.header { req.Header.Add(name, value) } resp, err := jf.httpClient.Do(req) - defer timeoutHandler("Jellyfin", jf.server, jf.noFail) + defer jf.timeoutHandler() if err != nil || resp.StatusCode != 200 { - if resp.StatusCode == 401 && jf.authenticated { - jf.authenticated = false - _, _, authErr := jf.authenticate(jf.username, jf.password) + if resp.StatusCode == 401 && jf.Authenticated { + jf.Authenticated = false + _, _, authErr := jf.Authenticate(jf.Username, jf.password) if authErr == nil { - v1, v2, v3 := jf._post(url, data, response) + v1, v2, v3 := jf.post(url, data, response) return v1, v2, v3 } } @@ -215,45 +207,48 @@ func (jf *Jellyfin) _post(url string, data map[string]interface{}, response bool return "", resp.StatusCode, nil } -func (jf *Jellyfin) deleteUser(id string) (int, error) { - url := fmt.Sprintf("%s/Users/%s", jf.server, id) +// DeleteUser deletes the user corresponding to the provided ID. +func (jf *Jellyfin) DeleteUser(id string) (int, error) { + url := fmt.Sprintf("%s/Users/%s", jf.Server, id) req, _ := http.NewRequest("DELETE", url, nil) for name, value := range jf.header { req.Header.Add(name, value) } resp, err := jf.httpClient.Do(req) - defer timeoutHandler("Jellyfin", jf.server, jf.noFail) + defer jf.timeoutHandler() return resp.StatusCode, err } -func (jf *Jellyfin) getUsers(public bool) ([]map[string]interface{}, int, error) { +// GetUsers returns all (visible) users on the Jellyfin instance. +func (jf *Jellyfin) GetUsers(public bool) ([]map[string]interface{}, int, error) { var result []map[string]interface{} var data string var status int var err error - if time.Now().After(jf.cacheExpiry) { + if time.Now().After(jf.CacheExpiry) { if public { - url := fmt.Sprintf("%s/users/public", jf.server) - data, status, err = jf._get(url, nil) + url := fmt.Sprintf("%s/users/public", jf.Server) + data, status, err = jf.get(url, nil) } else { - url := fmt.Sprintf("%s/users", jf.server) - data, status, err = jf._get(url, jf.loginParams) + url := fmt.Sprintf("%s/users", jf.Server) + data, status, err = jf.get(url, jf.loginParams) } if err != nil || status != 200 { return nil, status, err } json.Unmarshal([]byte(data), &result) jf.userCache = result - jf.cacheExpiry = time.Now().Add(time.Minute * time.Duration(jf.cacheLength)) + jf.CacheExpiry = time.Now().Add(time.Minute * time.Duration(jf.cacheLength)) return result, status, nil } return jf.userCache, 200, nil } -func (jf *Jellyfin) userByName(username string, public bool) (map[string]interface{}, int, error) { +// UserByName returns the user corresponding to the provided username. +func (jf *Jellyfin) UserByName(username string, public bool) (map[string]interface{}, int, error) { var match map[string]interface{} find := func() (map[string]interface{}, int, error) { - users, status, err := jf.getUsers(public) + users, status, err := jf.GetUsers(public) if err != nil || status != 200 { return nil, status, err } @@ -266,48 +261,49 @@ func (jf *Jellyfin) userByName(username string, public bool) (map[string]interfa } match, status, err := find() if match == nil { - jf.cacheExpiry = time.Now() + jf.CacheExpiry = time.Now() match, status, err = find() } return match, status, err } -func (jf *Jellyfin) userById(userId string, public bool) (map[string]interface{}, int, error) { - if jf.cacheExpiry.After(time.Now()) { +// UserByID returns the user corresponding to the provided ID. +func (jf *Jellyfin) UserByID(userID string, public bool) (map[string]interface{}, int, error) { + if jf.CacheExpiry.After(time.Now()) { for _, user := range jf.userCache { - if user["Id"].(string) == userId { + if user["Id"].(string) == userID { return user, 200, nil } } } if public { - users, status, err := jf.getUsers(public) + users, status, err := jf.GetUsers(public) if err != nil || status != 200 { return nil, status, err } for _, user := range users { - if user["Id"].(string) == userId { + if user["Id"].(string) == userID { return user, status, nil } } return nil, status, err - } else { - var result map[string]interface{} - var data string - var status int - var err error - url := fmt.Sprintf("%s/users/%s", jf.server, userId) - data, status, err = jf._get(url, jf.loginParams) - if err != nil || status != 200 { - return nil, status, err - } - json.Unmarshal([]byte(data), &result) - return result, status, nil } + var result map[string]interface{} + var data string + var status int + var err error + url := fmt.Sprintf("%s/users/%s", jf.Server, userID) + data, status, err = jf.get(url, jf.loginParams) + if err != nil || status != 200 { + return nil, status, err + } + json.Unmarshal([]byte(data), &result) + return result, status, nil } -func (jf *Jellyfin) newUser(username, password string) (map[string]interface{}, int, error) { - url := fmt.Sprintf("%s/Users/New", jf.server) +// NewUser creates a new user with the provided username and password. +func (jf *Jellyfin) NewUser(username, password string) (map[string]interface{}, int, error) { + url := fmt.Sprintf("%s/Users/New", jf.Server) stringData := map[string]string{ "Name": username, "Password": password, @@ -316,7 +312,7 @@ func (jf *Jellyfin) newUser(username, password string) (map[string]interface{}, for key, value := range stringData { data[key] = value } - response, status, err := jf._post(url, data, true) + response, status, err := jf.post(url, data, true) var recv map[string]interface{} json.Unmarshal([]byte(response), &recv) if err != nil || !(status == 200 || status == 204) { @@ -325,24 +321,27 @@ func (jf *Jellyfin) newUser(username, password string) (map[string]interface{}, return recv, status, nil } -func (jf *Jellyfin) setPolicy(userId string, policy map[string]interface{}) (int, error) { - url := fmt.Sprintf("%s/Users/%s/Policy", jf.server, userId) - _, status, err := jf._post(url, policy, false) +// SetPolicy sets the access policy for the user corresponding to the provided ID. +func (jf *Jellyfin) SetPolicy(userID string, policy map[string]interface{}) (int, error) { + url := fmt.Sprintf("%s/Users/%s/Policy", jf.Server, userID) + _, status, err := jf.post(url, policy, false) if err != nil || status != 200 { return status, err } return status, nil } -func (jf *Jellyfin) setConfiguration(userId string, configuration map[string]interface{}) (int, error) { - url := fmt.Sprintf("%s/Users/%s/Configuration", jf.server, userId) - _, status, err := jf._post(url, configuration, false) +// SetConfiguration sets the configuration (part of homescreen layout) for the user corresponding to the provided ID. +func (jf *Jellyfin) SetConfiguration(userID string, configuration map[string]interface{}) (int, error) { + url := fmt.Sprintf("%s/Users/%s/Configuration", jf.Server, userID) + _, status, err := jf.post(url, configuration, false) return status, err } -func (jf *Jellyfin) getDisplayPreferences(userId string) (map[string]interface{}, int, error) { - url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId) - data, status, err := jf._get(url, nil) +// GetDisplayPreferences gets the displayPreferences (part of homescreen layout) for the user corresponding to the provided ID. +func (jf *Jellyfin) GetDisplayPreferences(userID string) (map[string]interface{}, int, error) { + url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.Server, userID) + data, status, err := jf.get(url, nil) if err != nil || !(status == 204 || status == 200) { return nil, status, err } @@ -354,9 +353,10 @@ func (jf *Jellyfin) getDisplayPreferences(userId string) (map[string]interface{} return displayprefs, status, nil } -func (jf *Jellyfin) setDisplayPreferences(userId string, displayprefs map[string]interface{}) (int, error) { - url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId) - _, status, err := jf._post(url, displayprefs, false) +// SetDisplayPreferences sets the displayPreferences (part of homescreen layout) for the user corresponding to the provided ID. +func (jf *Jellyfin) SetDisplayPreferences(userID string, displayprefs map[string]interface{}) (int, error) { + url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.Server, userID) + _, status, err := jf.post(url, displayprefs, false) if err != nil || !(status == 204 || status == 200) { return status, err } diff --git a/main.go b/main.go index 63dbf37..508a6cb 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,10 @@ import ( "github.com/gin-contrib/pprof" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" + "github.com/hrfee/jfa-go/common" _ "github.com/hrfee/jfa-go/docs" + "github.com/hrfee/jfa-go/jfapi" + "github.com/hrfee/jfa-go/ombi" "github.com/lithammer/shortuuid/v3" "github.com/logrusorgru/aurora/v3" swaggerFiles "github.com/swaggo/files" @@ -51,9 +54,9 @@ type appContext struct { jellyfinLogin bool users []User invalidTokens []string - jf *Jellyfin - authJf *Jellyfin - ombi *Ombi + jf *jfapi.Jellyfin + authJf *jfapi.Jellyfin + ombi *ombi.Ombi datePattern string timePattern string storage Storage @@ -139,18 +142,18 @@ var ( func test(app *appContext) { fmt.Printf("\n\n----\n\n") settings := map[string]interface{}{ - "server": app.jf.server, - "server version": app.jf.serverInfo.Version, - "server name": app.jf.serverInfo.Name, - "authenticated?": app.jf.authenticated, - "access token": app.jf.accessToken, - "username": app.jf.username, + "server": app.jf.Server, + "server version": app.jf.ServerInfo.Version, + "server name": app.jf.ServerInfo.Name, + "authenticated?": app.jf.Authenticated, + "access token": app.jf.AccessToken, + "username": app.jf.Username, } for n, v := range settings { fmt.Println(n, ":", v) } - users, status, err := app.jf.getUsers(false) - fmt.Printf("getUsers: code %d err %s maplength %d\n", status, err, len(users)) + users, status, err := app.jf.GetUsers(false) + fmt.Printf("GetUsers: code %d err %s maplength %d\n", status, err, len(users)) fmt.Printf("View output? [y/n]: ") var choice string fmt.Scanln(&choice) @@ -161,8 +164,8 @@ func test(app *appContext) { fmt.Printf("Enter a user to grab: ") var username string fmt.Scanln(&username) - user, status, err := app.jf.userByName(username, false) - fmt.Printf("userByName (%s): code %d err %s", username, status, err) + user, status, err := app.jf.UserByName(username, false) + fmt.Printf("UserByName (%s): code %d err %s", username, status, err) out, err := json.MarshalIndent(user, "", " ") fmt.Print(string(out)) } @@ -358,19 +361,14 @@ func start(asDaemon, firstCall bool) { app.debug.Println("Loading storage") - // app.storage.invite_path = filepath.Join(app.data_path, "invites.json") app.storage.invite_path = app.config.Section("files").Key("invites").String() app.storage.loadInvites() - // app.storage.emails_path = filepath.Join(app.data_path, "emails.json") app.storage.emails_path = app.config.Section("files").Key("emails").String() app.storage.loadEmails() - // app.storage.policy_path = filepath.Join(app.data_path, "user_template.json") app.storage.policy_path = app.config.Section("files").Key("user_template").String() app.storage.loadPolicy() - // app.storage.configuration_path = filepath.Join(app.data_path, "user_configuration.json") app.storage.configuration_path = app.config.Section("files").Key("user_configuration").String() app.storage.loadConfiguration() - // app.storage.displayprefs_path = filepath.Join(app.data_path, "user_displayprefs.json") app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String() app.storage.loadDisplayprefs() @@ -397,16 +395,18 @@ func start(asDaemon, firstCall bool) { if app.config.Section("ombi").Key("enabled").MustBool(false) { app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String() app.storage.loadOmbiTemplate() - app.ombi = newOmbi( - app.config.Section("ombi").Key("server").String(), + ombiServer := app.config.Section("ombi").Key("server").String() + app.ombi = ombi.NewOmbi( + ombiServer, app.config.Section("ombi").Key("api_key").String(), - true, + common.NewTimeoutHandler("Ombi", ombiServer, true), ) + } app.configBase_path = filepath.Join(app.local_path, "config-base.json") - config_base, _ := ioutil.ReadFile(app.configBase_path) - json.Unmarshal(config_base, &app.configBase) + configBase, _ := ioutil.ReadFile(app.configBase_path) + json.Unmarshal(configBase, &app.configBase) themes := map[string]string{ "Jellyfin (Dark)": fmt.Sprintf("bs%d-jf.css", app.bsVersion), @@ -435,20 +435,21 @@ func start(asDaemon, firstCall bool) { } server := app.config.Section("jellyfin").Key("server").String() - app.jf, _ = newJellyfin( + app.jf, _ = jfapi.NewJellyfin( server, app.config.Section("jellyfin").Key("client").String(), app.config.Section("jellyfin").Key("version").String(), app.config.Section("jellyfin").Key("device").String(), app.config.Section("jellyfin").Key("device_id").String(), + common.NewTimeoutHandler("Jellyfin", server, true), ) var status int - _, status, err = app.jf.authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String()) + _, status, err = app.jf.Authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String()) if status != 200 || err != nil { app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status) } app.info.Printf("Authenticated with %s", server) - app.authJf, _ = newJellyfin(server, "jfa-go", app.version, "auth", "auth") + app.authJf, _ = jfapi.NewJellyfin(server, "jfa-go", app.version, "auth", "auth", common.NewTimeoutHandler("Jellyfin", server, true)) app.loadStrftime() diff --git a/ombi/go.mod b/ombi/go.mod new file mode 100644 index 0000000..bdc5ffa --- /dev/null +++ b/ombi/go.mod @@ -0,0 +1,5 @@ +module github.com/hrfee/jfa-go/ombi + +replace github.com/hrfee/jfa-go/common => ../common + +go 1.15 diff --git a/ombi.go b/ombi/ombi.go similarity index 65% rename from ombi.go rename to ombi/ombi.go index 00067df..b045d26 100644 --- a/ombi.go +++ b/ombi/ombi.go @@ -1,4 +1,4 @@ -package main +package ombi import ( "bytes" @@ -9,36 +9,40 @@ import ( "net/http" "strings" "time" + + "github.com/hrfee/jfa-go/common" ) +// Ombi represents a running Ombi instance. type Ombi struct { - server, key string - header map[string]string - httpClient *http.Client - noFail bool - userCache []map[string]interface{} - cacheExpiry time.Time - cacheLength int + server, key string + header map[string]string + httpClient *http.Client + userCache []map[string]interface{} + cacheExpiry time.Time + cacheLength int + timeoutHandler common.TimeoutHandler } -func newOmbi(server, key string, noFail bool) *Ombi { +// NewOmbi returns an Ombi object. +func NewOmbi(server, key string, timeoutHandler common.TimeoutHandler) *Ombi { return &Ombi{ server: server, key: key, - noFail: noFail, httpClient: &http.Client{ Timeout: 10 * time.Second, }, header: map[string]string{ "ApiKey": key, }, - cacheLength: 30, - cacheExpiry: time.Now(), + cacheLength: 30, + cacheExpiry: time.Now(), + timeoutHandler: timeoutHandler, } } // does a GET and returns the response as a string. -func (ombi *Ombi) _getJSON(url string, params map[string]string) (string, int, error) { +func (ombi *Ombi) getJSON(url string, params map[string]string) (string, int, error) { if ombi.key == "" { return "", 401, fmt.Errorf("No API key provided") } @@ -53,7 +57,7 @@ func (ombi *Ombi) _getJSON(url string, params map[string]string) (string, int, e req.Header.Add(name, value) } resp, err := ombi.httpClient.Do(req) - defer timeoutHandler("Ombi", ombi.server, ombi.noFail) + defer ombi.timeoutHandler() if err != nil || resp.StatusCode != 200 { if resp.StatusCode == 401 { return "", 401, fmt.Errorf("Invalid API Key") @@ -77,7 +81,7 @@ func (ombi *Ombi) _getJSON(url string, params map[string]string) (string, int, e } // does a POST and optionally returns response as string. Returns a string instead of an io.reader bcs i couldn't get it working otherwise. -func (ombi *Ombi) _send(mode string, url string, data map[string]interface{}, response bool) (string, int, error) { +func (ombi *Ombi) send(mode string, url string, data map[string]interface{}, response bool) (string, int, error) { responseText := "" params, _ := json.Marshal(data) req, _ := http.NewRequest(mode, url, bytes.NewBuffer(params)) @@ -86,7 +90,7 @@ func (ombi *Ombi) _send(mode string, url string, data map[string]interface{}, re req.Header.Add(name, value) } resp, err := ombi.httpClient.Do(req) - defer timeoutHandler("Ombi", ombi.server, ombi.noFail) + defer ombi.timeoutHandler() if err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201) { if resp.StatusCode == 401 { return "", 401, fmt.Errorf("Invalid API Key") @@ -112,24 +116,26 @@ func (ombi *Ombi) _send(mode string, url string, data map[string]interface{}, re return responseText, resp.StatusCode, nil } -func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool) (string, int, error) { - return ombi._send("POST", url, data, response) +func (ombi *Ombi) post(url string, data map[string]interface{}, response bool) (string, int, error) { + return ombi.send("POST", url, data, response) } -func (ombi *Ombi) _put(url string, data map[string]interface{}, response bool) (string, int, error) { - return ombi._send("PUT", url, data, response) +func (ombi *Ombi) put(url string, data map[string]interface{}, response bool) (string, int, error) { + return ombi.send("PUT", url, data, response) } -func (ombi *Ombi) modifyUser(user map[string]interface{}) (status int, err error) { +// ModifyUser applies the given modified user object to the corresponding user. +func (ombi *Ombi) ModifyUser(user map[string]interface{}) (status int, err error) { if _, ok := user["id"]; !ok { err = fmt.Errorf("No ID provided") return } - _, status, err = ombi._put(ombi.server+"/api/v1/Identity", user, false) + _, status, err = ombi.put(ombi.server+"/api/v1/Identity", user, false) return } -func (ombi *Ombi) deleteUser(id string) (code int, err error) { +// DeleteUser deletes the user corresponding to the given ID. +func (ombi *Ombi) DeleteUser(id string) (code int, err error) { url := fmt.Sprintf("%s/api/v1/Identity/%s", ombi.server, id) req, _ := http.NewRequest("DELETE", url, nil) req.Header.Add("Content-Type", "application/json") @@ -137,21 +143,21 @@ func (ombi *Ombi) deleteUser(id string) (code int, err error) { req.Header.Add(name, value) } resp, err := ombi.httpClient.Do(req) - defer timeoutHandler("Ombi", ombi.server, ombi.noFail) + defer ombi.timeoutHandler() return resp.StatusCode, err } -// gets an ombi user by their ID. -func (ombi *Ombi) userByID(id string) (result map[string]interface{}, code int, err error) { - resp, code, err := ombi._getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil) +// UserByID returns the user corresponding to the provided ID. +func (ombi *Ombi) UserByID(id string) (result map[string]interface{}, code int, err error) { + resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil) json.Unmarshal([]byte(resp), &result) return } -// gets a list of all users. -func (ombi *Ombi) getUsers() ([]map[string]interface{}, int, error) { +// GetUsers returns all users on the Ombi instance. +func (ombi *Ombi) GetUsers() ([]map[string]interface{}, int, error) { if time.Now().After(ombi.cacheExpiry) { - resp, code, err := ombi._getJSON(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil) + resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil) var result []map[string]interface{} json.Unmarshal([]byte(resp), &result) ombi.userCache = result @@ -175,9 +181,9 @@ var stripFromOmbi = []string{ "userName", } -// returns a template based on the user corresponding to the provided ID's settings. -func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code int, err error) { - result, code, err = ombi.userByID(id) +// TemplateByID returns a template based on the user corresponding to the provided ID's settings. +func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, code int, err error) { + result, code, err = ombi.UserByID(id) if err != nil || code != 200 { return } @@ -194,14 +200,14 @@ func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code i return } -// creates a new user. -func (ombi *Ombi) newUser(username, password, email string, template map[string]interface{}) ([]string, int, error) { +// NewUser creates a new user with the given username, password and email address. +func (ombi *Ombi) NewUser(username, password, email string, template map[string]interface{}) ([]string, int, error) { url := fmt.Sprintf("%s/api/v1/Identity", ombi.server) user := template user["userName"] = username user["password"] = password user["emailAddress"] = email - resp, code, err := ombi._post(url, user, true) + resp, code, err := ombi.post(url, user, true) var data map[string]interface{} json.Unmarshal([]byte(resp), &data) if err != nil || code != 200 { diff --git a/pwreset.go b/pwreset.go index 915ea09..2ca7e1a 100644 --- a/pwreset.go +++ b/pwreset.go @@ -59,7 +59,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) { } app.info.Printf("New password reset for user \"%s\"", pwr.Username) if currentTime := time.Now(); pwr.Expiry.After(currentTime) { - user, status, err := app.jf.userByName(pwr.Username, false) + user, status, err := app.jf.UserByName(pwr.Username, false) if !(status == 200 || status == 204) || err != nil { app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.debug.Printf("Error: %s", err) diff --git a/setup.go b/setup.go index 4c6c3df..2822882 100644 --- a/setup.go +++ b/setup.go @@ -2,6 +2,8 @@ package main import ( "github.com/gin-gonic/gin" + "github.com/hrfee/jfa-go/common" + "github.com/hrfee/jfa-go/jfapi" ) type testReq struct { @@ -13,9 +15,8 @@ type testReq struct { func (app *appContext) TestJF(gc *gin.Context) { var req testReq gc.BindJSON(&req) - tempjf, _ := newJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth") - tempjf.noFail = true - _, status, err := tempjf.authenticate(req.Username, req.Password) + tempjf, _ := jfapi.NewJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth", common.NewTimeoutHandler("authJF", req.Host, true)) + _, status, err := tempjf.Authenticate(req.Username, req.Password) if !(status == 200 || status == 204) || err != nil { app.info.Printf("Auth failed with code %d (%s)", status, err) gc.JSON(401, map[string]bool{"success": false})