Wim Brand
4 years ago
12 changed files with 473 additions and 1 deletions
-
1.gitignore
-
3README.md
-
3go.mod
-
26pixelapi/admin.go
-
24pixelapi/file.go
-
18pixelapi/filesystem.go
-
8pixelapi/list.go
-
27pixelapi/misc.go
-
16pixelapi/patreon.go
-
194pixelapi/pixelapi.go
-
19pixelapi/subscription.go
-
135pixelapi/user.go
@ -0,0 +1 @@ |
|||||
|
go.sum |
@ -1,3 +1,4 @@ |
|||||
# pixeldrain_api_client |
# pixeldrain_api_client |
||||
|
|
||||
Client for the pixeldrain API. Used by pixeldrain itself for tranferring data between the web UI and API server. And for rendering JSON responses |
|
||||
|
Client for the pixeldrain API. Used by pixeldrain itself for tranferring data |
||||
|
between the web UI and API server. And for rendering JSON responses |
@ -0,0 +1,3 @@ |
|||||
|
module fornaxian.tech/pixeldrain_api_client |
||||
|
|
||||
|
go 1.16 |
@ -0,0 +1,26 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"net/url" |
||||
|
|
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// AdminGetGlobals returns the global API settings
|
||||
|
func (p *PixelAPI) AdminGetGlobals() (resp []apitype.AdminGlobal, err error) { |
||||
|
return resp, p.jsonRequest("GET", "admin/globals", &resp) |
||||
|
} |
||||
|
|
||||
|
// AdminSetGlobals sets a global API setting
|
||||
|
func (p *PixelAPI) AdminSetGlobals(key, value string) (err error) { |
||||
|
return p.form("POST", "admin/globals", url.Values{"key": {key}, "value": {value}}, nil) |
||||
|
} |
||||
|
|
||||
|
// AdminBlockFiles blocks files from being downloaded
|
||||
|
func (p *PixelAPI) AdminBlockFiles(text, abuseType, reporter string) (bl apitype.AdminBlockFiles, err error) { |
||||
|
return bl, p.form( |
||||
|
"POST", "admin/block_files", |
||||
|
url.Values{"text": {text}, "type": {abuseType}, "reporter": {reporter}}, |
||||
|
&bl, |
||||
|
) |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"io" |
||||
|
"net/url" |
||||
|
|
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// GetFile makes a file download request and returns a readcloser. Don't forget
|
||||
|
// to close it!
|
||||
|
func (p *PixelAPI) GetFile(id string) (io.ReadCloser, error) { |
||||
|
return p.getRaw("file/" + id) |
||||
|
} |
||||
|
|
||||
|
// GetFileInfo gets the FileInfo from the pixeldrain API
|
||||
|
func (p *PixelAPI) GetFileInfo(id string) (resp apitype.FileInfo, err error) { |
||||
|
return resp, p.jsonRequest("GET", "file/"+id+"/info", &resp) |
||||
|
} |
||||
|
|
||||
|
// PostFileView adds a view to a file
|
||||
|
func (p *PixelAPI) PostFileView(id, viewtoken string) (err error) { |
||||
|
return p.form("POST", "file/"+id+"/view", url.Values{"token": {viewtoken}}, nil) |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"net/url" |
||||
|
|
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// GetFilesystemBuckets returns a list of buckets for the user. You need to be
|
||||
|
// authenticated
|
||||
|
func (p *PixelAPI) GetFilesystemBuckets() (resp []apitype.Bucket, err error) { |
||||
|
return resp, p.jsonRequest("GET", "filesystem", &resp) |
||||
|
} |
||||
|
|
||||
|
// GetFilesystemPath opens a filesystem path
|
||||
|
func (p *PixelAPI) GetFilesystemPath(path string) (resp apitype.FilesystemPath, err error) { |
||||
|
return resp, p.jsonRequest("GET", "filesystem/"+url.PathEscape(path)+"?stat", &resp) |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import "fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
|
||||
|
// GetListID get a List from the pixeldrain API
|
||||
|
func (p *PixelAPI) GetListID(id string) (resp apitype.ListInfo, err error) { |
||||
|
return resp, p.jsonRequest("GET", "list/"+id, &resp) |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import "fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
|
||||
|
// Recaptcha stores the reCaptcha site key
|
||||
|
type Recaptcha struct { |
||||
|
SiteKey string `json:"site_key"` |
||||
|
} |
||||
|
|
||||
|
// GetMiscRecaptcha gets the reCaptcha site key from the pixelapi server. If
|
||||
|
// reCaptcha is disabled the key will be empty
|
||||
|
func (p *PixelAPI) GetMiscRecaptcha() (resp Recaptcha, err error) { |
||||
|
return resp, p.jsonRequest("GET", "misc/recaptcha", &resp) |
||||
|
} |
||||
|
|
||||
|
// GetMiscViewToken requests a viewtoken from the server. The viewtoken is valid
|
||||
|
// for a limited amount of time and can be used to add views to a file.
|
||||
|
// Viewtokens can only be requested from localhost
|
||||
|
func (p *PixelAPI) GetMiscViewToken() (resp string, err error) { |
||||
|
return resp, p.jsonRequest("GET", "misc/viewtoken", &resp) |
||||
|
} |
||||
|
|
||||
|
// GetSiaPrice gets the price of one siacoin
|
||||
|
func (p *PixelAPI) GetSiaPrice() (resp float64, err error) { |
||||
|
var sp apitype.SiaPrice |
||||
|
return sp.Price, p.jsonRequest("GET", "misc/sia_price", &sp) |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// GetPatreonByID returns information about a patron by the ID
|
||||
|
func (p *PixelAPI) GetPatreonByID(id string) (resp apitype.Patron, err error) { |
||||
|
return resp, p.jsonRequest("GET", "patreon/"+id, &resp) |
||||
|
} |
||||
|
|
||||
|
// PostPatreonLink links a patreon subscription to the pixeldrain account which
|
||||
|
// is logged into this API client
|
||||
|
func (p *PixelAPI) PostPatreonLink(id string) (err error) { |
||||
|
return p.jsonRequest("POST", "patreon/"+id+"/link_subscription", nil) |
||||
|
} |
@ -0,0 +1,194 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"io/ioutil" |
||||
|
"net" |
||||
|
"net/http" |
||||
|
"net/url" |
||||
|
"strings" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// PixelAPI is the Pixeldrain API client
|
||||
|
type PixelAPI struct { |
||||
|
client *http.Client |
||||
|
apiEndpoint string |
||||
|
key string |
||||
|
realIP string |
||||
|
} |
||||
|
|
||||
|
// New creates a new Pixeldrain API client to query the Pixeldrain API with
|
||||
|
func New(apiEndpoint string) (api PixelAPI) { |
||||
|
api.client = &http.Client{Timeout: time.Minute * 5} |
||||
|
api.apiEndpoint = apiEndpoint |
||||
|
|
||||
|
// Pixeldrain uses unix domain sockets on its servers to minimize latency
|
||||
|
// between the web interface daemon and API daemon. Golang does not
|
||||
|
// understand that it needs to dial a unix socket on this case so we create
|
||||
|
// a custom HTTP transport which uses the unix socket instead of TCP
|
||||
|
if strings.HasPrefix(apiEndpoint, "http://unix:") { |
||||
|
// Get the socket path from the API endpoint
|
||||
|
var sockPath = strings.TrimPrefix(apiEndpoint, "http://unix:") |
||||
|
|
||||
|
// Fake the dialer to use a unix socket instead of TCP
|
||||
|
api.client.Transport = &http.Transport{ |
||||
|
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { |
||||
|
return net.Dial("unix", sockPath) |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
// Fake a domain name to stop Go's HTTP client from complaining about
|
||||
|
// the domain name. This string will be completely ignored during
|
||||
|
// requests
|
||||
|
api.apiEndpoint = "http://api.sock" |
||||
|
} else { |
||||
|
api.client.Transport = http.DefaultTransport |
||||
|
} |
||||
|
|
||||
|
return api |
||||
|
} |
||||
|
|
||||
|
// Login logs a user into the pixeldrain API. The original PixelAPI does not get
|
||||
|
// logged in, only the returned PixelAPI
|
||||
|
func (p PixelAPI) Login(apiKey string) PixelAPI { |
||||
|
p.key = apiKey |
||||
|
return p |
||||
|
} |
||||
|
|
||||
|
// RealIP sets the real IP address to use when making API requests
|
||||
|
func (p PixelAPI) RealIP(ip string) PixelAPI { |
||||
|
p.realIP = ip |
||||
|
return p |
||||
|
} |
||||
|
|
||||
|
// Standard response types
|
||||
|
|
||||
|
// Error is an error returned by the pixeldrain API. If the request failed
|
||||
|
// before it could reach the API the error will be on a different type
|
||||
|
type Error struct { |
||||
|
Status int `json:"-"` // One of the http.Status types
|
||||
|
Success bool `json:"success"` |
||||
|
StatusCode string `json:"value"` |
||||
|
Message string `json:"message"` |
||||
|
|
||||
|
// In case of the multiple_errors code this array will be populated with
|
||||
|
// more errors
|
||||
|
Errors []Error `json:"errors,omitempty"` |
||||
|
|
||||
|
// Metadata regarding the error
|
||||
|
Extra map[string]interface{} `json:"extra,omitempty"` |
||||
|
} |
||||
|
|
||||
|
func (e Error) Error() string { return e.StatusCode } |
||||
|
|
||||
|
// ErrIsServerError returns true if the error is a server-side error
|
||||
|
func ErrIsServerError(err error) bool { |
||||
|
if apierr, ok := err.(Error); ok && apierr.Status >= 500 { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// ErrIsClientError returns true if the error is a client-side error
|
||||
|
func ErrIsClientError(err error) bool { |
||||
|
if apierr, ok := err.(Error); ok && apierr.Status >= 400 && apierr.Status < 500 { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (p *PixelAPI) do(r *http.Request) (*http.Response, error) { |
||||
|
if p.key != "" { |
||||
|
r.SetBasicAuth("", p.key) |
||||
|
} |
||||
|
if p.realIP != "" { |
||||
|
r.Header.Set("X-Real-IP", p.realIP) |
||||
|
} |
||||
|
|
||||
|
return p.client.Do(r) |
||||
|
} |
||||
|
|
||||
|
func (p *PixelAPI) getString(path string) (string, error) { |
||||
|
req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil) |
||||
|
if err != nil { |
||||
|
return "", err |
||||
|
} |
||||
|
resp, err := p.do(req) |
||||
|
if err != nil { |
||||
|
return "", err |
||||
|
} |
||||
|
|
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
bodyBytes, err := ioutil.ReadAll(resp.Body) |
||||
|
|
||||
|
return string(bodyBytes), err |
||||
|
} |
||||
|
|
||||
|
func (p *PixelAPI) getRaw(path string) (io.ReadCloser, error) { |
||||
|
req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
resp, err := p.do(req) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return resp.Body, err |
||||
|
} |
||||
|
|
||||
|
func (p *PixelAPI) jsonRequest(method, path string, target interface{}) error { |
||||
|
req, err := http.NewRequest(method, p.apiEndpoint+"/"+path, nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
resp, err := p.do(req) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
defer resp.Body.Close() |
||||
|
return parseJSONResponse(resp, target) |
||||
|
} |
||||
|
|
||||
|
func (p *PixelAPI) form(method, url string, vals url.Values, target interface{}) error { |
||||
|
req, err := http.NewRequest(method, url, strings.NewReader(vals.Encode())) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
||||
|
|
||||
|
resp, err := p.do(req) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
defer resp.Body.Close() |
||||
|
return parseJSONResponse(resp, target) |
||||
|
} |
||||
|
|
||||
|
func parseJSONResponse(resp *http.Response, target interface{}) (err error) { |
||||
|
// Test for client side and server side errors
|
||||
|
if resp.StatusCode >= 400 { |
||||
|
errResp := Error{Status: resp.StatusCode} |
||||
|
if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return errResp |
||||
|
} |
||||
|
|
||||
|
if target == nil { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
if err = json.NewDecoder(resp.Body).Decode(target); err != nil { |
||||
|
return fmt.Errorf("failed to decode json response: %w", err) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"net/url" |
||||
|
|
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// GetSubscriptionID returns the subscription object identified by the given ID
|
||||
|
func (p *PixelAPI) GetSubscriptionID(id string) (resp apitype.Subscription, err error) { |
||||
|
return resp, p.jsonRequest("GET", p.apiEndpoint+"/subscription/"+url.PathEscape(id), &resp) |
||||
|
} |
||||
|
|
||||
|
// PostSubscriptionLink links a subscription to the logged in user account. Use
|
||||
|
// Login() before calling this function to select the account to use. This
|
||||
|
// action cannot be undone.
|
||||
|
func (p *PixelAPI) PostSubscriptionLink(id string) (err error) { |
||||
|
return p.jsonRequest("POST", p.apiEndpoint+"/subscription/"+url.PathEscape(id)+"/link", nil) |
||||
|
} |
@ -0,0 +1,135 @@ |
|||||
|
package pixelapi |
||||
|
|
||||
|
import ( |
||||
|
"net/url" |
||||
|
"strconv" |
||||
|
|
||||
|
"fornaxian.tech/pixeldrain_server/api/restapi/apitype" |
||||
|
) |
||||
|
|
||||
|
// UserRegister registers a new user on the Pixeldrain server. username and
|
||||
|
// password are always required. email is optional, but without it you will not
|
||||
|
// be able to reset your password in case you forget it. captcha depends on
|
||||
|
// whether reCaptcha is enabled on the Pixeldrain server, this can be checked
|
||||
|
// through the GetRecaptcha function.
|
||||
|
//
|
||||
|
// The register API can return multiple errors, which will be stored in the
|
||||
|
// Errors array. Check for len(Errors) == 0 to see if an error occurred. If err
|
||||
|
// != nil it means a connection error occurred
|
||||
|
func (p *PixelAPI) UserRegister(username, email, password, captcha string) (err error) { |
||||
|
return p.form( |
||||
|
"POST", "user/register", |
||||
|
url.Values{ |
||||
|
"username": {username}, |
||||
|
"email": {email}, |
||||
|
"password": {password}, |
||||
|
"recaptcha_response": {captcha}, |
||||
|
}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PostUserLogin logs a user in with the provided credentials. The response will
|
||||
|
// contain the returned API key. If saveKey is true the API key will also be
|
||||
|
// saved in the client and following requests with this client will be
|
||||
|
// autenticated
|
||||
|
func (p *PixelAPI) PostUserLogin(username, password string) (resp apitype.UserSession, err error) { |
||||
|
return resp, p.form( |
||||
|
"POST", "user/login", |
||||
|
url.Values{"username": {username}, "password": {password}}, |
||||
|
&resp, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// GetUser returns information about the logged in user. Requires an API key
|
||||
|
func (p *PixelAPI) GetUser() (resp apitype.UserInfo, err error) { |
||||
|
return resp, p.jsonRequest("GET", "user", &resp) |
||||
|
} |
||||
|
|
||||
|
// PostUserSession creates a new user sessions
|
||||
|
func (p *PixelAPI) PostUserSession() (resp apitype.UserSession, err error) { |
||||
|
return resp, p.jsonRequest("POST", "user/session", &resp) |
||||
|
} |
||||
|
|
||||
|
// GetUserSession lists all active user sessions
|
||||
|
func (p *PixelAPI) GetUserSession() (resp []apitype.UserSession, err error) { |
||||
|
return resp, p.jsonRequest("GET", "user/session", &resp) |
||||
|
} |
||||
|
|
||||
|
// DeleteUserSession destroys an API key so it can no longer be used to perform
|
||||
|
// actions
|
||||
|
func (p *PixelAPI) DeleteUserSession(key string) (err error) { |
||||
|
return p.jsonRequest("DELETE", "user/session", nil) |
||||
|
} |
||||
|
|
||||
|
// GetUserFiles gets files uploaded by a user
|
||||
|
func (p *PixelAPI) GetUserFiles() (resp apitype.FileInfoSlice, err error) { |
||||
|
return resp, p.jsonRequest("GET", "user/files", &resp) |
||||
|
} |
||||
|
|
||||
|
// GetUserLists gets lists created by a user
|
||||
|
func (p *PixelAPI) GetUserLists() (resp apitype.ListInfoSlice, err error) { |
||||
|
return resp, p.jsonRequest("GET", "user/lists", &resp) |
||||
|
} |
||||
|
|
||||
|
// PutUserPassword changes the user's password
|
||||
|
func (p *PixelAPI) PutUserPassword(oldPW, newPW string) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/password", |
||||
|
url.Values{"old_password": {oldPW}, "new_password": {newPW}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PutUserEmailReset starts the e-mail change process. An email will be sent to
|
||||
|
// the new address to verify that it's real. Once the link in the e-mail is
|
||||
|
// clicked the key it contains can be sent to the API with UserEmailResetConfirm
|
||||
|
// and the change will be applied
|
||||
|
func (p *PixelAPI) PutUserEmailReset(email string, delete bool) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/email_reset", |
||||
|
url.Values{"new_email": {email}, "delete": {strconv.FormatBool(delete)}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PutUserEmailResetConfirm finishes process of changing a user's e-mail address
|
||||
|
func (p *PixelAPI) PutUserEmailResetConfirm(key string) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/email_reset_confirm", |
||||
|
url.Values{"key": {key}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PutUserPasswordReset starts the password reset process. An email will be sent
|
||||
|
// the user to verify that it really wanted to reset the password. Once the link
|
||||
|
// in the e-mail is clicked the key it contains can be sent to the API with
|
||||
|
// UserPasswordResetConfirm and a new password can be set
|
||||
|
func (p *PixelAPI) PutUserPasswordReset(email string, recaptchaResponse string) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/password_reset", |
||||
|
url.Values{"email": {email}, "recaptcha_response": {recaptchaResponse}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PutUserPasswordResetConfirm finishes process of resetting a user's password.
|
||||
|
// If the key is valid the new_password parameter will be saved as the new
|
||||
|
// password
|
||||
|
func (p *PixelAPI) PutUserPasswordResetConfirm(key string, newPassword string) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/password_reset_confirm", |
||||
|
url.Values{"key": {key}, "new_password": {newPassword}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// PutUserUsername changes the user's username.
|
||||
|
func (p *PixelAPI) PutUserUsername(username string) (err error) { |
||||
|
return p.form( |
||||
|
"PUT", "user/username", |
||||
|
url.Values{"new_username": {username}}, |
||||
|
nil, |
||||
|
) |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue