mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-05 01:40:10 +00:00
Harvey Tindall
802f957d22
closing the settings modal to immediately open another caused the 'modal-open' class on the body to get deleted, which meant scrolling stopped working inside them. Also fix mistake added to jfapi in last commit.
345 lines
9.7 KiB
Go
345 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ServerInfo struct {
|
|
LocalAddress string `json:"LocalAddress"`
|
|
Name string `json:"ServerName"`
|
|
Version string `json:"Version"`
|
|
Os string `json:"OperatingSystem"`
|
|
Id string `json:"Id"`
|
|
}
|
|
|
|
type Jellyfin struct {
|
|
server string
|
|
client string
|
|
version string
|
|
device string
|
|
deviceId string
|
|
useragent string
|
|
auth string
|
|
header map[string]string
|
|
serverInfo ServerInfo
|
|
username string
|
|
password string
|
|
authenticated bool
|
|
accessToken string
|
|
userId string
|
|
httpClient *http.Client
|
|
loginParams map[string]string
|
|
userCache []map[string]interface{}
|
|
cacheExpiry time.Time
|
|
cacheLength int
|
|
noFail bool
|
|
}
|
|
|
|
func timeoutHandler(name, addr string, noFail bool) {
|
|
if r := recover(); r != nil {
|
|
out := fmt.Sprintf("Failed to authenticate with %s @ %s: Timed out", name, addr)
|
|
if noFail {
|
|
log.Printf(out)
|
|
} else {
|
|
log.Fatalf(out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, error) {
|
|
jf := &Jellyfin{}
|
|
jf.server = server
|
|
jf.client = client
|
|
jf.version = version
|
|
jf.device = device
|
|
jf.deviceId = deviceId
|
|
jf.useragent = fmt.Sprintf("%s/%s", client, version)
|
|
jf.auth = fmt.Sprintf("MediaBrowser Client=%s, Device=%s, DeviceId=%s, Version=%s", client, device, deviceId, version)
|
|
jf.header = map[string]string{
|
|
"Accept": "application/json",
|
|
"Content-type": "application/json; charset=UTF-8",
|
|
"X-Application": jf.useragent,
|
|
"Accept-Charset": "UTF-8,*",
|
|
"Accept-Encoding": "gzip",
|
|
"User-Agent": jf.useragent,
|
|
"X-Emby-Authorization": jf.auth,
|
|
}
|
|
jf.httpClient = &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
infoUrl := fmt.Sprintf("%s/System/Info/Public", server)
|
|
req, _ := http.NewRequest("GET", infoUrl, nil)
|
|
resp, err := jf.httpClient.Do(req)
|
|
defer timeoutHandler("Jellyfin", jf.server, jf.noFail)
|
|
if err == nil {
|
|
data, _ := ioutil.ReadAll(resp.Body)
|
|
json.Unmarshal(data, &jf.serverInfo)
|
|
}
|
|
jf.cacheLength = 30
|
|
jf.cacheExpiry = time.Now()
|
|
return jf, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) {
|
|
jf.username = username
|
|
jf.password = password
|
|
jf.loginParams = map[string]string{
|
|
"Username": username,
|
|
"Pw": password,
|
|
"Password": password,
|
|
}
|
|
buffer := &bytes.Buffer{}
|
|
encoder := json.NewEncoder(buffer)
|
|
encoder.SetEscapeHTML(false)
|
|
err := encoder.Encode(jf.loginParams)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
// loginParams, _ := json.Marshal(jf.loginParams)
|
|
url := fmt.Sprintf("%s/Users/authenticatebyname", jf.server)
|
|
req, err := http.NewRequest("POST", url, buffer)
|
|
defer timeoutHandler("Jellyfin", jf.server, jf.noFail)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
for name, value := range jf.header {
|
|
req.Header.Add(name, value)
|
|
}
|
|
resp, err := jf.httpClient.Do(req)
|
|
if err != nil || resp.StatusCode != 200 {
|
|
return nil, 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
|
|
}
|
|
var respData map[string]interface{}
|
|
json.NewDecoder(data).Decode(&respData)
|
|
jf.accessToken = respData["AccessToken"].(string)
|
|
user := respData["User"].(map[string]interface{})
|
|
jf.userId = respData["User"].(map[string]interface{})["Id"].(string)
|
|
jf.auth = fmt.Sprintf("MediaBrowser Client=\"%s\", Device=\"%s\", DeviceId=\"%s\", Version=\"%s\", Token=\"%s\"", jf.client, jf.device, jf.deviceId, jf.version, jf.accessToken)
|
|
jf.header["X-Emby-Authorization"] = jf.auth
|
|
jf.authenticated = true
|
|
return user, resp.StatusCode, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) _get(url string, params map[string]string) (string, int, error) {
|
|
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 jf.header {
|
|
req.Header.Add(name, value)
|
|
}
|
|
resp, err := jf.httpClient.Do(req)
|
|
defer timeoutHandler("Jellyfin", jf.server, jf.noFail)
|
|
if err != nil || resp.StatusCode != 200 {
|
|
if resp.StatusCode == 401 && jf.authenticated {
|
|
jf.authenticated = false
|
|
_, _, authErr := jf.authenticate(jf.username, jf.password)
|
|
if authErr == nil {
|
|
v1, v2, v3 := jf._get(url, params)
|
|
return v1, v2, v3
|
|
}
|
|
}
|
|
return "", resp.StatusCode, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var data io.Reader
|
|
encoding := resp.Header.Get("Content-Encoding")
|
|
if TEST {
|
|
fmt.Println("response encoding:", encoding)
|
|
}
|
|
switch encoding {
|
|
case "gzip":
|
|
data, _ = gzip.NewReader(resp.Body)
|
|
default:
|
|
data = resp.Body
|
|
}
|
|
buf := new(strings.Builder)
|
|
io.Copy(buf, data)
|
|
//var respData map[string]interface{}
|
|
//json.NewDecoder(data).Decode(&respData)
|
|
return buf.String(), resp.StatusCode, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) _post(url string, data map[string]interface{}, response bool) (string, int, error) {
|
|
params, _ := json.Marshal(data)
|
|
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(params))
|
|
for name, value := range jf.header {
|
|
req.Header.Add(name, value)
|
|
}
|
|
resp, err := jf.httpClient.Do(req)
|
|
defer timeoutHandler("Jellyfin", jf.server, jf.noFail)
|
|
if err != nil || resp.StatusCode != 200 {
|
|
if resp.StatusCode == 401 && jf.authenticated {
|
|
jf.authenticated = false
|
|
_, _, authErr := jf.authenticate(jf.username, jf.password)
|
|
if authErr == nil {
|
|
v1, v2, v3 := jf._post(url, data, response)
|
|
return v1, v2, v3
|
|
}
|
|
}
|
|
return "", resp.StatusCode, err
|
|
}
|
|
if response {
|
|
defer resp.Body.Close()
|
|
var outData io.Reader
|
|
switch resp.Header.Get("Content-Encoding") {
|
|
case "gzip":
|
|
outData, _ = gzip.NewReader(resp.Body)
|
|
default:
|
|
outData = resp.Body
|
|
}
|
|
buf := new(strings.Builder)
|
|
io.Copy(buf, outData)
|
|
return buf.String(), resp.StatusCode, nil
|
|
}
|
|
return "", resp.StatusCode, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) getUsers(public bool) ([]map[string]interface{}, int, error) {
|
|
var result []map[string]interface{}
|
|
var data string
|
|
var status int
|
|
var err error
|
|
if time.Now().After(jf.cacheExpiry) {
|
|
if public {
|
|
url := fmt.Sprintf("%s/users/public", jf.server)
|
|
data, status, err = jf._get(url, nil)
|
|
} else {
|
|
url := fmt.Sprintf("%s/users", jf.server)
|
|
data, status, err = jf._get(url, jf.loginParams)
|
|
}
|
|
if err != nil || status != 200 {
|
|
return nil, status, err
|
|
}
|
|
json.Unmarshal([]byte(data), &result)
|
|
jf.userCache = result
|
|
jf.cacheExpiry = time.Now().Add(time.Minute * time.Duration(jf.cacheLength))
|
|
return result, status, nil
|
|
}
|
|
return jf.userCache, 200, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) userByName(username string, public bool) (map[string]interface{}, int, error) {
|
|
users, status, err := jf.getUsers(public)
|
|
if err != nil || status != 200 {
|
|
return nil, status, err
|
|
}
|
|
for _, user := range users {
|
|
if user["Name"].(string) == username {
|
|
return user, status, nil
|
|
}
|
|
}
|
|
return nil, status, err
|
|
}
|
|
|
|
func (jf *Jellyfin) userById(userId string, public bool) (map[string]interface{}, int, error) {
|
|
if jf.cacheExpiry.After(time.Now()) {
|
|
for _, user := range jf.userCache {
|
|
if user["Id"].(string) == userId {
|
|
return user, 200, nil
|
|
}
|
|
}
|
|
}
|
|
if public {
|
|
users, status, err := jf.getUsers(public)
|
|
if err != nil || status != 200 {
|
|
return nil, status, err
|
|
}
|
|
for _, user := range users {
|
|
if user["Id"].(string) == userId {
|
|
return user, status, nil
|
|
}
|
|
}
|
|
return nil, status, err
|
|
} else {
|
|
var result map[string]interface{}
|
|
var data string
|
|
var status int
|
|
var err error
|
|
url := fmt.Sprintf("%s/users/%s", jf.server, userId)
|
|
data, status, err = jf._get(url, jf.loginParams)
|
|
if err != nil || status != 200 {
|
|
return nil, status, err
|
|
}
|
|
json.Unmarshal([]byte(data), &result)
|
|
return result, status, nil
|
|
}
|
|
}
|
|
|
|
func (jf *Jellyfin) newUser(username, password string) (map[string]interface{}, int, error) {
|
|
url := fmt.Sprintf("%s/Users/New", jf.server)
|
|
stringData := map[string]string{
|
|
"Name": username,
|
|
"Password": password,
|
|
}
|
|
data := make(map[string]interface{})
|
|
for key, value := range stringData {
|
|
data[key] = value
|
|
}
|
|
response, status, err := jf._post(url, data, true)
|
|
var recv map[string]interface{}
|
|
json.Unmarshal([]byte(response), &recv)
|
|
if err != nil || !(status == 200 || status == 204) {
|
|
return nil, status, err
|
|
}
|
|
return recv, status, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) setPolicy(userId string, policy map[string]interface{}) (int, error) {
|
|
url := fmt.Sprintf("%s/Users/%s/Policy", jf.server, userId)
|
|
_, status, err := jf._post(url, policy, false)
|
|
if err != nil || status != 200 {
|
|
return status, err
|
|
}
|
|
return status, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) setConfiguration(userId string, configuration map[string]interface{}) (int, error) {
|
|
url := fmt.Sprintf("%s/Users/%s/Configuration", jf.server, userId)
|
|
_, status, err := jf._post(url, configuration, false)
|
|
return status, err
|
|
}
|
|
|
|
func (jf *Jellyfin) getDisplayPreferences(userId string) (map[string]interface{}, int, error) {
|
|
url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId)
|
|
data, status, err := jf._get(url, nil)
|
|
if err != nil || !(status == 204 || status == 200) {
|
|
return nil, status, err
|
|
}
|
|
var displayprefs map[string]interface{}
|
|
err = json.Unmarshal([]byte(data), &displayprefs)
|
|
if err != nil {
|
|
return nil, status, err
|
|
}
|
|
return displayprefs, status, nil
|
|
}
|
|
|
|
func (jf *Jellyfin) setDisplayPreferences(userId string, displayprefs map[string]interface{}) (int, error) {
|
|
url := fmt.Sprintf("%s/DisplayPreferences/usersettings?userId=%s&client=emby", jf.server, userId)
|
|
_, status, err := jf._post(url, displayprefs, false)
|
|
if err != nil || !(status == 204 || status == 200) {
|
|
return status, err
|
|
}
|
|
return status, nil
|
|
}
|