refactor; separate jfapi and ombi into modules

pull/20/head
Harvey Tindall 4 years ago
parent 0f4e77364b
commit c84ea17af4
No known key found for this signature in database
GPG Key ID: BBC65952848FB1A2

113
api.go

@ -108,13 +108,17 @@ func (app *appContext) checkInvites() {
changed := false changed := false
for code, data := range app.storage.invites { for code, data := range app.storage.invites {
expiry := data.ValidTill expiry := data.ValidTill
if current_time.After(expiry) { if !current_time.After(expiry) {
continue
}
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := data.Notify notify := data.Notify
if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code) app.debug.Printf("%s: Expiry notification", code)
for address, settings := range notify { for address, settings := range notify {
if settings["notify-expiry"] { if !settings["notify-expiry"] {
continue
}
go func() { go func() {
msg, err := app.email.constructExpiry(code, data, app) msg, err := app.email.constructExpiry(code, data, app)
if err != nil { if err != nil {
@ -129,11 +133,9 @@ func (app *appContext) checkInvites() {
}() }()
} }
} }
}
changed = true changed = true
delete(app.storage.invites, code) delete(app.storage.invites, code)
} }
}
if changed { if changed {
app.storage.storeInvites() app.storage.storeInvites()
} }
@ -143,7 +145,10 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
current_time := time.Now() current_time := time.Now()
app.storage.loadInvites() app.storage.loadInvites()
changed := false changed := false
if inv, match := app.storage.invites[code]; match { inv, match := app.storage.invites[code]
if !match {
return false
}
expiry := inv.ValidTill expiry := inv.ValidTill
if current_time.After(expiry) { if current_time.After(expiry) {
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.debug.Printf("Housekeeping: Deleting old invite %s", code)
@ -191,15 +196,13 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
return match return match
} }
return false
}
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) { 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 { if err != nil || code != 200 {
return nil, code, err 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 { if err != nil || code != 200 {
return nil, code, err 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) { func (app *appContext) NewUserAdmin(gc *gin.Context) {
var req newUserDTO var req newUserDTO
gc.BindJSON(&req) gc.BindJSON(&req)
existingUser, _, _ := app.jf.userByName(req.Username, false) existingUser, _, _ := app.jf.UserByName(req.Username, false)
if existingUser != nil { if existingUser != nil {
msg := fmt.Sprintf("User already exists named %s", req.Username) msg := fmt.Sprintf("User already exists named %s", req.Username)
app.info.Printf("%s New user failed: %s", req.Username, msg) app.info.Printf("%s New user failed: %s", req.Username, msg)
respond(401, msg, gc) respond(401, msg, gc)
return 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Username, status) app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Username, status)
respond(401, "Unknown error", gc) respond(401, "Unknown error", gc)
@ -247,15 +250,15 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
id = user["Id"].(string) id = user["Id"].(string)
} }
if len(app.storage.policy) != 0 { 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) { if !(status == 200 || status == 204) {
app.err.Printf("%s: Failed to set user policy: Code %d", req.Username, status) 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 { 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 { 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 { } else {
app.err.Printf("%s: Failed to set configuration template: Code %d", req.Username, status) 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) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.storage.loadOmbiTemplate() app.storage.loadOmbiTemplate()
if len(app.storage.ombi_template) != 0 { 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 { if err != nil || code != 200 {
app.info.Printf("Failed to create Ombi user (%d): %s", code, err) app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) 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 // @Summary Creates a new Jellyfin user via invite code
@ -309,14 +312,14 @@ func (app *appContext) NewUser(gc *gin.Context) {
gc.Abort() gc.Abort()
return return
} }
existingUser, _, _ := app.jf.userByName(req.Username, false) existingUser, _, _ := app.jf.UserByName(req.Username, false)
if existingUser != nil { if existingUser != nil {
msg := fmt.Sprintf("User already exists named %s", req.Username) msg := fmt.Sprintf("User already exists named %s", req.Username)
app.info.Printf("%s New user failed: %s", req.Code, msg) app.info.Printf("%s New user failed: %s", req.Code, msg)
respond(401, msg, gc) respond(401, msg, gc)
return 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Code, status) app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Code, status)
respond(401, "Unknown error", gc) respond(401, "Unknown error", gc)
@ -355,16 +358,16 @@ func (app *appContext) NewUser(gc *gin.Context) {
} }
if len(profile.Policy) != 0 { if len(profile.Policy) != 0 {
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile) 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) { if !(status == 200 || status == 204) {
app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status) app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status)
} }
} }
if len(profile.Configuration) != 0 && len(profile.Displayprefs) != 0 { if len(profile.Configuration) != 0 && len(profile.Displayprefs) != 0 {
app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile) 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 { if (status == 200 || status == 204) && err == nil {
status, err = app.jf.setDisplayPreferences(id, profile.Displayprefs) status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
} else { } else {
app.err.Printf("%s: Failed to set configuration template: Code %d", req.Code, status) 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) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.storage.loadOmbiTemplate() app.storage.loadOmbiTemplate()
if len(app.storage.ombi_template) != 0 { 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 { if err != nil || code != 200 {
app.info.Printf("Failed to create Ombi user (%d): %s", code, err) app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) 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) ombiUser, code, err := app.getOmbiUser(userID)
if code == 200 && err == nil { if code == 200 && err == nil {
if id, ok := ombiUser["id"]; ok { 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 { if err != nil || status != 200 {
app.err.Printf("Failed to delete ombi user: %d %s", status, err) app.err.Printf("Failed to delete ombi user: %d %s", status, err)
errors[userID] = fmt.Sprintf("Ombi: %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 { if !(status == 200 || status == 204) || err != nil {
msg := fmt.Sprintf("%d: %s", status, err) msg := fmt.Sprintf("%d: %s", status, err)
if _, ok := errors[userID]; !ok { 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) { if len(errors) == len(req.Users) {
respondBool(500, false, gc) respondBool(500, false, gc)
app.err.Printf("Account deletion failed: %s", errors[req.Users[0]]) 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") app.info.Println("Profile creation requested")
var req newProfileDTO var req newProfileDTO
gc.BindJSON(&req) 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin: Code %d", status) app.err.Printf("Failed to get user from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) 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)) app.debug.Printf("Creating profile from user \"%s\"", user["Name"].(string))
if req.Homescreen { if req.Homescreen {
profile.Configuration = user["Configuration"].(map[string]interface{}) 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs: Code %d", status) app.err.Printf("Failed to get DisplayPrefs: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -844,7 +847,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
app.debug.Println("Users requested") app.debug.Println("Users requested")
var resp getUsersDTO var resp getUsersDTO
resp.UserList = []respUser{} resp.UserList = []respUser{}
users, status, err := app.jf.getUsers(false) users, status, err := app.jf.GetUsers(false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -879,7 +882,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
// @tags Ombi // @tags Ombi
func (app *appContext) OmbiUsers(gc *gin.Context) { func (app *appContext) OmbiUsers(gc *gin.Context) {
app.debug.Println("Ombi users requested") app.debug.Println("Ombi users requested")
users, status, err := app.ombi.getUsers() users, status, err := app.ombi.GetUsers()
if err != nil || status != 200 { if err != nil || status != 200 {
app.err.Printf("Failed to get users from Ombi: Code %d", status) app.err.Printf("Failed to get users from Ombi: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -907,7 +910,7 @@ func (app *appContext) OmbiUsers(gc *gin.Context) {
func (app *appContext) SetOmbiDefaults(gc *gin.Context) { func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
var req ombiUser var req ombiUser
gc.BindJSON(&req) 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 { if err != nil || code != 200 || len(template) == 0 {
app.err.Printf("Couldn't get user from Ombi: %d %s", code, err) app.err.Printf("Couldn't get user from Ombi: %d %s", code, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
@ -930,7 +933,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
var req modifyEmailsDTO var req modifyEmailsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Println("Email modification requested") 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -946,7 +949,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
ombiUser, code, err := app.getOmbiUser(id) ombiUser, code, err := app.getOmbiUser(id)
if code == 200 && err == nil { if code == 200 && err == nil {
ombiUser["emailAddress"] = address ombiUser["emailAddress"] = address
code, err = app.ombi.modifyUser(ombiUser) code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil { if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address: %d %s", ombiUser["userName"].(string), code, err) 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) 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. // @Summary Apply settings to a list of users, either from a profile or from another user.
// @Produce json // @Produce json
// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings" // @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 policy = app.storage.profiles[req.Profile].Policy
} else if req.From == "user" { } else if req.From == "user" {
applyingFrom = "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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin: Code %d", status) app.err.Printf("Failed to get user from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -1038,7 +1005,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
applyingFrom = "\"" + user["Name"].(string) + "\"" applyingFrom = "\"" + user["Name"].(string) + "\""
policy = user["Policy"].(map[string]interface{}) policy = user["Policy"].(map[string]interface{})
if req.Homescreen { 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs: Code %d", status) app.err.Printf("Failed to get DisplayPrefs: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
@ -1054,17 +1021,17 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
"homescreen": map[string]string{}, "homescreen": map[string]string{},
} }
for _, id := range req.ApplyTo { 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 { if !(status == 200 || status == 204) || err != nil {
errors["policy"][id] = fmt.Sprintf("%d: %s", status, err) errors["policy"][id] = fmt.Sprintf("%d: %s", status, err)
} }
if req.Homescreen { if req.Homescreen {
status, err = app.jf.setConfiguration(id, configuration) status, err = app.jf.SetConfiguration(id, configuration)
errorString := "" errorString := ""
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Configuration %d: %s ", status, err) errorString += fmt.Sprintf("Configuration %d: %s ", status, err)
} else { } else {
status, err = app.jf.setDisplayPreferences(id, displayprefs) status, err = app.jf.SetDisplayPreferences(id, displayprefs)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Displayprefs %d: %s ", status, err) errorString += fmt.Sprintf("Displayprefs %d: %s ", status, err)
} }

@ -146,7 +146,7 @@ func (app *appContext) getToken(gc *gin.Context) {
var status int var status int
var err error var err error
var user map[string]interface{} 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 != 200 || err != nil {
if status == 401 || status == 400 { if status == 401 || status == 400 {
app.info.Println("Auth denied: Invalid username/password (Jellyfin)") app.info.Println("Auth denied: Invalid username/password (Jellyfin)")

@ -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)
}
}
}

@ -0,0 +1,3 @@
module github.com/hrfee/jfa-go/common
go 1.15

@ -4,6 +4,12 @@ go 1.14
replace github.com/hrfee/jfa-go/docs => ./docs 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 ( require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible 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/go-playground/validator/v10 v10.4.0 // indirect
github.com/golang/protobuf v1.4.2 github.com/golang/protobuf v1.4.2
github.com/google/uuid v1.1.2 // indirect 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/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/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible
github.com/json-iterator/go v1.1.10 // indirect github.com/json-iterator/go v1.1.10 // indirect
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e

@ -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

@ -1,4 +1,4 @@
package main package jfapi
import ( import (
"bytes" "bytes"
@ -7,63 +7,57 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/hrfee/jfa-go/common"
) )
type ServerInfo struct { type serverInfo struct {
LocalAddress string `json:"LocalAddress"` LocalAddress string `json:"LocalAddress"`
Name string `json:"ServerName"` Name string `json:"ServerName"`
Version string `json:"Version"` Version string `json:"Version"`
Os string `json:"OperatingSystem"` OS string `json:"OperatingSystem"`
Id string `json:"Id"` ID string `json:"Id"`
} }
// Jellyfin represents a running Jellyfin instance.
type Jellyfin struct { type Jellyfin struct {
server string Server string
client string client string
version string version string
device string device string
deviceId string deviceID string
useragent string useragent string
auth string auth string
header map[string]string header map[string]string
serverInfo ServerInfo ServerInfo serverInfo
username string Username string
password string password string
authenticated bool Authenticated bool
accessToken string AccessToken string
userId string userID string
httpClient *http.Client httpClient *http.Client
loginParams map[string]string loginParams map[string]string
userCache []map[string]interface{} userCache []map[string]interface{}
cacheExpiry time.Time CacheExpiry time.Time
cacheLength int cacheLength int
noFail bool noFail bool
timeoutHandler common.TimeoutHandler
} }
func timeoutHandler(name, addr string, noFail bool) { // NewJellyfin returns a new Jellyfin object.
if r := recover(); r != nil { func NewJellyfin(server, client, version, device, deviceID string, timeoutHandler common.TimeoutHandler) (*Jellyfin, error) {
out := fmt.Sprintf("Failed to authenticate with %s @ %s: Timed out", name, addr)
if noFail {
log.Printf(out)
} else {
log.Fatalf(out)
}
}
}
func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, error) {
jf := &Jellyfin{} jf := &Jellyfin{}
jf.server = server jf.Server = server
jf.client = client jf.client = client
jf.version = version jf.version = version
jf.device = device jf.device = device
jf.deviceId = deviceId jf.deviceID = deviceID
jf.useragent = fmt.Sprintf("%s/%s", client, version) 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{ jf.header = map[string]string{
"Accept": "application/json", "Accept": "application/json",
"Content-type": "application/json; charset=UTF-8", "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{ jf.httpClient = &http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
infoUrl := fmt.Sprintf("%s/System/Info/Public", server) infoURL := fmt.Sprintf("%s/System/Info/Public", server)
req, _ := http.NewRequest("GET", infoUrl, nil) req, _ := http.NewRequest("GET", infoURL, nil)
resp, err := jf.httpClient.Do(req) resp, err := jf.httpClient.Do(req)
defer timeoutHandler("Jellyfin", jf.server, jf.noFail) defer jf.timeoutHandler()
if err == nil { if err == nil {
data, _ := ioutil.ReadAll(resp.Body) data, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(data, &jf.serverInfo) json.Unmarshal(data, &jf.ServerInfo)
} }
jf.cacheLength = 30 jf.cacheLength = 30
jf.cacheExpiry = time.Now() jf.CacheExpiry = time.Now()
return jf, nil return jf, nil
} }
func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) { // Authenticate attempts to authenticate using a username & password
jf.username = username func (jf *Jellyfin) Authenticate(username, password string) (map[string]interface{}, int, error) {
jf.Username = username
jf.password = password jf.password = password
jf.loginParams = map[string]string{ jf.loginParams = map[string]string{
"Username": username, "Username": username,
@ -105,9 +100,9 @@ func (jf *Jellyfin) authenticate(username, password string) (map[string]interfac
return nil, 0, err return nil, 0, err
} }
// loginParams, _ := json.Marshal(jf.loginParams) // 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) req, err := http.NewRequest("POST", url, buffer)
defer timeoutHandler("Jellyfin", jf.server, jf.noFail) defer jf.timeoutHandler()
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -128,16 +123,16 @@ func (jf *Jellyfin) authenticate(username, password string) (map[string]interfac
} }
var respData map[string]interface{} var respData map[string]interface{}
json.NewDecoder(data).Decode(&respData) json.NewDecoder(data).Decode(&respData)
jf.accessToken = respData["AccessToken"].(string) jf.AccessToken = respData["AccessToken"].(string)
user := respData["User"].(map[string]interface{}) user := respData["User"].(map[string]interface{})
jf.userId = respData["User"].(map[string]interface{})["Id"].(string) 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.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.header["X-Emby-Authorization"] = jf.auth
jf.authenticated = true jf.Authenticated = true
return user, resp.StatusCode, nil 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 var req *http.Request
if params != nil { if params != nil {
jsonParams, _ := json.Marshal(params) 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) req.Header.Add(name, value)
} }
resp, err := jf.httpClient.Do(req) resp, err := jf.httpClient.Do(req)
defer timeoutHandler("Jellyfin", jf.server, jf.noFail) defer jf.timeoutHandler()
if err != nil || resp.StatusCode != 200 { if err != nil || resp.StatusCode != 200 {
if resp.StatusCode == 401 && jf.authenticated { if resp.StatusCode == 401 && jf.Authenticated {
jf.authenticated = false jf.Authenticated = false
_, _, authErr := jf.authenticate(jf.username, jf.password) _, _, authErr := jf.Authenticate(jf.Username, jf.password)
if authErr == nil { if authErr == nil {
v1, v2, v3 := jf._get(url, params) v1, v2, v3 := jf.get(url, params)
return v1, v2, v3 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() defer resp.Body.Close()
var data io.Reader var data io.Reader
encoding := resp.Header.Get("Content-Encoding") encoding := resp.Header.Get("Content-Encoding")
if TEST {
fmt.Println("response encoding:", encoding)
}
switch encoding { switch encoding {
case "gzip": case "gzip":
data, _ = gzip.NewReader(resp.Body) 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 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) params, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(params)) req, _ := http.NewRequest("POST", url, bytes.NewBuffer(params))
for name, value := range jf.header { for name, value := range jf.header {
req.Header.Add(name, value) req.Header.Add(name, value)
} }
resp, err := jf.httpClient.Do(req) resp, err := jf.httpClient.Do(req)
defer timeoutHandler("Jellyfin", jf.server, jf.noFail) defer jf.timeoutHandler()
if err != nil || resp.StatusCode != 200 { if err != nil || resp.StatusCode != 200 {
if resp.StatusCode == 401 && jf.authenticated { if resp.StatusCode == 401 && jf.Authenticated {
jf.authenticated = false jf.Authenticated = false
_, _, authErr := jf.authenticate(jf.username, jf.password) _, _, authErr := jf.Authenticate(jf.Username, jf.password)
if authErr == nil { if authErr == nil {
v1, v2, v3 := jf._post(url, data, response) v1, v2, v3 := jf.post(url, data, response)
return v1, v2, v3 return v1, v2, v3
} }
} }
@ -215,45 +207,48 @@ func (jf *Jellyfin) _post(url string, data map[string]interface{}, response bool
return "", resp.StatusCode, nil return "", resp.StatusCode, nil
} }
func (jf *Jellyfin) deleteUser(id string) (int, error) { // DeleteUser deletes the user corresponding to the provided ID.
url := fmt.Sprintf("%s/Users/%s", jf.server, id) func (jf *Jellyfin) DeleteUser(id string) (int, error) {
url := fmt.Sprintf("%s/Users/%s", jf.Server, id)
req, _ := http.NewRequest("DELETE", url, nil) req, _ := http.NewRequest("DELETE", url, nil)
for name, value := range jf.header { for name, value := range jf.header {
req.Header.Add(name, value) req.Header.Add(name, value)
} }
resp, err := jf.httpClient.Do(req) resp, err := jf.httpClient.Do(req)
defer timeoutHandler("Jellyfin", jf.server, jf.noFail) defer jf.timeoutHandler()
return resp.StatusCode, err 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 result []map[string]interface{}
var data string var data string
var status int var status int
var err error var err error
if time.Now().After(jf.cacheExpiry) { if time.Now().After(jf.CacheExpiry) {
if public { if public {
url := fmt.Sprintf("%s/users/public", jf.server) url := fmt.Sprintf("%s/users/public", jf.Server)
data, status, err = jf._get(url, nil) data, status, err = jf.get(url, nil)
} else { } else {
url := fmt.Sprintf("%s/users", jf.server) url := fmt.Sprintf("%s/users", jf.Server)
data, status, err = jf._get(url, jf.loginParams) data, status, err = jf.get(url, jf.loginParams)
} }
if err != nil || status != 200 { if err != nil || status != 200 {
return nil, status, err return nil, status, err
} }
json.Unmarshal([]byte(data), &result) json.Unmarshal([]byte(data), &result)
jf.userCache = 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 result, status, nil
} }
return jf.userCache, 200, 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{} var match map[string]interface{}
find := func() (map[string]interface{}, int, error) { find := func() (map[string]interface{}, int, error) {
users, status, err := jf.getUsers(public) users, status, err := jf.GetUsers(public)
if err != nil || status != 200 { if err != nil || status != 200 {
return nil, status, err return nil, status, err
} }
@ -266,48 +261,49 @@ func (jf *Jellyfin) userByName(username string, public bool) (map[string]interfa
} }
match, status, err := find() match, status, err := find()
if match == nil { if match == nil {
jf.cacheExpiry = time.Now() jf.CacheExpiry = time.Now()
match, status, err = find() match, status, err = find()
} }
return match, status, err return match, status, err
} }
func (jf *Jellyfin) userById(userId string, public bool) (map[string]interface{}, int, error) { // UserByID returns the user corresponding to the provided ID.
if jf.cacheExpiry.After(time.Now()) { func (jf *Jellyfin) UserByID(userID string, public bool) (map[string]interface{}, int, error) {
if jf.CacheExpiry.After(time.Now()) {
for _, user := range jf.userCache { for _, user := range jf.userCache {
if user["Id"].(string) == userId { if user["Id"].(string) == userID {
return user, 200, nil return user, 200, nil
} }
} }
} }
if public { if public {
users, status, err := jf.getUsers(public) users, status, err := jf.GetUsers(public)
if err != nil || status != 200 { if err != nil || status != 200 {
return nil, status, err return nil, status, err
} }
for _, user := range users { for _, user := range users {
if user["Id"].(string) == userId { if user["Id"].(string) == userID {
return user, status, nil return user, status, nil
} }
} }
return nil, status, err return nil, status, err
} else { }
var result map[string]interface{} var result map[string]interface{}
var data string var data string
var status int var status int
var err error var err error
url := fmt.Sprintf("%s/users/%s", jf.server, userId) url := fmt.Sprintf("%s/users/%s", jf.Server, userID)
data, status, err = jf._get(url, jf.loginParams) data, status, err = jf.get(url, jf.loginParams)
if err != nil || status != 200 { if err != nil || status != 200 {
return nil, status, err return nil, status, err
} }
json.Unmarshal([]byte(data), &result) json.Unmarshal([]byte(data), &result)
return result, status, nil return result, status, nil
} }
}
func (jf *Jellyfin) newUser(username, password string) (map[string]interface{}, int, error) { // NewUser creates a new user with the provided username and password.
url := fmt.Sprintf("%s/Users/New", jf.server) func (jf *Jellyfin) NewUser(username, password string) (map[string]interface{}, int, error) {
url := fmt.Sprintf("%s/Users/New", jf.Server)
stringData := map[string]string{ stringData := map[string]string{
"Name": username, "Name": username,
"Password": password, "Password": password,
@ -316,7 +312,7 @@ func (jf *Jellyfin) newUser(username, password string) (map[string]interface{},
for key, value := range stringData { for key, value := range stringData {
data[key] = value data[key] = value
} }
response, status, err := jf._post(url, data, true) response, status, err := jf.post(url, data, true)
var recv map[string]interface{} var recv map[string]interface{}
json.Unmarshal([]byte(response), &recv) json.Unmarshal([]byte(response), &recv)
if err != nil || !(status == 200 || status == 204) { 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 return recv, status, nil
} }
func (jf *Jellyfin) setPolicy(userId string, policy map[string]interface{}) (int, error) { // SetPolicy sets the access policy for the user corresponding to the provided ID.
url := fmt.Sprintf("%s/Users/%s/Policy", jf.server, userId) func (jf *Jellyfin) SetPolicy(userID string, policy map[string]interface{}) (int, error) {
_, status, err := jf._post(url, policy, false) url := fmt.Sprintf("%s/Users/%s/Policy", jf.Server, userID)
_, status, err := jf.post(url, policy, false)
if err != nil || status != 200 { if err != nil || status != 200 {
return status, err return status, err
} }
return status, nil return status, nil
} }
func (jf *Jellyfin) setConfiguration(userId string, configuration map[string]interface{}) (int, error) { // SetConfiguration sets the configuration (part of homescreen layout) for the user corresponding to the provided ID.
url := fmt.Sprintf("%s/Users/%s/Configuration", jf.server, userId) func (jf *Jellyfin) SetConfiguration(userID string, configuration map[string]interface{}) (int, error) {
_, status, err := jf._post(url, configuration, false) url := fmt.Sprintf("%s/Users/%s/Configuration", jf.Server, userID)
_, status, err := jf.post(url, configuration, false)
return status, err return status, err
} }
func (jf *Jellyfin) getDisplayPreferences(userId string) (map[string]interface{}, int, error) { // GetDisplayPreferences gets the displayPreferences (part of homescreen layout) for the user corresponding to the provided ID.
url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId) func (jf *Jellyfin) GetDisplayPreferences(userID string) (map[string]interface{}, int, error) {
data, status, err := jf._get(url, nil) 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) { if err != nil || !(status == 204 || status == 200) {
return nil, status, err return nil, status, err
} }
@ -354,9 +353,10 @@ func (jf *Jellyfin) getDisplayPreferences(userId string) (map[string]interface{}
return displayprefs, status, nil return displayprefs, status, nil
} }
func (jf *Jellyfin) setDisplayPreferences(userId string, displayprefs map[string]interface{}) (int, error) { // SetDisplayPreferences sets the displayPreferences (part of homescreen layout) for the user corresponding to the provided ID.
url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId) func (jf *Jellyfin) SetDisplayPreferences(userID string, displayprefs map[string]interface{}) (int, error) {
_, status, err := jf._post(url, displayprefs, false) 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) { if err != nil || !(status == 204 || status == 200) {
return status, err return status, err
} }

@ -23,7 +23,10 @@ import (
"github.com/gin-contrib/pprof" "github.com/gin-contrib/pprof"
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
_ "github.com/hrfee/jfa-go/docs" _ "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/lithammer/shortuuid/v3"
"github.com/logrusorgru/aurora/v3" "github.com/logrusorgru/aurora/v3"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
@ -51,9 +54,9 @@ type appContext struct {
jellyfinLogin bool jellyfinLogin bool
users []User users []User
invalidTokens []string invalidTokens []string
jf *Jellyfin jf *jfapi.Jellyfin
authJf *Jellyfin authJf *jfapi.Jellyfin
ombi *Ombi ombi *ombi.Ombi
datePattern string datePattern string
timePattern string timePattern string
storage Storage storage Storage
@ -139,18 +142,18 @@ var (
func test(app *appContext) { func test(app *appContext) {
fmt.Printf("\n\n----\n\n") fmt.Printf("\n\n----\n\n")
settings := map[string]interface{}{ settings := map[string]interface{}{
"server": app.jf.server, "server": app.jf.Server,
"server version": app.jf.serverInfo.Version, "server version": app.jf.ServerInfo.Version,
"server name": app.jf.serverInfo.Name, "server name": app.jf.ServerInfo.Name,
"authenticated?": app.jf.authenticated, "authenticated?": app.jf.Authenticated,
"access token": app.jf.accessToken, "access token": app.jf.AccessToken,
"username": app.jf.username, "username": app.jf.Username,
} }
for n, v := range settings { for n, v := range settings {
fmt.Println(n, ":", v) fmt.Println(n, ":", v)
} }
users, status, err := app.jf.getUsers(false) users, status, err := app.jf.GetUsers(false)
fmt.Printf("getUsers: code %d err %s maplength %d\n", status, err, len(users)) fmt.Printf("GetUsers: code %d err %s maplength %d\n", status, err, len(users))
fmt.Printf("View output? [y/n]: ") fmt.Printf("View output? [y/n]: ")
var choice string var choice string
fmt.Scanln(&choice) fmt.Scanln(&choice)
@ -161,8 +164,8 @@ func test(app *appContext) {
fmt.Printf("Enter a user to grab: ") fmt.Printf("Enter a user to grab: ")
var username string var username string
fmt.Scanln(&username) fmt.Scanln(&username)
user, status, err := app.jf.userByName(username, false) user, status, err := app.jf.UserByName(username, false)
fmt.Printf("userByName (%s): code %d err %s", username, status, err) fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
out, err := json.MarshalIndent(user, "", " ") out, err := json.MarshalIndent(user, "", " ")
fmt.Print(string(out)) fmt.Print(string(out))
} }
@ -358,19 +361,14 @@ func start(asDaemon, firstCall bool) {
app.debug.Println("Loading storage") 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.invite_path = app.config.Section("files").Key("invites").String()
app.storage.loadInvites() 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.emails_path = app.config.Section("files").Key("emails").String()
app.storage.loadEmails() 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.policy_path = app.config.Section("files").Key("user_template").String()
app.storage.loadPolicy() 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.configuration_path = app.config.Section("files").Key("user_configuration").String()
app.storage.loadConfiguration() 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.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
app.storage.loadDisplayprefs() app.storage.loadDisplayprefs()
@ -397,16 +395,18 @@ func start(asDaemon, firstCall bool) {
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String() app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String()
app.storage.loadOmbiTemplate() app.storage.loadOmbiTemplate()
app.ombi = newOmbi( ombiServer := app.config.Section("ombi").Key("server").String()
app.config.Section("ombi").Key("server").String(), app.ombi = ombi.NewOmbi(
ombiServer,
app.config.Section("ombi").Key("api_key").String(), 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") app.configBase_path = filepath.Join(app.local_path, "config-base.json")
config_base, _ := ioutil.ReadFile(app.configBase_path) configBase, _ := ioutil.ReadFile(app.configBase_path)
json.Unmarshal(config_base, &app.configBase) json.Unmarshal(configBase, &app.configBase)
themes := map[string]string{ themes := map[string]string{
"Jellyfin (Dark)": fmt.Sprintf("bs%d-jf.css", app.bsVersion), "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() server := app.config.Section("jellyfin").Key("server").String()
app.jf, _ = newJellyfin( app.jf, _ = jfapi.NewJellyfin(
server, server,
app.config.Section("jellyfin").Key("client").String(), app.config.Section("jellyfin").Key("client").String(),
app.config.Section("jellyfin").Key("version").String(), app.config.Section("jellyfin").Key("version").String(),
app.config.Section("jellyfin").Key("device").String(), app.config.Section("jellyfin").Key("device").String(),
app.config.Section("jellyfin").Key("device_id").String(), app.config.Section("jellyfin").Key("device_id").String(),
common.NewTimeoutHandler("Jellyfin", server, true),
) )
var status int 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 { if status != 200 || err != nil {
app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status) app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status)
} }
app.info.Printf("Authenticated with %s", server) 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() app.loadStrftime()

@ -0,0 +1,5 @@
module github.com/hrfee/jfa-go/ombi
replace github.com/hrfee/jfa-go/common => ../common
go 1.15

@ -1,4 +1,4 @@
package main package ombi
import ( import (
"bytes" "bytes"
@ -9,23 +9,26 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/hrfee/jfa-go/common"
) )
// Ombi represents a running Ombi instance.
type Ombi struct { type Ombi struct {
server, key string server, key string
header map[string]string header map[string]string
httpClient *http.Client httpClient *http.Client
noFail bool
userCache []map[string]interface{} userCache []map[string]interface{}
cacheExpiry time.Time cacheExpiry time.Time
cacheLength int 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{ return &Ombi{
server: server, server: server,
key: key, key: key,
noFail: noFail,
httpClient: &http.Client{ httpClient: &http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
}, },
@ -34,11 +37,12 @@ func newOmbi(server, key string, noFail bool) *Ombi {
}, },
cacheLength: 30, cacheLength: 30,
cacheExpiry: time.Now(), cacheExpiry: time.Now(),
timeoutHandler: timeoutHandler,
} }
} }
// does a GET and returns the response as a string. // 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 == "" { if ombi.key == "" {
return "", 401, fmt.Errorf("No API key provided") 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) req.Header.Add(name, value)
} }
resp, err := ombi.httpClient.Do(req) resp, err := ombi.httpClient.Do(req)
defer timeoutHandler("Ombi", ombi.server, ombi.noFail) defer ombi.timeoutHandler()
if err != nil || resp.StatusCode != 200 { if err != nil || resp.StatusCode != 200 {
if resp.StatusCode == 401 { if resp.StatusCode == 401 {
return "", 401, fmt.Errorf("Invalid API Key") 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. // 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 := "" responseText := ""
params, _ := json.Marshal(data) params, _ := json.Marshal(data)
req, _ := http.NewRequest(mode, url, bytes.NewBuffer(params)) 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) req.Header.Add(name, value)
} }
resp, err := ombi.httpClient.Do(req) 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 err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201) {
if resp.StatusCode == 401 { if resp.StatusCode == 401 {
return "", 401, fmt.Errorf("Invalid API Key") 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 return responseText, resp.StatusCode, nil
} }
func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool) (string, int, error) { func (ombi *Ombi) post(url string, data map[string]interface{}, response bool) (string, int, error) {
return ombi._send("POST", url, data, response) return ombi.send("POST", url, data, response)
} }
func (ombi *Ombi) _put(url string, data map[string]interface{}, response bool) (string, int, error) { func (ombi *Ombi) put(url string, data map[string]interface{}, response bool) (string, int, error) {
return ombi._send("PUT", url, data, response) 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 { if _, ok := user["id"]; !ok {
err = fmt.Errorf("No ID provided") err = fmt.Errorf("No ID provided")
return return
} }
_, status, err = ombi._put(ombi.server+"/api/v1/Identity", user, false) _, status, err = ombi.put(ombi.server+"/api/v1/Identity", user, false)
return 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) url := fmt.Sprintf("%s/api/v1/Identity/%s", ombi.server, id)
req, _ := http.NewRequest("DELETE", url, nil) req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Content-Type", "application/json") 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) req.Header.Add(name, value)
} }
resp, err := ombi.httpClient.Do(req) resp, err := ombi.httpClient.Do(req)
defer timeoutHandler("Ombi", ombi.server, ombi.noFail) defer ombi.timeoutHandler()
return resp.StatusCode, err return resp.StatusCode, err
} }
// gets an ombi user by their ID. // UserByID returns the user corresponding to the provided ID.
func (ombi *Ombi) userByID(id string) (result map[string]interface{}, code int, err error) { 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) resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil)
json.Unmarshal([]byte(resp), &result) json.Unmarshal([]byte(resp), &result)
return return
} }
// gets a list of all users. // GetUsers returns all users on the Ombi instance.
func (ombi *Ombi) getUsers() ([]map[string]interface{}, int, error) { func (ombi *Ombi) GetUsers() ([]map[string]interface{}, int, error) {
if time.Now().After(ombi.cacheExpiry) { 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{} var result []map[string]interface{}
json.Unmarshal([]byte(resp), &result) json.Unmarshal([]byte(resp), &result)
ombi.userCache = result ombi.userCache = result
@ -175,9 +181,9 @@ var stripFromOmbi = []string{
"userName", "userName",
} }
// returns a template based on the user corresponding to the provided ID's settings. // 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) { func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, code int, err error) {
result, code, err = ombi.userByID(id) result, code, err = ombi.UserByID(id)
if err != nil || code != 200 { if err != nil || code != 200 {
return return
} }
@ -194,14 +200,14 @@ func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code i
return return
} }
// creates a new user. // 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) { func (ombi *Ombi) NewUser(username, password, email string, template map[string]interface{}) ([]string, int, error) {
url := fmt.Sprintf("%s/api/v1/Identity", ombi.server) url := fmt.Sprintf("%s/api/v1/Identity", ombi.server)
user := template user := template
user["userName"] = username user["userName"] = username
user["password"] = password user["password"] = password
user["emailAddress"] = email user["emailAddress"] = email
resp, code, err := ombi._post(url, user, true) resp, code, err := ombi.post(url, user, true)
var data map[string]interface{} var data map[string]interface{}
json.Unmarshal([]byte(resp), &data) json.Unmarshal([]byte(resp), &data)
if err != nil || code != 200 { if err != nil || code != 200 {

@ -59,7 +59,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
} }
app.info.Printf("New password reset for user \"%s\"", pwr.Username) app.info.Printf("New password reset for user \"%s\"", pwr.Username)
if currentTime := time.Now(); pwr.Expiry.After(currentTime) { 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 { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)

@ -2,6 +2,8 @@ package main
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
"github.com/hrfee/jfa-go/jfapi"
) )
type testReq struct { type testReq struct {
@ -13,9 +15,8 @@ type testReq struct {
func (app *appContext) TestJF(gc *gin.Context) { func (app *appContext) TestJF(gc *gin.Context) {
var req testReq var req testReq
gc.BindJSON(&req) gc.BindJSON(&req)
tempjf, _ := newJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth") tempjf, _ := jfapi.NewJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth", common.NewTimeoutHandler("authJF", req.Host, true))
tempjf.noFail = true _, status, err := tempjf.Authenticate(req.Username, req.Password)
_, status, err := tempjf.authenticate(req.Username, req.Password)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.info.Printf("Auth failed with code %d (%s)", status, err) app.info.Printf("Auth failed with code %d (%s)", status, err)
gc.JSON(401, map[string]bool{"success": false}) gc.JSON(401, map[string]bool{"success": false})

Loading…
Cancel
Save