├── test_data └── testImage.jpg ├── .github ├── dependabot.yml └── workflows │ └── go.yml ├── go.mod ├── config.go ├── .gitignore ├── client_test.go ├── .goreleaser.yml ├── README.md ├── LICENSE ├── client.go ├── comment.go ├── getURL.go ├── image_test.go ├── account_test.go ├── galleryImage_test.go ├── rateLimit_test.go ├── galleryAlbum_test.go ├── http_test.go ├── uploadImage_test.go ├── go.sum ├── rateLimit.go ├── account.go ├── album.go ├── imgurcmd └── main.go ├── album_test.go ├── uploadImage.go ├── galleryAlbum.go ├── image.go ├── galleryImage.go ├── fromURL.go ├── serverError_test.go └── fromURL_test.go /test_data/testImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koffeinsource/go-imgur/HEAD/test_data/testImage.jpg -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/koffeinsource/go-imgur 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/koffeinsource/go-klogger v0.1.1 7 | github.com/stretchr/testify v1.8.2 8 | ) 9 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | const ( 4 | apiEndpoint = "https://api.imgur.com/3/" 5 | apiEndpointRapidAPI = "https://imgur-apiv3.p.rapidapi.com/3/" 6 | apiEndpointGenerateAccessToken = "https://api.imgur.com/oauth2/token" 7 | ) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | imgurcmd/imgurcmd 27 | .vscode 28 | dist/ 29 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestClientCreationWithoutClientID(t *testing.T) { 11 | client, err := NewClient(new(http.Client), "", "") 12 | require.Error(t, err) 13 | require.Nil(t, client) 14 | 15 | client, err = NewClient(new(http.Client), "some client id", "") 16 | require.NoError(t, err) 17 | require.NotNil(t, client) 18 | } 19 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | project_name: go-imgur 4 | before: 5 | hooks: 6 | # You may remove this if you don't use go modules. 7 | - go mod download 8 | builds: 9 | - skip: true 10 | archives: 11 | - replacements: 12 | darwin: Darwin 13 | linux: Linux 14 | windows: Windows 15 | 386: i386 16 | amd64: x86_64 17 | checksum: 18 | name_template: 'checksums.txt' 19 | snapshot: 20 | name_template: "{{ .Tag }}-next" 21 | changelog: 22 | sort: asc 23 | filters: 24 | exclude: 25 | - '^docs:' 26 | - '^test:' 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-imgur 2 | 3 | ![Go](https://github.com/koffeinsource/go-imgur/workflows/Go/badge.svg) 4 | [![GoDoc](https://godoc.org/github.com/koffeinsource/go-imgur?status.svg)](https://pkg.go.dev/github.com/koffeinsource/go-imgur?tab=doc) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/koffeinsource/go-imgur)](https://goreportcard.com/report/github.com/koffeinsource/go-imgur) 6 | [![Coverage Status]( 7 | https://coveralls.io/repos/github/koffeinsource/go-imgur/badge.svg?branch=master)](https://coveralls.io/github/koffeinsource/go-imgur?branch=master) 8 | 9 | Go library to use the imgur.com API. At the moment only the anonymous part of the API is supported, but that is used in a production environment. 10 | 11 | ## Example 12 | 13 | To see some simple example code please take a look at the command line client found in `imgurcmd/main.go`. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 koffeinsource 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | go: ['1.13', '1.22'] 18 | steps: 19 | - uses: actions/setup-go@v1 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - uses: actions/checkout@v2 24 | 25 | - name: Build 26 | run: go build -v . 27 | 28 | - name: Test 29 | env: 30 | IMGURCLIENTID: ${{ secrets. IMGURCLIENTID }} 31 | RapidAPIKEY: ${{ secrets. RapidAPIKEY }} 32 | run: go test -v -coverprofile=profile.cov ./... 33 | 34 | - name: Send Coverage 35 | uses: shogo82148/actions-goveralls@v1 36 | with: 37 | path-to-profile: profile.cov 38 | parallel: true 39 | 40 | # notifies that all test jobs are finished. 41 | finish: 42 | needs: test 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: shogo82148/actions-goveralls@v1 46 | with: 47 | parallel-finished: true 48 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/koffeinsource/go-klogger" 8 | ) 9 | 10 | // ClientAccount describe authontification 11 | type ClientAccount struct { 12 | clientID string // client ID received after registration 13 | accessToken string // is your secret key used to access the user's data 14 | } 15 | 16 | // Client used to for go-imgur 17 | type Client struct { 18 | Log klogger.KLogger 19 | httpClient *http.Client 20 | imgurAccount ClientAccount 21 | rapidAPIKey string 22 | } 23 | 24 | // NewClient simply creates an imgur client. RapidAPIKEY is "" if you are using the free API. 25 | func NewClient(httpClient *http.Client, clientID string, rapidAPIKey string) (*Client, error) { 26 | logger := new(klogger.CLILogger) 27 | 28 | if len(clientID) == 0 { 29 | msg := "imgur client ID is empty" 30 | logger.Errorf(msg) 31 | return nil, fmt.Errorf(msg) 32 | } 33 | 34 | if len(rapidAPIKey) == 0 { 35 | logger.Infof("rapid api key is empty") 36 | } 37 | 38 | return &Client{ 39 | httpClient: httpClient, 40 | Log: logger, 41 | rapidAPIKey: rapidAPIKey, 42 | imgurAccount: ClientAccount{ 43 | clientID: clientID, 44 | }, 45 | }, nil 46 | } 47 | -------------------------------------------------------------------------------- /comment.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | // Comment is an imgur comment 4 | type Comment struct { 5 | ID int `json:"id"` // The ID for the comment 6 | ImageID string `json:"image_id"` //The ID of the image that the comment is for 7 | Comment string `json:"comment"` // The comment itself. 8 | Author string `json:"author"` // Username of the author of the comment 9 | AuthorID int `json:"author_id"` // The account ID for the author 10 | OnAlbum bool `json:"on_album"` // If this comment was done to an album 11 | AlbumCover string `json:"album_cover"` // The ID of the album cover image, this is what should be displayed for album comments 12 | Ups int `json:"ups"` // Number of upvotes for the comment 13 | Downs int `json:"downs"` // The number of downvotes for the comment 14 | Points float32 `json:"points"` // the number of upvotes - downvotes 15 | Datetime int `json:"datetime"` // Timestamp of creation, epoch time 16 | ParentID int `json:"parent_id"` // If this is a reply, this will be the value of the comment_id for the caption this a reply for. 17 | Deleted bool `json:"deleted"` // Marked true if this caption has been deleted 18 | Vote string `json:"vote"` // The current user's vote on the comment. null if not signed in or if the user hasn't voted on it. 19 | Children []Comment `json:"children"` // All of the replies for this comment. If there are no replies to the comment then this is an empty set. 20 | } 21 | -------------------------------------------------------------------------------- /getURL.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | func (client *Client) createAPIURL(u string) string { 10 | if client.rapidAPIKey == "" { 11 | return apiEndpoint + u 12 | } 13 | return apiEndpointRapidAPI + u 14 | } 15 | 16 | // getURL returns 17 | // - body as string 18 | // - RateLimit with current limits 19 | // - error in case something broke 20 | func (client *Client) getURL(URL string) (string, *RateLimit, error) { 21 | URL = client.createAPIURL(URL) 22 | client.Log.Infof("Requesting URL %v\n", URL) 23 | req, err := http.NewRequest("GET", URL, nil) 24 | if err != nil { 25 | return "", nil, errors.New("Could not create request for " + URL + " - " + err.Error()) 26 | } 27 | 28 | req.Header.Add("Authorization", "Client-ID "+client.imgurAccount.clientID) 29 | if client.rapidAPIKey != "" { 30 | req.Header.Add("x-rapidapi-host", "imgur-apiv3.p.rapidapi.com") 31 | req.Header.Add("x-rapidapi-key", client.rapidAPIKey) 32 | } 33 | 34 | // Make a request to the sourceURL 35 | res, err := client.httpClient.Do(req) 36 | if err != nil { 37 | return "", nil, errors.New("Could not get " + URL + " - " + err.Error()) 38 | } 39 | defer res.Body.Close() 40 | 41 | if !(res.StatusCode >= 200 && res.StatusCode <= 300) { 42 | return "", nil, errors.New("HTTP status indicates an error for " + URL + " - " + res.Status) 43 | } 44 | 45 | // Read the whole body 46 | body, err := ioutil.ReadAll(res.Body) 47 | if err != nil { 48 | return "", nil, errors.New("Problem reading the body for " + URL + " - " + err.Error()) 49 | } 50 | 51 | // Get RateLimit headers 52 | rl, err := extractRateLimits(res.Header) 53 | if err != nil { 54 | client.Log.Infof("Problem with extracting rate limits: %v", err) 55 | } 56 | 57 | return string(body[:]), rl, nil 58 | } 59 | -------------------------------------------------------------------------------- /image_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestImageImgurSimulated(t *testing.T) { 10 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"ClF8rLe\",\"title\":null,\"description\":null,\"datetime\":1451248840,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":2448,\"height\":3264,\"size\":1071339,\"views\":176,\"bandwidth\":188555664,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/ClF8rLe.jpg\"},\"success\":true,\"status\":200}") 11 | defer server.Close() 12 | 13 | client, _ := NewClient(httpC, "testing", "") 14 | img, status, err := client.GetImageInfo("ClF8rLe") 15 | 16 | if err != nil { 17 | t.Errorf("GetImageInfo() failed with error: %v", err) 18 | t.FailNow() 19 | } 20 | 21 | if img.Animated != false || img.Bandwidth != 188555664 || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" || img.Views != 176 { 22 | t.Fail() 23 | } 24 | 25 | if status != 200 { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestImageImgurReal(t *testing.T) { 31 | key := os.Getenv("IMGURCLIENTID") 32 | if key == "" { 33 | t.Skip("IMGURCLIENTID environment variable not set.") 34 | } 35 | RapidAPIKey := os.Getenv("RapidAPIKEY") 36 | 37 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 38 | 39 | img, status, err := client.GetImageInfo("ClF8rLe") 40 | 41 | if err != nil { 42 | t.Errorf("GetImageInfo() failed with error: %v", err) 43 | t.FailNow() 44 | } 45 | 46 | if img.Animated != false || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" { 47 | t.Fail() 48 | } 49 | 50 | if status != 200 { 51 | t.Fail() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /account_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestVerifyEmptyInputParams(t *testing.T) { 12 | client, err := NewClient(http.DefaultClient, "testing", "") 13 | require.NoError(t, err) 14 | 15 | _, err = client.RefreshAccessToken("", "secret") 16 | require.Error(t, err) 17 | 18 | _, err = client.RefreshAccessToken("12345", "") 19 | require.Error(t, err) 20 | } 21 | 22 | func TestStubbedClientAuthorization(t *testing.T) { 23 | responseBody := ` 24 | { 25 | "access_token": "argus", 26 | "expires_in": 315360000, 27 | "token_type": "bearer", 28 | "scope": null, 29 | "refresh_token": "red bull", 30 | "account_id": 111111111, 31 | "account_username": "Locker" 32 | }` 33 | httpC, server := testHTTPClientJSON(responseBody) 34 | defer server.Close() 35 | 36 | client, err := NewClient(httpC, "testing", "") 37 | require.NoError(t, err) 38 | 39 | newRefreshToken, err := client.RefreshAccessToken("12345", "to secret") 40 | require.NoError(t, err) 41 | require.Equal(t, "red bull", newRefreshToken) 42 | require.Equal(t, "argus", client.imgurAccount.accessToken) 43 | } 44 | 45 | func TestRealClientAuthorization(t *testing.T) { 46 | clientID := os.Getenv("IMGURCLIENTID") 47 | refreshToken := os.Getenv("IMGURCLIENTREFRESHTOKEN") 48 | clientSecret := os.Getenv("IMGURCLIENTSECRET") 49 | if clientID == "" || refreshToken == "" || clientSecret == "" { 50 | t.Skipf("Environment variables not set. ID: %t , refresh: %t , secret: %t", 51 | clientID == "", refreshToken == "", clientSecret == "") 52 | } 53 | 54 | client, err := NewClient(&http.Client{}, clientID, "") 55 | require.NoError(t, err) 56 | 57 | refreshToken, err = client.RefreshAccessToken(refreshToken, clientSecret) 58 | require.NoError(t, err) 59 | 60 | client.imgurAccount.accessToken = "access token" 61 | // update refresh token 62 | err = os.Setenv("IMGURCLIENTREFRESHTOKEN", refreshToken) 63 | require.NoError(t, err) 64 | require.NotEqual(t, "access token", client.imgurAccount.accessToken) 65 | } 66 | -------------------------------------------------------------------------------- /galleryImage_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestGalleryImageImgurSimulated(t *testing.T) { 10 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"Hf6cs\",\"title\":\"The Tridge. (three way bridge)\",\"description\":null,\"datetime\":1316367003,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":1700,\"height\":1133,\"size\":268126,\"views\":1342557,\"bandwidth\":359974438182,\"vote\":null,\"favorite\":false,\"nsfw\":false,\"section\":\"pics\",\"account_url\":null,\"account_id\":null,\"in_gallery\":true,\"topic\":null,\"topic_id\":0,\"link\":\"https:\\/\\/i.imgur.com\\/Hf6cs.jpg\",\"comment_count\":90,\"ups\":585,\"downs\":3,\"points\":582,\"score\":1136,\"is_album\":false},\"success\":true,\"status\":200}") 11 | defer server.Close() 12 | 13 | client, _ := NewClient(httpC, "testing", "") 14 | img, status, err := client.GetGalleryImageInfo("Hf6cs") 15 | 16 | if err != nil { 17 | t.Errorf("GetImageInfo() failed with error: %v", err) 18 | t.FailNow() 19 | } 20 | 21 | if img.Title != "The Tridge. (three way bridge)" || img.Animated != false || img.Bandwidth != 359974438182 || img.Datetime != 1316367003 || img.Description != "" || img.Height != 1133 || img.Width != 1700 || img.ID != "Hf6cs" || img.Link != "https://i.imgur.com/Hf6cs.jpg" || img.Views != 1342557 { 22 | t.Fail() 23 | } 24 | 25 | if status != 200 { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestGalleryImageImgurReal(t *testing.T) { 31 | key := os.Getenv("IMGURCLIENTID") 32 | if key == "" { 33 | t.Skip("IMGURCLIENTID environment variable not set.") 34 | } 35 | RapidAPIKey := os.Getenv("RapidAPIKEY") 36 | 37 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 38 | 39 | img, status, err := client.GetGalleryImageInfo("Hf6cs") 40 | 41 | if err != nil { 42 | t.Errorf("GetImageInfo() failed with error: %v", err) 43 | t.FailNow() 44 | } 45 | 46 | if img.Title != "The Tridge. (three way bridge) " || img.Animated != false || img.Description != "" || img.Height != 1133 || img.Width != 1700 || img.ID != "Hf6cs" || img.Link != "https://i.imgur.com/Hf6cs.jpg" { 47 | t.Fail() 48 | } 49 | 50 | if status != 200 { 51 | t.Fail() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rateLimit_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestRateLimitImgurSimulated(t *testing.T) { 10 | httpC, server := testHTTPClientJSON("{\"success\": true, \"status\": 200 }") 11 | 12 | defer server.Close() 13 | 14 | client, _ := NewClient(httpC, "testing", "") 15 | rl, err := client.GetRateLimit() 16 | 17 | if err != nil { 18 | t.Errorf("GetRateLimit() failed with error: %v", err) 19 | t.FailNow() 20 | } 21 | 22 | if rl.ClientLimit != 40 || rl.UserLimit != 10 || rl.UserRemaining != 2 || rl.ClientRemaining != 5 { 23 | client.Log.Debugf("Found ClientLimit: %v and UserLimit: %v", rl.ClientLimit, rl.UserLimit) 24 | t.Error("Client/User limits are wrong. Probably something broken. Or IMGUR changed their limits. Or you are not using a free account for testing. Sorry. No real good way to test this.") 25 | } 26 | } 27 | 28 | func TestRateLimitRealRapidAPI(t *testing.T) { 29 | key := os.Getenv("IMGURCLIENTID") 30 | if key == "" { 31 | t.Skip("IMGURCLIENTID environment variable not set.") 32 | } 33 | RapidAPIKey := os.Getenv("RapidAPIKEY") 34 | if RapidAPIKey == "" { 35 | t.Skip("RapidAPIKEY environment variable not set.") 36 | } 37 | 38 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 39 | 40 | rl, err := client.GetRateLimit() 41 | 42 | if err != nil { 43 | t.Errorf("GetRateLimit() failed with error: %v", err) 44 | t.FailNow() 45 | } 46 | 47 | // There seem to be not rate limites when using the paid API 48 | if rl.ClientLimit != 0 || rl.UserLimit != 0 { 49 | client.Log.Debugf("Found ClientLimit: %v and UserLimit: %v", rl.ClientLimit, rl.UserLimit) 50 | t.Error("Client/User limits are wrong. Probably something broken. Or IMGUR changed their limits. Or you are using a free account for testing. Sorry. No real good way to test this.") 51 | } 52 | } 53 | 54 | func TestRateLimitRealImgur(t *testing.T) { 55 | key := os.Getenv("IMGURCLIENTID") 56 | if key == "" { 57 | t.Skip("IMGURCLIENTID environment variable not set.") 58 | } 59 | 60 | client, _ := NewClient(new(http.Client), key, "") 61 | 62 | rl, err := client.GetRateLimit() 63 | 64 | if err != nil { 65 | t.Errorf("GetRateLimit() failed with error: %v", err) 66 | t.FailNow() 67 | } 68 | 69 | if rl.ClientLimit != 12500 || rl.UserLimit != 500 { 70 | client.Log.Debugf("Found ClientLimit: %v and UserLimit: %v", rl.ClientLimit, rl.UserLimit) 71 | t.Error("Client/User limits are wrong. Probably something broken. Or IMGUR changed their limits. Or you are not using a free account for testing. Sorry. No real good way to test this.") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /galleryAlbum_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestGalleryAlbumImgurSimulated(t *testing.T) { 10 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"VZQXk\",\"title\":\"As it turns out, most people cannot draw a bike.\",\"description\":null,\"datetime\":1460715031,\"cover\":\"CJCA0gW\",\"cover_width\":1200,\"cover_height\":786,\"account_url\":\"mrcassette\",\"account_id\":157430,\"privacy\":\"public\",\"layout\":\"blog\",\"views\":667581,\"link\":\"https:\\/\\/imgur.com\\/a\\/VZQXk\",\"ups\":13704,\"downs\":113,\"favorite\":false,\"nsfw\":false,\"section\":\"pics\",\"images_count\":1,\"in_gallery\":true,\"images\":[{\"id\":\"CJCA0gW\",\"title\":null,\"description\":\"by Designer Gianluca Gimini\\nhttps:\\/\\/www.behance.net\\/gallery\\/35437979\\/Velocipedia\",\"datetime\":1460715032,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":1200,\"height\":786,\"size\":362373,\"views\":4420880,\"bandwidth\":1602007548240,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/CJCA0gW.jpg\"}]},\"success\":true,\"status\":200}") 11 | defer server.Close() 12 | 13 | client, _ := NewClient(httpC, "testing", "") 14 | alb, status, err := client.GetGalleryAlbumInfo("VZQXk") 15 | 16 | if err != nil { 17 | t.Errorf("GetAlbumInfo() failed with error: %v", err) 18 | t.FailNow() 19 | } 20 | 21 | if alb.Title != "As it turns out, most people cannot draw a bike." || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 1 || alb.Images[0].ID != "CJCA0gW" || alb.Ups != 13704 || alb.Downs != 113 { 22 | t.Fail() 23 | } 24 | 25 | if status != 200 { 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestGalleryAlbumImgurReal(t *testing.T) { 31 | key := os.Getenv("IMGURCLIENTID") 32 | if key == "" { 33 | t.Skip("IMGURCLIENTID environment variable not set.") 34 | } 35 | RapidAPIKey := os.Getenv("RapidAPIKEY") 36 | 37 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 38 | 39 | alb, status, err := client.GetGalleryAlbumInfo("VZQXk") 40 | 41 | if err != nil { 42 | t.Errorf("GetAlbumInfo() failed with error: %v", err) 43 | t.FailNow() 44 | } 45 | 46 | if alb.Title != "As it turns out, most people cannot draw a bike." || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 14 || alb.Images[0].ID != "CJCA0gW" { 47 | t.Fail() 48 | } 49 | 50 | if status != 200 { 51 | t.Fail() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /http_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "path" 10 | ) 11 | 12 | func testHTTPClientJSON(json string) (*http.Client, *httptest.Server) { 13 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | w.Header().Set("Content-Type", "application/json") 15 | w.Header().Set("X-RateLimit-UserLimit", "10") 16 | w.Header().Set("X-RateLimit-UserRemaining", "2") 17 | w.Header().Set("X-RateLimit-UserReset", "3") 18 | w.Header().Set("X-RateLimit-ClientLimit", "40") 19 | w.Header().Set("X-RateLimit-ClientRemaining", "5") 20 | w.WriteHeader(200) 21 | 22 | fmt.Fprintln(w, json) 23 | })) 24 | 25 | u, err := url.Parse(server.URL) 26 | if err != nil { 27 | log.Fatalln("failed to parse httptest.Server URL:", err) 28 | } 29 | 30 | http.DefaultClient.Transport = rewriteTransport{URL: u} 31 | return http.DefaultClient, server 32 | } 33 | 34 | func testHTTPClient500() (*http.Client, *httptest.Server) { 35 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 | w.WriteHeader(500) 37 | })) 38 | 39 | u, err := url.Parse(server.URL) 40 | if err != nil { 41 | log.Fatalln("failed to parse httptest.Server URL:", err) 42 | } 43 | http.DefaultClient.Transport = rewriteTransport{URL: u} 44 | 45 | return http.DefaultClient, server 46 | } 47 | 48 | func testHTTPClientInvalidJSON() (*http.Client, *httptest.Server) { 49 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 50 | w.Header().Set("Content-Type", "application/json") 51 | 52 | // some broken headers 53 | w.Header().Set("X-RateLimit-UserLimit", "asd123") 54 | w.Header().Set("X-RateLimit-UserRemaining", "asd123") 55 | w.Header().Set("X-RateLimit-UserReset", "asd123") 56 | w.Header().Set("X-RateLimit-ClientLimit", "asd123") 57 | w.Header().Set("X-RateLimit-ClientRemaining", "asd123") 58 | w.WriteHeader(200) 59 | 60 | // some invalid json 61 | fmt.Fprintln(w, `[broken json.. :)]`) 62 | })) 63 | 64 | u, err := url.Parse(server.URL) 65 | if err != nil { 66 | log.Fatalln("failed to parse httptest.Server URL:", err) 67 | } 68 | http.DefaultClient.Transport = rewriteTransport{URL: u} 69 | 70 | return http.DefaultClient, server 71 | } 72 | 73 | type rewriteTransport struct { 74 | Transport http.RoundTripper 75 | URL *url.URL 76 | } 77 | 78 | func (t rewriteTransport) RoundTrip(r *http.Request) (*http.Response, error) { 79 | r.URL.Scheme = t.URL.Scheme 80 | r.URL.Host = t.URL.Host 81 | r.URL.Path = path.Join(t.URL.Path, r.URL.Path) 82 | rt := t.Transport 83 | if rt == nil { 84 | rt = http.DefaultTransport 85 | } 86 | return rt.RoundTrip(r) 87 | } 88 | -------------------------------------------------------------------------------- /uploadImage_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | const ( 10 | descr = "test upload for the go-imgr library" 11 | title = "go-imgur test upload" 12 | ) 13 | 14 | func TestUploadImageErrors(t *testing.T) { 15 | httpC, server := testHTTPClientJSON("") 16 | defer server.Close() 17 | 18 | client, _ := NewClient(httpC, "testing", "") 19 | 20 | // should fail because of nil image 21 | ii, _, err := client.UploadImage(nil, "album", "type", "name", "desc") 22 | if err == nil && ii == nil { 23 | t.Error("UploadImage() did not result in an error even though it should have.") 24 | t.Fail() 25 | } 26 | 27 | img := make([]byte, 5, 5) 28 | 29 | // should fail because of invalid type 30 | ii, _, err = client.UploadImage(img, "album", "type", "name", "desc") 31 | if err == nil && ii == nil { 32 | t.Error("UploadImage() did not result in an error even though it should have.") 33 | t.Fail() 34 | } 35 | 36 | ii, _, err = client.UploadImageFromFile("notExistingFile.youtcantseeme", "", title, descr) 37 | if err == nil && ii == nil { 38 | t.Error("UploadImageFromFile() did not result in an error even though it should have.") 39 | t.Fail() 40 | } 41 | } 42 | 43 | func TestUploadImageReal(t *testing.T) { 44 | key := os.Getenv("IMGURCLIENTID") 45 | if key == "" { 46 | t.Skip("IMGURCLIENTID environment variable not set.") 47 | } 48 | RapidAPIKey := os.Getenv("RapidAPIKEY") 49 | 50 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 51 | 52 | ii, status, err := client.UploadImageFromFile("test_data/testImage.jpg", "", title, descr) 53 | 54 | if err != nil || ii == nil { 55 | t.Errorf("UploadImageFromFile() failed with error: %v", err) 56 | t.FailNow() 57 | } 58 | 59 | if ii.Description != descr || ii.Title != title { 60 | t.Fail() 61 | } 62 | 63 | if status != 200 { 64 | t.Fail() 65 | } 66 | } 67 | 68 | func TestUploadImageSimulated(t *testing.T) { 69 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"ClF8rLe\",\"title\":\"" + title + "\",\"description\":\"" + descr + "\",\"datetime\":1451248840,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":2448,\"height\":3264,\"size\":1071339,\"views\":176,\"bandwidth\":188555664,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/ClF8rLe.jpg\"},\"success\":true,\"status\":200}") 70 | defer server.Close() 71 | 72 | client, _ := NewClient(httpC, "testing", "") 73 | ii, status, err := client.UploadImageFromFile("test_data/testImage.jpg", "ALBUMID", title, descr) 74 | 75 | if err != nil || ii == nil { 76 | t.Errorf("UploadImageFromFile() failed with error: %v", err) 77 | t.FailNow() 78 | } 79 | 80 | if ii.Description != descr || ii.Title != title { 81 | t.Fail() 82 | } 83 | 84 | if status != 200 { 85 | t.Fail() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 5 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= 6 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 7 | github.com/koffeinsource/go-klogger v0.1.1 h1:FImHHVcDwEV4Ze3uOtRmBTQdJdzuBHtrvR4B8ssKkbw= 8 | github.com/koffeinsource/go-klogger v0.1.1/go.mod h1:oqHKXZOZt4uktar7WIYuEyWJRRlrkRSX+Uj1DWGZ79I= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 13 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 16 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 17 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 20 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= 21 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 22 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 23 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 25 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 26 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 27 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 28 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 32 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 33 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 34 | -------------------------------------------------------------------------------- /rateLimit.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type rateLimitDataWrapper struct { 13 | Rl *rateLimitInternal `json:"data"` 14 | Success bool `json:"success"` 15 | Status int `json:"status"` 16 | } 17 | 18 | // internal representation used for the json parser 19 | type rateLimitInternal struct { 20 | UserLimit int64 21 | UserRemaining int64 22 | UserReset int64 23 | ClientLimit int64 24 | ClientRemaining int64 25 | } 26 | 27 | // RateLimit details can be found here: https://api.imgur.com/#limits 28 | type RateLimit struct { 29 | // Total credits that can be allocated. 30 | UserLimit int64 31 | // Total credits available. 32 | UserRemaining int64 33 | // Timestamp for when the credits will be reset. 34 | UserReset time.Time 35 | // Total credits that can be allocated for the application in a day. 36 | ClientLimit int64 37 | // Total credits remaining for the application in a day. 38 | ClientRemaining int64 39 | } 40 | 41 | func extractRateLimits(h http.Header) (rl *RateLimit, err error) { 42 | err = nil 43 | var r RateLimit 44 | rl = &r 45 | 46 | userLimitStr := h.Get("X-RateLimit-UserLimit") 47 | if userLimitStr != "" { 48 | rl.UserLimit, err = strconv.ParseInt(userLimitStr, 10, 32) 49 | } 50 | 51 | userRemainingStr := h.Get("X-RateLimit-UserRemaining") 52 | if userRemainingStr != "" { 53 | rl.UserRemaining, err = strconv.ParseInt(userRemainingStr, 10, 32) 54 | } 55 | 56 | unixTimeStr := h.Get("X-RateLimit-UserReset") 57 | if unixTimeStr != "" { 58 | var userReset int64 59 | userReset, err = strconv.ParseInt(unixTimeStr, 10, 64) 60 | rl.UserReset = time.Unix(userReset, 0) 61 | } 62 | 63 | clientLimitStr := h.Get("X-RateLimit-ClientLimit") 64 | if clientLimitStr != "" { 65 | rl.ClientLimit, err = strconv.ParseInt(clientLimitStr, 10, 32) 66 | } 67 | 68 | clientRemainingStr := h.Get("X-RateLimit-ClientRemaining") 69 | if clientRemainingStr != "" { 70 | rl.ClientRemaining, err = strconv.ParseInt(clientRemainingStr, 10, 32) 71 | } 72 | 73 | return 74 | } 75 | 76 | // GetRateLimit returns the current rate limit without doing anything else 77 | func (client *Client) GetRateLimit() (*RateLimit, error) { 78 | // We are requesting any URL and parse the returned HTTP headers 79 | body, rl, err := client.getURL("account/kaffeeshare") 80 | 81 | if err != nil { 82 | return nil, errors.New("Problem getting URL for rate - " + err.Error()) 83 | } 84 | //client.Log.Debugf("%v\n", body) 85 | 86 | dec := json.NewDecoder(strings.NewReader(body)) 87 | 88 | var bodyDecoded rateLimitDataWrapper 89 | if err := dec.Decode(&bodyDecoded); err != nil { 90 | return nil, errors.New("Problem decoding json for ratelimit - " + err.Error()) 91 | } 92 | 93 | if !bodyDecoded.Success { 94 | return nil, errors.New("Request to imgur failed for ratelimit - " + strconv.Itoa(bodyDecoded.Status)) 95 | } 96 | 97 | var ret RateLimit 98 | ret.ClientLimit = rl.ClientLimit 99 | ret.ClientRemaining = rl.ClientRemaining 100 | ret.UserLimit = rl.UserLimit 101 | ret.UserRemaining = rl.UserRemaining 102 | ret.UserReset = rl.UserReset 103 | 104 | return &ret, nil 105 | } 106 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | type GenerateAccessTokenRequest struct { 12 | RefreshToken string `json:"refresh_token"` // The refresh token returned from the authorization code exchange 13 | ClientID string `json:"client_id"` // The client_id obtained during application registration 14 | ClientSecret string `json:"client_secret"` // The client secret obtained during application registration 15 | GrandType string `json:"grant_type"` // As defined in the OAuth2 specification, this field must contain a value of: refresh_token 16 | } 17 | 18 | type GenerateAccessTokenResponse struct { 19 | AccessToken string `json:"access_token"` // TNew access token to use 20 | ExpiresIn uint64 `json:"expires_in"` // These parameters describe the lifetime of the token in seconds, and the kind of token that is being returned 21 | TokenType string `json:"token_type"` 22 | Scope string `json:"scope,omitempty"` // Scope which were provided earlier during creation access_token 23 | RefreshToken string `json:"refresh_token"` // New refresh token 24 | AccountID int `json:"account_id,omitempty"` // not specified in documentation 25 | AccountUserName string `json:"account_username,omitempty"` // not specified in documentation 26 | } 27 | 28 | // RefreshAccessToken let you reissue expired access_token 29 | func (c *Client) RefreshAccessToken(refreshToken string, clientSecret string) (string, error) { 30 | if len(refreshToken) == 0 { 31 | msg := "Refresh token is empty" 32 | c.Log.Errorf(msg) 33 | return "", fmt.Errorf(msg) 34 | } 35 | 36 | if len(clientSecret) == 0 { 37 | msg := "Client secret is empty" 38 | c.Log.Errorf(msg) 39 | return "", fmt.Errorf(msg) 40 | } 41 | 42 | rawBody, err := json.Marshal( 43 | GenerateAccessTokenRequest{ 44 | RefreshToken: refreshToken, 45 | ClientID: c.imgurAccount.clientID, 46 | ClientSecret: clientSecret, 47 | GrandType: "refresh_token", 48 | }) 49 | if err != nil { 50 | c.Log.Errorf("Failed to marshal GenerateAccessToken. %v", err) 51 | return "", err 52 | } 53 | 54 | c.Log.Debugf("Prepared body %v", string(rawBody)) 55 | 56 | url := apiEndpointGenerateAccessToken 57 | req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(rawBody)) 58 | if err != nil { 59 | c.Log.Errorf("Failed to create new request for refresh access token. %v", err) 60 | return "", err 61 | } 62 | 63 | c.Log.Infof("Sending request to refresh access token") 64 | resp, err := c.httpClient.Do(req) 65 | if err != nil { 66 | c.Log.Errorf("HTTP request was failed. %v", err) 67 | return "", err 68 | } 69 | defer resp.Body.Close() 70 | 71 | body, err := ioutil.ReadAll(resp.Body) 72 | if err != nil { 73 | c.Log.Errorf("Reading response body was failed. %v", err) 74 | return "", err 75 | } 76 | 77 | response := GenerateAccessTokenResponse{} 78 | decoder := json.NewDecoder(bytes.NewReader(body)) 79 | decoder.UseNumber() 80 | if err = decoder.Decode(&response); err != nil { 81 | c.Log.Errorf("Decoding response was failed. %v", err) 82 | return "", err 83 | } 84 | 85 | c.Log.Infof("Token was success updated and it will be relevant within next %v seconds", response.ExpiresIn) 86 | c.Log.Debugf("New token: %v New refresh token: %v", response.AccessToken, response.RefreshToken) 87 | 88 | c.imgurAccount.accessToken = response.AccessToken 89 | return response.RefreshToken, nil 90 | } 91 | -------------------------------------------------------------------------------- /album.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type albumInfoDataWrapper struct { 11 | Ai *AlbumInfo `json:"data"` 12 | Success bool `json:"success"` 13 | Status int `json:"status"` 14 | } 15 | 16 | // AlbumInfo contains all album information provided by imgur 17 | type AlbumInfo struct { 18 | ID string `json:"id"` // The ID for the album 19 | Title string `json:"title"` // The title of the album in the gallery 20 | Description string `json:"description"` // The description of the album in the gallery 21 | DateTime int `json:"datetime"` // Time inserted into the gallery, epoch time 22 | Cover string `json:"cover"` // The ID of the album cover image 23 | CoverWidth int `json:"cover_width"` // The width, in pixels, of the album cover image 24 | CoverHeight int `json:"cover_height"` // The height, in pixels, of the album cover image 25 | AccountURL string `json:"account_url"` // The account username or null if it's anonymous. 26 | AccountID int `json:"account_id"` // The account ID or null if it's anonymous. 27 | Privacy string `json:"privacy"` // The privacy level of the album, you can only view public if not logged in as album owner 28 | Layout string `json:"layout"` // The view layout of the album. 29 | Views int `json:"views"` // The number of album views 30 | Link string `json:"link"` // The URL link to the album 31 | Favorite bool `json:"favorite"` // Indicates if the current user favorited the image. Defaults to false if not signed in. 32 | Nsfw bool `json:"nsfw"` // Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available. 33 | Section string `json:"section"` // If the image has been categorized by our backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc) 34 | Order int `json:"order"` // Order number of the album on the user's album page (defaults to 0 if their albums haven't been reordered) 35 | Deletehash string `json:"deletehash,omitempty"` // OPTIONAL, the deletehash, if you're logged in as the album owner 36 | ImagesCount int `json:"images_count"` // The total number of images in the album 37 | Images []ImageInfo `json:"images"` // An array of all the images in the album (only available when requesting the direct album) 38 | InGallery bool `json:"in_gallery"` // True if the image has been submitted to the gallery, false if otherwise. 39 | Limit *RateLimit // Current rate limit 40 | } 41 | 42 | // GetAlbumInfo queries imgur for information on a album 43 | // returns album info, status code of the request, error 44 | func (client *Client) GetAlbumInfo(id string) (*AlbumInfo, int, error) { 45 | body, rl, err := client.getURL("album/" + id) 46 | if err != nil { 47 | return nil, -1, errors.New("Problem getting URL for album info ID " + id + " - " + err.Error()) 48 | } 49 | //client.Log.Debugf("%v\n", body) 50 | 51 | dec := json.NewDecoder(strings.NewReader(body)) 52 | var alb albumInfoDataWrapper 53 | if err := dec.Decode(&alb); err != nil { 54 | return nil, -1, errors.New("Problem decoding json for albumID " + id + " - " + err.Error()) 55 | } 56 | 57 | if !alb.Success { 58 | return nil, alb.Status, errors.New("Request to imgur failed for albumID " + id + " - " + strconv.Itoa(alb.Status)) 59 | } 60 | 61 | alb.Ai.Limit = rl 62 | return alb.Ai, alb.Status, nil 63 | } 64 | -------------------------------------------------------------------------------- /imgurcmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/koffeinsource/go-imgur" 9 | ) 10 | 11 | func printRate(client *imgur.Client) { 12 | client.Log.Infof("*** RATE LIMIT ***\n") 13 | rl, err := client.GetRateLimit() 14 | if err != nil { 15 | client.Log.Errorf("Error in GetRateLimit: %v\n", err) 16 | return 17 | } 18 | client.Log.Infof("%v\n", *rl) 19 | } 20 | 21 | func printImage(client *imgur.Client, image *string) { 22 | client.Log.Infof("*** IMAGE ***\n") 23 | img, _, err := client.GetImageInfo(*image) 24 | if err != nil { 25 | client.Log.Errorf("Error in GetImageInfo: %v\n", err) 26 | return 27 | } 28 | client.Log.Infof("%v\n", img) 29 | } 30 | 31 | func printAlbum(client *imgur.Client, album *string) { 32 | client.Log.Infof("*** ALBUM ***\n") 33 | img, _, err := client.GetAlbumInfo(*album) 34 | if err != nil { 35 | client.Log.Errorf("Error in GetAlbumInfo: %v\n", err) 36 | return 37 | } 38 | client.Log.Infof("%v\n", img) 39 | } 40 | 41 | func printGImage(client *imgur.Client, gimage *string) { 42 | client.Log.Infof("*** GALLERY IMAGE ***\n") 43 | img, _, err := client.GetGalleryImageInfo(*gimage) 44 | if err != nil { 45 | client.Log.Errorf("Error in GetGalleryImageInfo: %v\n", err) 46 | return 47 | } 48 | client.Log.Infof("%v\n", img) 49 | } 50 | 51 | func printGAlbum(client *imgur.Client, galbum *string) { 52 | client.Log.Infof("*** GALLERY ALBUM ***\n") 53 | img, _, err := client.GetGalleryAlbumInfo(*galbum) 54 | if err != nil { 55 | client.Log.Errorf("Error in GetGalleryAlbumInfo: %v\n", err) 56 | return 57 | } 58 | client.Log.Infof("%v\n", img) 59 | } 60 | 61 | func printURL(client *imgur.Client, url *string) { 62 | client.Log.Infof("*** URL ***\n") 63 | img, _, err := client.GetInfoFromURL(*url) 64 | if err != nil { 65 | client.Log.Errorf("Error in GetInfoFromURL: %v\n", err) 66 | return 67 | } 68 | client.Log.Infof("Image: %+v\n", img.Image) 69 | client.Log.Infof("Album: %+v\n", img.Album) 70 | client.Log.Infof("GImage: %+v\n", img.GImage) 71 | client.Log.Infof("GAlbum: %+v\n", img.GAlbum) 72 | } 73 | 74 | func main() { 75 | imgurClientID := flag.String("id", "", "Your imgur client id. REQUIRED!") 76 | url := flag.String("url", "", "Gets information based on the URL passed.") 77 | upload := flag.String("upload", "", "Filepath to an image that will be uploaded to imgur.") 78 | image := flag.String("image", "", "The image ID to be queried.") 79 | album := flag.String("album", "", "The album ID to be queried.") 80 | gimage := flag.String("gimage", "", "The gallery image ID to be queried.") 81 | galbum := flag.String("galbum", "", "The gallery album ID to be queried.") 82 | rate := flag.Bool("rate", false, "Get the current rate limit.") 83 | flag.Parse() 84 | 85 | // Check if there is anything todo 86 | if flag.NFlag() >= 3 || *imgurClientID == "" { 87 | flag.PrintDefaults() 88 | return 89 | } 90 | 91 | client, err := imgur.NewClient(new(http.Client), *imgurClientID, "") 92 | if err != nil { 93 | fmt.Printf("failed during imgur client creation. %+v\n", err) 94 | return 95 | } 96 | 97 | if *upload != "" { 98 | _, st, err := client.UploadImageFromFile(*upload, "", "test title", "test desc") 99 | if st != 200 || err != nil { 100 | fmt.Printf("Status: %v\n", st) 101 | fmt.Printf("Err: %v\n", err) 102 | } 103 | } 104 | 105 | if *rate { 106 | printRate(client) 107 | } 108 | 109 | if *image != "" { 110 | printImage(client, image) 111 | } 112 | 113 | if *album != "" { 114 | printAlbum(client, album) 115 | } 116 | 117 | if *gimage != "" { 118 | printGImage(client, gimage) 119 | } 120 | 121 | if *galbum != "" { 122 | printGAlbum(client, galbum) 123 | } 124 | 125 | if *url != "" { 126 | printURL(client, url) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /album_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestAlbumImgurSimulated(t *testing.T) { 10 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"VZQXk\",\"title\":\"Gianluca Gimini's bikes\",\"description\":null,\"datetime\":1460715031,\"cover\":\"CJCA0gW\",\"cover_width\":1200,\"cover_height\":786,\"account_url\":\"mrcassette\",\"account_id\":157430,\"privacy\":\"public\",\"layout\":\"blog\",\"views\":667581,\"link\":\"https:\\/\\/imgur.com\\/a\\/VZQXk\",\"favorite\":false,\"nsfw\":false,\"section\":\"pics\",\"images_count\":1,\"in_gallery\":true,\"images\":[{\"id\":\"CJCA0gW\",\"title\":null,\"description\":\"by Designer Gianluca Gimini\\nhttps:\\/\\/www.behance.net\\/gallery\\/35437979\\/Velocipedia\",\"datetime\":1460715032,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":1200,\"height\":786,\"size\":362373,\"views\":4420880,\"bandwidth\":1602007548240,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/CJCA0gW.jpg\"}]},\"success\":true,\"status\":200}") 11 | defer server.Close() 12 | 13 | client, _ := NewClient(httpC, "testing", "") 14 | alb, status, err := client.GetAlbumInfo("VZQXk") 15 | 16 | if err != nil { 17 | t.Errorf("GetAlbumInfo() failed with error: %v", err) 18 | t.FailNow() 19 | } 20 | 21 | if alb.Title != "Gianluca Gimini's bikes" || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 1 || alb.Images[0].ID != "CJCA0gW" { 22 | t.Error("Data comparison failed.") 23 | 24 | if alb.Title != "Gianluca Gimini's bikes" { 25 | t.Errorf("Title is %v.\n", alb.Title) 26 | } 27 | if alb.Cover != "CJCA0gW" { 28 | t.Errorf("Cover is %v.\n", alb.Cover) 29 | } 30 | if alb.CoverWidth != 1200 { 31 | t.Errorf("CoverWidth is %v.\n", alb.CoverWidth) 32 | } 33 | if alb.CoverHeight != 786 { 34 | t.Errorf("CoverHeight is %v.\n", alb.CoverHeight) 35 | } 36 | if alb.Link != "https://imgur.com/a/VZQXk" { 37 | t.Errorf("Link is %v.\n", alb.Link) 38 | } 39 | if alb.ImagesCount != 14 { 40 | t.Errorf("ImagesCount is %v.\n", alb.ImagesCount) 41 | } 42 | if alb.Images[0].ID != "CJCA0gW" { 43 | t.Errorf("Images is %v.\n", alb.Images) 44 | } 45 | t.Fail() 46 | } 47 | 48 | if status != 200 { 49 | t.Errorf("Statsu != 200. It was %v.", status) 50 | t.Fail() 51 | } 52 | } 53 | 54 | func TestAlbumImgurReal(t *testing.T) { 55 | key := os.Getenv("IMGURCLIENTID") 56 | if key == "" { 57 | t.Skip("IMGURCLIENTID environment variable not set.") 58 | } 59 | RapidAPIKey := os.Getenv("RapidAPIKEY") 60 | 61 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 62 | 63 | alb, status, err := client.GetAlbumInfo("VZQXk") 64 | 65 | if err != nil { 66 | t.Errorf("GetAlbumInfo() failed with error: %v", err) 67 | t.FailNow() 68 | } 69 | 70 | if alb.Title != "Gianluca Gimini's bikes" || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 14 || alb.Images[0].ID != "CJCA0gW" { 71 | t.Error("Data comparison failed.") 72 | 73 | if alb.Title != "Gianluca Gimini's bikes" { 74 | t.Errorf("Title is %v.\n", alb.Title) 75 | } 76 | if alb.Cover != "CJCA0gW" { 77 | t.Errorf("Cover is %v.\n", alb.Cover) 78 | } 79 | if alb.CoverWidth != 1200 { 80 | t.Errorf("CoverWidth is %v.\n", alb.CoverWidth) 81 | } 82 | if alb.CoverHeight != 786 { 83 | t.Errorf("CoverHeight is %v.\n", alb.CoverHeight) 84 | } 85 | if alb.Link != "https://imgur.com/a/VZQXk" { 86 | t.Errorf("Link is %v.\n", alb.Link) 87 | } 88 | if alb.ImagesCount != 14 { 89 | t.Errorf("ImagesCount is %v.\n", alb.ImagesCount) 90 | } 91 | if alb.Images[0].ID != "CJCA0gW" { 92 | t.Errorf("Images is %v.\n", alb.Images) 93 | } 94 | t.Fail() 95 | } 96 | 97 | if status != 200 { 98 | t.Errorf("Statsu != 200. It was %v.", status) 99 | t.Fail() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /uploadImage.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strconv" 13 | ) 14 | 15 | // UploadImage uploads the image to imgur 16 | // image Can be a binary file, base64 data, or a URL for an image. (up to 10MB) 17 | // album optional The id of the album you want to add the image to. 18 | // For anonymous albums, album should be the deletehash that is returned at creation. 19 | // dtype The type of the file that's being sent; file, base64 or URL 20 | // title optional The title of the image. 21 | // description optional The description of the image. 22 | // returns image info, status code of the upload, error 23 | func (client *Client) UploadImage(image []byte, album string, dtype string, title string, description string) (*ImageInfo, int, error) { 24 | if image == nil { 25 | return nil, -1, errors.New("Invalid image") 26 | } 27 | if dtype != "file" && dtype != "base64" && dtype != "URL" { 28 | return nil, -1, errors.New("Passed invalid dtype: " + dtype + ". Please use file/base64/URL.") 29 | } 30 | 31 | form := createUploadForm(image, album, dtype, title, description) 32 | 33 | URL := client.createAPIURL("image") 34 | req, err := http.NewRequest("POST", URL, bytes.NewBufferString(form.Encode())) 35 | client.Log.Debugf("Posting to URL %v\n", URL) 36 | if err != nil { 37 | return nil, -1, errors.New("Could create request for " + URL + " - " + err.Error()) 38 | } 39 | 40 | req.Header.Add("Authorization", "Client-ID "+client.imgurAccount.clientID) 41 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 42 | if client.rapidAPIKey != "" { 43 | req.Header.Add("X-RapidAPI-Key", client.rapidAPIKey) 44 | } 45 | 46 | res, err := client.httpClient.Do(req) 47 | if err != nil { 48 | return nil, -1, errors.New("Could not post " + URL + " - " + err.Error()) 49 | } 50 | defer res.Body.Close() 51 | 52 | // Read the whole body 53 | body, err := ioutil.ReadAll(res.Body) 54 | if err != nil { 55 | return nil, -1, errors.New("Problem reading the body of " + URL + " - " + err.Error()) 56 | } 57 | 58 | // client.Log.Debugf("%v\n", string(body[:])) 59 | 60 | dec := json.NewDecoder(bytes.NewReader(body)) 61 | var img imageInfoDataWrapper 62 | if err = dec.Decode(&img); err != nil { 63 | return nil, -1, errors.New("Problem decoding json result from image upload - " + err.Error() + ". JSON(?): " + string(body)) 64 | } 65 | 66 | if !img.Success { 67 | return nil, img.Status, errors.New("Upload to imgur failed with status: " + strconv.Itoa(img.Status)) 68 | } 69 | 70 | img.Ii.Limit, _ = extractRateLimits(res.Header) 71 | 72 | return img.Ii, img.Status, nil 73 | } 74 | 75 | func createUploadForm(image []byte, album string, dtype string, title string, description string) url.Values { 76 | form := url.Values{} 77 | 78 | form.Add("image", string(image[:])) 79 | form.Add("type", dtype) 80 | 81 | if album != "" { 82 | form.Add("album", album) 83 | } 84 | if title != "" { 85 | form.Add("title", title) 86 | } 87 | if description != "" { 88 | form.Add("description", description) 89 | } 90 | 91 | return form 92 | } 93 | 94 | // UploadImageFromFile uploads a file given by the filename string to imgur. 95 | func (client *Client) UploadImageFromFile(filename string, album string, title string, description string) (*ImageInfo, int, error) { 96 | client.Log.Infof("*** IMAGE UPLOAD ***\n") 97 | f, err := os.Open(filename) 98 | if err != nil { 99 | return nil, 500, fmt.Errorf("Could not open file %v - Error: %v", filename, err) 100 | } 101 | defer f.Close() 102 | fileinfo, err := f.Stat() 103 | if err != nil { 104 | return nil, 500, fmt.Errorf("Could not stat file %v - Error: %v", filename, err) 105 | } 106 | size := fileinfo.Size() 107 | b := make([]byte, size) 108 | n, err := f.Read(b) 109 | if err != nil || int64(n) != size { 110 | return nil, 500, fmt.Errorf("Could not read file %v - Error: %v", filename, err) 111 | } 112 | 113 | return client.UploadImage(b, album, "file", title, description) 114 | } 115 | -------------------------------------------------------------------------------- /galleryAlbum.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type galleryAlbumInfoDataWrapper struct { 11 | Ai *GalleryAlbumInfo `json:"data"` 12 | Success bool `json:"success"` 13 | Status int `json:"status"` 14 | } 15 | 16 | // GalleryAlbumInfo contains all information provided by imgur of a gallery album 17 | type GalleryAlbumInfo struct { 18 | ID string `json:"id"` // The ID for the album 19 | Title string `json:"title"` // The title of the album in the gallery 20 | Description string `json:"description"` // The description of the album in the gallery 21 | DateTime int `json:"datetime"` // Time inserted into the gallery, epoch time 22 | Cover string `json:"cover"` // The ID of the album cover image 23 | CoverWidth int `json:"cover_width"` // The width, in pixels, of the album cover image 24 | CoverHeight int `json:"cover_height"` // The height, in pixels, of the album cover image 25 | AccountURL string `json:"account_url"` // The account username or null if it's anonymous. 26 | AccountID int `json:"account_id"` // The account ID or null if it's anonymous. 27 | Privacy string `json:"privacy"` // The privacy level of the album, you can only view public if not logged in as album owner 28 | Layout string `json:"layout"` // The view layout of the album. 29 | Views int `json:"views"` // The number of album views 30 | Link string `json:"link"` // The URL link to the album 31 | Ups int `json:"ups"` // Upvotes for the image 32 | Downs int `json:"downs"` // Number of downvotes for the image 33 | Points int `json:"points"` // Upvotes minus downvotes 34 | Score int `json:"score"` // Imgur popularity score 35 | IsAlbum bool `json:"is_album"` // if it's an album or not 36 | Vote string `json:"vote"` // The current user's vote on the album. null if not signed in or if the user hasn't voted on it. 37 | Favorite bool `json:"favorite"` // Indicates if the current user favorited the image. Defaults to false if not signed in. 38 | Nsfw bool `json:"nsfw"` // Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available. 39 | CommentCount int `json:"comment_count"` // Number of comments on the gallery album. 40 | Topic string `json:"topic"` // Topic of the gallery album. 41 | TopicID int `json:"topic_id"` // Topic ID of the gallery album. 42 | ImagesCount int `json:"images_count"` // The total number of images in the album 43 | Images []ImageInfo `json:"images,omitempty"` // An array of all the images in the album (only available when requesting the direct album) 44 | InMostViral bool `json:"in_most_viral"` // Indicates if the album is in the most viral gallery or not. 45 | Limit *RateLimit // Current rate limit 46 | } 47 | 48 | // GetGalleryAlbumInfo queries imgur for information on a gallery album 49 | // returns album info, status code of the request, error 50 | func (client *Client) GetGalleryAlbumInfo(id string) (*GalleryAlbumInfo, int, error) { 51 | body, rl, err := client.getURL("gallery/album/" + id) 52 | if err != nil { 53 | return nil, -1, errors.New("Problem getting URL for gallery album info ID " + id + " - " + err.Error()) 54 | } 55 | // client.Log.Debugf("%v\n", body) 56 | 57 | dec := json.NewDecoder(strings.NewReader(body)) 58 | var alb galleryAlbumInfoDataWrapper 59 | if err := dec.Decode(&alb); err != nil { 60 | return nil, -1, errors.New("Problem decoding json for gallery albumID " + id + " - " + err.Error()) 61 | } 62 | alb.Ai.Limit = rl 63 | 64 | if !alb.Success { 65 | return nil, alb.Status, errors.New("Request to imgur failed for gallery albumID " + id + " - " + strconv.Itoa(alb.Status)) 66 | } 67 | return alb.Ai, alb.Status, nil 68 | } 69 | -------------------------------------------------------------------------------- /image.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type imageInfoDataWrapper struct { 11 | Ii *ImageInfo `json:"data"` 12 | Success bool `json:"success"` 13 | Status int `json:"status"` 14 | } 15 | 16 | // ImageInfo contains all image information provided by imgur 17 | type ImageInfo struct { 18 | ID string `json:"id"` // The ID for the image 19 | Title string `json:"title"` // The title of the image. 20 | Description string `json:"description"` // Description of the image. 21 | Datetime int `json:"datetime"` // Time uploaded, epoch time 22 | MimeType string `json:"type"` // Image MIME type. 23 | Animated bool `json:"animated"` // is the image animated 24 | Width int `json:"width"` // The width of the image in pixels 25 | Height int `json:"height"` // The height of the image in pixels 26 | Size int `json:"size"` // The size of the image in bytes 27 | Views int `json:"views"` // The number of image views 28 | Bandwidth int `json:"bandwidth"` // Bandwidth consumed by the image in bytes 29 | Deletehash string `json:"deletehash,omitempty"` // OPTIONAL, the deletehash, if you're logged in as the image owner 30 | Name string `json:"name,omitempty"` // OPTIONAL, the original filename, if you're logged in as the image owner 31 | Section string `json:"section"` // If the image has been categorized by our backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc) 32 | Link string `json:"link"` // The direct link to the the image. (Note: if fetching an animated GIF that was over 20MB in original size, a .gif thumbnail will be returned) 33 | Gifv string `json:"gifv,omitempty"` // OPTIONAL, The .gifv link. Only available if the image is animated and type is 'image/gif'. 34 | Mp4 string `json:"mp4,omitempty"` // OPTIONAL, The direct link to the .mp4. Only available if the image is animated and type is 'image/gif'. 35 | Mp4Size int `json:"mp4_size,omitempty"` // OPTIONAL, The Content-Length of the .mp4. Only available if the image is animated and type is 'image/gif'. Note that a zero value (0) is possible if the video has not yet been generated 36 | Looping bool `json:"looping,omitempty"` // OPTIONAL, Whether the image has a looping animation. Only available if the image is animated and type is 'image/gif'. 37 | Favorite bool `json:"favorite"` // Indicates if the current user favorited the image. Defaults to false if not signed in. 38 | Nsfw bool `json:"nsfw"` // Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available. 39 | Vote string `json:"vote"` // The current user's vote on the album. null if not signed in, if the user hasn't voted on it, or if not submitted to the gallery. 40 | InGallery bool `json:"in_gallery"` // True if the image has been submitted to the gallery, false if otherwise. 41 | Limit *RateLimit // Current rate limit 42 | } 43 | 44 | // GetImageInfo queries imgur for information on a image 45 | // returns image info, status code of the request, error 46 | func (client *Client) GetImageInfo(id string) (*ImageInfo, int, error) { 47 | body, rl, err := client.getURL("image/" + id) 48 | if err != nil { 49 | return nil, -1, errors.New("Problem getting URL for image info ID " + id + " - " + err.Error()) 50 | } 51 | 52 | dec := json.NewDecoder(strings.NewReader(body)) 53 | var img imageInfoDataWrapper 54 | if err := dec.Decode(&img); err != nil { 55 | return nil, -1, errors.New("Problem decoding json for imageID " + id + " - " + err.Error()) 56 | } 57 | img.Ii.Limit = rl 58 | 59 | if !img.Success { 60 | return nil, img.Status, errors.New("Request to imgur failed for imageID " + id + " - " + strconv.Itoa(img.Status)) 61 | } 62 | return img.Ii, img.Status, nil 63 | } 64 | -------------------------------------------------------------------------------- /galleryImage.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type galleryImageInfoDataWrapper struct { 11 | Ii *GalleryImageInfo `json:"data"` 12 | Success bool `json:"success"` 13 | Status int `json:"status"` 14 | } 15 | 16 | // GalleryImageInfo contains all gallery image information provided by imgur 17 | type GalleryImageInfo struct { 18 | ID string `json:"id"` // The ID for the image 19 | Title string `json:"title"` // The title of the image. 20 | Description string `json:"description"` // Description of the image. 21 | Datetime int `json:"datetime"` // Time uploaded, epoch time 22 | MimeType string `json:"type"` // Image MIME type. 23 | Animated bool `json:"animated"` // is the image animated 24 | Width int `json:"width"` // The width of the image in pixels 25 | Height int `json:"height"` // The height of the image in pixels 26 | Size int `json:"size"` // The size of the image in bytes 27 | Views int `json:"views"` // The number of image views 28 | Bandwidth int `json:"bandwidth"` // Bandwidth consumed by the image in bytes 29 | Deletehash string `json:"deletehash,omitempty"` // OPTIONAL, the deletehash, if you're logged in as the image owner 30 | Link string `json:"link"` // The direct link to the the image. (Note: if fetching an animated GIF that was over 20MB in original size, a .gif thumbnail will be returned) 31 | Gifv string `json:"gifv,omitempty"` // OPTIONAL, The .gifv link. Only available if the image is animated and type is 'image/gif'. 32 | Mp4 string `json:"mp4,omitempty"` // OPTIONAL, The direct link to the .mp4. Only available if the image is animated and type is 'image/gif'. 33 | Mp4Size int `json:"mp4_size,omitempty"` // OPTIONAL, The Content-Length of the .mp4. Only available if the image is animated and type is 'image/gif'. Note that a zero value (0) is possible if the video has not yet been generated 34 | Looping bool `json:"looping,omitempty"` // OPTIONAL, Whether the image has a looping animation. Only available if the image is animated and type is 'image/gif'. 35 | Vote string `json:"vote"` // The current user's vote on the album. null if not signed in or if the user hasn't voted on it. 36 | Favorite bool `json:"favorite"` // Indicates if the current user favorited the image. Defaults to false if not signed in. 37 | Nsfw bool `json:"nsfw"` // Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available. 38 | CommentCount int `json:"comment_count"` // Number of comments on the gallery album. 39 | Topic string `json:"topic"` // Topic of the gallery album. 40 | TopicID int `json:"topic_id"` // Topic ID of the gallery album. 41 | Section string `json:"section"` // If the image has been categorized by our backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc) 42 | AccountURL string `json:"account_url"` // The username of the account that uploaded it, or null. 43 | AccountID int `json:"account_id"` // The account ID of the account that uploaded it, or null. 44 | Ups int `json:"ups"` // Upvotes for the image 45 | Downs int `json:"downs"` // Number of downvotes for the image 46 | Points int `json:"points"` // Upvotes minus downvotes 47 | Score int `json:"score"` // Imgur popularity score 48 | IsAlbum bool `json:"is_album"` // if it's an album or not 49 | InMostViral bool `json:"in_most_viral"` // Indicates if the album is in the most viral gallery or not. 50 | Limit *RateLimit // Current rate limit 51 | } 52 | 53 | // GetGalleryImageInfo queries imgur for information on a image 54 | // returns image info, status code of the request, error 55 | func (client *Client) GetGalleryImageInfo(id string) (*GalleryImageInfo, int, error) { 56 | body, rl, err := client.getURL("gallery/image/" + id) 57 | if err != nil { 58 | return nil, -1, errors.New("Problem getting URL for gallery image info ID " + id + " - " + err.Error()) 59 | } 60 | // client.Log.Debugf("%v\n", body) 61 | 62 | dec := json.NewDecoder(strings.NewReader(body)) 63 | var img galleryImageInfoDataWrapper 64 | if err := dec.Decode(&img); err != nil { 65 | return nil, -1, errors.New("Problem decoding json for gallery imageID " + id + " - " + err.Error()) 66 | } 67 | img.Ii.Limit = rl 68 | 69 | if !img.Success { 70 | return nil, img.Status, errors.New("Request to imgur failed for gallery imageID " + id + " - " + strconv.Itoa(img.Status)) 71 | } 72 | return img.Ii, img.Status, nil 73 | } 74 | -------------------------------------------------------------------------------- /fromURL.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // GenericInfo is returned from functions for which the final result type is not known beforehand. 9 | // Only one pointer is != nil 10 | type GenericInfo struct { 11 | Image *ImageInfo 12 | Album *AlbumInfo 13 | GImage *GalleryImageInfo 14 | GAlbum *GalleryAlbumInfo 15 | Limit *RateLimit 16 | } 17 | 18 | var directURLPatterns = []string{ 19 | "://i.imgur.com/", 20 | "://i.imgur.io/", 21 | } 22 | 23 | var albumURLPatterns = []string{ 24 | "://imgur.com/a/", 25 | "://m.imgur.com/a/", 26 | "://imgur.io/a/", 27 | "://m.imgur.io/a/", 28 | } 29 | 30 | var galleryURLPatterns = []string{ 31 | "://imgur.com/gallery/", 32 | "://m.imgur.com/gallery/", 33 | "://imgur.io/gallery/", 34 | "://m.imgur.io/gallery/", 35 | } 36 | 37 | var imageURLPatterns = []string{ 38 | "://imgur.com/", 39 | "://m.imgur.com/", 40 | "://imgur.io/", 41 | "://m.imgur.io/", 42 | } 43 | 44 | func matchesSlice(url string, validFormats []string) bool { 45 | for _, format := range validFormats { 46 | if strings.Contains(url, format) { 47 | return true 48 | } 49 | } 50 | 51 | return false 52 | } 53 | 54 | // GetInfoFromURL tries to query imgur based on information identified in the URL. 55 | // returns image/album info, status code of the request, error 56 | func (client *Client) GetInfoFromURL(url string) (*GenericInfo, int, error) { 57 | url = strings.TrimSpace(url) 58 | 59 | // https://i.imgur.com/.jpg -> image 60 | if matchesSlice(url, directURLPatterns) { 61 | return client.directImageURL(url) 62 | } 63 | 64 | // https://imgur.com/a/ -> album 65 | if matchesSlice(url, albumURLPatterns) { 66 | return client.albumURL(url) 67 | } 68 | 69 | // https://imgur.com/gallery/ -> gallery album 70 | if matchesSlice(url, galleryURLPatterns) { 71 | return client.galleryURL(url) 72 | } 73 | 74 | // https://imgur.com/ -> image 75 | if matchesSlice(url, imageURLPatterns) { 76 | return client.imageURL(url) 77 | } 78 | 79 | return nil, -1, errors.New("URL pattern matching for URL " + url + " failed.") 80 | } 81 | 82 | func (client *Client) directImageURL(url string) (*GenericInfo, int, error) { 83 | var ret GenericInfo 84 | start := strings.LastIndex(url, "/") + 1 85 | end := strings.LastIndex(url, ".") 86 | if start+1 >= end { 87 | return nil, -1, errors.New("Could not find ID in URL " + url + ". I was going down i.imgur.com path.") 88 | } 89 | id := url[start:end] 90 | client.Log.Debugf("Detected imgur image ID %v. Was going down the i.imgur.com/ path.", id) 91 | gii, status, err := client.GetGalleryImageInfo(id) 92 | if err == nil && status < 400 { 93 | ret.GImage = gii 94 | } else { 95 | var ii *ImageInfo 96 | ii, status, err = client.GetImageInfo(id) 97 | ret.Image = ii 98 | } 99 | return &ret, status, err 100 | } 101 | 102 | func (client *Client) albumURL(url string) (*GenericInfo, int, error) { 103 | var ret GenericInfo 104 | 105 | start := strings.LastIndex(url, "/") + 1 106 | end := strings.LastIndex(url, "?") 107 | if end == -1 { 108 | end = len(url) 109 | } 110 | id := url[start:end] 111 | if id == "" { 112 | return nil, -1, errors.New("Could not find ID in URL " + url + ". I was going down imgur.com/a/ path.") 113 | } 114 | client.Log.Debugf("Detected imgur album ID %v. Was going down the imgur.com/a/ path.", id) 115 | ai, status, err := client.GetAlbumInfo(id) 116 | ret.Album = ai 117 | return &ret, status, err 118 | } 119 | 120 | func (client *Client) galleryURL(url string) (*GenericInfo, int, error) { 121 | var ret GenericInfo 122 | 123 | start := strings.LastIndex(url, "/") + 1 124 | end := strings.LastIndex(url, "?") 125 | if end == -1 { 126 | end = len(url) 127 | } 128 | id := url[start:end] 129 | if id == "" { 130 | return nil, -1, errors.New("Could not find ID in URL " + url + ". I was going down imgur.com/gallery/ path.") 131 | } 132 | client.Log.Debugf("Detected imgur gallery ID %v. Was going down the imgur.com/gallery/ path.", id) 133 | ai, status, err := client.GetGalleryAlbumInfo(id) 134 | if err == nil && status < 400 { 135 | ret.GAlbum = ai 136 | return &ret, status, err 137 | } 138 | // fallback to GetGalleryImageInfo 139 | client.Log.Debugf("Failed to retrieve imgur gallery album. Attempting to retrieve imgur gallery image. err: %v status: %d", err, status) 140 | ii, status, err := client.GetGalleryImageInfo(id) 141 | ret.GImage = ii 142 | return &ret, status, err 143 | } 144 | 145 | func (client *Client) imageURL(url string) (*GenericInfo, int, error) { 146 | var ret GenericInfo 147 | 148 | start := strings.LastIndex(url, "/") + 1 149 | end := strings.LastIndex(url, "?") 150 | if end == -1 { 151 | end = len(url) 152 | } 153 | id := url[start:end] 154 | if id == "" { 155 | return nil, -1, errors.New("Could not find ID in URL " + url + ". I was going down imgur.com/ path.") 156 | } 157 | client.Log.Debugf("Detected imgur image ID %v. Was going down the imgur.com/ path.", id) 158 | ii, status, err := client.GetGalleryImageInfo(id) 159 | if err == nil && status < 400 { 160 | ret.GImage = ii 161 | 162 | return &ret, status, err 163 | } 164 | 165 | i, st, err := client.GetImageInfo(id) 166 | ret.Image = i 167 | return &ret, st, err 168 | } 169 | -------------------------------------------------------------------------------- /serverError_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import "testing" 4 | 5 | func TestImgurNotSuccess(t *testing.T) { 6 | httpC, server := testHTTPClientJSON("{\"data\": {}, \"success\": false, \"status\": 200 }") 7 | defer server.Close() 8 | 9 | client, _ := NewClient(httpC, "testing", "") 10 | 11 | _, err := client.GetRateLimit() 12 | 13 | if err == nil { 14 | t.Error("GetRateLimit() should have failed, but didn't") 15 | } 16 | 17 | _, _, err = client.GetImageInfo("asd") 18 | 19 | if err == nil { 20 | t.Error("GetImageInfo() should have failed, but didn't") 21 | } 22 | 23 | _, _, err = client.GetAlbumInfo("asd") 24 | 25 | if err == nil { 26 | t.Error("GetAlbumInfo() should have failed, but didn't") 27 | } 28 | 29 | _, _, err = client.GetGalleryAlbumInfo("asd") 30 | 31 | if err == nil { 32 | t.Error("GetGalleryAlbumInfo() should have failed, but didn't") 33 | } 34 | 35 | _, _, err = client.GetGalleryImageInfo("asd") 36 | 37 | if err == nil { 38 | t.Error("GetGalleryImageInfo() should have failed, but didn't") 39 | } 40 | 41 | _, _, err = client.GetInfoFromURL("asd") 42 | 43 | if err == nil { 44 | t.Error("GetInfoFromURL() should have failed, but didn't") 45 | } 46 | 47 | var im []byte 48 | _, _, err = client.UploadImage(im, "", "file", "t", "d") 49 | 50 | if err == nil { 51 | t.Error("UploadImage() should have failed, but didn't") 52 | } 53 | } 54 | 55 | func TestJsonError(t *testing.T) { 56 | httpC, server := testHTTPClientInvalidJSON() 57 | defer server.Close() 58 | 59 | client, _ := NewClient(httpC, "testing", "") 60 | 61 | img, _, err := client.GetImageInfo("asd") 62 | 63 | if err == nil || img != nil { 64 | t.Error("GetImageInfo() should have failed, but didn't") 65 | } 66 | 67 | ab, _, err := client.GetAlbumInfo("asd") 68 | 69 | if err == nil || ab != nil { 70 | t.Error("GetAlbumInfo() should have failed, but didn't") 71 | } 72 | 73 | gab, _, err := client.GetGalleryAlbumInfo("asd") 74 | 75 | if err == nil || gab != nil { 76 | t.Error("GetGalleryAlbumInfo() should have failed, but didn't") 77 | } 78 | 79 | gim, _, err := client.GetGalleryImageInfo("asd") 80 | 81 | if err == nil || gim != nil { 82 | t.Error("GetGalleryImageInfo() should have failed, but didn't") 83 | } 84 | 85 | ge, _, err := client.GetInfoFromURL("asd") 86 | 87 | if err == nil || ge != nil { 88 | t.Error("GetInfoFromURL() should have failed, but didn't") 89 | } 90 | 91 | var im []byte 92 | img, _, err = client.UploadImage(im, "", "file", "t", "d") 93 | 94 | if err == nil || img != nil { 95 | t.Error("UploadImage() should have failed, but didn't") 96 | } 97 | 98 | } 99 | 100 | func TestServerError(t *testing.T) { 101 | httpC, server := testHTTPClient500() 102 | defer server.Close() 103 | 104 | client, _ := NewClient(httpC, "testing", "") 105 | 106 | _, err := client.GetRateLimit() 107 | 108 | if err == nil { 109 | t.Error("GetRateLimit() should have failed, but didn't") 110 | } 111 | 112 | _, _, err = client.GetImageInfo("asd") 113 | 114 | if err == nil { 115 | t.Error("GetImageInfo() should have failed, but didn't") 116 | } 117 | 118 | _, _, err = client.GetAlbumInfo("asd") 119 | 120 | if err == nil { 121 | t.Error("GetAlbumInfo() should have failed, but didn't") 122 | } 123 | 124 | _, _, err = client.GetGalleryAlbumInfo("asd") 125 | 126 | if err == nil { 127 | t.Error("GetGalleryAlbumInfo() should have failed, but didn't") 128 | } 129 | 130 | _, _, err = client.GetGalleryImageInfo("asd") 131 | 132 | if err == nil { 133 | t.Error("GetGalleryImageInfo() should have failed, but didn't") 134 | } 135 | 136 | _, _, err = client.GetInfoFromURL("asd") 137 | 138 | if err == nil { 139 | t.Error("GetInfoFromURL() should have failed, but didn't") 140 | } 141 | 142 | var im []byte 143 | _, _, err = client.UploadImage(im, "", "file", "t", "d") 144 | 145 | if err == nil { 146 | t.Error("UploadImage() should have failed, but didn't") 147 | } 148 | } 149 | 150 | func TestImgurError(t *testing.T) { 151 | httpC, server := testHTTPClientJSON("{'data' : {}, 'success' : false, 'status' : 500}") 152 | defer server.Close() 153 | 154 | client, _ := NewClient(httpC, "testing", "") 155 | _, err := client.GetRateLimit() 156 | 157 | if err == nil { 158 | t.Error("GetRateLimit() should have failed, but didn't") 159 | } 160 | 161 | _, _, err = client.GetImageInfo("asd") 162 | 163 | if err == nil { 164 | t.Error("GetImageInfo() should have failed, but didn't") 165 | } 166 | 167 | _, _, err = client.GetAlbumInfo("asd") 168 | 169 | if err == nil { 170 | t.Error("GetAlbumInfo() should have failed, but didn't") 171 | } 172 | 173 | _, _, err = client.GetGalleryAlbumInfo("asd") 174 | 175 | if err == nil { 176 | t.Error("GetGalleryAlbumInfo() should have failed, but didn't") 177 | } 178 | 179 | _, _, err = client.GetGalleryImageInfo("asd") 180 | 181 | if err == nil { 182 | t.Error("GetGalleryImageInfo() should have failed, but didn't") 183 | } 184 | 185 | _, _, err = client.GetInfoFromURL("asd") 186 | 187 | if err == nil { 188 | t.Error("GetInfoFromURL() should have failed, but didn't") 189 | } 190 | 191 | var im []byte 192 | _, _, err = client.UploadImage(im, "", "file", "t", "d") 193 | 194 | if err == nil { 195 | t.Error("UploadImage() should have failed, but didn't") 196 | } 197 | } 198 | 199 | func TestServerDown(t *testing.T) { 200 | httpC, server := testHTTPClient500() 201 | server.Close() 202 | 203 | client, _ := NewClient(httpC, "testing", "") 204 | _, err := client.GetRateLimit() 205 | 206 | if err == nil { 207 | t.Error("GetRateLimit() should have failed, but didn't") 208 | } 209 | 210 | _, _, err = client.GetImageInfo("asd") 211 | 212 | if err == nil { 213 | t.Error("GetImageInfo() should have failed, but didn't") 214 | } 215 | 216 | _, _, err = client.GetAlbumInfo("asd") 217 | 218 | if err == nil { 219 | t.Error("GetAlbumInfo() should have failed, but didn't") 220 | } 221 | 222 | _, _, err = client.GetGalleryAlbumInfo("asd") 223 | 224 | if err == nil { 225 | t.Error("GetGalleryAlbumInfo() should have failed, but didn't") 226 | } 227 | 228 | _, _, err = client.GetGalleryImageInfo("asd") 229 | 230 | if err == nil { 231 | t.Error("GetGalleryImageInfo() should have failed, but didn't") 232 | } 233 | 234 | _, _, err = client.GetInfoFromURL("asd") 235 | 236 | if err == nil { 237 | t.Error("GetInfoFromURL() should have failed, but didn't") 238 | } 239 | 240 | var im []byte 241 | _, _, err = client.UploadImage(im, "", "file", "t", "d") 242 | 243 | if err == nil { 244 | t.Error("UploadImage() should have failed, but didn't") 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /fromURL_test.go: -------------------------------------------------------------------------------- 1 | package imgur 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | 8 | "github.com/koffeinsource/go-klogger" 9 | ) 10 | 11 | func TestGetFromURLAlbumSimulated(t *testing.T) { 12 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"VZQXk\",\"title\":\"Gianluca Gimini's bikes\",\"description\":null,\"datetime\":1460715031,\"cover\":\"CJCA0gW\",\"cover_width\":1200,\"cover_height\":786,\"account_url\":\"mrcassette\",\"account_id\":157430,\"privacy\":\"public\",\"layout\":\"blog\",\"views\":667581,\"link\":\"https:\\/\\/imgur.com\\/a\\/VZQXk\",\"favorite\":false,\"nsfw\":false,\"section\":\"pics\",\"images_count\":1,\"in_gallery\":true,\"images\":[{\"id\":\"CJCA0gW\",\"title\":null,\"description\":\"by Designer Gianluca Gimini\\nhttps:\\/\\/www.behance.net\\/gallery\\/35437979\\/Velocipedia\",\"datetime\":1460715032,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":1200,\"height\":786,\"size\":362373,\"views\":4420880,\"bandwidth\":1602007548240,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/CJCA0gW.jpg\"}]},\"success\":true,\"status\":200}") 13 | defer server.Close() 14 | 15 | client, _ := NewClient(httpC, "testing", "") 16 | ge, status, err := client.GetInfoFromURL("https://imgur.com/a/VZQXk") 17 | if err != nil { 18 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 19 | t.FailNow() 20 | } 21 | 22 | if ge.Album == nil || ge.GAlbum != nil || ge.GImage != nil || ge.Image != nil { 23 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 24 | t.FailNow() 25 | } 26 | 27 | alb := ge.Album 28 | 29 | if alb.Title != "Gianluca Gimini's bikes" || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 1 || alb.Images[0].ID != "CJCA0gW" { 30 | t.Fail() 31 | } 32 | 33 | if status != 200 { 34 | t.Fail() 35 | } 36 | } 37 | 38 | func TestGetFromURLAlbumReal(t *testing.T) { 39 | key := os.Getenv("IMGURCLIENTID") 40 | if key == "" { 41 | t.Skip("IMGURCLIENTID environment variable not set.") 42 | } 43 | RapidAPIKey := os.Getenv("RapidAPIKEY") 44 | 45 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 46 | 47 | checker := func(url string) { 48 | ge, status, err := client.GetInfoFromURL(url) 49 | if err != nil { 50 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 51 | t.FailNow() 52 | } 53 | 54 | if ge.Album == nil || ge.GAlbum != nil || ge.GImage != nil || ge.Image != nil { 55 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 56 | t.FailNow() 57 | } 58 | 59 | alb := ge.Album 60 | 61 | if alb.Title != "Gianluca Gimini's bikes" || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 14 || alb.Images[0].ID != "CJCA0gW" { 62 | t.Fail() 63 | } 64 | 65 | if status != 200 { 66 | t.Fail() 67 | } 68 | } 69 | 70 | TEST_URLS := []string{ 71 | "https://imgur.com/a/VZQXk", 72 | "https://imgur.io/a/VZQXk", 73 | } 74 | 75 | for _, url := range TEST_URLS { 76 | checker(url) 77 | } 78 | } 79 | 80 | func TestGetFromURLAlbumNoID(t *testing.T) { 81 | httpC, server := testHTTPClient500() 82 | defer server.Close() 83 | client, _ := NewClient(httpC, "testing", "") 84 | 85 | checker := func(url string) { 86 | _, _, err := client.GetInfoFromURL(url) 87 | 88 | if err == nil { 89 | t.Error("GetInfoFromURL() did not failed but should have.") 90 | t.FailNow() 91 | } 92 | } 93 | 94 | TEST_URLS := []string{ 95 | "https://imgur.com/a/", 96 | "https://imgur.io/a/", 97 | } 98 | 99 | for _, url := range TEST_URLS { 100 | checker(url) 101 | } 102 | } 103 | 104 | func TestGetFromURLGalleryNoID(t *testing.T) { 105 | httpC, server := testHTTPClient500() 106 | defer server.Close() 107 | client, _ := NewClient(httpC, "testing", "") 108 | 109 | checker := func(url string) { 110 | _, _, err := client.GetInfoFromURL(url) 111 | 112 | if err == nil { 113 | t.Error("GetInfoFromURL() did not failed but should have.") 114 | t.FailNow() 115 | } 116 | } 117 | 118 | TEST_URLS := []string{ 119 | "https://imgur.com/gallery/", 120 | "https://imgur.io/gallery/", 121 | } 122 | 123 | for _, url := range TEST_URLS { 124 | checker(url) 125 | } 126 | } 127 | 128 | func TestGetFromURLGAlbumSimulated(t *testing.T) { 129 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"VZQXk\",\"title\":\"As it turns out, most people cannot draw a bike.\",\"description\":null,\"datetime\":1460715031,\"cover\":\"CJCA0gW\",\"cover_width\":1200,\"cover_height\":786,\"account_url\":\"mrcassette\",\"account_id\":157430,\"privacy\":\"public\",\"layout\":\"blog\",\"views\":667581,\"link\":\"https:\\/\\/imgur.com\\/a\\/VZQXk\",\"ups\":13704,\"downs\":113,\"favorite\":false,\"nsfw\":false,\"section\":\"pics\",\"images_count\":1,\"in_gallery\":true,\"images\":[{\"id\":\"CJCA0gW\",\"title\":null,\"description\":\"by Designer Gianluca Gimini\\nhttps:\\/\\/www.behance.net\\/gallery\\/35437979\\/Velocipedia\",\"datetime\":1460715032,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":1200,\"height\":786,\"size\":362373,\"views\":4420880,\"bandwidth\":1602007548240,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/CJCA0gW.jpg\"}]},\"success\":true,\"status\":200}") 130 | defer server.Close() 131 | 132 | client, _ := NewClient(httpC, "testing", "") 133 | ge, status, err := client.GetInfoFromURL("https://imgur.com/gallery/VZQXk") 134 | if err != nil { 135 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 136 | t.FailNow() 137 | } 138 | 139 | if ge.Album != nil || ge.GAlbum == nil || ge.GImage != nil || ge.Image != nil { 140 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 141 | t.FailNow() 142 | } 143 | 144 | alb := ge.GAlbum 145 | 146 | if alb.Title != "As it turns out, most people cannot draw a bike." || alb.Cover != "CJCA0gW" || alb.CoverWidth != 1200 || alb.CoverHeight != 786 || alb.Link != "https://imgur.com/a/VZQXk" || alb.ImagesCount != 1 || alb.Images[0].ID != "CJCA0gW" || alb.Ups != 13704 || alb.Downs != 113 { 147 | t.Fail() 148 | } 149 | 150 | if status != 200 { 151 | t.Fail() 152 | } 153 | } 154 | 155 | func TestGetFromURLGAlbumReal(t *testing.T) { 156 | key := os.Getenv("IMGURCLIENTID") 157 | if key == "" { 158 | t.Skip("IMGURCLIENTID environment variable not set.") 159 | } 160 | RapidAPIKey := os.Getenv("RapidAPIKEY") 161 | 162 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 163 | 164 | tests := []struct { 165 | galleryURL string 166 | expected map[string]interface{} 167 | }{ 168 | { 169 | galleryURL: "https://imgur.com/gallery/VZQXk", 170 | expected: map[string]interface{}{ 171 | "title": "As it turns out, most people cannot draw a bike.", 172 | "cover": "CJCA0gW", 173 | "coverWidth": 1200, 174 | "coverHeight": 786, 175 | "link": "https://imgur.com/a/VZQXk", 176 | "imagesCount": 14, 177 | "firstImageID": "CJCA0gW", 178 | }, 179 | }, 180 | { 181 | galleryURL: "https://imgur.com/gallery/t6l1GiW", 182 | expected: map[string]interface{}{ 183 | "title": "Funny Random Meme and Twitter Dump", 184 | "cover": "60wTouU", 185 | "coverWidth": 1242, 186 | "coverHeight": 1512, 187 | "link": "https://imgur.com/a/t6l1GiW", 188 | "imagesCount": 50, 189 | "firstImageID": "60wTouU", 190 | }, 191 | }, 192 | { 193 | galleryURL: "https://imgur.io/gallery/VZQXk", 194 | expected: map[string]interface{}{ 195 | "title": "As it turns out, most people cannot draw a bike.", 196 | "cover": "CJCA0gW", 197 | "coverWidth": 1200, 198 | "coverHeight": 786, 199 | "link": "https://imgur.com/a/VZQXk", 200 | "imagesCount": 14, 201 | "firstImageID": "CJCA0gW", 202 | }, 203 | }, 204 | { 205 | galleryURL: "https://imgur.io/gallery/t6l1GiW", 206 | expected: map[string]interface{}{ 207 | "title": "Funny Random Meme and Twitter Dump", 208 | "cover": "60wTouU", 209 | "coverWidth": 1242, 210 | "coverHeight": 1512, 211 | "link": "https://imgur.com/a/t6l1GiW", 212 | "imagesCount": 50, 213 | "firstImageID": "60wTouU", 214 | }, 215 | }, 216 | } 217 | for _, test := range tests { 218 | ge, status, err := client.GetInfoFromURL(test.galleryURL) 219 | if err != nil { 220 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 221 | t.FailNow() 222 | } 223 | if ge.Album != nil || ge.GAlbum == nil || ge.GImage != nil || ge.Image != nil { 224 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 225 | t.FailNow() 226 | } 227 | if ge.GAlbum.Title != test.expected["title"] { 228 | t.Errorf("title mismatch: %s != %s", ge.GAlbum.Title, test.expected["title"]) 229 | t.Fail() 230 | } 231 | if ge.GAlbum.Cover != test.expected["cover"] { 232 | t.Errorf("cover mismatch: %s != %s", ge.GAlbum.Cover, test.expected["cover"]) 233 | t.Fail() 234 | } 235 | if ge.GAlbum.CoverWidth != test.expected["coverWidth"] { 236 | t.Errorf("coverWidth mismatch: %d != %d", ge.GAlbum.CoverWidth, test.expected["coverWidth"]) 237 | t.Fail() 238 | } 239 | if ge.GAlbum.CoverHeight != test.expected["coverHeight"] { 240 | t.Errorf("coverHeight mismatch: %d != %d", ge.GAlbum.CoverHeight, test.expected["coverHeight"]) 241 | t.Fail() 242 | } 243 | if ge.GAlbum.Link != test.expected["link"] { 244 | t.Errorf("link mismatch: %s != %s", ge.GAlbum.Link, test.expected["link"]) 245 | t.Fail() 246 | } 247 | if ge.GAlbum.ImagesCount != test.expected["imagesCount"] { 248 | t.Errorf("imagesCount mismatch: %d != %d", ge.GAlbum.ImagesCount, test.expected["imagesCount"]) 249 | t.Fail() 250 | } 251 | if ge.GAlbum.Images[0].ID != test.expected["firstImageID"] { 252 | t.Errorf("firstImageID mismatch: %s != %s", ge.GAlbum.Images[0].ID, test.expected["firstImageID"]) 253 | t.Fail() 254 | } 255 | if status != http.StatusOK { 256 | t.Errorf("status mismatch: %d != %d", status, http.StatusOK) 257 | t.Fail() 258 | } 259 | } 260 | } 261 | 262 | func TestGetURLGalleryImageReal(t *testing.T) { 263 | key := os.Getenv("IMGURCLIENTID") 264 | if key == "" { 265 | t.Skip("IMGURCLIENTID environment variable not set.") 266 | } 267 | RapidAPIKey := os.Getenv("RapidAPIKEY") 268 | 269 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 270 | 271 | checker := func(url string) { 272 | ge, status, err := client.GetInfoFromURL(url) 273 | if err != nil { 274 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 275 | t.FailNow() 276 | } 277 | 278 | if ge.Album != nil || ge.GAlbum != nil || ge.GImage == nil || ge.Image != nil { 279 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 280 | t.FailNow() 281 | } 282 | 283 | img := ge.GImage 284 | 285 | if img.Title != "An abandoned Chinese fishing village" || img.Animated != false || img.Description != "" || img.Height != 445 || img.Width != 800 || img.ID != "uPI76jY" || img.Link != "https://i.imgur.com/uPI76jY.jpg" { 286 | t.Fail() 287 | } 288 | 289 | if status != 200 { 290 | t.Fail() 291 | } 292 | } 293 | 294 | TEST_URLS := []string{ 295 | "https://imgur.com/gallery/uPI76jY", 296 | "https://imgur.io/gallery/uPI76jY", 297 | } 298 | 299 | for _, url := range TEST_URLS { 300 | checker(url) 301 | } 302 | } 303 | 304 | func TestGetURLImageSimulated(t *testing.T) { 305 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"ClF8rLe\",\"title\":null,\"description\":null,\"datetime\":1451248840,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":2448,\"height\":3264,\"size\":1071339,\"views\":176,\"bandwidth\":188555664,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/ClF8rLe.jpg\"},\"success\":true,\"status\":200}") 306 | defer server.Close() 307 | 308 | client := new(Client) 309 | client.httpClient = httpC 310 | client.Log = new(klogger.CLILogger) 311 | client.imgurAccount.clientID = "testing" 312 | 313 | ge, status, err := client.GetInfoFromURL("https://imgur.com/ClF8rLe") 314 | if err != nil { 315 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 316 | t.FailNow() 317 | } 318 | 319 | if ge.Album != nil || ge.GAlbum != nil { 320 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 321 | t.FailNow() 322 | } 323 | 324 | if ge.Image == nil && ge.GImage == nil { 325 | t.FailNow() 326 | } 327 | 328 | if ge.Image != nil { 329 | img := ge.Image 330 | 331 | if img.Animated != false || img.Bandwidth != 188555664 || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" || img.Views != 176 { 332 | t.Fail() 333 | } 334 | } 335 | 336 | if ge.GImage != nil { 337 | img := ge.GImage 338 | 339 | if img.Animated != false || img.Bandwidth != 188555664 || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" || img.Views != 176 { 340 | t.Fail() 341 | } 342 | } 343 | 344 | if status != 200 { 345 | t.Fail() 346 | } 347 | } 348 | 349 | func TestGetURLImageReal(t *testing.T) { 350 | key := os.Getenv("IMGURCLIENTID") 351 | if key == "" { 352 | t.Skip("IMGURCLIENTID environment variable not set.") 353 | } 354 | RapidAPIKey := os.Getenv("RapidAPIKEY") 355 | 356 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 357 | 358 | checker := func(url string) { 359 | ge, status, err := client.GetInfoFromURL(url) 360 | if err != nil { 361 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 362 | t.FailNow() 363 | } 364 | 365 | if ge.Album != nil || ge.GAlbum != nil { 366 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 367 | t.FailNow() 368 | } 369 | 370 | if ge.Image == nil && ge.GImage == nil { 371 | t.FailNow() 372 | } 373 | 374 | if ge.Image != nil { 375 | img := ge.Image 376 | 377 | if img.Animated != false || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" { 378 | t.Fail() 379 | } 380 | } 381 | 382 | if ge.GImage != nil { 383 | img := ge.GImage 384 | 385 | if img.Animated != false || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" { 386 | t.Fail() 387 | } 388 | } 389 | 390 | if status != 200 { 391 | t.Fail() 392 | } 393 | } 394 | 395 | TEST_URLS := []string{ 396 | "https://imgur.com/ClF8rLe", 397 | "https://imgur.io/ClF8rLe", 398 | } 399 | 400 | for _, url := range TEST_URLS { 401 | checker(url) 402 | } 403 | } 404 | 405 | func TestGetFromURLImageNoID(t *testing.T) { 406 | httpC, server := testHTTPClient500() 407 | defer server.Close() 408 | 409 | client, _ := NewClient(httpC, "testing", "") 410 | 411 | checker := func(url string) { 412 | _, _, err := client.GetInfoFromURL(url) 413 | 414 | if err == nil { 415 | t.Error("GetInfoFromURL() did not failed but should have.") 416 | t.FailNow() 417 | } 418 | } 419 | 420 | TEST_URLS := []string{ 421 | "https://imgur.com/", 422 | "https://imgur.io/", 423 | } 424 | 425 | for _, url := range TEST_URLS { 426 | checker(url) 427 | } 428 | } 429 | 430 | func TestGetURLDirectImageSimulated(t *testing.T) { 431 | httpC, server := testHTTPClientJSON("{\"data\":{\"id\":\"ClF8rLe\",\"title\":null,\"description\":null,\"datetime\":1451248840,\"type\":\"image\\/jpeg\",\"animated\":false,\"width\":2448,\"height\":3264,\"size\":1071339,\"views\":176,\"bandwidth\":188555664,\"vote\":null,\"favorite\":false,\"nsfw\":null,\"section\":null,\"account_url\":null,\"account_id\":null,\"in_gallery\":false,\"link\":\"https:\\/\\/i.imgur.com\\/ClF8rLe.jpg\"},\"success\":true,\"status\":200}") 432 | defer server.Close() 433 | 434 | client, _ := NewClient(httpC, "testing", "") 435 | ge, status, err := client.GetInfoFromURL("https://i.imgur.com/ClF8rLe.jpg") 436 | if err != nil { 437 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 438 | t.FailNow() 439 | } 440 | 441 | if ge.Album != nil || ge.GAlbum != nil { 442 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 443 | t.FailNow() 444 | } 445 | 446 | if ge.Image == nil && ge.GImage == nil { 447 | t.FailNow() 448 | } 449 | 450 | if ge.Image != nil { 451 | img := ge.Image 452 | 453 | if img.Animated != false || img.Bandwidth != 188555664 || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" || img.Views != 176 { 454 | t.Fail() 455 | } 456 | } 457 | 458 | if ge.GImage != nil { 459 | img := ge.GImage 460 | 461 | if img.Animated != false || img.Bandwidth != 188555664 || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" || img.Views != 176 { 462 | t.Fail() 463 | } 464 | } 465 | 466 | if status != 200 { 467 | t.Fail() 468 | } 469 | } 470 | 471 | func TestGetURLDirectImageReal(t *testing.T) { 472 | key := os.Getenv("IMGURCLIENTID") 473 | if key == "" { 474 | t.Skip("IMGURCLIENTID environment variable not set.") 475 | } 476 | RapidAPIKey := os.Getenv("RapidAPIKEY") 477 | 478 | client, _ := NewClient(new(http.Client), key, RapidAPIKey) 479 | 480 | checker := func(url string) { 481 | ge, status, err := client.GetInfoFromURL(url) 482 | if err != nil { 483 | t.Errorf("GetInfoFromURL() failed with error: %v", err) 484 | t.FailNow() 485 | } 486 | 487 | if ge.Album != nil || ge.GAlbum != nil { 488 | t.Error("GetInfoFromURL() failed. Returned wrong type.") 489 | t.FailNow() 490 | } 491 | 492 | if ge.Image == nil && ge.GImage == nil { 493 | t.FailNow() 494 | } 495 | 496 | if ge.Image != nil { 497 | img := ge.Image 498 | 499 | if img.Animated != false || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" { 500 | t.Fail() 501 | } 502 | } 503 | 504 | if ge.GImage != nil { 505 | img := ge.GImage 506 | 507 | if img.Animated != false || img.Datetime != 1451248840 || img.Description != "" || img.Height != 3264 || img.Width != 2448 || img.ID != "ClF8rLe" || img.Link != "https://i.imgur.com/ClF8rLe.jpg" { 508 | t.Fail() 509 | } 510 | } 511 | 512 | if status != 200 { 513 | t.Fail() 514 | } 515 | } 516 | 517 | TEST_URLS := []string{ 518 | "https://i.imgur.com/ClF8rLe.jpg", 519 | "https://i.imgur.io/ClF8rLe.jpg", 520 | } 521 | 522 | for _, url := range TEST_URLS { 523 | checker(url) 524 | } 525 | } 526 | 527 | func TestGetFromURLDirectImageNoID(t *testing.T) { 528 | httpC, server := testHTTPClient500() 529 | defer server.Close() 530 | 531 | client, _ := NewClient(httpC, "testing", "") 532 | 533 | checker := func(url string) { 534 | _, _, err := client.GetInfoFromURL(url) 535 | 536 | if err == nil { 537 | t.Error("GetInfoFromURL() did not failed but should have.") 538 | t.FailNow() 539 | } 540 | } 541 | 542 | TEST_URLS := []string{ 543 | "https://i.imgur.com/", 544 | "https://i.imgur.io/", 545 | } 546 | 547 | for _, url := range TEST_URLS { 548 | checker(url) 549 | } 550 | } 551 | --------------------------------------------------------------------------------