├── LICENSE ├── README.md ├── go.mod ├── go.sum └── pixelapi ├── admin.go ├── file.go ├── filesystem.go ├── list.go ├── misc.go ├── patreon.go ├── pixelapi.go ├── subscription.go └── user.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixeldrain_api_client 2 | 3 | Client for the pixeldrain API. Used by pixeldrain itself for tranferring data 4 | between the web UI and API server. And for rendering JSON responses. 5 | 6 | The structs defined in this package are the real structs that the API server 7 | uses to render its JSON responses, so they are guaranteed to up up-to-date. 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module fornaxian.tech/pixeldrain_api_client 2 | 3 | go 1.22 4 | 5 | require github.com/gocql/gocql v1.3.1 6 | 7 | require ( 8 | github.com/golang/snappy v0.0.4 // indirect 9 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 10 | gopkg.in/inf.v0 v0.9.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 2 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 3 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 4 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/gocql/gocql v0.0.0-20211015133455-b225f9b53fa1 h1:px9qUCy/RNJNsfCam4m2IxWGxNuimkrioEF0vrrbPsg= 7 | github.com/gocql/gocql v0.0.0-20211015133455-b225f9b53fa1/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= 8 | github.com/gocql/gocql v1.3.1 h1:BTwM4rux+ah5G3oH6/MQa+tur/TDd/XAAOXDxBBs7rg= 9 | github.com/gocql/gocql v1.3.1/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= 10 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 11 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 12 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 13 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 14 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= 15 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 16 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 17 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 19 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 20 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 24 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 25 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 26 | -------------------------------------------------------------------------------- /pixelapi/admin.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | 7 | "github.com/gocql/gocql" 8 | ) 9 | 10 | // AdminGlobal is a global setting in pixeldrain's back-end 11 | type AdminGlobal struct { 12 | Key string `json:"key"` 13 | Value string `json:"value"` 14 | } 15 | 16 | // AdminBlockFiles is an array of files which were blocked 17 | type AdminBlockFiles struct { 18 | FilesBlocked []string `json:"files_blocked"` 19 | FilesystemNodesBlocked []string `json:"filesystem_nodes_blocked"` 20 | } 21 | 22 | // AdminAbuseReporter is an e-mail address which is allowed to send abuse 23 | // reports to abuse@pixeldrain.com 24 | type AdminAbuseReporter struct { 25 | FromAddress string `json:"from_address"` 26 | Name string `json:"name"` 27 | Status string `json:"status"` 28 | Created time.Time `json:"created"` 29 | ReportsSent int `json:"reports_sent"` 30 | FilesBlocked int `json:"files_blocked"` 31 | LastUsed time.Time `json:"last_used"` 32 | LastMessageSubject string `json:"last_message_subject"` 33 | LastMessageText string `json:"last_message_text"` 34 | LastMessageHTML string `json:"last_message_html"` 35 | } 36 | 37 | type AdminAbuseReportContainer struct { 38 | ID gocql.UUID `json:"id"` 39 | Reports []AdminAbuseReport `json:"reports"` 40 | File FileInfo `json:"file"` 41 | Type string `json:"type"` 42 | Status string `json:"status"` 43 | FirstReportTime time.Time `json:"first_report_time"` 44 | } 45 | 46 | // AdminAbuseReport is a report someone submitted for a file 47 | type AdminAbuseReport struct { 48 | FileInstanceID gocql.UUID `json:"file_id"` 49 | IPAddress string `json:"ip_address"` 50 | Time time.Time `json:"time"` 51 | Status string `json:"status"` // pending, rejected, granted 52 | Type string `json:"type"` 53 | EMail string `json:"email"` 54 | Description string `json:"description"` 55 | } 56 | 57 | type AdminIPBan struct { 58 | Address string `json:"address"` 59 | Offences []AdminBanOffence `json:"offences"` 60 | } 61 | 62 | type AdminUserBan struct { 63 | UserID string `json:"user_id"` 64 | User UserInfo `json:"user"` 65 | Offences []AdminBanOffence `json:"offences"` 66 | } 67 | 68 | type AdminBanOffence struct { 69 | BanTime time.Time `json:"ban_time"` 70 | ExpireTime time.Time `json:"expire_time"` 71 | Reason string `json:"reason"` 72 | Reporter string `json:"reporter"` 73 | FileID gocql.UUID `json:"file_id"` 74 | FileLink string `json:"file_link"` 75 | FileName string `json:"file_name"` 76 | } 77 | 78 | // AdminGetGlobals returns the global API settings 79 | func (p *PixelAPI) AdminGetGlobals() (resp []AdminGlobal, err error) { 80 | return resp, p.jsonRequest("GET", "admin/globals", &resp) 81 | } 82 | 83 | // AdminSetGlobals sets a global API setting 84 | func (p *PixelAPI) AdminSetGlobals(key, value string) (err error) { 85 | return p.form("POST", "admin/globals", url.Values{"key": {key}, "value": {value}}, nil) 86 | } 87 | 88 | // AdminBlockFiles blocks files from being downloaded 89 | func (p *PixelAPI) AdminBlockFiles(text, abuseType, reporter string) (bl AdminBlockFiles, err error) { 90 | return bl, p.form( 91 | "POST", "admin/block_files", 92 | url.Values{"text": {text}, "type": {abuseType}, "reporter": {reporter}}, 93 | &bl, 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /pixelapi/file.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | "time" 7 | ) 8 | 9 | // FileID is returned when a file has been sucessfully uploaded 10 | type FileID struct { 11 | ID string `json:"id"` 12 | } 13 | 14 | // FileInfo is the public file information response 15 | type FileInfo struct { 16 | ID string `json:"id"` 17 | Name string `json:"name"` 18 | Size int `json:"size"` 19 | Views int `json:"views"` 20 | BandwidthUsed int `json:"bandwidth_used"` 21 | BandwidthUsedPaid int `json:"bandwidth_used_paid"` 22 | Downloads int `json:"downloads"` 23 | DateUpload time.Time `json:"date_upload"` 24 | DateLastView time.Time `json:"date_last_view"` 25 | MimeType string `json:"mime_type"` 26 | ThumbnailHREF string `json:"thumbnail_href"` 27 | HashSHA256 string `json:"hash_sha256"` 28 | 29 | // Custom deletion options 30 | DeleteAfterDate time.Time `json:"delete_after_date"` 31 | DeleteAfterDownloads int `json:"delete_after_downloads"` 32 | 33 | // Abuse report information 34 | Availability string `json:"availability"` 35 | AvailabilityMessage string `json:"availability_message"` 36 | AbuseType string `json:"abuse_type"` 37 | AbuseReporterName string `json:"abuse_reporter_name"` 38 | 39 | // Personalization 40 | Branding map[string]string `json:"branding,omitempty"` 41 | 42 | // Based on user permissions 43 | CanEdit bool `json:"can_edit"` 44 | CanDownload bool `json:"can_download"` 45 | ShowAds bool `json:"show_ads"` 46 | AllowVideoPlayer bool `json:"allow_video_player"` 47 | DownloadSpeedLimit int `json:"download_speed_limit"` 48 | SkipFileViewer bool `json:"skip_file_viewer,omitempty"` 49 | } 50 | 51 | // FileStats contains realtime statistics for a file 52 | type FileStats struct { 53 | Views int `json:"views"` 54 | Downloads int `json:"downloads"` 55 | Bandwidth int `json:"bandwidth"` 56 | BandwidthPaid int `json:"bandwidth_paid"` 57 | } 58 | 59 | // FileTimeSeries returns historic data for a file 60 | type FileTimeSeries struct { 61 | Views TimeSeries `json:"views"` 62 | Downloads TimeSeries `json:"downloads"` 63 | Bandwidth TimeSeries `json:"bandwidth"` 64 | BandwidthPaid TimeSeries `json:"bandwidth_paid"` 65 | } 66 | 67 | // TimeSeries contains data captures over a time span 68 | type TimeSeries struct { 69 | Timestamps []time.Time `json:"timestamps"` 70 | Amounts []int `json:"amounts"` 71 | } 72 | 73 | // GetFile makes a file download request and returns a readcloser. Don't forget 74 | // to close it! 75 | func (p *PixelAPI) GetFile(id string) (io.ReadCloser, error) { 76 | return p.getRaw("file/" + id) 77 | } 78 | 79 | // GetFileInfo gets the FileInfo from the pixeldrain API 80 | func (p *PixelAPI) GetFileInfo(id string) (resp FileInfo, err error) { 81 | return resp, p.jsonRequest("GET", "file/"+id+"/info", &resp) 82 | } 83 | 84 | // PostFileView adds a view to a file 85 | func (p *PixelAPI) PostFileView(id, viewtoken string) (err error) { 86 | return p.form("POST", "file/"+id+"/view", url.Values{"token": {viewtoken}}, nil) 87 | } 88 | -------------------------------------------------------------------------------- /pixelapi/filesystem.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | ) 7 | 8 | // FilesystemPath contains a filesystem with a bucket and all its children 9 | // leading up to the requested node 10 | type FilesystemPath struct { 11 | Path []FilesystemNode `json:"path"` 12 | BaseIndex int `json:"base_index"` 13 | Children []FilesystemNode `json:"children"` 14 | Permissions Permissions `json:"permissions"` 15 | Context FilesystemContext `json:"context"` 16 | } 17 | 18 | // FilesystemNode is the return value of the GET /filesystem/ API 19 | type FilesystemNode struct { 20 | Type string `json:"type"` 21 | Path string `json:"path"` 22 | Name string `json:"name"` 23 | Created time.Time `json:"created"` 24 | Modified time.Time `json:"modified"` 25 | ModeStr string `json:"mode_string"` 26 | ModeOctal string `json:"mode_octal"` 27 | CreatedBy string `json:"created_by"` 28 | 29 | AbuseType string `json:"abuse_type,omitempty"` 30 | AbuseReportTime *time.Time `json:"abuse_report_time,omitempty"` 31 | 32 | // File params 33 | FileSize int `json:"file_size"` 34 | FileType string `json:"file_type"` 35 | SHA256Sum string `json:"sha256_sum"` 36 | 37 | // Meta params 38 | ID string `json:"id,omitempty"` 39 | Properties map[string]string `json:"properties,omitempty"` 40 | LoggingEnabledAt time.Time `json:"logging_enabled_at"` 41 | LinkPermissions *Permissions `json:"link_permissions,omitempty"` 42 | UserPermissions map[string]Permissions `json:"user_permissions,omitempty"` 43 | PasswordPermissions map[string]Permissions `json:"password_permissions,omitempty"` 44 | CustomDomainName string `json:"custom_domain_name,omitempty"` 45 | } 46 | 47 | // Permissions contains the actions a user can perform on an object 48 | type Permissions struct { 49 | Owner bool `json:"owner"` 50 | Read bool `json:"read"` 51 | Write bool `json:"write"` 52 | Delete bool `json:"delete"` 53 | } 54 | 55 | type FilesystemContext struct { 56 | PremiumTransfer bool `json:"premium_transfer"` 57 | } 58 | 59 | // FileTimeSeries returns historic data for a filesystem node 60 | type FilesystemTimeSeries struct { 61 | Downloads TimeSeries `json:"downloads"` 62 | TransferFree TimeSeries `json:"transfer_free"` 63 | TransferPaid TimeSeries `json:"transfer_paid"` 64 | } 65 | 66 | // GetFilesystemBuckets returns a list of filesystems for the user. You need to 67 | // be authenticated 68 | func (p *PixelAPI) GetFilesystems() (resp []FilesystemNode, err error) { 69 | return resp, p.jsonRequest("GET", "filesystem", &resp) 70 | } 71 | 72 | // GetFilesystemPath opens a filesystem path 73 | func (p *PixelAPI) GetFilesystemPath(path string) (resp FilesystemPath, err error) { 74 | return resp, p.jsonRequest("GET", "filesystem/"+url.PathEscape(path)+"?stat", &resp) 75 | } 76 | -------------------------------------------------------------------------------- /pixelapi/list.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // ListID is returned when a list has been sucessfully created 8 | type ListID struct { 9 | ID string `json:"id"` 10 | } 11 | 12 | // ListInfo information object from the pixeldrain API 13 | type ListInfo struct { 14 | ID string `json:"id"` 15 | Title string `json:"title"` 16 | DateCreated time.Time `json:"date_created"` 17 | FileCount int `json:"file_count"` 18 | Files []ListFile `json:"files"` 19 | CanEdit bool `json:"can_edit"` 20 | } 21 | 22 | // ListFile information object from the pixeldrain API 23 | type ListFile struct { 24 | DetailHREF string `json:"detail_href"` 25 | Description string `json:"description"` 26 | FileInfo `json:""` 27 | } 28 | 29 | // GetListID get a List from the pixeldrain API 30 | func (p *PixelAPI) GetListID(id string) (resp ListInfo, err error) { 31 | return resp, p.jsonRequest("GET", "list/"+id, &resp) 32 | } 33 | -------------------------------------------------------------------------------- /pixelapi/misc.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | // Recaptcha stores the reCaptcha site key 4 | type Recaptcha struct { 5 | SiteKey string `json:"site_key"` 6 | } 7 | 8 | // GetMiscRecaptcha gets the reCaptcha site key from the pixelapi server. If 9 | // reCaptcha is disabled the key will be empty 10 | func (p *PixelAPI) GetMiscRecaptcha() (resp Recaptcha, err error) { 11 | return resp, p.jsonRequest("GET", "misc/recaptcha", &resp) 12 | } 13 | 14 | // SiaPrice is the price of one siacoin 15 | type SiaPrice struct { 16 | Price float64 `json:"price"` 17 | } 18 | 19 | // GetSiaPrice gets the price of one siacoin 20 | func (p *PixelAPI) GetSiaPrice() (resp float64, err error) { 21 | var sp SiaPrice 22 | return sp.Price, p.jsonRequest("GET", "misc/sia_price", &sp) 23 | } 24 | 25 | type RateLimits struct { 26 | ServerOverload bool `json:"server_overload"` 27 | SpeedLimit int `json:"speed_limit"` 28 | DownloadLimit int `json:"download_limit"` 29 | DownloadLimitUsed int `json:"download_limit_used"` 30 | TransferLimit int `json:"transfer_limit"` 31 | TransferLimitUsed int `json:"transfer_limit_used"` 32 | } 33 | 34 | func (p *PixelAPI) GetMiscRateLimits() (rl RateLimits, err error) { 35 | return rl, p.jsonRequest("GET", "misc/rate_limits", &rl) 36 | } 37 | 38 | type ClusterSpeed struct { 39 | ServerTX int64 `json:"server_tx"` 40 | ServerRX int64 `json:"server_rx"` 41 | CacheTX int64 `json:"cache_tx"` 42 | CacheRX int64 `json:"cache_rx"` 43 | StorageTX int64 `json:"storage_tx"` 44 | StorageRX int64 `json:"storage_rx"` 45 | } 46 | 47 | func (p *PixelAPI) GetMiscClusterSpeed() (s ClusterSpeed, err error) { 48 | return s, p.jsonRequest("GET", "misc/cluster_speed", &s) 49 | } 50 | -------------------------------------------------------------------------------- /pixelapi/patreon.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import "time" 4 | 5 | // Patron is a backer on pixeldrain's patreon campaign 6 | type Patron struct { 7 | PatreonUserID string `json:"patreon_user_id"` 8 | FullName string `json:"full_name"` 9 | LastChargeDate time.Time `json:"last_charge_date"` 10 | LastChargeStatus string `json:"last_charge_status"` 11 | LifetimeSupportCents int `json:"lifetime_support_cents"` 12 | PatronStatus string `json:"patron_status"` 13 | PledgeAmountCents int `json:"pledge_amount_cents"` 14 | PledgeRelationshipStart time.Time `json:"pledge_relationship_start"` 15 | UserEmail string `json:"user_email"` 16 | Subscription SubscriptionType `json:"subscription"` 17 | } 18 | 19 | // GetPatreonByID returns information about a patron by the ID 20 | func (p *PixelAPI) GetPatreonByID(id string) (resp Patron, err error) { 21 | return resp, p.jsonRequest("GET", "patreon/"+id, &resp) 22 | } 23 | 24 | // PostPatreonLink links a patreon subscription to the pixeldrain account which 25 | // is logged into this API client 26 | func (p *PixelAPI) PostPatreonLink(id string) (err error) { 27 | return p.jsonRequest("POST", "patreon/"+id+"/link_subscription", nil) 28 | } 29 | -------------------------------------------------------------------------------- /pixelapi/pixelapi.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | // PixelAPI is the Pixeldrain API client 16 | type PixelAPI struct { 17 | client *http.Client 18 | apiEndpoint string 19 | key string 20 | realIP string 21 | realAgent string 22 | } 23 | 24 | // New creates a new Pixeldrain API client to query the Pixeldrain API with 25 | func New(apiEndpoint string) (api PixelAPI) { 26 | return PixelAPI{ 27 | client: &http.Client{Timeout: time.Minute * 5}, 28 | apiEndpoint: apiEndpoint, 29 | } 30 | } 31 | 32 | func (p PixelAPI) UnixSocketPath(socket string) PixelAPI { 33 | // Pixeldrain uses unix domain sockets on its servers to minimize latency 34 | // between the web interface daemon and API daemon. Golang does not 35 | // understand that it needs to dial a unix socket on this case so we create 36 | // a custom HTTP transport which uses the unix socket instead of TCP 37 | 38 | // Fake the dialer to use a unix socket instead of TCP 39 | p.client.Transport = &http.Transport{ 40 | DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { 41 | return net.Dial("unix", socket) 42 | }, 43 | } 44 | 45 | // The hostname part of the URL is not used, but the protocol and path are. 46 | // The pixeldrain unix socket doesn't use https so we need to disable it 47 | p.apiEndpoint = strings.Replace(p.apiEndpoint, "https://", "http://", 1) 48 | return p 49 | } 50 | 51 | // Login logs a user into the pixeldrain API. The original PixelAPI does not get 52 | // logged in, only the returned PixelAPI 53 | func (p PixelAPI) Login(apiKey string) PixelAPI { 54 | p.key = apiKey 55 | return p 56 | } 57 | 58 | // RealIP sets the real IP address to use when making API requests 59 | func (p PixelAPI) RealIP(ip string) PixelAPI { 60 | p.realIP = ip 61 | return p 62 | } 63 | 64 | // RealAgent sets the real user agent to use when making API requests 65 | func (p PixelAPI) RealAgent(agent string) PixelAPI { 66 | p.realAgent = agent 67 | return p 68 | } 69 | 70 | // Standard response types 71 | 72 | // Error is an error returned by the pixeldrain API. If the request failed 73 | // before it could reach the API the error will be on a different type 74 | type Error struct { 75 | Status int `json:"-"` // One of the http.Status types 76 | Success bool `json:"success"` 77 | StatusCode string `json:"value"` 78 | Message string `json:"message"` 79 | 80 | // In case of the multiple_errors code this array will be populated with 81 | // more errors 82 | Errors []Error `json:"errors,omitempty"` 83 | 84 | // Metadata regarding the error 85 | Extra map[string]interface{} `json:"extra,omitempty"` 86 | } 87 | 88 | func (e Error) Error() string { return e.StatusCode } 89 | 90 | // ErrIsServerError returns true if the error is a server-side error 91 | func ErrIsServerError(err error) bool { 92 | if apierr, ok := err.(Error); ok && apierr.Status >= 500 { 93 | return true 94 | } 95 | return false 96 | } 97 | 98 | // ErrIsClientError returns true if the error is a client-side error 99 | func ErrIsClientError(err error) bool { 100 | if apierr, ok := err.(Error); ok && apierr.Status >= 400 && apierr.Status < 500 { 101 | return true 102 | } 103 | return false 104 | } 105 | 106 | func (p *PixelAPI) do(r *http.Request) (*http.Response, error) { 107 | if p.key != "" { 108 | r.SetBasicAuth("", p.key) 109 | } 110 | if p.realIP != "" { 111 | r.Header.Set("X-Real-IP", p.realIP) 112 | } 113 | if p.realAgent != "" { 114 | r.Header.Set("User-Agent", p.realAgent) 115 | } 116 | 117 | return p.client.Do(r) 118 | } 119 | 120 | func (p *PixelAPI) getRaw(path string) (io.ReadCloser, error) { 121 | req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil) 122 | if err != nil { 123 | return nil, err 124 | } 125 | resp, err := p.do(req) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | return resp.Body, err 131 | } 132 | 133 | func (p *PixelAPI) jsonRequest(method, path string, target interface{}) error { 134 | req, err := http.NewRequest(method, p.apiEndpoint+"/"+path, nil) 135 | if err != nil { 136 | return err 137 | } 138 | resp, err := p.do(req) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | defer resp.Body.Close() 144 | return parseJSONResponse(resp, target) 145 | } 146 | 147 | func (p *PixelAPI) form(method, url string, vals url.Values, target interface{}) error { 148 | req, err := http.NewRequest(method, p.apiEndpoint+"/"+url, strings.NewReader(vals.Encode())) 149 | if err != nil { 150 | return fmt.Errorf("prepare request failed: %w", err) 151 | } 152 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 153 | 154 | resp, err := p.do(req) 155 | if err != nil { 156 | return fmt.Errorf("do request failed: %w", err) 157 | } 158 | 159 | defer resp.Body.Close() 160 | return parseJSONResponse(resp, target) 161 | } 162 | 163 | func parseJSONResponse(resp *http.Response, target interface{}) (err error) { 164 | // Test for client side and server side errors 165 | if resp.StatusCode >= 400 { 166 | errResp := Error{Status: resp.StatusCode} 167 | if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil { 168 | return fmt.Errorf("failed to decode json error: %w", err) 169 | } 170 | return errResp 171 | } 172 | 173 | if target == nil { 174 | return nil 175 | } 176 | 177 | if err = json.NewDecoder(resp.Body).Decode(target); err != nil { 178 | return fmt.Errorf("failed to decode json response: %w", err) 179 | } 180 | 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /pixelapi/subscription.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | 7 | "github.com/gocql/gocql" 8 | ) 9 | 10 | // Subscription contains information about a user's subscription. When it 11 | // started, when it ends, and what type of subscription it is 12 | type Subscription struct { 13 | ID gocql.UUID `json:"id"` 14 | Used bool `json:"used"` 15 | DurationDays int `json:"duration_days"` 16 | StartTime time.Time `json:"start_date"` 17 | WarningDate time.Time `json:"warning_date"` 18 | EndDate time.Time `json:"end_date"` 19 | SubscriptionType SubscriptionType `json:"subscription_type"` 20 | } 21 | 22 | // SubscriptionType contains information about a subscription type. It's not the 23 | // active subscription itself, only the properties of the subscription. Like the 24 | // perks and cost 25 | type SubscriptionType struct { 26 | ID string `json:"id"` 27 | Name string `json:"name"` 28 | Type string `json:"type"` 29 | FileSizeLimit int64 `json:"file_size_limit"` 30 | FileExpiryDays int64 `json:"file_expiry_days"` 31 | StorageSpace int64 `json:"storage_space"` 32 | PricePerTBStorage int64 `json:"price_per_tb_storage"` 33 | PricePerTBBandwidth int64 `json:"price_per_tb_bandwidth"` 34 | MonthlyTransferCap int64 `json:"monthly_transfer_cap"` 35 | FileViewerBranding bool `json:"file_viewer_branding"` 36 | FilesystemAccess bool `json:"filesystem_access"` 37 | FilesystemStorageLimit int64 `json:"filesystem_storage_limit"` 38 | } 39 | 40 | // GetSubscriptionID returns the subscription object identified by the given ID 41 | func (p *PixelAPI) GetSubscriptionID(id string) (resp Subscription, err error) { 42 | return resp, p.jsonRequest("GET", "subscription/"+url.PathEscape(id), &resp) 43 | } 44 | 45 | // PostSubscriptionLink links a subscription to the logged in user account. Use 46 | // Login() before calling this function to select the account to use. This 47 | // action cannot be undone. 48 | func (p *PixelAPI) PostSubscriptionLink(id string) (err error) { 49 | return p.jsonRequest("POST", "subscription/"+url.PathEscape(id)+"/link", nil) 50 | } 51 | 52 | type CouponCode struct { 53 | ID string `json:"id"` 54 | Credit int64 `json:"credit"` 55 | Uses int `json:"uses"` 56 | } 57 | 58 | func (p *PixelAPI) GetCouponID(id string) (resp CouponCode, err error) { 59 | return resp, p.jsonRequest("GET", "coupon/"+url.PathEscape(id), &resp) 60 | } 61 | 62 | func (p *PixelAPI) PostCouponRedeem(id string) (err error) { 63 | return p.jsonRequest("POST", "coupon/"+url.PathEscape(id)+"/redeem", nil) 64 | } 65 | 66 | type Invoice struct { 67 | ID string `json:"id"` 68 | Time time.Time `json:"time"` 69 | Amount int64 `json:"amount"` 70 | VAT int64 `json:"vat"` 71 | Country string `json:"country"` 72 | PaymentGateway string `json:"payment_gateway"` 73 | PaymentMethod string `json:"payment_method"` 74 | Status string `json:"status"` 75 | ProcessingFee int64 `json:"processing_fee"` 76 | } 77 | 78 | func (p *PixelAPI) GetBTCPayInvoices() (resp []Invoice, err error) { 79 | return resp, p.jsonRequest("GET", "btcpay/invoice", &resp) 80 | } 81 | -------------------------------------------------------------------------------- /pixelapi/user.go: -------------------------------------------------------------------------------- 1 | package pixelapi 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/gocql/gocql" 9 | ) 10 | 11 | // UserInfo contains information about the logged in user 12 | type UserInfo struct { 13 | Username string `json:"username"` 14 | Email string `json:"email"` 15 | EmailVerified bool `json:"email_verified"` 16 | OTPEnabled bool `json:"otp_enabled"` 17 | Subscription SubscriptionType `json:"subscription"` 18 | StorageSpaceUsed int `json:"storage_space_used"` 19 | FilesystemStorageUsed int `json:"filesystem_storage_used"` 20 | IsAdmin bool `json:"is_admin"` 21 | BalanceMicroEUR int64 `json:"balance_micro_eur"` 22 | Hotlinking bool `json:"hotlinking_enabled"` 23 | MonthlyTransferCap int `json:"monthly_transfer_cap"` 24 | MonthlyTransferUsed int `json:"monthly_transfer_used"` 25 | FileViewerBranding map[string]string `json:"file_viewer_branding"` 26 | FileEmbedDomains string `json:"file_embed_domains"` 27 | SkipFileViewer bool `json:"skip_file_viewer"` 28 | AffiliateUserName string `json:"affiliate_user_name"` 29 | CheckoutCountry string `json:"checkout_country"` 30 | CheckoutName string `json:"checkout_name"` 31 | CheckoutProvider string `json:"checkout_provider"` 32 | } 33 | 34 | // UserSession is one user session 35 | type UserSession struct { 36 | AuthKey gocql.UUID `json:"auth_key"` 37 | CreationIP string `json:"creation_ip_address"` 38 | UserAgent string `json:"user_agent"` 39 | AppName string `json:"app_name"` 40 | CreationTime time.Time `json:"creation_time"` 41 | LastUsedTime time.Time `json:"last_used_time"` 42 | } 43 | 44 | // UserRegister registers a new user on the Pixeldrain server. username and 45 | // password are always required. email is optional, but without it you will not 46 | // be able to reset your password in case you forget it. captcha depends on 47 | // whether reCaptcha is enabled on the Pixeldrain server, this can be checked 48 | // through the GetRecaptcha function. 49 | // 50 | // The register API can return multiple errors, which will be stored in the 51 | // Errors array. Check for len(Errors) == 0 to see if an error occurred. If err 52 | // != nil it means a connection error occurred 53 | func (p *PixelAPI) UserRegister(username, email, password string) (err error) { 54 | return p.form( 55 | "POST", "user/register", 56 | url.Values{ 57 | "username": {username}, 58 | "email": {email}, 59 | "password": {password}, 60 | }, 61 | nil, 62 | ) 63 | } 64 | 65 | // PostUserLogin logs a user in with the provided credentials. The response will 66 | // contain the returned API key. The app name is saved in the database and can 67 | // be found on the user's API keys page. 68 | func (p *PixelAPI) PostUserLogin(username, password, app string) (resp UserSession, err error) { 69 | return resp, p.form( 70 | "POST", "user/login", 71 | url.Values{ 72 | "username": {username}, 73 | "password": {password}, 74 | "app_name": {app}, 75 | }, 76 | &resp, 77 | ) 78 | } 79 | 80 | // GetUser returns information about the logged in user. Requires an API key 81 | func (p *PixelAPI) GetUser() (resp UserInfo, err error) { 82 | return resp, p.jsonRequest("GET", "user", &resp) 83 | } 84 | 85 | // PostUserSession creates a new user sessions 86 | func (p *PixelAPI) PostUserSession(app string) (resp UserSession, err error) { 87 | return resp, p.form("POST", "user/session", url.Values{"app_name": {app}}, &resp) 88 | } 89 | 90 | // GetUserSession lists all active user sessions 91 | func (p *PixelAPI) GetUserSession() (resp []UserSession, err error) { 92 | return resp, p.jsonRequest("GET", "user/session", &resp) 93 | } 94 | 95 | // DeleteUserSession destroys an API key so it can no longer be used to perform 96 | // actions 97 | func (p *PixelAPI) DeleteUserSession(key string) (err error) { 98 | return p.jsonRequest("DELETE", "user/session", nil) 99 | } 100 | 101 | // FileInfoSlice a collection of files which belong to a user 102 | type FileInfoSlice struct { 103 | Files []FileInfo `json:"files"` 104 | } 105 | 106 | // GetUserFiles gets files uploaded by a user 107 | func (p *PixelAPI) GetUserFiles() (resp FileInfoSlice, err error) { 108 | return resp, p.jsonRequest("GET", "user/files", &resp) 109 | } 110 | 111 | // ListInfoSlice is a collection of lists which belong to a user 112 | type ListInfoSlice struct { 113 | Lists []ListInfo `json:"lists"` 114 | } 115 | 116 | // GetUserLists gets lists created by a user 117 | func (p *PixelAPI) GetUserLists() (resp ListInfoSlice, err error) { 118 | return resp, p.jsonRequest("GET", "user/lists", &resp) 119 | } 120 | 121 | type UserTransaction struct { 122 | Time time.Time `json:"time"` 123 | NewBalance int64 `json:"new_balance"` 124 | DepositAmount int64 `json:"deposit_amount"` 125 | SubscriptionCharge int64 `json:"subscription_charge"` 126 | StorageCharge int64 `json:"storage_charge"` 127 | StorageUsed int `json:"storage_used"` 128 | BandwidthCharge int64 `json:"bandwidth_charge"` 129 | BandwidthUsed int `json:"bandwidth_used"` 130 | AffiliateAmount int64 `json:"affiliate_amount"` 131 | AffiliateCount int `json:"affiliate_count"` 132 | } 133 | 134 | func (p *PixelAPI) GetUserTransactions() (resp []UserTransaction, err error) { 135 | return resp, p.jsonRequest("GET", "user/transactions", &resp) 136 | } 137 | 138 | type UserActivity struct { 139 | Time time.Time `json:"time"` 140 | Event string `json:"event"` 141 | FileID string `json:"file_id"` 142 | FileName string `json:"file_name"` 143 | FileRemovalReason string `json:"file_removal_reason"` 144 | } 145 | 146 | func (p *PixelAPI) GetUserActivity() (resp []UserActivity, err error) { 147 | return resp, p.jsonRequest("GET", "user/activity", &resp) 148 | } 149 | 150 | // PutUserPassword changes the user's password 151 | func (p *PixelAPI) PutUserPassword(oldPW, newPW string) (err error) { 152 | return p.form( 153 | "PUT", "user/password", 154 | url.Values{"old_password": {oldPW}, "new_password": {newPW}}, 155 | nil, 156 | ) 157 | } 158 | 159 | // PutUserEmailReset starts the e-mail change process. An email will be sent to 160 | // the new address to verify that it's real. Once the link in the e-mail is 161 | // clicked the key it contains can be sent to the API with UserEmailResetConfirm 162 | // and the change will be applied 163 | func (p *PixelAPI) PutUserEmailReset(email string, delete bool) (err error) { 164 | return p.form( 165 | "PUT", "user/email_reset", 166 | url.Values{"new_email": {email}, "delete": {strconv.FormatBool(delete)}}, 167 | nil, 168 | ) 169 | } 170 | 171 | // PutUserEmailResetConfirm finishes process of changing a user's e-mail address 172 | func (p *PixelAPI) PutUserEmailResetConfirm(key string) (err error) { 173 | return p.form( 174 | "PUT", "user/email_reset_confirm", 175 | url.Values{"key": {key}}, 176 | nil, 177 | ) 178 | } 179 | 180 | // PutUserPasswordReset starts the password reset process. An email will be sent 181 | // the user to verify that it really wanted to reset the password. Once the link 182 | // in the e-mail is clicked the key it contains can be sent to the API with 183 | // UserPasswordResetConfirm and a new password can be set 184 | func (p *PixelAPI) PutUserPasswordReset(email string, recaptchaResponse string) (err error) { 185 | return p.form( 186 | "PUT", "user/password_reset", 187 | url.Values{"email": {email}, "recaptcha_response": {recaptchaResponse}}, 188 | nil, 189 | ) 190 | } 191 | 192 | // PutUserPasswordResetConfirm finishes process of resetting a user's password. 193 | // If the key is valid the new_password parameter will be saved as the new 194 | // password 195 | func (p *PixelAPI) PutUserPasswordResetConfirm(key string, newPassword string) (err error) { 196 | return p.form( 197 | "PUT", "user/password_reset_confirm", 198 | url.Values{"key": {key}, "new_password": {newPassword}}, 199 | nil, 200 | ) 201 | } 202 | 203 | // PutUserUsername changes the user's username. 204 | func (p *PixelAPI) PutUserUsername(username string) (err error) { 205 | return p.form( 206 | "PUT", "user/username", 207 | url.Values{"new_username": {username}}, 208 | nil, 209 | ) 210 | } 211 | --------------------------------------------------------------------------------