1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-28 03:50:10 +00:00
jfa-go/ombi/ombi.go
Harvey Tindall 523902f951
proxy: add http/socks5 support, use for Jellyfin
can be found in advanced. Currently only used for the main Jellyfin
client.
2023-10-05 11:25:58 +01:00

252 lines
7.2 KiB
Go

package ombi
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/hrfee/jfa-go/common"
)
const (
NotifAgentDiscord = 1
NotifAgentTelegram = 4
)
// Ombi represents a running Ombi instance.
type Ombi struct {
server, key string
header map[string]string
httpClient *http.Client
userCache []map[string]interface{}
cacheExpiry time.Time
cacheLength int
timeoutHandler common.TimeoutHandler
}
// NewOmbi returns an Ombi object.
func NewOmbi(server, key string, timeoutHandler common.TimeoutHandler) *Ombi {
return &Ombi{
server: server,
key: key,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
header: map[string]string{
"ApiKey": key,
},
cacheLength: 30,
cacheExpiry: time.Now(),
timeoutHandler: timeoutHandler,
}
}
// does a GET and returns the response as a string.
func (ombi *Ombi) getJSON(url string, params map[string]string) (string, int, error) {
if ombi.key == "" {
return "", 401, fmt.Errorf("No API key provided")
}
var req *http.Request
if params != nil {
jsonParams, _ := json.Marshal(params)
req, _ = http.NewRequest("GET", url, bytes.NewBuffer(jsonParams))
} else {
req, _ = http.NewRequest("GET", url, nil)
}
for name, value := range ombi.header {
req.Header.Add(name, value)
}
resp, err := ombi.httpClient.Do(req)
defer ombi.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 (ombi *Ombi) send(mode string, url string, data interface{}, response bool, headers map[string]string) (string, int, error) {
responseText := ""
params, _ := json.Marshal(data)
req, _ := http.NewRequest(mode, url, bytes.NewBuffer(params))
req.Header.Add("Content-Type", "application/json")
for name, value := range ombi.header {
req.Header.Add(name, value)
}
for name, value := range headers {
req.Header.Add(name, value)
}
resp, err := ombi.httpClient.Do(req)
defer ombi.timeoutHandler()
if err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201) {
if resp.StatusCode == 401 {
return "", 401, fmt.Errorf("Invalid API Key")
}
return responseText, resp.StatusCode, err
}
if response {
defer resp.Body.Close()
var out io.Reader
switch resp.Header.Get("Content-Encoding") {
case "gzip":
out, _ = gzip.NewReader(resp.Body)
default:
out = resp.Body
}
buf := new(strings.Builder)
_, err = io.Copy(buf, out)
if err != nil {
return "", 500, err
}
responseText = buf.String()
}
return responseText, resp.StatusCode, nil
}
func (ombi *Ombi) post(url string, data map[string]interface{}, response bool) (string, int, error) {
return ombi.send("POST", url, data, response, nil)
}
func (ombi *Ombi) put(url string, data map[string]interface{}, response bool) (string, int, error) {
return ombi.send("PUT", url, data, response, nil)
}
// ModifyUser applies the given modified user object to the corresponding user.
func (ombi *Ombi) ModifyUser(user map[string]interface{}) (status int, err error) {
if _, ok := user["id"]; !ok {
err = fmt.Errorf("No ID provided")
return
}
_, status, err = ombi.put(ombi.server+"/api/v1/Identity/", user, false)
return
}
// DeleteUser deletes the user corresponding to the given ID.
func (ombi *Ombi) DeleteUser(id string) (code int, err error) {
url := fmt.Sprintf("%s/api/v1/Identity/%s", ombi.server, id)
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Content-Type", "application/json")
for name, value := range ombi.header {
req.Header.Add(name, value)
}
resp, err := ombi.httpClient.Do(req)
defer ombi.timeoutHandler()
return resp.StatusCode, err
}
// UserByID returns the user corresponding to the provided ID.
func (ombi *Ombi) UserByID(id string) (result map[string]interface{}, code int, err error) {
resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil)
json.Unmarshal([]byte(resp), &result)
return
}
// GetUsers returns all users on the Ombi instance.
func (ombi *Ombi) GetUsers() ([]map[string]interface{}, int, error) {
if time.Now().After(ombi.cacheExpiry) {
resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil)
var result []map[string]interface{}
json.Unmarshal([]byte(resp), &result)
ombi.userCache = result
if (code == 200 || code == 204) && err == nil {
ombi.cacheExpiry = time.Now().Add(time.Minute * time.Duration(ombi.cacheLength))
}
return result, code, err
}
return ombi.userCache, 200, nil
}
// Strip these from a user when saving as a template.
// We also need to strip userQualityProfiles{"id", "userId"}
var stripFromOmbi = []string{
"alias",
"emailAddress",
"hasLoggedIn",
"id",
"lastLoggedIn",
"password",
"userName",
}
// TemplateByID returns a template based on the user corresponding to the provided ID's settings.
func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, code int, err error) {
result, code, err = ombi.UserByID(id)
if err != nil || code != 200 {
return
}
for _, key := range stripFromOmbi {
if _, ok := result[key]; ok {
delete(result, key)
}
}
if qp, ok := result["userQualityProfiles"].(map[string]interface{}); ok {
delete(qp, "id")
delete(qp, "userId")
result["userQualityProfiles"] = qp
}
return
}
// NewUser creates a new user with the given username, password and email address.
func (ombi *Ombi) NewUser(username, password, email string, template map[string]interface{}) ([]string, int, error) {
url := fmt.Sprintf("%s/api/v1/Identity", ombi.server)
user := template
user["userName"] = username
user["password"] = password
user["emailAddress"] = email
resp, code, err := ombi.post(url, user, true)
var data map[string]interface{}
json.Unmarshal([]byte(resp), &data)
if err != nil || code != 200 {
var lst []string
if data["errors"] != nil {
lst = data["errors"].([]string)
}
return lst, code, err
}
ombi.cacheExpiry = time.Now()
return nil, code, err
}
type NotificationPref struct {
Agent int `json:"agent"`
UserID string `json:"userId"`
Value string `json:"value"`
Enabled bool `json:"enabled"`
}
func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, telegramUser string) (result string, code int, err error) {
id := user["id"].(string)
url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server)
data := []NotificationPref{}
if discordID != "" {
data = append(data, NotificationPref{NotifAgentDiscord, id, discordID, true})
}
if telegramUser != "" {
data = append(data, NotificationPref{NotifAgentTelegram, id, telegramUser, true})
}
result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)})
return
}