From 566aa0fca758d1a0e1d2c45fa3d058e7d5f95024 Mon Sep 17 00:00:00 2001 From: Cenk Alti Date: Tue, 6 Aug 2019 15:43:14 +0300 Subject: [PATCH] vendor: add github.com/putdotio/go-putio for putio client --- go.mod | 1 + go.sum | 2 + vendor/github.com/putdotio/go-putio/LICENSE | 22 + .../putdotio/go-putio/putio/account.go | 42 ++ .../putdotio/go-putio/putio/client.go | 180 ++++++++ .../github.com/putdotio/go-putio/putio/doc.go | 11 + .../putdotio/go-putio/putio/error.go | 25 + .../putdotio/go-putio/putio/events.go | 44 ++ .../putdotio/go-putio/putio/files.go | 429 ++++++++++++++++++ .../putdotio/go-putio/putio/friends.go | 124 +++++ .../putdotio/go-putio/putio/time.go | 33 ++ .../putdotio/go-putio/putio/transfers.go | 154 +++++++ .../putdotio/go-putio/putio/types.go | 166 +++++++ .../putdotio/go-putio/putio/zips.go | 80 ++++ vendor/modules.txt | 2 + 15 files changed, 1315 insertions(+) create mode 100644 vendor/github.com/putdotio/go-putio/LICENSE create mode 100644 vendor/github.com/putdotio/go-putio/putio/account.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/client.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/doc.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/error.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/events.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/files.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/friends.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/time.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/transfers.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/types.go create mode 100644 vendor/github.com/putdotio/go-putio/putio/zips.go diff --git a/go.mod b/go.mod index 31d243ad5..0e5c12304 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.8.1 github.com/pkg/sftp v1.10.1-0.20190523025818-e98a7bef6829 + github.com/putdotio/go-putio v0.0.0-20190731220109-37c0795af843 github.com/rfjakob/eme v0.0.0-20171028163933-2222dbd4ba46 github.com/sevlyar/go-daemon v0.1.5 github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index fa4dbc8ac..7144e993c 100644 --- a/go.sum +++ b/go.sum @@ -151,6 +151,8 @@ github.com/pkg/sftp v1.10.1-0.20190523025818-e98a7bef6829 h1:I+1BDgqX1nXLUL5Uio2 github.com/pkg/sftp v1.10.1-0.20190523025818-e98a7bef6829/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/putdotio/go-putio v0.0.0-20190731220109-37c0795af843 h1:Hf9Zw1N+0gQolpRwWOwisCombJ/9anLxUwFkLMQL22c= +github.com/putdotio/go-putio v0.0.0-20190731220109-37c0795af843/go.mod h1:EWtDL88jJLLWZzywr0QaPO+mGP8gFpvl8dcox8qTk3Y= github.com/rfjakob/eme v0.0.0-20171028163933-2222dbd4ba46 h1:w2CpS5muK+jyydnmlkqpAhzKmHmMBzBkfYUDjQNS1Dk= github.com/rfjakob/eme v0.0.0-20171028163933-2222dbd4ba46/go.mod h1:U2bmx0hDj8EyDdcxmD5t3XHDnBFnyNNc22n1R4008eM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= diff --git a/vendor/github.com/putdotio/go-putio/LICENSE b/vendor/github.com/putdotio/go-putio/LICENSE new file mode 100644 index 000000000..c2c63a7e2 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 H. İbrahim Güngör + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/putdotio/go-putio/putio/account.go b/vendor/github.com/putdotio/go-putio/putio/account.go new file mode 100644 index 000000000..cd3963888 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/account.go @@ -0,0 +1,42 @@ +package putio + +import "context" + +// AccountService is the service to gather information about user account. +type AccountService struct { + client *Client +} + +// Info retrieves user account information. +func (a *AccountService) Info(ctx context.Context) (AccountInfo, error) { + req, err := a.client.NewRequest(ctx, "GET", "/v2/account/info", nil) + if err != nil { + return AccountInfo{}, nil + } + + var r struct { + Info AccountInfo + } + _, err = a.client.Do(req, &r) + if err != nil { + return AccountInfo{}, err + } + return r.Info, nil +} + +// Settings retrieves user preferences. +func (a *AccountService) Settings(ctx context.Context) (Settings, error) { + req, err := a.client.NewRequest(ctx, "GET", "/v2/account/settings", nil) + if err != nil { + return Settings{}, nil + } + var r struct { + Settings Settings + } + _, err = a.client.Do(req, &r) + if err != nil { + return Settings{}, err + } + + return r.Settings, nil +} diff --git a/vendor/github.com/putdotio/go-putio/putio/client.go b/vendor/github.com/putdotio/go-putio/putio/client.go new file mode 100644 index 000000000..4ccc31354 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/client.go @@ -0,0 +1,180 @@ +package putio + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + defaultUserAgent = "go-putio" + defaultMediaType = "application/json" + defaultBaseURL = "https://api.put.io" + defaultUploadURL = "https://upload.put.io" +) + +// Client manages communication with Put.io v2 API. +type Client struct { + // HTTP client used to communicate with Put.io API + client *http.Client + + // Base URL for API requests + BaseURL *url.URL + + // base url for upload requests + uploadURL *url.URL + + // User agent for client + UserAgent string + + // ExtraHeaders are passed to the API server on every request. + ExtraHeaders http.Header + + // Services used for communicating with the API + Account *AccountService + Files *FilesService + Transfers *TransfersService + Zips *ZipsService + Friends *FriendsService + Events *EventsService +} + +// NewClient returns a new Put.io API client, using the htttpClient, which must +// be a new Oauth2 enabled http.Client. If httpClient is not defined, default +// HTTP client is used. +func NewClient(httpClient *http.Client) *Client { + if httpClient == nil { + httpClient = http.DefaultClient + } + + baseURL, _ := url.Parse(defaultBaseURL) + uploadURL, _ := url.Parse(defaultUploadURL) + c := &Client{ + client: httpClient, + BaseURL: baseURL, + uploadURL: uploadURL, + UserAgent: defaultUserAgent, + ExtraHeaders: make(http.Header), + } + + c.Account = &AccountService{client: c} + c.Files = &FilesService{client: c} + c.Transfers = &TransfersService{client: c} + c.Zips = &ZipsService{client: c} + c.Friends = &FriendsService{client: c} + c.Events = &EventsService{client: c} + + return c +} + +func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) { + req, err := c.NewRequest(ctx, "GET", "/v2/oauth2/validate", nil) + if err != nil { + return + } + var r struct { + UserID *int64 `json:"user_id"` + } + _, err = c.Do(req, &r) + return r.UserID, err +} + +// NewRequest creates an API request. A relative URL can be provided via +// relURL, which will be resolved to the BaseURL of the Client. +func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.Reader) (*http.Request, error) { + rel, err := url.Parse(relURL) + if err != nil { + return nil, err + } + + var u *url.URL + // XXX: workaroud for upload endpoint. upload method has a different base url, + // so we've a special case for testing purposes. + if relURL == "/v2/files/upload" { + u = c.uploadURL.ResolveReference(rel) + } else { + u = c.BaseURL.ResolveReference(rel) + } + + req, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, err + } + + req = req.WithContext(ctx) + req.Header.Set("Accept", defaultMediaType) + req.Header.Set("User-Agent", c.UserAgent) + + // merge headers with extra headers + for header, values := range c.ExtraHeaders { + for _, value := range values { + req.Header.Add(header, value) + } + } + + return req, nil +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. Response body is closed at all cases except +// v is nil. If v is nil, response body is not closed and the body can be used +// for streaming. +func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) { + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + + err = checkResponse(resp) + if err != nil { + // close the body at all times if there is an http error + resp.Body.Close() + return resp, err + } + + if v == nil { + return resp, nil + } + + // close the body for all cases from here + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(v) + if err != nil { + return resp, err + } + + return resp, nil +} + +// checkResponse is the entrypoint to reading the API response. If the response +// status code is not in success range, it will try to return a structured +// error. +func checkResponse(r *http.Response) error { + status := r.StatusCode + switch { + case status >= 200 && status <= 399: + return nil + case status >= 400 && status <= 599: + // server returns json + default: + return fmt.Errorf("unexpected status code: %d", status) + } + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return fmt.Errorf("body read error: %s. status: %v. Details: %v:", err, status, string(data[:250])) + } + if len(data) > 0 { + err = json.Unmarshal(data, errorResponse) + if err != nil { + return fmt.Errorf("json decod error: %s. status: %v. Details: %v:", err, status, string(data[:250])) + } + } + return errorResponse +} diff --git a/vendor/github.com/putdotio/go-putio/putio/doc.go b/vendor/github.com/putdotio/go-putio/putio/doc.go new file mode 100644 index 000000000..8fcebc7f2 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/doc.go @@ -0,0 +1,11 @@ +// Package putio is the Put.io API v2 client for Go. +// +// The go-putio package does not directly handle authentication. Instead, when +// creating a new client, pass an http.Client that can handle authentication for +// you. The easiest and recommended way to do this is using the golang.org/x/oauth2 +// library, but you can always use any other library that provides an http.Client. +// +// Note that when using an authenticated Client, all calls made by the client will +// include the specified OAuth token. Therefore, authenticated clients should +// almost never be shared between different users. +package putio diff --git a/vendor/github.com/putdotio/go-putio/putio/error.go b/vendor/github.com/putdotio/go-putio/putio/error.go new file mode 100644 index 000000000..659727806 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/error.go @@ -0,0 +1,25 @@ +package putio + +import ( + "fmt" + "net/http" +) + +// ErrorResponse reports the error caused by an API request. +type ErrorResponse struct { + Response *http.Response `json:"-"` + + Message string `json:"error_message"` + Type string `json:"error_type"` +} + +func (e *ErrorResponse) Error() string { + return fmt.Sprintf( + "Type: %v Message: %q. Original error: %v %v: %v", + e.Type, + e.Message, + e.Response.Request.Method, + e.Response.Request.URL, + e.Response.Status, + ) +} diff --git a/vendor/github.com/putdotio/go-putio/putio/events.go b/vendor/github.com/putdotio/go-putio/putio/events.go new file mode 100644 index 000000000..0b0db2fe6 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/events.go @@ -0,0 +1,44 @@ +package putio + +import "context" + +// EventsService is the service to gather information about user's events. +type EventsService struct { + client *Client +} + +// FIXME: events list returns inconsistent data structures. + +// List gets list of dashboard events. It includes downloads and share events. +func (e *EventsService) List(ctx context.Context) ([]Event, error) { + req, err := e.client.NewRequest(ctx, "GET", "/v2/events/list", nil) + if err != nil { + return nil, err + } + + var r struct { + Events []Event + } + _, err = e.client.Do(req, &r) + if err != nil { + return nil, err + } + return r.Events, nil + +} + +// Delete Clears all all dashboard events. +func (e *EventsService) Delete(ctx context.Context) error { + req, err := e.client.NewRequest(ctx, "POST", "/v2/events/delete", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = e.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/putdotio/go-putio/putio/files.go b/vendor/github.com/putdotio/go-putio/putio/files.go new file mode 100644 index 000000000..079ab493b --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/files.go @@ -0,0 +1,429 @@ +package putio + +import ( + "bytes" + "context" + "fmt" + "io" + "mime/multipart" + "net/url" + "strconv" + "strings" +) + +// FilesService is a general service to gather information about user files, +// such as listing, searching, creating new ones, or just fetching a single +// file. +type FilesService struct { + client *Client +} + +// Get fetches file metadata for given file ID. +func (f *FilesService) Get(ctx context.Context, id int64) (File, error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id), nil) + if err != nil { + return File{}, err + } + + var r struct { + File File `json:"file"` + } + _, err = f.client.Do(req, &r) + if err != nil { + return File{}, err + } + return r.File, nil +} + +// List fetches children for given directory ID. +func (f *FilesService) List(ctx context.Context, id int64) (children []File, parent File, err error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/list?parent_id="+itoa(id)+"&per_page=1000", nil) + if err != nil { + return + } + var r struct { + Files []File `json:"files"` + Parent File `json:"parent"` + Cursor string `json:"cursor"` + } + _, err = f.client.Do(req, &r) + if err != nil { + return + } + children = append(children, r.Files...) + parent = r.Parent + for r.Cursor != "" { + body := strings.NewReader(`{"cursor": "` + r.Cursor + `"}`) + req, err = f.client.NewRequest(ctx, "POST", "/v2/files/list/continue", body) + if err != nil { + return + } + req.Header.Set("content-type", "application/json") + r.Files = nil + r.Cursor = "" + _, err = f.client.Do(req, &r) + if err != nil { + return + } + children = append(children, r.Files...) + } + return +} + +// URL returns a URL of the file for downloading or streaming. +func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (string, error) { + notunnel := "notunnel=1" + if useTunnel { + notunnel = "notunnel=0" + } + + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/url?"+notunnel, nil) + if err != nil { + return "", err + } + + var r struct { + URL string `json:"url"` + } + + _, err = f.client.Do(req, &r) + if err != nil { + return "", err + } + + return r.URL, nil +} + +// CreateFolder creates a new folder under parent. +func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int64) (File, error) { + if name == "" { + return File{}, fmt.Errorf("empty folder name") + } + + params := url.Values{} + params.Set("name", name) + params.Set("parent_id", itoa(parent)) + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/create-folder", strings.NewReader(params.Encode())) + if err != nil { + return File{}, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + var r struct { + File File `json:"file"` + } + _, err = f.client.Do(req, &r) + if err != nil { + return File{}, err + } + + return r.File, nil +} + +// Delete deletes given files. +func (f *FilesService) Delete(ctx context.Context, files ...int64) error { + if len(files) == 0 { + return fmt.Errorf("no file id is given") + } + + var ids []string + for _, id := range files { + ids = append(ids, itoa(id)) + } + + params := url.Values{} + params.Set("file_ids", strings.Join(ids, ",")) + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/delete", strings.NewReader(params.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + return nil +} + +// Rename change the name of the file to newname. +func (f *FilesService) Rename(ctx context.Context, id int64, newname string) error { + if newname == "" { + return fmt.Errorf("new filename cannot be empty") + } + + params := url.Values{} + params.Set("file_id", itoa(id)) + params.Set("name", newname) + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/rename", strings.NewReader(params.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} + +// Move moves files to the given destination. +func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) error { + if len(files) == 0 { + return fmt.Errorf("no files given") + } + + var ids []string + for _, file := range files { + ids = append(ids, itoa(file)) + } + + params := url.Values{} + params.Set("file_ids", strings.Join(ids, ",")) + params.Set("parent_id", itoa(parent)) + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + return nil +} + +// Upload reads from given io.Reader and uploads the file contents to Put.io +// servers under directory given by parent. If parent is negative, user's +// prefered folder is used. +// +// If the uploaded file is a torrent file, Put.io will interpret it as a +// transfer and Transfer field will be present to represent the status of the +// tranfer. Likewise, if the uploaded file is a regular file, Transfer field +// would be nil and the uploaded file will be represented by the File field. +// +// This method reads the file contents into the memory, so it should be used for +// <150MB files. +func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string, parent int64) (Upload, error) { + if filename == "" { + return Upload{}, fmt.Errorf("filename cannot be empty") + } + + var buf bytes.Buffer + mw := multipart.NewWriter(&buf) + + // negative parent means use user's prefered download folder. + if parent >= 0 { + err := mw.WriteField("parent_id", itoa(parent)) + if err != nil { + return Upload{}, err + } + } + + formfile, err := mw.CreateFormFile("file", filename) + if err != nil { + return Upload{}, err + } + + _, err = io.Copy(formfile, r) + if err != nil { + return Upload{}, err + } + + err = mw.Close() + if err != nil { + return Upload{}, err + } + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf) + if err != nil { + return Upload{}, err + } + req.Header.Set("Content-Type", mw.FormDataContentType()) + + var response struct { + Upload + } + _, err = f.client.Do(req, &response) + if err != nil { + return Upload{}, err + } + return response.Upload, nil +} + +// Search makes a search request with the given query. Servers return 50 +// results at a time. The URL for the next 50 results are in Next field. If +// page is -1, all results are returned. +func (f *FilesService) Search(ctx context.Context, query string, page int64) (Search, error) { + if page == 0 || page < -1 { + return Search{}, fmt.Errorf("invalid page number") + } + if query == "" { + return Search{}, fmt.Errorf("no query given") + } + + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/search/"+query+"/page/"+itoa(page), nil) + if err != nil { + return Search{}, err + } + + var r Search + _, err = f.client.Do(req, &r) + if err != nil { + return Search{}, err + } + + return r, nil +} + +// Shared returns list of shared files and share information. +func (f *FilesService) shared(ctx context.Context) ([]share, error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/shared", nil) + if err != nil { + return nil, err + } + + var r struct { + Shared []share + } + _, err = f.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Shared, nil +} + +// SharedWith returns list of users the given file is shared with. +func (f *FilesService) sharedWith(ctx context.Context, id int64) ([]share, error) { + // FIXME: shared-with returns different json structure than /shared/ + // endpoint. so it's not an exported method until a common structure is + // decided + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/shared-with", nil) + if err != nil { + return nil, err + } + + var r struct { + Shared []share `json:"shared-with"` + } + _, err = f.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Shared, nil +} + +// Subtitles lists available subtitles for the given file for user's prefered +// subtitle language. +func (f *FilesService) Subtitles(ctx context.Context, id int64) ([]Subtitle, error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles", nil) + if err != nil { + return nil, err + } + + var r struct { + Subtitles []Subtitle + Default string + } + _, err = f.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Subtitles, nil +} + +// DownloadSubtitle sends the contents of the subtitle file. If the key is empty string, +// `default` key is used. This key is used to search for a subtitle in the +// following order and returns the first match: +// - A subtitle file that has identical parent folder and name with the video. +// - Subtitle file extracted from video if the format is MKV. +// - First match from OpenSubtitles.org. +func (f *FilesService) DownloadSubtitle(ctx context.Context, id int64, key string, format string) (io.ReadCloser, error) { + if key == "" { + key = "default" + } + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles/"+key, nil) + if err != nil { + return nil, err + } + + resp, err := f.client.Do(req, nil) + if err != nil { + return nil, err + } + + return resp.Body, nil +} + +// HLSPlaylist serves a HLS playlist for a video file. Use “all” as +// subtitleKey to get available subtitles for user’s preferred languages. +func (f *FilesService) HLSPlaylist(ctx context.Context, id int64, subtitleKey string) (io.ReadCloser, error) { + if subtitleKey == "" { + return nil, fmt.Errorf("empty subtitle key is given") + } + + req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey, nil) + if err != nil { + return nil, err + } + + resp, err := f.client.Do(req, nil) + if err != nil { + return nil, err + } + + return resp.Body, nil +} + +// SetVideoPosition sets default video position for a video file. +func (f *FilesService) SetVideoPosition(ctx context.Context, id int64, t int) error { + if t < 0 { + return fmt.Errorf("time cannot be negative") + } + + params := url.Values{} + params.Set("time", strconv.Itoa(t)) + + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from", strings.NewReader(params.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} + +// DeleteVideoPosition deletes video position for a video file. +func (f *FilesService) DeleteVideoPosition(ctx context.Context, id int64) error { + req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from/delete", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} + +func itoa(i int64) string { + return strconv.FormatInt(i, 10) +} diff --git a/vendor/github.com/putdotio/go-putio/putio/friends.go b/vendor/github.com/putdotio/go-putio/putio/friends.go new file mode 100644 index 000000000..cf2976151 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/friends.go @@ -0,0 +1,124 @@ +package putio + +import ( + "context" + "fmt" +) + +// FriendsService is the service to operate on user friends. +type FriendsService struct { + client *Client +} + +// List lists users friends. +func (f *FriendsService) List(ctx context.Context) ([]Friend, error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/list", nil) + if err != nil { + return nil, err + } + + var r struct { + Friends []Friend + Total int + } + _, err = f.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Friends, nil +} + +// WaitingRequests lists user's pending friend requests. +func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error) { + req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/waiting-requests", nil) + if err != nil { + return nil, err + } + + var r struct { + Friends []Friend + } + _, err = f.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Friends, nil +} + +// Request sends a friend request to the given username. +func (f *FriendsService) Request(ctx context.Context, username string) error { + if username == "" { + return fmt.Errorf("empty username") + } + req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/request", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} + +// Approve approves a friend request from the given username. +func (f *FriendsService) Approve(ctx context.Context, username string) error { + if username == "" { + return fmt.Errorf("empty username") + } + + req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/approve", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + return nil +} + +// Deny denies a friend request from the given username. +func (f *FriendsService) Deny(ctx context.Context, username string) error { + if username == "" { + return fmt.Errorf("empty username") + } + + req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/deny", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + return nil +} + +// Unfriend removed friend from user's friend list. +func (f *FriendsService) Unfriend(ctx context.Context, username string) error { + if username == "" { + return fmt.Errorf("empty username") + } + + req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/unfriend", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = f.client.Do(req, &struct{}{}) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/putdotio/go-putio/putio/time.go b/vendor/github.com/putdotio/go-putio/putio/time.go new file mode 100644 index 000000000..7684ce8b2 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/time.go @@ -0,0 +1,33 @@ +package putio + +import "time" + +// Time is a wrapper around time.Time that can be unmarshalled from a JSON +// string formatted as "2016-04-19T15:44:42". All methods of time.Time can be +// called on Time. +type Time struct { + time.Time +} + +func (t *Time) String() string { + return t.Time.String() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *Time) UnmarshalJSON(data []byte) error { + // put.io API has inconsistent time layouts for different endpoints, such + // as /files and /events + var timeLayouts = []string{`"2006-01-02T15:04:05"`, `"2006-01-02 15:04:05"`} + + s := string(data) + var err error + var tm time.Time + for _, layout := range timeLayouts { + tm, err = time.ParseInLocation(layout, s, time.UTC) + if err == nil { + t.Time = tm + return nil + } + } + return err +} diff --git a/vendor/github.com/putdotio/go-putio/putio/transfers.go b/vendor/github.com/putdotio/go-putio/putio/transfers.go new file mode 100644 index 000000000..627dff488 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/transfers.go @@ -0,0 +1,154 @@ +package putio + +import ( + "context" + "fmt" + "net/url" + "strings" +) + +// TransfersService is the service to operate on torrent transfers, such as +// adding a torrent or magnet link, retrying a current one etc. +type TransfersService struct { + client *Client +} + +// List lists all active transfers. If a transfer is completed, it will not be +// available in response. +func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) { + req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/list", nil) + if err != nil { + return nil, err + } + + var r struct { + Transfers []Transfer + } + _, err = t.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Transfers, nil +} + +// Add creates a new transfer. A valid torrent or a magnet URL is expected. +// Parent is the folder where the new transfer is downloaded to. If a negative +// value is given, user's preferred download folder is used. CallbackURL is +// used to send a POST request after the transfer is finished downloading. +func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64, callbackURL string) (Transfer, error) { + if urlStr == "" { + return Transfer{}, fmt.Errorf("empty URL") + } + + params := url.Values{} + params.Set("url", urlStr) + // negative values indicate user's preferred download folder. don't include + // it in the request + if parent >= 0 { + params.Set("save_parent_id", itoa(parent)) + } + if callbackURL != "" { + params.Set("callback_url", callbackURL) + } + + req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/add", strings.NewReader(params.Encode())) + if err != nil { + return Transfer{}, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + var r struct { + Transfer Transfer + } + _, err = t.client.Do(req, &r) + if err != nil { + return Transfer{}, err + } + + return r.Transfer, nil +} + +// Get returns the given transfer's properties. +func (t *TransfersService) Get(ctx context.Context, id int64) (Transfer, error) { + req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/"+itoa(id), nil) + if err != nil { + return Transfer{}, err + } + + var r struct { + Transfer Transfer + } + _, err = t.client.Do(req, &r) + if err != nil { + return Transfer{}, err + } + + return r.Transfer, nil +} + +// Retry retries previously failed transfer. +func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error) { + params := url.Values{} + params.Set("id", itoa(id)) + + req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/retry", strings.NewReader(params.Encode())) + if err != nil { + return Transfer{}, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + var r struct { + Transfer Transfer + } + _, err = t.client.Do(req, &r) + if err != nil { + return Transfer{}, err + } + + return r.Transfer, nil +} + +// Cancel deletes given transfers. +func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error { + if len(ids) == 0 { + return fmt.Errorf("no id given") + } + + var transfers []string + for _, id := range ids { + transfers = append(transfers, itoa(id)) + } + + params := url.Values{} + params.Set("transfer_ids", strings.Join(transfers, ",")) + + req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/cancel", strings.NewReader(params.Encode())) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = t.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} + +// Clean removes completed transfers from the transfer list. +func (t *TransfersService) Clean(ctx context.Context) error { + req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/clean", nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + _, err = t.client.Do(req, &struct{}{}) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/putdotio/go-putio/putio/types.go b/vendor/github.com/putdotio/go-putio/putio/types.go new file mode 100644 index 000000000..6a5ae84f0 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/types.go @@ -0,0 +1,166 @@ +package putio + +import "fmt" + +// File represents a Put.io file. +type File struct { + ID int64 `json:"id"` + Name string `json:"name"` + Size int64 `json:"size"` + ContentType string `json:"content_type"` + CreatedAt *Time `json:"created_at"` + UpdatedAt *Time `json:"updated_at"` + FirstAccessedAt *Time `json:"first_accessed_at"` + ParentID int64 `json:"parent_id"` + Screenshot string `json:"screenshot"` + OpensubtitlesHash string `json:"opensubtitles_hash"` + IsMP4Available bool `json:"is_mp4_available"` + Icon string `json:"icon"` + CRC32 string `json:"crc32"` + IsShared bool `json:"is_shared"` +} + +func (f *File) String() string { + return fmt.Sprintf("", f.ID, f.Name, f.Size) +} + +// IsDir reports whether the file is a directory. +func (f *File) IsDir() bool { + return f.ContentType == "application/x-directory" +} + +// Upload represents a Put.io upload. If the uploaded file is a torrent file, +// Transfer field will represent the status of the transfer. +type Upload struct { + File *File `json:"file"` + Transfer *Transfer `json:"transfer"` +} + +// Search represents a search response. +type Search struct { + Files []File `json:"files"` + Next string `json:"next"` +} + +// Transfer represents a Put.io transfer state. +type Transfer struct { + Availability int `json:"availability"` + CallbackURL string `json:"callback_url"` + CreatedAt *Time `json:"created_at"` + CreatedTorrent bool `json:"created_torrent"` + ClientIP string `json:"client_ip"` + + // FIXME: API returns either string or float non-deterministically. + // CurrentRatio float32 `json:"current_ratio"` + + DownloadSpeed int `json:"down_speed"` + Downloaded int64 `json:"downloaded"` + DownloadID int64 `json:"download_id"` + ErrorMessage string `json:"error_message"` + EstimatedTime int64 `json:"estimated_time"` + Extract bool `json:"extract"` + FileID int64 `json:"file_id"` + FinishedAt *Time `json:"finished_at"` + ID int64 `json:"id"` + IsPrivate bool `json:"is_private"` + MagnetURI string `json:"magneturi"` + Name string `json:"name"` + PeersConnected int `json:"peers_connected"` + PeersGettingFromUs int `json:"peers_getting_from_us"` + PeersSendingToUs int `json:"peers_sending_to_us"` + PercentDone int `json:"percent_done"` + SaveParentID int64 `json:"save_parent_id"` + SecondsSeeding int `json:"seconds_seeding"` + Size int `json:"size"` + Source string `json:"source"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + SubscriptionID int `json:"subscription_id"` + TorrentLink string `json:"torrent_link"` + TrackerMessage string `json:"tracker_message"` + Trackers string `json:"tracker"` + Type string `json:"type"` + UploadSpeed int `json:"up_speed"` + Uploaded int64 `json:"uploaded"` +} + +// AccountInfo represents user's account information. +type AccountInfo struct { + AccountActive bool `json:"account_active"` + AvatarURL string `json:"avatar_url"` + DaysUntilFilesDeletion int `json:"days_until_files_deletion"` + DefaultSubtitleLanguage string `json:"default_subtitle_language"` + Disk struct { + Avail int64 `json:"avail"` + Size int64 `json:"size"` + Used int64 `json:"used"` + } `json:"disk"` + HasVoucher bool `json:"has_voucher"` + Mail string `json:"mail"` + PlanExpirationDate string `json:"plan_expiration_date"` + Settings Settings `json:"settings"` + SimultaneousDownloadLimit int `json:"simultaneous_download_limit"` + SubtitleLanguages []string `json:"subtitle_languages"` + UserID int64 `json:"user_id"` + Username string `json:"username"` +} + +// Settings represents user's personal settings. +type Settings struct { + CallbackURL string `json:"callback_url"` + DefaultDownloadFolder int64 `json:"default_download_folder"` + DefaultSubtitleLanguage string `json:"default_subtitle_language"` + DownloadFolderUnset bool `json:"download_folder_unset"` + IsInvisible bool `json:"is_invisible"` + Nextepisode bool `json:"nextepisode"` + PrivateDownloadHostIP interface{} `json:"private_download_host_ip"` + PushoverToken string `json:"pushover_token"` + Routing string `json:"routing"` + Sorting string `json:"sorting"` + SSLEnabled bool `json:"ssl_enabled"` + StartFrom bool `json:"start_from"` + SubtitleLanguages []string `json:"subtitle_languages"` +} + +// Friend represents Put.io user's friend. +type Friend struct { + ID int64 `json:"id"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` +} + +// Zip represents Put.io zip file. +type Zip struct { + ID int64 `json:"id"` + CreatedAt *Time `json:"created_at"` + + Size int64 `json:"size"` + Status string `json:"status"` + URL string `json:"url"` +} + +// Subtitle represents a subtitle. +type Subtitle struct { + Key string + Language string + Name string + Source string +} + +// Event represents a Put.io event. It could be a transfer or a shared file. +type Event struct { + ID int64 `json:"id"` + FileID int64 `json:"file_id"` + Source string `json:"source"` + Type string `json:"type"` + TransferName string `json:"transfer_name"` + TransferSize int64 `json:"transfer_size"` + CreatedAt *Time `json:"created_at"` +} + +type share struct { + FileID int64 `json:"file_id"` + Filename string `json:"file_name"` + // Number of friends the file is shared with + SharedWith int64 `json:"shared_with"` +} diff --git a/vendor/github.com/putdotio/go-putio/putio/zips.go b/vendor/github.com/putdotio/go-putio/putio/zips.go new file mode 100644 index 000000000..66e33cc84 --- /dev/null +++ b/vendor/github.com/putdotio/go-putio/putio/zips.go @@ -0,0 +1,80 @@ +package putio + +import ( + "context" + "fmt" + "net/url" + "strings" +) + +// ZipsService is the service manage zip streams. +type ZipsService struct { + client *Client +} + +// Get gives detailed information about the given zip file id. +func (z *ZipsService) Get(ctx context.Context, id int64) (Zip, error) { + req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/"+itoa(id), nil) + if err != nil { + return Zip{}, err + } + + var r Zip + _, err = z.client.Do(req, &r) + if err != nil { + return Zip{}, err + } + + return r, nil + +} + +// List lists active zip files. +func (z *ZipsService) List(ctx context.Context) ([]Zip, error) { + req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/list", nil) + if err != nil { + return nil, err + } + + var r struct { + Zips []Zip + } + _, err = z.client.Do(req, &r) + if err != nil { + return nil, err + } + + return r.Zips, nil +} + +// Create creates zip files for given file IDs. If the operation is successful, +// a zip ID will be returned to keep track of zip process. +func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, error) { + if len(fileIDs) == 0 { + return 0, fmt.Errorf("no file id given") + } + + var ids []string + for _, id := range fileIDs { + ids = append(ids, itoa(id)) + } + + params := url.Values{} + params.Set("file_ids", strings.Join(ids, ",")) + + req, err := z.client.NewRequest(ctx, "POST", "/v2/zips/create", strings.NewReader(params.Encode())) + if err != nil { + return 0, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + var r struct { + ID int64 `json:"zip_id"` + } + _, err = z.client.Do(req, &r) + if err != nil { + return 0, err + } + + return r.ID, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5b860c7f8..633fe5e79 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -143,6 +143,8 @@ github.com/pkg/errors github.com/pkg/sftp # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib +# github.com/putdotio/go-putio v0.0.0-20190731220109-37c0795af843 +github.com/putdotio/go-putio/putio # github.com/rfjakob/eme v0.0.0-20171028163933-2222dbd4ba46 github.com/rfjakob/eme # github.com/russross/blackfriday v1.5.2