Initial commit

This commit is contained in:
eleksir
2024-09-17 23:58:51 +03:00
commit a9e0095dc9
1569 changed files with 656940 additions and 0 deletions

30
vendor/gopkg.in/masci/flickr.v3/.gitignore generated vendored Normal file
View 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
View 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
View 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.
[![GoDoc](https://godoc.org/gopkg.in/masci/flickr.v3?status.svg)](https://godoc.org/gopkg.in/masci/flickr.v3)
[![build-test](https://github.com/masci/flickr/actions/workflows/test.yml/badge.svg)](https://github.com/masci/flickr/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/masci/flickr/badge.svg)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}