Initial commit
This commit is contained in:
30
vendor/gopkg.in/masci/flickr.v3/.gitignore
generated
vendored
Normal file
30
vendor/gopkg.in/masci/flickr.v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
flick-rsync
|
||||
*.swp
|
||||
*.swo
|
||||
coverage.out
|
||||
profile.cov
|
||||
.DS_Store
|
||||
.env
|
||||
21
vendor/gopkg.in/masci/flickr.v3/LICENSE.md
generated
vendored
Normal file
21
vendor/gopkg.in/masci/flickr.v3/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Massimiliano Pippi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
161
vendor/gopkg.in/masci/flickr.v3/README.md
generated
vendored
Normal file
161
vendor/gopkg.in/masci/flickr.v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# flickr
|
||||
|
||||
A go library to easily consume Flickr API.
|
||||
The project is currently under heavy development, so it hasn't a version number yet.
|
||||
|
||||
[](https://godoc.org/gopkg.in/masci/flickr.v3)
|
||||
[](https://github.com/masci/flickr/actions/workflows/test.yml)
|
||||
[](https://coveralls.io/r/masci/flickr)
|
||||
|
||||
## Usage
|
||||
|
||||
`flickr` aims to expose a Go Api matching Flickr REST Api, so that you don't need
|
||||
to build HTTP requests and parse HTTP response manually. For example, the Flickr
|
||||
method `flickr.photosets.create` is implemented with the `Create` function in the `flickr/photosets`
|
||||
package:
|
||||
|
||||
```go
|
||||
import "fmt"
|
||||
import "gopkg.in/masci/flickr.v3"
|
||||
import "gopkg.in/masci/flickr.v3/photosets"
|
||||
|
||||
// create an API client with credentials
|
||||
client := flickr.NewFlickrClient("your_apikey", "your_apisecret")
|
||||
client.OAuthToken = "your_token"
|
||||
client.OAuthTokenSecret = "your_tokenSecret"
|
||||
|
||||
response, _ := photosets.Create(client, "My Set", "Description", "primary_photo_id")
|
||||
fmt.Println("New photoset created:", response.Photoset.Id)
|
||||
```
|
||||
|
||||
`flickr` responses implement `flickr.FlickrResponse` interface. A response contains error codes
|
||||
and error messages (if any) produced by Flickr or the specific data returned by the api call.
|
||||
Different methods may return different kind of responses.
|
||||
|
||||
### Upload a photo
|
||||
|
||||
There are a number of functions that don't map any actual Flickr Api method
|
||||
(see below for the detailed list). For example, to upload a photo, you call the
|
||||
`UploadFile` or `UploadReader` functions in the `flickr` package:
|
||||
|
||||
```go
|
||||
import "gopkg.in/masci/flickr.v3"
|
||||
|
||||
|
||||
// upload the image file with default (nil) options
|
||||
resp, err := flickr.UploadFile(client, "/path/to/image", nil)
|
||||
```
|
||||
Files are uploaded through an io.Pipe fueled in a separate goroutine, so the process is pretty efficient.
|
||||
|
||||
### Authentication (or how to retrieve OAuth credentials)
|
||||
|
||||
Several api calls must be authenticated and authorized: `flickr` only supports
|
||||
OAuth since the original token-based method has been deprecated by Flickr. This
|
||||
is an example describing the OAuth worflow from a command line application:
|
||||
|
||||
```go
|
||||
import "gopkg.in/masci/flickr.v3"
|
||||
|
||||
client := flickr.NewFlickrClient("your_apikey", "your_apisecret")
|
||||
|
||||
// first, get a request token
|
||||
requestTok, _ := flickr.GetRequestToken(client)
|
||||
|
||||
// build the authorizatin URL
|
||||
url, _ := flickr.GetAuthorizeUrl(client, requestTok)
|
||||
|
||||
// ask user to hit the authorization url with
|
||||
// their browser, authorize this application and coming
|
||||
// back with the confirmation token
|
||||
|
||||
// finally, get the access token, setup the client and start making requests
|
||||
accessTok, err := flickr.GetAccessToken(client, requestTok, "oauth_confirmation_code")
|
||||
client.OAuthToken = accessTok.OAuthToken
|
||||
client.OAuthTokenSecret = accessTok.OAuthTokenSecret
|
||||
```
|
||||
|
||||
### Api coverage
|
||||
|
||||
Only a small part of the Flickr Api is implemented as Go functions: even if it's quite
|
||||
simple to write the code for the mapping, I only did it for methods I actually need in my projects
|
||||
(contributions well accepted). Anyway, if you need to call a Flickr Api method that wasn't
|
||||
already mapped, you can do it manually:
|
||||
|
||||
```go
|
||||
import "fmt"
|
||||
import "gopkg.in/masci/flickr.v3"
|
||||
|
||||
client := flickr.NewFlickrClient("your_apikey", "your_apisecret")
|
||||
client.Init()
|
||||
client.Args.Set("method", "flickr.cameras.getBrandModels")
|
||||
client.Args.Set("brand", "nikon")
|
||||
|
||||
client.OAuthSign()
|
||||
response := &flickr.BasicResponse{}
|
||||
err := flickr.DoGet(client, response)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
} else {
|
||||
fmt.Println("Api response:", response.Extra)
|
||||
}
|
||||
```
|
||||
|
||||
Checkout the `example` folder and the docs pages for more details.
|
||||
|
||||
## Note on Go versions
|
||||
|
||||
The latest version `v2` only supports go `1.6` and above, for Go `< 1.6` use the `v1` package:
|
||||
```
|
||||
go get gopkg.in/masci/flickr.v1
|
||||
```
|
||||
|
||||
## API Methods
|
||||
|
||||
### Extra-API Methods
|
||||
These are methods that are not actually part of the Flickr API
|
||||
|
||||
* Get OAuth request token
|
||||
* Get OAuth authorize URL
|
||||
* Get OAuth access token
|
||||
* Upload photo
|
||||
|
||||
### auth.oauth
|
||||
* flickr.auth.oauth.checkToken
|
||||
|
||||
### photos
|
||||
* flickr.photos.delete
|
||||
* flickr.photos.getInfo
|
||||
* flickr.photos.setDates
|
||||
* flickr.photos.setPerms
|
||||
* flickr.photos.addTags
|
||||
* flickr.photos.getSizes
|
||||
|
||||
### photosets
|
||||
* flickr.photosets.addPhoto
|
||||
* flickr.photosets.create
|
||||
* flickr.photosets.delete
|
||||
* flickr.photosets.editMeta
|
||||
* flickr.photosets.editPhotos
|
||||
* flickr.photosets.getInfo
|
||||
* flickr.photosets.getList
|
||||
* flickr.photosets.getPhotos
|
||||
* flickr.photosets.orderSets
|
||||
* flickr.photosets.removePhoto
|
||||
* flickr.photosets.removePhotos
|
||||
* flickr.photosets.reorderPhotos
|
||||
* flickr.photosets.setPrimaryPhoto
|
||||
|
||||
### people
|
||||
* flickr.people.getPhotos
|
||||
|
||||
### Groups
|
||||
* flickr.groups.pools.add
|
||||
* flickr.groups.pools.getGroups
|
||||
* flickr.groups.getInfo
|
||||
|
||||
|
||||
### test
|
||||
* flickr.test.echo
|
||||
* flickr.test.login
|
||||
* flickr.test.null
|
||||
153
vendor/gopkg.in/masci/flickr.v3/authentication.go
generated
vendored
Normal file
153
vendor/gopkg.in/masci/flickr.v3/authentication.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
flickErr "gopkg.in/masci/flickr.v3/error"
|
||||
)
|
||||
|
||||
// Type representing a request token during the exchange process
|
||||
type RequestToken struct {
|
||||
// Whether the callback url matches the one provided in Flickr dashboard
|
||||
OauthCallbackConfirmed bool
|
||||
// Request token
|
||||
OauthToken string
|
||||
// Request token secret
|
||||
OauthTokenSecret string
|
||||
// OAuth failing reason in case of errors
|
||||
OAuthProblem string
|
||||
}
|
||||
|
||||
// Extract a RequestToken from the response body
|
||||
func ParseRequestToken(response string) (*RequestToken, error) {
|
||||
val, err := url.ParseQuery(strings.TrimSpace(response))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &RequestToken{}
|
||||
|
||||
oauth_problem := val.Get("oauth_problem")
|
||||
if oauth_problem != "" {
|
||||
ret.OAuthProblem = oauth_problem
|
||||
return ret, flickErr.NewError(flickErr.RequestTokenError, oauth_problem)
|
||||
}
|
||||
|
||||
confirmed, _ := strconv.ParseBool(val.Get("oauth_callback_confirmed"))
|
||||
ret.OauthCallbackConfirmed = confirmed
|
||||
ret.OauthToken = val.Get("oauth_token")
|
||||
ret.OauthTokenSecret = val.Get("oauth_token_secret")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Type representing a OAuth access token along with its owner's data
|
||||
type OAuthToken struct {
|
||||
// OAuth access token
|
||||
OAuthToken string
|
||||
// OAuth access token secret
|
||||
OAuthTokenSecret string
|
||||
// Flickr ID of token's owner
|
||||
UserNsid string
|
||||
// Flickr Username of token's owner
|
||||
Username string
|
||||
// Flickr full name of token's owner
|
||||
Fullname string
|
||||
// OAuth failing reason in case of errors
|
||||
OAuthProblem string
|
||||
}
|
||||
|
||||
// Extract a OAuthToken from the response body
|
||||
func ParseOAuthToken(response string) (*OAuthToken, error) {
|
||||
val, err := url.ParseQuery(strings.TrimSpace(response))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &OAuthToken{}
|
||||
|
||||
oauth_problem := val.Get("oauth_problem")
|
||||
if oauth_problem != "" {
|
||||
ret.OAuthProblem = oauth_problem
|
||||
return ret, flickErr.NewError(flickErr.OAuthTokenError, oauth_problem)
|
||||
}
|
||||
|
||||
ret.OAuthToken = val.Get("oauth_token")
|
||||
ret.OAuthTokenSecret = val.Get("oauth_token_secret")
|
||||
ret.Fullname = val.Get("fullname")
|
||||
ret.UserNsid = val.Get("user_nsid")
|
||||
ret.Username = val.Get("username")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Retrieve a request token: this is the first step to get a fully functional
|
||||
// access token from Flickr
|
||||
func GetRequestToken(client *FlickrClient) (*RequestToken, error) {
|
||||
client.EndpointUrl = REQUEST_TOKEN_URL
|
||||
client.SetOAuthDefaults()
|
||||
client.Args.Set("oauth_consumer_key", client.ApiKey)
|
||||
client.Args.Set("oauth_callback", "oob")
|
||||
|
||||
// we don't have token secret at this stage, pass an empty string
|
||||
client.Sign("")
|
||||
|
||||
res, err := client.HTTPClient.Get(client.GetUrl())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseRequestToken(string(body))
|
||||
}
|
||||
|
||||
// Returns the URL users need to reach to grant permission to our application
|
||||
func GetAuthorizeUrl(client *FlickrClient, reqToken *RequestToken) (string, error) {
|
||||
client.EndpointUrl = AUTHORIZE_URL
|
||||
client.Args = url.Values{}
|
||||
client.Args.Set("oauth_token", reqToken.OauthToken)
|
||||
// TODO make permission value parametric
|
||||
client.Args.Set("perms", "delete")
|
||||
|
||||
return client.GetUrl(), nil
|
||||
}
|
||||
|
||||
// Get an access token providing an OAuth verifier provided by Flickr once the user
|
||||
// authorizes your application
|
||||
func GetAccessToken(client *FlickrClient, reqToken *RequestToken, oauthVerifier string) (*OAuthToken, error) {
|
||||
client.EndpointUrl = ACCESS_TOKEN_URL
|
||||
client.SetOAuthDefaults()
|
||||
client.Args.Set("oauth_verifier", oauthVerifier)
|
||||
client.Args.Set("oauth_consumer_key", client.ApiKey)
|
||||
client.Args.Set("oauth_token", reqToken.OauthToken)
|
||||
// use the request token for signing
|
||||
client.Sign(reqToken.OauthTokenSecret)
|
||||
|
||||
res, err := client.HTTPClient.Get(client.GetUrl())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessTok, err := ParseOAuthToken(string(body))
|
||||
|
||||
// set client params for convenience
|
||||
client.OAuthToken = accessTok.OAuthToken
|
||||
client.OAuthTokenSecret = accessTok.OAuthTokenSecret
|
||||
client.Id = accessTok.UserNsid
|
||||
|
||||
return accessTok, err
|
||||
}
|
||||
161
vendor/gopkg.in/masci/flickr.v3/client.go
generated
vendored
Normal file
161
vendor/gopkg.in/masci/flickr.v3/client.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generate a random string of 8 chars, needed for OAuth signature
|
||||
func generateNonce() string {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
// For convenience, use a set of chars we don't need to url-escape
|
||||
var letters = []rune("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
|
||||
b := make([]rune, 8)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// An utility type to wrap all resources and data needed to complete requests
|
||||
// to the Flickr API
|
||||
type FlickrClient struct {
|
||||
// Flickr application api key
|
||||
ApiKey string
|
||||
// Flickr application api secret
|
||||
ApiSecret string
|
||||
// A generic HTTP client to perform GET and POST requests
|
||||
HTTPClient *http.Client
|
||||
// The base url for API endpoints
|
||||
EndpointUrl string
|
||||
// A string containing POST or GET, needed for OAuth signing
|
||||
HTTPVerb string
|
||||
// A set of url params to query the API
|
||||
Args url.Values
|
||||
// User access token
|
||||
OAuthToken string
|
||||
// User secret token
|
||||
OAuthTokenSecret string
|
||||
// User flickr ID
|
||||
Id string
|
||||
}
|
||||
|
||||
// Create a Flickr client, apiKey and apiSecret are mandatory
|
||||
func NewFlickrClient(apiKey string, apiSecret string) *FlickrClient {
|
||||
return &FlickrClient{
|
||||
ApiKey: apiKey,
|
||||
ApiSecret: apiSecret,
|
||||
HTTPClient: &http.Client{},
|
||||
HTTPVerb: "GET",
|
||||
Args: url.Values{},
|
||||
}
|
||||
}
|
||||
|
||||
// Sign the next request performed by the FlickrClient
|
||||
func (c *FlickrClient) Sign(tokenSecret string) {
|
||||
// the "oauth_signature" param must not be included in the signing process
|
||||
c.Args.Del("oauth_signature")
|
||||
c.Args.Set("oauth_signature", c.getSignature(tokenSecret))
|
||||
}
|
||||
|
||||
// Set the mandatory params for an OAuth request
|
||||
func (c *FlickrClient) SetOAuthDefaults() {
|
||||
c.Args.Add("oauth_version", "1.0")
|
||||
c.Args.Add("oauth_signature_method", "HMAC-SHA1")
|
||||
c.Args.Add("oauth_nonce", generateNonce())
|
||||
c.Args.Add("oauth_timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
||||
}
|
||||
|
||||
// Sign the request with a default set of OAuth parameters, needed to authorize
|
||||
// users for certain writing/destructive operations.
|
||||
func (c *FlickrClient) OAuthSign() {
|
||||
c.SetOAuthDefaults()
|
||||
c.Args.Set("oauth_token", c.OAuthToken)
|
||||
c.Args.Set("oauth_consumer_key", c.ApiKey)
|
||||
c.Args.Set("api_key", c.ApiKey)
|
||||
|
||||
c.Sign(c.OAuthTokenSecret)
|
||||
}
|
||||
|
||||
// Specific signing process for API calls: not the same as OAuth sign, used
|
||||
// for requests that don't need user authorizations.
|
||||
func (c *FlickrClient) ApiSign() {
|
||||
c.Args.Set("api_key", c.ApiKey)
|
||||
// the "api_sig" param must not be included in the signing process
|
||||
c.Args.Del("api_sig")
|
||||
c.Args.Set("api_sig", c.getApiSignature(c.ApiSecret))
|
||||
}
|
||||
|
||||
// Evaluate the complete URL to make requests (base url + params)
|
||||
func (c *FlickrClient) GetUrl() string {
|
||||
return fmt.Sprintf("%s?%s", c.EndpointUrl, c.Args.Encode())
|
||||
}
|
||||
|
||||
// Remove all query params
|
||||
func (c *FlickrClient) ClearArgs() {
|
||||
c.Args = url.Values{}
|
||||
}
|
||||
|
||||
// Reset Args and set the default endpoint
|
||||
func (c *FlickrClient) Init() {
|
||||
c.ClearArgs()
|
||||
c.EndpointUrl = API_ENDPOINT
|
||||
}
|
||||
|
||||
// Get the base string to compose the signature
|
||||
func (c *FlickrClient) getSigningBaseString() string {
|
||||
request_url := url.QueryEscape(c.EndpointUrl)
|
||||
flickr_encoded := strings.Replace(c.Args.Encode(), "+", "%20", -1)
|
||||
query := url.QueryEscape(flickr_encoded)
|
||||
|
||||
ret := fmt.Sprintf("%s&%s&%s", c.HTTPVerb, request_url, query)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Compute the signature of a signed request
|
||||
func (c *FlickrClient) getSignature(token_secret string) string {
|
||||
key := fmt.Sprintf("%s&%s", url.QueryEscape(c.ApiSecret), url.QueryEscape(token_secret))
|
||||
base_string := c.getSigningBaseString()
|
||||
|
||||
mac := hmac.New(sha1.New, []byte(key))
|
||||
mac.Write([]byte(base_string))
|
||||
|
||||
ret := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Sign API requests. This method differs from the signing process needed for
|
||||
// OAuth authenticated requests.
|
||||
func (c *FlickrClient) getApiSignature(token_secret string) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(token_secret)
|
||||
|
||||
keys := make([]string, 0, len(c.Args))
|
||||
for k := range c.Args {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
// args needs to be in alphabetical order
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
arg := c.Args[k][0]
|
||||
buf.WriteString(k)
|
||||
buf.WriteString(arg)
|
||||
}
|
||||
|
||||
base := buf.String()
|
||||
|
||||
data := []byte(base)
|
||||
return fmt.Sprintf("%x", md5.Sum(data))
|
||||
}
|
||||
35
vendor/gopkg.in/masci/flickr.v3/error/error.go
generated
vendored
Normal file
35
vendor/gopkg.in/masci/flickr.v3/error/error.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Flickr.go error system
|
||||
package error
|
||||
|
||||
// here we define ONLY errors from the library NOT from flickr
|
||||
// error from flickr have already a code and a message that are returned
|
||||
// along with the HTTP Response
|
||||
|
||||
const (
|
||||
ApiError = 10
|
||||
RequestTokenError = 20
|
||||
OAuthTokenError = 30
|
||||
)
|
||||
|
||||
var errors = map[int]string{
|
||||
ApiError: "Flickr API returned an error: ",
|
||||
RequestTokenError: "An error occurred during token request: ",
|
||||
OAuthTokenError: "An error occurred while getting the OAuth token: ",
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
ErrorCode int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Implement error interface
|
||||
func (e Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func NewError(errorCode int, message string) *Error {
|
||||
return &Error{
|
||||
ErrorCode: errorCode,
|
||||
Message: errors[errorCode] + message,
|
||||
}
|
||||
}
|
||||
60
vendor/gopkg.in/masci/flickr.v3/flickr.go
generated
vendored
Normal file
60
vendor/gopkg.in/masci/flickr.v3/flickr.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Flickr.go is a Go library for accessing Flickr API https://www.flickr.com/services/api
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
const (
|
||||
API_ENDPOINT = "https://api.flickr.com/services/rest"
|
||||
UPLOAD_ENDPOINT = "https://up.flickr.com/services/upload/"
|
||||
AUTHORIZE_URL = "https://www.flickr.com/services/oauth/authorize"
|
||||
REQUEST_TOKEN_URL = "https://www.flickr.com/services/oauth/request_token"
|
||||
ACCESS_TOKEN_URL = "https://www.flickr.com/services/oauth/access_token"
|
||||
)
|
||||
|
||||
// Perform a GET request to the Flickr API with the configured FlickrClient passed as first
|
||||
// parameter. Results will be unmarshalled to fill in a FlickrResponse struct passed as
|
||||
// second parameter.
|
||||
func DoGet(client *FlickrClient, r FlickrResponse) error {
|
||||
res, err := client.HTTPClient.Get(client.GetUrl())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parseApiResponse(res, r)
|
||||
}
|
||||
|
||||
// Perform a POST request to the Flickr API with the configured FlickrClient, the
|
||||
// request body and the body content type. Results will be unmarshalled in a FlickrResponse
|
||||
// struct.
|
||||
func DoPostBody(client *FlickrClient, body *bytes.Buffer, bodyType string, r FlickrResponse) error {
|
||||
res, err := client.HTTPClient.Post(client.EndpointUrl, bodyType, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parseApiResponse(res, r)
|
||||
}
|
||||
|
||||
// Perform a POST request to the Flickr API with the configured FlickrClient,
|
||||
// dumping client Args into the request Body.
|
||||
func DoPost(client *FlickrClient, r FlickrResponse) error {
|
||||
// instance an empty request body
|
||||
body := &bytes.Buffer{}
|
||||
// multipart writer to fill the body
|
||||
writer := multipart.NewWriter(body)
|
||||
// dump params
|
||||
for key, val := range client.Args {
|
||||
_ = writer.WriteField(key, val[0])
|
||||
}
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// evaluate the content type and the boundary
|
||||
contentType := writer.FormDataContentType()
|
||||
|
||||
return DoPostBody(client, body, contentType, r)
|
||||
}
|
||||
93
vendor/gopkg.in/masci/flickr.v3/response.go
generated
vendored
Normal file
93
vendor/gopkg.in/masci/flickr.v3/response.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
flickErr "gopkg.in/masci/flickr.v3/error"
|
||||
)
|
||||
|
||||
// Interface for Flickr request objects
|
||||
type FlickrResponse interface {
|
||||
HasErrors() bool
|
||||
ErrorCode() int
|
||||
ErrorMsg() string
|
||||
SetErrorStatus(bool)
|
||||
SetErrorCode(int)
|
||||
SetErrorMsg(string)
|
||||
}
|
||||
|
||||
// Base type representing responses from Flickr API
|
||||
type BasicResponse struct {
|
||||
XMLName xml.Name `xml:"rsp"`
|
||||
// Status might contain "fail" or "ok" strings
|
||||
Status string `xml:"stat,attr"`
|
||||
// Flickr API error detail
|
||||
Error struct {
|
||||
Code int `xml:"code,attr"`
|
||||
Message string `xml:"msg,attr"`
|
||||
} `xml:"err"`
|
||||
Extra string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// Return whether a response contains errors
|
||||
func (r *BasicResponse) HasErrors() bool {
|
||||
return r.Status != "ok"
|
||||
}
|
||||
|
||||
// Return the error code (0 if no errors)
|
||||
func (r *BasicResponse) ErrorCode() int {
|
||||
return r.Error.Code
|
||||
}
|
||||
|
||||
// Return error message string (empty string if no errors)
|
||||
func (r *BasicResponse) ErrorMsg() string {
|
||||
return r.Error.Message
|
||||
}
|
||||
|
||||
// Set error status explicitly
|
||||
func (r *BasicResponse) SetErrorStatus(hasErrors bool) {
|
||||
if hasErrors {
|
||||
r.Status = "fail"
|
||||
} else {
|
||||
r.Status = "ok"
|
||||
}
|
||||
}
|
||||
|
||||
// Set error code explicitly
|
||||
func (r *BasicResponse) SetErrorCode(code int) {
|
||||
r.Error.Code = code
|
||||
}
|
||||
|
||||
// Set error message explicitly
|
||||
func (r *BasicResponse) SetErrorMsg(msg string) {
|
||||
r.Error.Message = msg
|
||||
}
|
||||
|
||||
// Given an http.Response retrieved from Flickr, unmarshal results
|
||||
// into a FlickrResponse struct.
|
||||
func parseApiResponse(res *http.Response, r FlickrResponse) error {
|
||||
defer res.Body.Close()
|
||||
responseBody, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(responseBody, r)
|
||||
if err != nil {
|
||||
// In case of OAuth errors (signature, parameters, etc) Flicker does not
|
||||
// return a REST response but raw text (!), so the unmarshalling could fail.
|
||||
// We need to artificially build a FlickrResponse and manually fill in
|
||||
// the error string.
|
||||
r.SetErrorStatus(true)
|
||||
r.SetErrorCode(-1)
|
||||
r.SetErrorMsg(string(responseBody))
|
||||
}
|
||||
|
||||
if r.HasErrors() {
|
||||
return flickErr.NewError(flickErr.ApiError, r.ErrorMsg())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
vendor/gopkg.in/masci/flickr.v3/runtests.sh
generated
vendored
Normal file
25
vendor/gopkg.in/masci/flickr.v3/runtests.sh
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Code based on https://github.com/hailiang/gosweep
|
||||
|
||||
set -e
|
||||
|
||||
# Run test coverage on each subdirectories and merge the coverage profile.
|
||||
echo "mode: count" > profile.cov
|
||||
|
||||
# Standard go tooling behavior is to ignore dirs with leading underscors
|
||||
for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -not -path './examples*' -type d);
|
||||
do
|
||||
if ls $dir/*.go &> /dev/null; then
|
||||
go test -short -covermode=count -coverprofile=$dir/profile.tmp $dir
|
||||
if [ -f $dir/profile.tmp ]
|
||||
then
|
||||
cat $dir/profile.tmp | tail -n +2 >> profile.cov
|
||||
rm $dir/profile.tmp
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
go tool cover -func profile.cov
|
||||
|
||||
# Submit test coverage to coveralls.io
|
||||
goveralls -coverprofile=profile.cov -service=github
|
||||
67
vendor/gopkg.in/masci/flickr.v3/test/test.go
generated
vendored
Normal file
67
vendor/gopkg.in/masci/flickr.v3/test/test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Package implementing methods: flickr.test.*
|
||||
package test
|
||||
|
||||
import (
|
||||
"gopkg.in/masci/flickr.v3"
|
||||
)
|
||||
|
||||
// Response type used by Login function
|
||||
type LoginResponse struct {
|
||||
flickr.BasicResponse
|
||||
// the user who provided authentication infos
|
||||
User struct {
|
||||
// Flickr ID
|
||||
ID string `xml:"id,attr"`
|
||||
// Flickr Username
|
||||
Username string `xml:"username"`
|
||||
} `xml:"user"`
|
||||
}
|
||||
|
||||
// Response type used by Echo function
|
||||
type EchoResponse struct {
|
||||
flickr.BasicResponse
|
||||
// API method name, dotted notation
|
||||
Method string `xml:"method"`
|
||||
// API Key provided
|
||||
ApiKey string `xml:"api_key"`
|
||||
// API data exchange format (ex. rest)
|
||||
Format string `xml:"format"`
|
||||
}
|
||||
|
||||
// A testing method which checks if the caller is logged in then returns their username.
|
||||
// This method requires authentication with 'read' permission.
|
||||
func Login(client *flickr.FlickrClient) (*LoginResponse, error) {
|
||||
client.Init()
|
||||
client.SetOAuthDefaults()
|
||||
client.Args.Set("method", "flickr.test.login")
|
||||
client.OAuthSign()
|
||||
|
||||
loginResponse := &LoginResponse{}
|
||||
err := flickr.DoGet(client, loginResponse)
|
||||
return loginResponse, err
|
||||
}
|
||||
|
||||
// Noop method
|
||||
// This method requires authentication with 'read' permission.
|
||||
func Null(client *flickr.FlickrClient) (*flickr.BasicResponse, error) {
|
||||
client.Init()
|
||||
client.SetOAuthDefaults()
|
||||
client.Args.Set("method", "flickr.test.null")
|
||||
client.OAuthSign()
|
||||
|
||||
response := &flickr.BasicResponse{}
|
||||
err := flickr.DoGet(client, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// A testing method which echo's all parameters back in the response.
|
||||
// This method does not require authentication.
|
||||
func Echo(client *flickr.FlickrClient) (*EchoResponse, error) {
|
||||
client.EndpointUrl = flickr.API_ENDPOINT
|
||||
client.Args.Set("method", "flickr.test.echo")
|
||||
client.Args.Set("oauth_consumer_key", client.ApiKey)
|
||||
|
||||
response := &EchoResponse{}
|
||||
err := flickr.DoGet(client, response)
|
||||
return response, err
|
||||
}
|
||||
109
vendor/gopkg.in/masci/flickr.v3/testutils.go
generated
vendored
Normal file
109
vendor/gopkg.in/masci/flickr.v3/testutils.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Expect(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
||||
// testing keys were published at http://www.wackylabs.net/2011/12/oauth-and-flickr-part-2/
|
||||
func GetTestClient() *FlickrClient {
|
||||
args := url.Values{}
|
||||
args.Set("oauth_nonce", "C2F26CD5C075BA9050AD8EE90644CF29")
|
||||
args.Set("oauth_timestamp", "1316657628")
|
||||
args.Set("oauth_consumer_key", "768fe946d252b119746fda82e1599980")
|
||||
args.Set("oauth_signature_method", "HMAC-SHA1")
|
||||
args.Set("oauth_version", "1.0")
|
||||
args.Set("oauth_callback", "http://www.wackylabs.net/oauth/test")
|
||||
|
||||
return &FlickrClient{
|
||||
EndpointUrl: "http://www.flickr.com/services/oauth/request_token",
|
||||
HTTPClient: &http.Client{},
|
||||
HTTPVerb: "GET",
|
||||
Args: args,
|
||||
ApiSecret: "1a3c208e172d3edc",
|
||||
}
|
||||
}
|
||||
|
||||
// mock the Flickr API
|
||||
type RewriteTransport struct {
|
||||
Transport http.RoundTripper
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
func (t RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.URL.Scheme = t.URL.Scheme
|
||||
req.URL.Host = t.URL.Host
|
||||
req.URL.Path = path.Join(t.URL.Path, req.URL.Path)
|
||||
rt := t.Transport
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
func FlickrMock(code int, body string, contentType string) (*httptest.Server, *http.Client) {
|
||||
if contentType == "" {
|
||||
contentType = "text/plain;charset=UTF-8"
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(code)
|
||||
w.Header().Set("content-type", contentType)
|
||||
fmt.Fprintln(w, body)
|
||||
}))
|
||||
|
||||
u, _ := url.Parse(server.URL)
|
||||
|
||||
return server, &http.Client{Transport: RewriteTransport{URL: u}}
|
||||
}
|
||||
|
||||
// A ReaderCloser to fake http.Response Body field
|
||||
type FakeBody struct {
|
||||
content *bytes.Buffer
|
||||
}
|
||||
|
||||
func (f *FakeBody) Read(p []byte) (n int, err error) {
|
||||
return f.content.Read(p)
|
||||
}
|
||||
|
||||
func (f FakeBody) Close() error {
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFakeBody(s string) *FakeBody {
|
||||
return &FakeBody{content: bytes.NewBufferString(s)}
|
||||
}
|
||||
|
||||
// TODO
|
||||
func AssertParamsInBody(t *testing.T, client *FlickrClient, params []string) {
|
||||
var handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
bodystr := string(body)
|
||||
fmt.Fprintln(w, "Hello, client")
|
||||
for _, p := range params {
|
||||
needle := fmt.Sprintf(`Content-Disposition: form-data; name="%s"`, p)
|
||||
Expect(t, strings.Contains(bodystr, needle), true)
|
||||
}
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer ts.Close()
|
||||
|
||||
client.EndpointUrl = ts.URL
|
||||
DoPost(client, &BasicResponse{})
|
||||
}
|
||||
195
vendor/gopkg.in/masci/flickr.v3/upload.go
generated
vendored
Normal file
195
vendor/gopkg.in/masci/flickr.v3/upload.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
package flickr
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// generate a random multipart boundary string,
|
||||
// shamelessly copypasted from the std library
|
||||
func randomBoundary() string {
|
||||
var buf [30]byte
|
||||
_, err := io.ReadFull(rand.Reader, buf[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf("%x", buf[:])
|
||||
}
|
||||
|
||||
// Encode the file and request parameters in a multipart body.
|
||||
// File contents are streamed into the request using an io.Pipe in a separated goroutine
|
||||
func streamUploadBody(client *FlickrClient, photo io.Reader, body *io.PipeWriter, fileName string, boundary string) {
|
||||
// multipart writer to fill the body
|
||||
defer body.Close()
|
||||
writer := multipart.NewWriter(body)
|
||||
writer.SetBoundary(boundary)
|
||||
|
||||
// create the "photo" field
|
||||
part, err := writer.CreateFormFile("photo", filepath.Base(fileName))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// fill the photo field
|
||||
_, err = io.Copy(part, photo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
// dump other params
|
||||
for key, val := range client.Args {
|
||||
_ = writer.WriteField(key, val[0])
|
||||
}
|
||||
|
||||
// close the form writer
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UploadParams is a convenience struct wrapping all optional upload parameters
|
||||
type UploadParams struct {
|
||||
Title, Description string
|
||||
Tags []string
|
||||
IsPublic, IsFamily, IsFriend bool
|
||||
ContentType int
|
||||
Hidden int
|
||||
SafetyLevel int
|
||||
}
|
||||
|
||||
// NewUploadParams provides meaningful default values
|
||||
func NewUploadParams() *UploadParams {
|
||||
ret := &UploadParams{}
|
||||
ret.ContentType = 1 // photo
|
||||
ret.Hidden = 2 // hidden from public searchesi
|
||||
ret.SafetyLevel = 1 // safe
|
||||
return ret
|
||||
}
|
||||
|
||||
// UploadResponse is a type representing a successful upload response from the api
|
||||
type UploadResponse struct {
|
||||
BasicResponse
|
||||
ID string `xml:"photoid"`
|
||||
}
|
||||
|
||||
// Set client query arguments based on the contents of the UploadParams struct
|
||||
func fillArgsWithParams(client *FlickrClient, params *UploadParams) {
|
||||
if params.Title != "" {
|
||||
client.Args.Set("title", params.Title)
|
||||
}
|
||||
|
||||
if params.Description != "" {
|
||||
client.Args.Set("description", params.Description)
|
||||
}
|
||||
|
||||
if len(params.Tags) > 0 {
|
||||
client.Args.Set("tags", strings.Join(params.Tags, " "))
|
||||
}
|
||||
|
||||
var boolString = func(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
client.Args.Set("is_public", boolString(params.IsPublic))
|
||||
client.Args.Set("is_friend", boolString(params.IsFriend))
|
||||
client.Args.Set("is_family", boolString(params.IsFamily))
|
||||
|
||||
if params.ContentType >= 1 && params.ContentType <= 3 {
|
||||
client.Args.Set("content_type", strconv.Itoa(params.ContentType))
|
||||
}
|
||||
|
||||
if params.Hidden >= 1 && params.Hidden <= 2 {
|
||||
client.Args.Set("hidden", strconv.Itoa(params.Hidden))
|
||||
}
|
||||
|
||||
if params.SafetyLevel >= 1 && params.SafetyLevel <= 3 {
|
||||
client.Args.Set("safety_level", strconv.Itoa(params.SafetyLevel))
|
||||
}
|
||||
}
|
||||
|
||||
// UploadFile performs a file upload using the Flickr API. If optionalParams is nil,
|
||||
// no parameters will be added to the request and Flickr will set User's
|
||||
// default preferences.
|
||||
// This call must be signed with write permissions
|
||||
func UploadFile(client *FlickrClient, path string, optionalParams *UploadParams) (*UploadResponse, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return UploadReader(client, file, file.Name(), optionalParams)
|
||||
}
|
||||
|
||||
// UploadReader does same as UploadFile but the photo file is passed as an io.Reader instead of a file path
|
||||
func UploadReader(client *FlickrClient, photoReader io.Reader, name string, optionalParams *UploadParams) (*UploadResponse, error) {
|
||||
return UploadReaderWithClient(client, photoReader, name, optionalParams, nil)
|
||||
}
|
||||
|
||||
// UploadReaderWithClient does same as UploadReader but allows passing a custom httpClient
|
||||
func UploadReaderWithClient(client *FlickrClient, photoReader io.Reader, name string, optionalParams *UploadParams, httpClient *http.Client) (*UploadResponse, error) {
|
||||
client.Init()
|
||||
client.EndpointUrl = UPLOAD_ENDPOINT
|
||||
client.HTTPVerb = "POST"
|
||||
|
||||
if optionalParams != nil {
|
||||
fillArgsWithParams(client, optionalParams)
|
||||
}
|
||||
|
||||
client.OAuthSign()
|
||||
|
||||
// write request body in a Pipe
|
||||
boundary := randomBoundary()
|
||||
r, w := io.Pipe()
|
||||
go streamUploadBody(client, photoReader, w, name, boundary)
|
||||
|
||||
// create an HTTP Request
|
||||
req, err := http.NewRequest("POST", client.EndpointUrl, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set content-type
|
||||
req.Header.Set("content-type", "multipart/form-data; boundary="+boundary)
|
||||
req.ContentLength = -1 // unknown
|
||||
|
||||
if (httpClient == nil) {
|
||||
// Create a Transport to explicitly use the http1.1 client
|
||||
// TODO: for some reason, when we use the http2 client flickr API responds
|
||||
// with HTTP: 411 (No Content Length : POST) whereas it should be ok to
|
||||
// upload using chunks. Explicitly setting `req.Header.Set("transfer-encoding", "chunked")`
|
||||
// does not help and try to compute the request size isn't the right thing to do IMHO.
|
||||
// We should investigate why this happens instead of forcing the downgrade to http1.1.
|
||||
tr := &http.Transport{
|
||||
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
|
||||
}
|
||||
|
||||
// instance an HTTP client
|
||||
httpClient = &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
// perform upload request streaming the file
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiResp := &UploadResponse{}
|
||||
err = parseApiResponse(resp, apiResp)
|
||||
return apiResp, err
|
||||
}
|
||||
Reference in New Issue
Block a user