mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-28 03:50:10 +00:00
Compare commits
4 Commits
2a6937228c
...
d60dea61db
Author | SHA1 | Date | |
---|---|---|---|
d60dea61db | |||
a136800ff2 | |||
db1c62cc46 | |||
1fa340f096 |
13
api-users.go
13
api-users.go
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -529,7 +528,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
|
|||||||
}
|
}
|
||||||
if telegramVerified {
|
if telegramVerified {
|
||||||
u, _ := app.storage.GetTelegramKey(user.ID)
|
u, _ := app.storage.GetTelegramKey(user.ID)
|
||||||
contactMethods[jellyseerr.FieldTelegram] = strconv.FormatInt(u.ChatID, 10)
|
contactMethods[jellyseerr.FieldTelegram] = u.ChatID
|
||||||
contactMethods[jellyseerr.FieldTelegramEnabled] = req.TelegramContact
|
contactMethods[jellyseerr.FieldTelegramEnabled] = req.TelegramContact
|
||||||
}
|
}
|
||||||
if emailEnabled || discordVerified || telegramVerified {
|
if emailEnabled || discordVerified || telegramVerified {
|
||||||
@ -1373,7 +1372,9 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
|||||||
configuration = profile.Configuration
|
configuration = profile.Configuration
|
||||||
displayprefs = profile.Displayprefs
|
displayprefs = profile.Displayprefs
|
||||||
}
|
}
|
||||||
|
if req.Policy {
|
||||||
policy = profile.Policy
|
policy = profile.Policy
|
||||||
|
}
|
||||||
if req.Ombi && app.config.Section("ombi").Key("enabled").MustBool(false) {
|
if req.Ombi && app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
if profile.Ombi != nil && len(profile.Ombi) != 0 {
|
if profile.Ombi != nil && len(profile.Ombi) != 0 {
|
||||||
ombi = profile.Ombi
|
ombi = profile.Ombi
|
||||||
@ -1395,7 +1396,9 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
applyingFrom = "\"" + user.Name + "\""
|
applyingFrom = "\"" + user.Name + "\""
|
||||||
|
if req.Policy {
|
||||||
policy = user.Policy
|
policy = user.Policy
|
||||||
|
}
|
||||||
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 {
|
||||||
@ -1422,10 +1425,14 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
|||||||
app.debug.Println("Adding delay between requests for large batch")
|
app.debug.Println("Adding delay between requests for large batch")
|
||||||
}
|
}
|
||||||
for _, id := range req.ApplyTo {
|
for _, id := range req.ApplyTo {
|
||||||
status, err := app.jf.SetPolicy(id, policy)
|
var status int
|
||||||
|
var err error
|
||||||
|
if req.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 shouldDelay {
|
if shouldDelay {
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
15
backups.go
15
backups.go
@ -161,20 +161,13 @@ func (app *appContext) loadPendingBackup() {
|
|||||||
LOADBAK = ""
|
LOADBAK = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBackupDaemon(app *appContext) *housekeepingDaemon {
|
func newBackupDaemon(app *appContext) *GenericDaemon {
|
||||||
interval := time.Duration(app.config.Section("backups").Key("every_n_minutes").MustInt(1440)) * time.Minute
|
interval := time.Duration(app.config.Section("backups").Key("every_n_minutes").MustInt(1440)) * time.Minute
|
||||||
daemon := housekeepingDaemon{
|
d := NewGenericDaemon(interval, app,
|
||||||
Stopped: false,
|
|
||||||
ShutdownChannel: make(chan string),
|
|
||||||
Interval: interval,
|
|
||||||
period: interval,
|
|
||||||
app: app,
|
|
||||||
}
|
|
||||||
daemon.jobs = []func(app *appContext){
|
|
||||||
func(app *appContext) {
|
func(app *appContext) {
|
||||||
app.debug.Println("Backups: Creating backup")
|
app.debug.Println("Backups: Creating backup")
|
||||||
app.makeBackup()
|
app.makeBackup()
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
return &daemon
|
return d
|
||||||
}
|
}
|
||||||
|
@ -1620,6 +1620,23 @@
|
|||||||
"value": "",
|
"value": "",
|
||||||
"depends_true": "enabled",
|
"depends_true": "enabled",
|
||||||
"description": "API Key. Get this from the first tab in Jellyseerr's settings."
|
"description": "API Key. Get this from the first tab in Jellyseerr's settings."
|
||||||
|
},
|
||||||
|
"import_existing": {
|
||||||
|
"name": "Import existing users to Jellyseerr",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"description": "Existing users (and those created outside jfa-go) will have their contact info imported to Jellyseerr."
|
||||||
|
},
|
||||||
|
"constraints_note": {
|
||||||
|
"name": "Unique Emails:",
|
||||||
|
"type": "note",
|
||||||
|
"value": "",
|
||||||
|
"depends_true": "import_existing",
|
||||||
|
"required": "false",
|
||||||
|
"description": "Jellyseerr requires email addresses to be unique. If this is not the case, you may see errors in jfa-go's logs. You can require unique addresses in Settings > Email."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
69
daemon.go
69
daemon.go
@ -116,32 +116,16 @@ func (app *appContext) clearActivities() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
|
func newHousekeepingDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||||
|
d := NewGenericDaemon(interval, app,
|
||||||
type housekeepingDaemon struct {
|
|
||||||
Stopped bool
|
|
||||||
ShutdownChannel chan string
|
|
||||||
Interval time.Duration
|
|
||||||
period time.Duration
|
|
||||||
jobs []func(app *appContext)
|
|
||||||
app *appContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemon {
|
|
||||||
daemon := housekeepingDaemon{
|
|
||||||
Stopped: false,
|
|
||||||
ShutdownChannel: make(chan string),
|
|
||||||
Interval: interval,
|
|
||||||
period: interval,
|
|
||||||
app: app,
|
|
||||||
}
|
|
||||||
daemon.jobs = []func(app *appContext){
|
|
||||||
func(app *appContext) {
|
func(app *appContext) {
|
||||||
app.debug.Println("Housekeeping: Checking for expired invites")
|
app.debug.Println("Housekeeping: Checking for expired invites")
|
||||||
app.checkInvites()
|
app.checkInvites()
|
||||||
},
|
},
|
||||||
func(app *appContext) { app.clearActivities() },
|
func(app *appContext) { app.clearActivities() },
|
||||||
}
|
)
|
||||||
|
|
||||||
|
d.Name("Housekeeping daemon")
|
||||||
|
|
||||||
clearEmail := app.config.Section("email").Key("require_unique").MustBool(false)
|
clearEmail := app.config.Section("email").Key("require_unique").MustBool(false)
|
||||||
clearDiscord := app.config.Section("discord").Key("require_unique").MustBool(false)
|
clearDiscord := app.config.Section("discord").Key("require_unique").MustBool(false)
|
||||||
@ -150,53 +134,24 @@ func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemo
|
|||||||
clearPWR := app.config.Section("captcha").Key("enabled").MustBool(false) && !app.config.Section("captcha").Key("recaptcha").MustBool(false)
|
clearPWR := app.config.Section("captcha").Key("enabled").MustBool(false) && !app.config.Section("captcha").Key("recaptcha").MustBool(false)
|
||||||
|
|
||||||
if clearEmail || clearDiscord || clearTelegram || clearMatrix {
|
if clearEmail || clearDiscord || clearTelegram || clearMatrix {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.jf.CacheExpiry = time.Now() })
|
d.appendJobs(func(app *appContext) { app.jf.CacheExpiry = time.Now() })
|
||||||
}
|
}
|
||||||
|
|
||||||
if clearEmail {
|
if clearEmail {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearEmails() })
|
d.appendJobs(func(app *appContext) { app.clearEmails() })
|
||||||
}
|
}
|
||||||
if clearDiscord {
|
if clearDiscord {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearDiscord() })
|
d.appendJobs(func(app *appContext) { app.clearDiscord() })
|
||||||
}
|
}
|
||||||
if clearTelegram {
|
if clearTelegram {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearTelegram() })
|
d.appendJobs(func(app *appContext) { app.clearTelegram() })
|
||||||
}
|
}
|
||||||
if clearMatrix {
|
if clearMatrix {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearMatrix() })
|
d.appendJobs(func(app *appContext) { app.clearMatrix() })
|
||||||
}
|
}
|
||||||
if clearPWR {
|
if clearPWR {
|
||||||
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearPWRCaptchas() })
|
d.appendJobs(func(app *appContext) { app.clearPWRCaptchas() })
|
||||||
}
|
}
|
||||||
|
|
||||||
return &daemon
|
return d
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *housekeepingDaemon) run() {
|
|
||||||
rt.app.info.Println("Invite daemon started")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-rt.ShutdownChannel:
|
|
||||||
rt.ShutdownChannel <- "Down"
|
|
||||||
return
|
|
||||||
case <-time.After(rt.period):
|
|
||||||
break
|
|
||||||
}
|
|
||||||
started := time.Now()
|
|
||||||
|
|
||||||
for _, job := range rt.jobs {
|
|
||||||
job(rt.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
finished := time.Now()
|
|
||||||
duration := finished.Sub(started)
|
|
||||||
rt.period = rt.Interval - duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *housekeepingDaemon) Shutdown() {
|
|
||||||
rt.Stopped = true
|
|
||||||
rt.ShutdownChannel <- "Down"
|
|
||||||
<-rt.ShutdownChannel
|
|
||||||
close(rt.ShutdownChannel)
|
|
||||||
}
|
}
|
||||||
|
65
genericdaemon.go
Normal file
65
genericdaemon.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
|
||||||
|
|
||||||
|
type GenericDaemon struct {
|
||||||
|
Stopped bool
|
||||||
|
ShutdownChannel chan string
|
||||||
|
Interval time.Duration
|
||||||
|
period time.Duration
|
||||||
|
jobs []func(app *appContext)
|
||||||
|
app *appContext
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GenericDaemon) appendJobs(jobs ...func(app *appContext)) {
|
||||||
|
d.jobs = append(d.jobs, jobs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenericDaemon returns a daemon which can be given jobs that utilize appContext.
|
||||||
|
func NewGenericDaemon(interval time.Duration, app *appContext, jobs ...func(app *appContext)) *GenericDaemon {
|
||||||
|
d := GenericDaemon{
|
||||||
|
Stopped: false,
|
||||||
|
ShutdownChannel: make(chan string),
|
||||||
|
Interval: interval,
|
||||||
|
period: interval,
|
||||||
|
app: app,
|
||||||
|
name: "Generic Daemon",
|
||||||
|
}
|
||||||
|
d.jobs = jobs
|
||||||
|
return &d
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GenericDaemon) Name(name string) { d.name = name }
|
||||||
|
|
||||||
|
func (d *GenericDaemon) run() {
|
||||||
|
d.app.info.Printf("%s started", d.name)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-d.ShutdownChannel:
|
||||||
|
d.ShutdownChannel <- "Down"
|
||||||
|
return
|
||||||
|
case <-time.After(d.period):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
started := time.Now()
|
||||||
|
|
||||||
|
for _, job := range d.jobs {
|
||||||
|
job(d.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
finished := time.Now()
|
||||||
|
duration := finished.Sub(started)
|
||||||
|
d.period = d.Interval - duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GenericDaemon) Shutdown() {
|
||||||
|
d.Stopped = true
|
||||||
|
d.ShutdownChannel <- "Down"
|
||||||
|
<-d.ShutdownChannel
|
||||||
|
close(d.ShutdownChannel)
|
||||||
|
}
|
@ -101,6 +101,10 @@
|
|||||||
<div class="select ~neutral @low unfocused">
|
<div class="select ~neutral @low unfocused">
|
||||||
<select id="modify-user-users"></select>
|
<select id="modify-user-users"></select>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="modify-user-configuration" checked>
|
||||||
|
<span>{{ .strings.applyConfigurationAndPolicy }}</span>
|
||||||
|
</label>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" id="modify-user-homescreen" checked>
|
<input type="checkbox" id="modify-user-homescreen" checked>
|
||||||
<span>{{ .strings.applyHomescreenLayout }}</span>
|
<span>{{ .strings.applyHomescreenLayout }}</span>
|
||||||
|
@ -30,6 +30,7 @@ type Jellyseerr struct {
|
|||||||
cacheLength time.Duration
|
cacheLength time.Duration
|
||||||
timeoutHandler common.TimeoutHandler
|
timeoutHandler common.TimeoutHandler
|
||||||
LogRequestBodies bool
|
LogRequestBodies bool
|
||||||
|
AutoImportUsers bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJellyseerr returns an Ombi object.
|
// NewJellyseerr returns an Ombi object.
|
||||||
@ -54,73 +55,60 @@ func NewJellyseerr(server, key string, timeoutHandler common.TimeoutHandler) *Je
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// does a GET and returns the response as a string.
|
func (js *Jellyseerr) req(mode string, uri string, data any, queryParams url.Values, headers map[string]string, response bool) (string, int, error) {
|
||||||
func (js *Jellyseerr) getJSON(url string, params map[string]string, queryParams url.Values) (string, int, error) {
|
var params []byte
|
||||||
if js.key == "" {
|
if data != nil {
|
||||||
return "", 401, fmt.Errorf("No API key provided")
|
params, _ = json.Marshal(data)
|
||||||
|
}
|
||||||
|
if js.LogRequestBodies {
|
||||||
|
fmt.Printf("Jellyseerr API Client: Sending Data \"%s\" to \"%s\"\n", string(params), uri)
|
||||||
|
}
|
||||||
|
if qp := queryParams.Encode(); qp != "" {
|
||||||
|
uri += "?" + qp
|
||||||
}
|
}
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
if params != nil {
|
if data != nil {
|
||||||
jsonParams, _ := json.Marshal(params)
|
req, _ = http.NewRequest(mode, uri, bytes.NewBuffer(params))
|
||||||
if js.LogRequestBodies {
|
|
||||||
fmt.Printf("Jellyseerr API Client: Sending Data \"%s\"\n", string(jsonParams))
|
|
||||||
}
|
|
||||||
req, _ = http.NewRequest("GET", url+"?"+queryParams.Encode(), bytes.NewBuffer(jsonParams))
|
|
||||||
} else {
|
} else {
|
||||||
req, _ = http.NewRequest("GET", url+"?"+queryParams.Encode(), nil)
|
req, _ = http.NewRequest(mode, uri, nil)
|
||||||
}
|
}
|
||||||
for name, value := range js.header {
|
|
||||||
req.Header.Add(name, value)
|
|
||||||
}
|
|
||||||
resp, err := js.httpClient.Do(req)
|
|
||||||
defer js.timeoutHandler()
|
|
||||||
if err != nil || resp.StatusCode != 200 {
|
|
||||||
if resp.StatusCode == 401 {
|
|
||||||
return "", 401, fmt.Errorf("Invalid API Key")
|
|
||||||
}
|
|
||||||
return "", resp.StatusCode, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
var data io.Reader
|
|
||||||
switch resp.Header.Get("Content-Encoding") {
|
|
||||||
case "gzip":
|
|
||||||
data, _ = gzip.NewReader(resp.Body)
|
|
||||||
default:
|
|
||||||
data = resp.Body
|
|
||||||
}
|
|
||||||
buf := new(strings.Builder)
|
|
||||||
_, err = io.Copy(buf, data)
|
|
||||||
if err != nil {
|
|
||||||
return "", 500, err
|
|
||||||
}
|
|
||||||
return buf.String(), resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (js *Jellyseerr) send(mode string, url string, data any, response bool, headers map[string]string) (string, int, error) {
|
|
||||||
responseText := ""
|
|
||||||
params, _ := json.Marshal(data)
|
|
||||||
if js.LogRequestBodies {
|
|
||||||
fmt.Printf("Jellyseerr API Client: Sending Data \"%s\"\n", string(params))
|
|
||||||
}
|
|
||||||
req, _ := http.NewRequest(mode, url, bytes.NewBuffer(params))
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
for name, value := range js.header {
|
for name, value := range js.header {
|
||||||
req.Header.Add(name, value)
|
req.Header.Add(name, value)
|
||||||
}
|
}
|
||||||
|
if headers != nil {
|
||||||
for name, value := range headers {
|
for name, value := range headers {
|
||||||
req.Header.Add(name, value)
|
req.Header.Add(name, value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resp, err := js.httpClient.Do(req)
|
resp, err := js.httpClient.Do(req)
|
||||||
|
reqFailed := err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201)
|
||||||
defer js.timeoutHandler()
|
defer js.timeoutHandler()
|
||||||
if err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201) {
|
var responseText string
|
||||||
if resp.StatusCode == 401 {
|
defer resp.Body.Close()
|
||||||
return "", 401, fmt.Errorf("Invalid API Key")
|
if response || reqFailed {
|
||||||
|
responseText, err = js.decodeResp(resp)
|
||||||
|
if err != nil {
|
||||||
|
return responseText, resp.StatusCode, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reqFailed {
|
||||||
|
var msg ErrorDTO
|
||||||
|
err = json.Unmarshal([]byte(responseText), &msg)
|
||||||
|
if err != nil {
|
||||||
|
return responseText, resp.StatusCode, err
|
||||||
|
}
|
||||||
|
if msg.Message == "" {
|
||||||
|
err = fmt.Errorf("failed (error %d)", resp.StatusCode)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("got %d: %s", resp.StatusCode, msg.Message)
|
||||||
}
|
}
|
||||||
return responseText, resp.StatusCode, err
|
return responseText, resp.StatusCode, err
|
||||||
}
|
}
|
||||||
if response {
|
return responseText, resp.StatusCode, err
|
||||||
defer resp.Body.Close()
|
}
|
||||||
|
|
||||||
|
func (js *Jellyseerr) decodeResp(resp *http.Response) (string, error) {
|
||||||
var out io.Reader
|
var out io.Reader
|
||||||
switch resp.Header.Get("Content-Encoding") {
|
switch resp.Header.Get("Content-Encoding") {
|
||||||
case "gzip":
|
case "gzip":
|
||||||
@ -129,21 +117,28 @@ func (js *Jellyseerr) send(mode string, url string, data any, response bool, hea
|
|||||||
out = resp.Body
|
out = resp.Body
|
||||||
}
|
}
|
||||||
buf := new(strings.Builder)
|
buf := new(strings.Builder)
|
||||||
_, err = io.Copy(buf, out)
|
_, err := io.Copy(buf, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 500, err
|
return "", err
|
||||||
}
|
}
|
||||||
responseText = buf.String()
|
return buf.String(), nil
|
||||||
}
|
|
||||||
return responseText, resp.StatusCode, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) post(url string, data any, response bool) (string, int, error) {
|
func (js *Jellyseerr) get(uri string, data any, params url.Values) (string, int, error) {
|
||||||
return js.send("POST", url, data, response, nil)
|
return js.req(http.MethodGet, uri, data, params, nil, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) put(url string, data any, response bool) (string, int, error) {
|
func (js *Jellyseerr) post(uri string, data any, response bool) (string, int, error) {
|
||||||
return js.send("PUT", url, data, response, nil)
|
return js.req(http.MethodPost, uri, data, url.Values{}, nil, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *Jellyseerr) put(uri string, data any, response bool) (string, int, error) {
|
||||||
|
return js.req(http.MethodPut, uri, data, url.Values{}, nil, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *Jellyseerr) delete(uri string, data any) (int, error) {
|
||||||
|
_, status, err := js.req(http.MethodDelete, uri, data, url.Values{}, nil, false)
|
||||||
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) ImportFromJellyfin(jfIDs ...string) ([]User, error) {
|
func (js *Jellyseerr) ImportFromJellyfin(jfIDs ...string) ([]User, error) {
|
||||||
@ -175,7 +170,7 @@ func (js *Jellyseerr) getUsers() error {
|
|||||||
pageCount := 1
|
pageCount := 1
|
||||||
pageIndex := 0
|
pageIndex := 0
|
||||||
for {
|
for {
|
||||||
res, err := js.getUserPage(0)
|
res, err := js.getUserPage(pageIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -197,9 +192,12 @@ func (js *Jellyseerr) getUsers() error {
|
|||||||
func (js *Jellyseerr) getUserPage(page int) (GetUsersDTO, error) {
|
func (js *Jellyseerr) getUserPage(page int) (GetUsersDTO, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("take", "30")
|
params.Add("take", "30")
|
||||||
params.Add("skip", strconv.Itoa(page))
|
params.Add("skip", strconv.Itoa(page*30))
|
||||||
params.Add("sort", "created")
|
params.Add("sort", "created")
|
||||||
resp, status, err := js.getJSON(js.server+"/user", nil, params)
|
if js.LogRequestBodies {
|
||||||
|
fmt.Printf("Jellyseerr API Client: Sending with URL params \"%+v\"\n", params)
|
||||||
|
}
|
||||||
|
resp, status, err := js.get(js.server+"/user", nil, params)
|
||||||
var data GetUsersDTO
|
var data GetUsersDTO
|
||||||
if status != 200 {
|
if status != 200 {
|
||||||
return data, fmt.Errorf("failed (error %d)", status)
|
return data, fmt.Errorf("failed (error %d)", status)
|
||||||
@ -211,29 +209,59 @@ func (js *Jellyseerr) getUserPage(page int) (GetUsersDTO, error) {
|
|||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustGetUser provides the same function as ImportFromJellyfin, but will always return the user,
|
|
||||||
// even if they already existed.
|
|
||||||
func (js *Jellyseerr) MustGetUser(jfID string) (User, error) {
|
func (js *Jellyseerr) MustGetUser(jfID string) (User, error) {
|
||||||
js.getUsers()
|
u, _, err := js.GetOrImportUser(jfID)
|
||||||
if u, ok := js.userCache[jfID]; ok {
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
users, err := js.ImportFromJellyfin(jfID)
|
|
||||||
var u User
|
|
||||||
if err != nil {
|
|
||||||
return u, err
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImportedUser provides the same function as ImportFromJellyfin, but will always return the user,
|
||||||
|
// even if they already existed. Also returns whether the user was imported or not,
|
||||||
|
func (js *Jellyseerr) GetOrImportUser(jfID string) (u User, imported bool, err error) {
|
||||||
|
imported = false
|
||||||
|
u, err = js.GetExistingUser(jfID)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var users []User
|
||||||
|
users, err = js.ImportFromJellyfin(jfID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if len(users) != 0 {
|
if len(users) != 0 {
|
||||||
return users[0], err
|
u = users[0]
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if u, ok := js.userCache[jfID]; ok {
|
err = fmt.Errorf("user not found or imported")
|
||||||
return u, nil
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *Jellyseerr) GetExistingUser(jfID string) (u User, err error) {
|
||||||
|
js.getUsers()
|
||||||
|
ok := false
|
||||||
|
err = nil
|
||||||
|
if u, ok = js.userCache[jfID]; ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return u, fmt.Errorf("user not found")
|
js.cacheExpiry = time.Now()
|
||||||
|
js.getUsers()
|
||||||
|
if u, ok = js.userCache[jfID]; ok {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("user not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *Jellyseerr) getUser(jfID string) (User, error) {
|
||||||
|
if js.AutoImportUsers {
|
||||||
|
return js.MustGetUser(jfID)
|
||||||
|
}
|
||||||
|
return js.GetExistingUser(jfID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) Me() (User, error) {
|
func (js *Jellyseerr) Me() (User, error) {
|
||||||
resp, status, err := js.getJSON(js.server+"/auth/me", nil, url.Values{})
|
resp, status, err := js.get(js.server+"/auth/me", nil, url.Values{})
|
||||||
var data User
|
var data User
|
||||||
data.ID = -1
|
data.ID = -1
|
||||||
if status != 200 {
|
if status != 200 {
|
||||||
@ -248,12 +276,12 @@ func (js *Jellyseerr) Me() (User, error) {
|
|||||||
|
|
||||||
func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) {
|
func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) {
|
||||||
data := permissionsDTO{Permissions: -1}
|
data := permissionsDTO{Permissions: -1}
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data.Permissions, err
|
return data.Permissions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, status, err := js.getJSON(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), nil, url.Values{})
|
resp, status, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), nil, url.Values{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data.Permissions, err
|
return data.Permissions, err
|
||||||
}
|
}
|
||||||
@ -265,7 +293,7 @@ func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error {
|
func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -283,7 +311,7 @@ func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error {
|
func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -304,7 +332,7 @@ func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error {
|
|||||||
if _, ok := conf[FieldEmail]; ok {
|
if _, ok := conf[FieldEmail]; ok {
|
||||||
return fmt.Errorf("email is read only, set with ModifyMainUserSettings instead")
|
return fmt.Errorf("email is read only, set with ModifyMainUserSettings instead")
|
||||||
}
|
}
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -322,12 +350,12 @@ func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) DeleteUser(jfID string) error {
|
func (js *Jellyseerr) DeleteUser(jfID string) error {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, status, err := js.send("DELETE", fmt.Sprintf(js.server+"/user/%d", u.ID), nil, false, nil)
|
status, err := js.delete(fmt.Sprintf(js.server+"/user/%d", u.ID), nil)
|
||||||
if status != 200 && status != 201 {
|
if status != 200 && status != 201 {
|
||||||
return fmt.Errorf("failed (error %d)", status)
|
return fmt.Errorf("failed (error %d)", status)
|
||||||
}
|
}
|
||||||
@ -339,7 +367,7 @@ func (js *Jellyseerr) DeleteUser(jfID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) GetNotificationPreferences(jfID string) (Notifications, error) {
|
func (js *Jellyseerr) GetNotificationPreferences(jfID string) (Notifications, error) {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Notifications{}, err
|
return Notifications{}, err
|
||||||
}
|
}
|
||||||
@ -348,7 +376,7 @@ func (js *Jellyseerr) GetNotificationPreferences(jfID string) (Notifications, er
|
|||||||
|
|
||||||
func (js *Jellyseerr) GetNotificationPreferencesByID(jellyseerrID int64) (Notifications, error) {
|
func (js *Jellyseerr) GetNotificationPreferencesByID(jellyseerrID int64) (Notifications, error) {
|
||||||
var data Notifications
|
var data Notifications
|
||||||
resp, status, err := js.getJSON(fmt.Sprintf(js.server+"/user/%d/settings/notifications", jellyseerrID), nil, url.Values{})
|
resp, status, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/notifications", jellyseerrID), nil, url.Values{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
@ -364,7 +392,7 @@ func (js *Jellyseerr) ApplyNotificationsTemplateToUser(jfID string, tmpl Notific
|
|||||||
/* if tmpl.NotifTypes.Empty() {
|
/* if tmpl.NotifTypes.Empty() {
|
||||||
tmpl.NotifTypes = nil
|
tmpl.NotifTypes = nil
|
||||||
}*/
|
}*/
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -380,7 +408,7 @@ func (js *Jellyseerr) ApplyNotificationsTemplateToUser(jfID string, tmpl Notific
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) ModifyNotifications(jfID string, conf map[NotificationsField]any) error {
|
func (js *Jellyseerr) ModifyNotifications(jfID string, conf map[NotificationsField]any) error {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -401,7 +429,7 @@ func (js *Jellyseerr) GetUsers() (map[string]User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) {
|
func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) {
|
||||||
resp, status, err := js.getJSON(js.server+fmt.Sprintf("/user/%d", jellyseerrID), nil, url.Values{})
|
resp, status, err := js.get(js.server+fmt.Sprintf("/user/%d", jellyseerrID), nil, url.Values{})
|
||||||
var data User
|
var data User
|
||||||
if status != 200 {
|
if status != 200 {
|
||||||
return data, fmt.Errorf("failed (error %d)", status)
|
return data, fmt.Errorf("failed (error %d)", status)
|
||||||
@ -414,12 +442,12 @@ func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (js *Jellyseerr) ModifyMainUserSettings(jfID string, conf MainUserSettings) error {
|
func (js *Jellyseerr) ModifyMainUserSettings(jfID string, conf MainUserSettings) error {
|
||||||
u, err := js.MustGetUser(jfID)
|
u, err := js.getUser(jfID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, status, err := js.put(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false)
|
_, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -130,3 +130,7 @@ type MainUserSettings struct {
|
|||||||
WatchlistSyncMovies any `json:"watchlistSyncMovies,omitempty"`
|
WatchlistSyncMovies any `json:"watchlistSyncMovies,omitempty"`
|
||||||
WatchlistSyncTv any `json:"watchlistSyncTv,omitempty"`
|
WatchlistSyncTv any `json:"watchlistSyncTv,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrorDTO struct {
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
81
jellyseerrdaemon.go
Normal file
81
jellyseerrdaemon.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hrfee/jfa-go/jellyseerr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||||
|
user, imported, err := app.js.GetOrImportUser(jfID)
|
||||||
|
if err != nil {
|
||||||
|
app.debug.Printf("Failed to get or trigger import for Jellyseerr (user \"%s\"): %v", jfID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if imported {
|
||||||
|
app.debug.Printf("Jellyseerr: Triggered import for Jellyfin user \"%s\" (ID %d)", jfID, user.ID)
|
||||||
|
}
|
||||||
|
notif, err := app.js.GetNotificationPreferencesByID(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
app.debug.Printf("Failed to get notification prefs for Jellyseerr (user \"%s\"): %v", jfID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contactMethods := map[jellyseerr.NotificationsField]any{}
|
||||||
|
email, ok := app.storage.GetEmailsKey(jfID)
|
||||||
|
if ok && email.Addr != "" && user.Email != email.Addr {
|
||||||
|
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err)
|
||||||
|
} else {
|
||||||
|
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if discordEnabled {
|
||||||
|
dcUser, ok := app.storage.GetDiscordKey(jfID)
|
||||||
|
if ok && dcUser.ID != "" && notif.DiscordID != dcUser.ID {
|
||||||
|
contactMethods[jellyseerr.FieldDiscord] = dcUser.ID
|
||||||
|
contactMethods[jellyseerr.FieldDiscordEnabled] = dcUser.Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if telegramEnabled {
|
||||||
|
tgUser, ok := app.storage.GetTelegramKey(jfID)
|
||||||
|
chatID, _ := strconv.ParseInt(notif.TelegramChatID, 10, 64)
|
||||||
|
if ok && tgUser.ChatID != 0 && chatID != tgUser.ChatID {
|
||||||
|
u, _ := app.storage.GetTelegramKey(jfID)
|
||||||
|
contactMethods[jellyseerr.FieldTelegram] = u.ChatID
|
||||||
|
contactMethods[jellyseerr.FieldTelegramEnabled] = tgUser.Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(contactMethods) != 0 {
|
||||||
|
err := app.js.ModifyNotifications(jfID, contactMethods)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *appContext) SynchronizeJellyseerrUsers() {
|
||||||
|
users, status, err := app.jf.GetUsers(false)
|
||||||
|
if err != nil || status != 200 {
|
||||||
|
app.err.Printf("Failed to get users (%d): %s", status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// I'm sure Jellyseerr can handle it,
|
||||||
|
// but past issues with the Jellyfin db scare me from
|
||||||
|
// running these concurrently. W/e, its a bg task anyway.
|
||||||
|
for _, user := range users {
|
||||||
|
app.SynchronizeJellyseerrUser(user.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJellyseerrDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||||
|
d := NewGenericDaemon(interval, app,
|
||||||
|
func(app *appContext) {
|
||||||
|
app.SynchronizeJellyseerrUsers()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
d.Name("Jellyseerr import daemon")
|
||||||
|
return d
|
||||||
|
}
|
@ -81,6 +81,7 @@
|
|||||||
"useInviteExpiry": "Set expiry from profile/invite",
|
"useInviteExpiry": "Set expiry from profile/invite",
|
||||||
"useInviteExpiryNote": "By default, invites expire after 90 days but can be renewed by the user. Enable for the referral to be disabled after the time set.",
|
"useInviteExpiryNote": "By default, invites expire after 90 days but can be renewed by the user. Enable for the referral to be disabled after the time set.",
|
||||||
"applyHomescreenLayout": "Apply homescreen layout",
|
"applyHomescreenLayout": "Apply homescreen layout",
|
||||||
|
"applyConfigurationAndPolicy": "Apply Jellyfin configuration/policy",
|
||||||
"applyOmbi": "Apply Ombi profile (if available)",
|
"applyOmbi": "Apply Ombi profile (if available)",
|
||||||
"applyJellyseerr": "Apply Jellyseerr profile (if available)",
|
"applyJellyseerr": "Apply Jellyseerr profile (if available)",
|
||||||
"sendDeleteNotificationEmail": "Send notification message",
|
"sendDeleteNotificationEmail": "Send notification message",
|
||||||
|
15
main.go
15
main.go
@ -369,6 +369,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.config.Section("jellyseerr").Key("api_key").String(),
|
app.config.Section("jellyseerr").Key("api_key").String(),
|
||||||
common.NewTimeoutHandler("Jellyseerr", jellyseerrServer, true),
|
common.NewTimeoutHandler("Jellyseerr", jellyseerrServer, true),
|
||||||
)
|
)
|
||||||
|
app.js.AutoImportUsers = app.config.Section("jellyseerr").Key("import_existing").MustBool(false)
|
||||||
// app.js.LogRequestBodies = true
|
// app.js.LogRequestBodies = true
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -480,13 +481,21 @@ func start(asDaemon, firstCall bool) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
invDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
|
invDaemon := newHousekeepingDaemon(time.Duration(60*time.Second), app)
|
||||||
go invDaemon.run()
|
go invDaemon.run()
|
||||||
defer invDaemon.Shutdown()
|
defer invDaemon.Shutdown()
|
||||||
|
|
||||||
userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
|
userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
|
||||||
go userDaemon.run()
|
go userDaemon.run()
|
||||||
defer userDaemon.shutdown()
|
defer userDaemon.Shutdown()
|
||||||
|
|
||||||
|
var jellyseerrDaemon *GenericDaemon
|
||||||
|
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) && app.config.Section("jellyseerr").Key("import_existing").MustBool(false) {
|
||||||
|
// jellyseerrDaemon = newJellyseerrDaemon(time.Duration(30*time.Second), app)
|
||||||
|
jellyseerrDaemon = newJellyseerrDaemon(time.Duration(10*time.Minute), app)
|
||||||
|
go jellyseerrDaemon.run()
|
||||||
|
defer jellyseerrDaemon.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
||||||
go app.StartPWR()
|
go app.StartPWR()
|
||||||
@ -496,7 +505,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
go app.checkForUpdates()
|
go app.checkForUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
var backupDaemon *housekeepingDaemon
|
var backupDaemon *GenericDaemon
|
||||||
if app.config.Section("backups").Key("enabled").MustBool(false) {
|
if app.config.Section("backups").Key("enabled").MustBool(false) {
|
||||||
backupDaemon = newBackupDaemon(app)
|
backupDaemon = newBackupDaemon(app)
|
||||||
go backupDaemon.run()
|
go backupDaemon.run()
|
||||||
|
@ -178,6 +178,9 @@ type userSettingsDTO struct {
|
|||||||
Profile string `json:"profile"` // Name of profile (if from = "profile")
|
Profile string `json:"profile"` // Name of profile (if from = "profile")
|
||||||
ApplyTo []string `json:"apply_to"` // Users to apply settings to
|
ApplyTo []string `json:"apply_to"` // Users to apply settings to
|
||||||
ID string `json:"id"` // ID of user (if from = "user")
|
ID string `json:"id"` // ID of user (if from = "user")
|
||||||
|
// Note confusing name: "Configuration" on the admin UI just means it in the sense
|
||||||
|
// of the account's settings.
|
||||||
|
Policy bool `json:"configuration"` // Whether to apply jf policy not
|
||||||
Homescreen bool `json:"homescreen"` // Whether to apply homescreen layout or not
|
Homescreen bool `json:"homescreen"` // Whether to apply homescreen layout or not
|
||||||
Ombi bool `json:"ombi"` // Whether to apply ombi profile or not
|
Ombi bool `json:"ombi"` // Whether to apply ombi profile or not
|
||||||
Jellyseerr bool `json:"jellyseerr"` // Whether to apply jellyseerr profile or not
|
Jellyseerr bool `json:"jellyseerr"` // Whether to apply jellyseerr profile or not
|
||||||
|
@ -795,7 +795,8 @@ export class accountsList {
|
|||||||
private _searchBox = document.getElementById("accounts-search") as HTMLInputElement;
|
private _searchBox = document.getElementById("accounts-search") as HTMLInputElement;
|
||||||
private _search: Search;
|
private _search: Search;
|
||||||
|
|
||||||
private _applyHomesreen = document.getElementById("modify-user-homescreen") as HTMLInputElement;
|
private _applyHomescreen = document.getElementById("modify-user-homescreen") as HTMLInputElement;
|
||||||
|
private _applyConfiguration = document.getElementById("modify-user-configuration") as HTMLInputElement;
|
||||||
private _applyOmbi = document.getElementById("modify-user-ombi") as HTMLInputElement;
|
private _applyOmbi = document.getElementById("modify-user-ombi") as HTMLInputElement;
|
||||||
private _applyJellyseerr = document.getElementById("modify-user-jellyseerr") as HTMLInputElement;
|
private _applyJellyseerr = document.getElementById("modify-user-jellyseerr") as HTMLInputElement;
|
||||||
|
|
||||||
@ -1490,7 +1491,8 @@ export class accountsList {
|
|||||||
toggleLoader(button);
|
toggleLoader(button);
|
||||||
let send = {
|
let send = {
|
||||||
"apply_to": list,
|
"apply_to": list,
|
||||||
"homescreen": this._applyHomesreen.checked,
|
"homescreen": this._applyHomescreen.checked,
|
||||||
|
"configuration": this._applyConfiguration.checked,
|
||||||
"ombi": this._applyOmbi.checked,
|
"ombi": this._applyOmbi.checked,
|
||||||
"jellyseerr": this._applyJellyseerr.checked
|
"jellyseerr": this._applyJellyseerr.checked
|
||||||
};
|
};
|
||||||
|
@ -7,47 +7,14 @@ import (
|
|||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userDaemon struct {
|
func newUserDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||||
Stopped bool
|
d := NewGenericDaemon(interval, app,
|
||||||
ShutdownChannel chan string
|
func(app *appContext) {
|
||||||
Interval time.Duration
|
app.checkUsers()
|
||||||
period time.Duration
|
},
|
||||||
app *appContext
|
)
|
||||||
}
|
d.Name("User daemon")
|
||||||
|
return d
|
||||||
func newUserDaemon(interval time.Duration, app *appContext) *userDaemon {
|
|
||||||
return &userDaemon{
|
|
||||||
Stopped: false,
|
|
||||||
ShutdownChannel: make(chan string),
|
|
||||||
Interval: interval,
|
|
||||||
period: interval,
|
|
||||||
app: app,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *userDaemon) run() {
|
|
||||||
rt.app.info.Println("User daemon started")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-rt.ShutdownChannel:
|
|
||||||
rt.ShutdownChannel <- "Down"
|
|
||||||
return
|
|
||||||
case <-time.After(rt.period):
|
|
||||||
break
|
|
||||||
}
|
|
||||||
started := time.Now()
|
|
||||||
rt.app.checkUsers()
|
|
||||||
finished := time.Now()
|
|
||||||
duration := finished.Sub(started)
|
|
||||||
rt.period = rt.Interval - duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *userDaemon) shutdown() {
|
|
||||||
rt.Stopped = true
|
|
||||||
rt.ShutdownChannel <- "Down"
|
|
||||||
<-rt.ShutdownChannel
|
|
||||||
close(rt.ShutdownChannel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) checkUsers() {
|
func (app *appContext) checkUsers() {
|
||||||
|
Loading…
Reference in New Issue
Block a user