mirror of
https://github.com/rclone/rclone.git
synced 2024-12-25 05:24:03 +08:00
167 lines
4.9 KiB
Go
167 lines
4.9 KiB
Go
// Package api provides functionality for interacting with the iCloud API.
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/fshttp"
|
|
"github.com/rclone/rclone/lib/rest"
|
|
)
|
|
|
|
const (
|
|
baseEndpoint = "https://www.icloud.com"
|
|
homeEndpoint = "https://www.icloud.com"
|
|
setupEndpoint = "https://setup.icloud.com/setup/ws/1"
|
|
authEndpoint = "https://idmsa.apple.com/appleauth/auth"
|
|
)
|
|
|
|
type sessionSave func(*Session)
|
|
|
|
// Client defines the client configuration
|
|
type Client struct {
|
|
appleID string
|
|
password string
|
|
srv *rest.Client
|
|
Session *Session
|
|
sessionSaveCallback sessionSave
|
|
|
|
drive *DriveService
|
|
}
|
|
|
|
// New creates a new Client instance with the provided Apple ID, password, trust token, cookies, and session save callback.
|
|
//
|
|
// Parameters:
|
|
// - appleID: the Apple ID of the user.
|
|
// - password: the password of the user.
|
|
// - trustToken: the trust token for the session.
|
|
// - clientID: the client id for the session.
|
|
// - cookies: the cookies for the session.
|
|
// - sessionSaveCallback: the callback function to save the session.
|
|
func New(appleID, password, trustToken string, clientID string, cookies []*http.Cookie, sessionSaveCallback sessionSave) (*Client, error) {
|
|
icloud := &Client{
|
|
appleID: appleID,
|
|
password: password,
|
|
srv: rest.NewClient(fshttp.NewClient(context.Background())),
|
|
Session: NewSession(),
|
|
sessionSaveCallback: sessionSaveCallback,
|
|
}
|
|
|
|
icloud.Session.TrustToken = trustToken
|
|
icloud.Session.Cookies = cookies
|
|
icloud.Session.ClientID = clientID
|
|
return icloud, nil
|
|
}
|
|
|
|
// DriveService returns the DriveService instance associated with the Client.
|
|
func (c *Client) DriveService() (*DriveService, error) {
|
|
var err error
|
|
if c.drive == nil {
|
|
c.drive, err = NewDriveService(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return c.drive, nil
|
|
}
|
|
|
|
// Request makes a request and retries it if the session is invalid.
|
|
//
|
|
// This function is the main entry point for making requests to the iCloud
|
|
// API. If the initial request returns a 401 (Unauthorized), it will try to
|
|
// reauthenticate and retry the request.
|
|
func (c *Client) Request(ctx context.Context, opts rest.Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
|
|
resp, err = c.Session.Request(ctx, opts, request, response)
|
|
if err != nil && resp != nil {
|
|
// try to reauth
|
|
if resp.StatusCode == 401 || resp.StatusCode == 421 {
|
|
err = c.Authenticate(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.Session.Requires2FA() {
|
|
return nil, errors.New("trust token expired, please reauth")
|
|
}
|
|
return c.RequestNoReAuth(ctx, opts, request, response)
|
|
}
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
// RequestNoReAuth makes a request without re-authenticating.
|
|
//
|
|
// This function is useful when you have a session that is already
|
|
// authenticated, but you need to make a request without triggering
|
|
// a re-authentication.
|
|
func (c *Client) RequestNoReAuth(ctx context.Context, opts rest.Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
|
|
// Make the request without re-authenticating
|
|
resp, err = c.Session.Request(ctx, opts, request, response)
|
|
return resp, err
|
|
}
|
|
|
|
// Authenticate authenticates the client with the iCloud API.
|
|
func (c *Client) Authenticate(ctx context.Context) error {
|
|
if c.Session.Cookies != nil {
|
|
if err := c.Session.ValidateSession(ctx); err == nil {
|
|
fs.Debugf("icloud", "Valid session, no need to reauth")
|
|
return nil
|
|
}
|
|
c.Session.Cookies = nil
|
|
}
|
|
|
|
fs.Debugf("icloud", "Authenticating as %s\n", c.appleID)
|
|
err := c.Session.SignIn(ctx, c.appleID, c.password)
|
|
|
|
if err == nil {
|
|
err = c.Session.AuthWithToken(ctx)
|
|
if err == nil && c.sessionSaveCallback != nil {
|
|
c.sessionSaveCallback(c.Session)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// SignIn signs in the client using the provided context and credentials.
|
|
func (c *Client) SignIn(ctx context.Context) error {
|
|
return c.Session.SignIn(ctx, c.appleID, c.password)
|
|
}
|
|
|
|
// IntoReader marshals the provided values into a JSON encoded reader
|
|
func IntoReader(values any) (*bytes.Reader, error) {
|
|
m, err := json.Marshal(values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bytes.NewReader(m), nil
|
|
}
|
|
|
|
// RequestError holds info on a result state, icloud can return a 200 but the result is unknown
|
|
type RequestError struct {
|
|
Status string
|
|
Text string
|
|
}
|
|
|
|
// Error satisfy the error interface.
|
|
func (e *RequestError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Text, e.Status)
|
|
}
|
|
|
|
func newRequestError(Status string, Text string) *RequestError {
|
|
return &RequestError{
|
|
Status: strings.ToLower(Status),
|
|
Text: Text,
|
|
}
|
|
}
|
|
|
|
// newErr orf makes a new error from sprintf parameters.
|
|
func newRequestErrorf(Status string, Text string, Parameters ...interface{}) *RequestError {
|
|
return newRequestError(strings.ToLower(Status), fmt.Sprintf(Text, Parameters...))
|
|
}
|