package main import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net/http" "os/signal" "syscall" "time" ) func main() { ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) defer can() jellyFrom, err := NewJelly(ctx, "squeaky2x3", "aFJKcZ4fUuN9FZ", "5c1de748f61145a085f272aea527c759", "213abf9acbe84d9fb9c3b06bbe1eec3b") if err != nil { panic(err) } log.Printf("from %+v", jellyFrom) jellyTo, err := NewJelly(ctx, "belly", "qBentOcpHMUjhD", "497b212a22e34b54be091055edbe264d", "b71b931108ba4323b75b675871a7738f") if err != nil { panic(err) } log.Printf("to %+v", jellyTo) log.Fatalf("not impl: %+v => %+v", jellyFrom, jellyTo) } type Jelly struct { u string apitoken string accesstoken string } func NewJelly(ctx context.Context, u, p, apitoken, optAccessToken string) (Jelly, error) { jelly := Jelly{u: u, apitoken: apitoken, accesstoken: optAccessToken} if optAccessToken == "" { resp, err := jelly.post(ctx, "https://jellyfin.home.blapointe.com/Users/AuthenticateByName", map[string]string{ "Username": u, "Pw": p, }, "Authorization", jelly._authHeader(jelly.apitoken)) if err != nil { return Jelly{}, err } var AccessToken struct { AccessToken string } if err := json.NewDecoder(resp.Body).Decode(&AccessToken); err != nil { return Jelly{}, err } jelly.accesstoken = AccessToken.AccessToken } return jelly, nil } func (jelly Jelly) authHeader() string { return jelly._authHeader(jelly.accesstoken) } func (jelly Jelly) _authHeader(token string) string { return fmt.Sprintf("MediaBrowser Token=%q, Client=%q, Version=%q, DeviceId=%q, Device=%q", token, "client", "version", "deviceId"+jelly.u, "device") } func (jelly Jelly) post(ctx context.Context, url string, body any, headers ...string) (*http.Response, error) { t := http.Transport{ DisableKeepAlives: true, } c := http.Client{ Timeout: time.Minute, Transport: &t, } b, _ := json.Marshal(body) req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", jelly.authHeader()) req = req.WithContext(ctx) for i := 0; i+1 < len(headers); i += 2 { req.Header.Set(headers[i], headers[i+1]) } resp, err := c.Do(req) if err != nil { return nil, err } defer resp.Body.Close() b, _ = io.ReadAll(resp.Body) resp.Body = io.NopCloser(bytes.NewReader(b)) if resp.StatusCode >= 400 { return nil, fmt.Errorf("(%d) %s", resp.StatusCode, b) } return resp, nil }