├── .circleci └── config.yml ├── LICENSE.txt ├── README.md ├── client.go ├── config.go ├── constants.go ├── credentials.go ├── godel └── config │ ├── check.yml │ ├── dist.yml │ ├── exclude.yml │ ├── format.yml │ ├── generate.yml │ ├── godel.properties │ ├── imports.yml │ ├── license.yml │ └── test.yml ├── godelw ├── login.go ├── session.go └── user.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.9 6 | 7 | working_directory: /go/src/github.com/joshdk/posh 8 | steps: 9 | - checkout 10 | - run: ./godelw version 11 | - run: ./godelw verify --apply=false 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright Ⓒ 2017 Josh Komoroske 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/joshdk/posh?status.svg)](https://godoc.org/github.com/joshdk/posh) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/joshdk/posh)](https://goreportcard.com/report/github.com/joshdk/posh) 3 | [![CircleCI](https://circleci.com/gh/joshdk/posh.svg?&style=shield)](https://circleci.com/gh/joshdk/posh/tree/master) 4 | 5 | # Posh 6 | 7 | Poshmark API client for the Go programming language 8 | 9 | ## Installing 10 | 11 | You can fetch this library by running the following 12 | 13 | go get -u github.com/joshdk/posh 14 | 15 | ## Example 16 | 17 | You can construct a simple client with the following 18 | 19 | ```go 20 | creds := posh.Credentials{ 21 | Email: "me@example.com", 22 | Password: "Pa$sw0rd", 23 | } 24 | 25 | config := posh.Config{ 26 | Credentials: &creds, 27 | } 28 | 29 | client, err := posh.NewClient(config) 30 | if err != nil { 31 | panic(err.Error()) 32 | } 33 | 34 | fmt.Println(client.Session()) 35 | ``` 36 | 37 | ## License 38 | 39 | This library is distributed under the [MIT License](https://opensource.org/licenses/MIT), see LICENSE.txt for more information. -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type Client struct { 12 | baseURL string 13 | session Session 14 | } 15 | 16 | func NewClient(config Config) (*Client, error) { 17 | client := Client{ 18 | baseURL: BaseURL, 19 | } 20 | 21 | if config.BaseURL != "" { 22 | client.baseURL = config.BaseURL 23 | } 24 | 25 | if config.Credentials != nil { 26 | request := LoginRequest{ 27 | UserHandle: config.Credentials.Email, 28 | Password: config.Credentials.Password, 29 | } 30 | 31 | response, err := client.Login(&request) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | client.session = Session{ 37 | Token: response.AccessToken, 38 | UserID: response.User.ID, 39 | } 40 | 41 | return &client, nil 42 | } 43 | 44 | return nil, fmt.Errorf("incomplete client config") 45 | } 46 | 47 | func (client *Client) Session() Session { 48 | return client.session 49 | } 50 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | type Config struct { 8 | BaseURL string 9 | Credentials *Credentials 10 | } 11 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | const ( 8 | EmailEnvironmentVariable = "POSHMARK_EMAIL" 9 | PasswordEnvironmentVariable = "POSHMARK_PASSWORD" 10 | UserIDEnvironmentVariable = "POSHMARK_USERID" 11 | TokenEnvironmentVariable = "POSHMARK_TOKEN" 12 | BaseURL = "https://api.poshmark.com/api" 13 | ) 14 | -------------------------------------------------------------------------------- /credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | type Credentials struct { 13 | Email string `json:"email"` 14 | Password string `json:"password"` 15 | } 16 | 17 | func NewCredentials(email string, password string) *Credentials { 18 | return &Credentials{ 19 | email, 20 | password, 21 | } 22 | } 23 | 24 | func NewEnvCredentials() (*Credentials, error) { 25 | 26 | email, found := os.LookupEnv(EmailEnvironmentVariable) 27 | if !found { 28 | return nil, fmt.Errorf("no %s found in environment", EmailEnvironmentVariable) 29 | } 30 | 31 | password, found := os.LookupEnv(PasswordEnvironmentVariable) 32 | if !found { 33 | return nil, fmt.Errorf("no %s found in environment", PasswordEnvironmentVariable) 34 | } 35 | 36 | return &Credentials{ 37 | email, 38 | password, 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /godel/config/check.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | golint: 3 | filters: 4 | - value: "should have comment or be unexported" 5 | - value: "or a comment on this block" 6 | -------------------------------------------------------------------------------- /godel/config/dist.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshdk/posh/8754bd90ae0a25e9697ab9999ed0c1b3ecdf8ac0/godel/config/dist.yml -------------------------------------------------------------------------------- /godel/config/exclude.yml: -------------------------------------------------------------------------------- 1 | names: 2 | - "\\..+" 3 | - "testdata" 4 | - "vendor" 5 | paths: 6 | - "godel" 7 | -------------------------------------------------------------------------------- /godel/config/format.yml: -------------------------------------------------------------------------------- 1 | formatters: 2 | gofmt: 3 | args: 4 | - "-s" 5 | -------------------------------------------------------------------------------- /godel/config/generate.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshdk/posh/8754bd90ae0a25e9697ab9999ed0c1b3ecdf8ac0/godel/config/generate.yml -------------------------------------------------------------------------------- /godel/config/godel.properties: -------------------------------------------------------------------------------- 1 | distributionURL=https://palantir.bintray.com/releases/com/palantir/godel/godel/0.27.0/godel-0.27.0.tgz 2 | distributionSHA256=0869fc0fb10b4cdd179185c0e59e28a3568c447a2a7ab3d379d6037900a96bf3 3 | -------------------------------------------------------------------------------- /godel/config/imports.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshdk/posh/8754bd90ae0a25e9697ab9999ed0c1b3ecdf8ac0/godel/config/imports.yml -------------------------------------------------------------------------------- /godel/config/license.yml: -------------------------------------------------------------------------------- 1 | header: | 2 | // Copyright 2017 Josh Komoroske. All rights reserved. 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE.txt file. 5 | -------------------------------------------------------------------------------- /godel/config/test.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshdk/posh/8754bd90ae0a25e9697ab9999ed0c1b3ecdf8ac0/godel/config/test.yml -------------------------------------------------------------------------------- /godelw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Version and checksums for godel. Values are populated by the godel "dist" task. 6 | VERSION=0.27.0 7 | DARWIN_CHECKSUM=8ad721795b43acca14174773b8aeb3cae2591b03f763f95ab6245db3276e7724 8 | LINUX_CHECKSUM=f57e428d87505727a33e56a6240e02d9bb9b7ed97bb74495885482c3a28c21ff 9 | 10 | # Downloads file at URL to destination path using wget or curl. Prints an error and exits if wget or curl is not present. 11 | function download { 12 | local url=$1 13 | local dst=$2 14 | 15 | # determine whether wget, curl or both are present 16 | set +e 17 | command -v wget >/dev/null 2>&1 18 | local wget_exists=$? 19 | command -v curl >/dev/null 2>&1 20 | local curl_exists=$? 21 | set -e 22 | 23 | # if one of wget or curl is not present, exit with error 24 | if [ "$wget_exists" -ne 0 -a "$curl_exists" -ne 0 ]; then 25 | echo "wget or curl must be present to download distribution. Install one of these programs and try again or install the distribution manually." 26 | exit 1 27 | fi 28 | 29 | if [ "$wget_exists" -eq 0 ]; then 30 | # attempt download using wget 31 | echo "Downloading $url to $dst..." 32 | local progress_opt="" 33 | if wget --help | grep -q '\--show-progress'; then 34 | progress_opt="-q --show-progress" 35 | fi 36 | set +e 37 | wget -O "$dst" $progress_opt "$url" 38 | rv=$? 39 | set -e 40 | if [ "$rv" -eq 0 ]; then 41 | # success 42 | return 43 | fi 44 | 45 | echo "Download failed using command: wget -O $dst $progress_opt $url" 46 | 47 | # curl does not exist, so nothing more to try: exit 48 | if [ "$curl_exists" -ne 0 ]; then 49 | echo "Download failed using wget and curl was not found. Verify that the distribution URL is correct and try again or install the distribution manually." 50 | exit 1 51 | fi 52 | # curl exists, notify that download will be attempted using curl 53 | echo "Attempting download using curl..." 54 | fi 55 | 56 | # attempt download using curl 57 | echo "Downloading $url to $dst..." 58 | set +e 59 | curl -f -L -o "$dst" "$url" 60 | rv=$? 61 | set -e 62 | if [ "$rv" -ne 0 ]; then 63 | echo "Download failed using command: curl -f -L -o $dst $url" 64 | if [ "$wget_exists" -eq 0 ]; then 65 | echo "Download failed using wget and curl. Verify that the distribution URL is correct and try again or install the distribution manually." 66 | else 67 | echo "Download failed using curl and wget was not found. Verify that the distribution URL is correct and try again or install the distribution manually." 68 | fi 69 | exit 1 70 | fi 71 | } 72 | 73 | # verifies that the provided checksum matches the computed SHA-256 checksum of the specified file. If not, echoes an 74 | # error and exits. 75 | function verify_checksum { 76 | local file=$1 77 | local expected_checksum=$2 78 | local computed_checksum=$(compute_sha256 $file) 79 | if [ "$expected_checksum" != "$computed_checksum" ]; then 80 | echo "SHA-256 checksum for $file did not match expected value." 81 | echo "Expected: $expected_checksum" 82 | echo "Actual: $computed_checksum" 83 | exit 1 84 | fi 85 | } 86 | 87 | # computes the SHA-256 hash of the provided file. Uses openssl, shasum or sha1sum program. 88 | function compute_sha256 { 89 | local file=$1 90 | if command -v openssl >/dev/null 2>&1; then 91 | # print SHA-256 hash using openssl 92 | openssl dgst -sha256 "$file" | sed -E 's/SHA256\(.*\)= //' 93 | elif command -v shasum >/dev/null 2>&1; then 94 | # Darwin systems ship with "shasum" utility 95 | shasum -a 256 "$file" | sed -E 's/[[:space:]]+.+//' 96 | elif command -v sha256sum >/dev/null 2>&1; then 97 | # Most Linux systems ship with sha256sum utility 98 | sha256sum "$file" | sed -E 's/[[:space:]]+.+//' 99 | else 100 | echo "Could not find program to calculate SHA-256 checksum for file" 101 | exit 1 102 | fi 103 | } 104 | 105 | # Verifies that the tgz file at the provided path contains the paths/files that would be expected in a valid gödel 106 | # distribution with the provided version. 107 | function verify_dist_tgz_valid { 108 | local tgz_path=$1 109 | local version=$2 110 | 111 | local expected_paths=("godel-$version/" "godel-$version/bin/darwin-amd64/godel" "godel-$version/bin/linux-amd64/godel" "godel-$version/wrapper/godelw" "godel-$version/wrapper/godel/config/") 112 | local files=($(tar -tf "$tgz_path")) 113 | 114 | # this is a double-for loop, but fine since $expected_paths is small and bash doesn't have good primitives for set/map/list manipulation 115 | for curr_line in "${files[@]}"; do 116 | # if all expected paths have been found, terminate 117 | if [[ ${#expected_paths[*]} == 0 ]]; then 118 | break 119 | fi 120 | 121 | # check for expected path and splice out if match is found 122 | idx=0 123 | for curr_expected in "${expected_paths[@]}"; do 124 | if [ "$curr_expected" = "$curr_line" ]; then 125 | expected_paths=(${expected_paths[@]:0:idx} ${expected_paths[@]:$(($idx + 1))}) 126 | break 127 | fi 128 | idx=$idx+1 129 | done 130 | done 131 | 132 | # if any expected paths still remain, raise error and exit 133 | if [[ ${#expected_paths[*]} > 0 ]]; then 134 | echo "Required paths were not present in $tgz_path: ${expected_paths[@]}" 135 | exit 1 136 | fi 137 | } 138 | 139 | # Verifies that the gödel binary in the distribution reports the expected version when called with the "version" 140 | # argument. Assumes that a valid gödel distribution directory for the given version exists in the provided directory. 141 | function verify_godel_version { 142 | local base_dir=$1 143 | local version=$2 144 | local os=$3 145 | 146 | local expected_output="godel version $version" 147 | local version_output=$($base_dir/godel-$version/bin/$os-amd64/godel --verify-path=false version) 148 | 149 | if [ "$expected_output" != "$version_output" ]; then 150 | echo "Version reported by godel executable did not match expected version: expected \"$expected_output\", was \"$version_output\"" 151 | exit 1 152 | fi 153 | } 154 | 155 | # directory of godelw script 156 | SCRIPT_HOME=$(cd "$(dirname "$0")" && pwd) 157 | 158 | # use $GODEL_HOME or default value 159 | GODEL_BASE_DIR=${GODEL_HOME:-$HOME/.godel} 160 | 161 | # determine OS 162 | OS="" 163 | EXPECTED_CHECKSUM="" 164 | case "$(uname)" in 165 | Darwin*) 166 | OS=darwin 167 | EXPECTED_CHECKSUM=$DARWIN_CHECKSUM 168 | ;; 169 | Linux*) 170 | OS=linux 171 | EXPECTED_CHECKSUM=$LINUX_CHECKSUM 172 | ;; 173 | *) 174 | echo "Unsupported operating system: $(uname)" 175 | exit 1 176 | ;; 177 | esac 178 | 179 | # path to godel binary 180 | CMD=$GODEL_BASE_DIR/dists/godel-$VERSION/bin/$OS-amd64/godel 181 | 182 | # godel binary is not present -- download distribution 183 | if [ ! -f "$CMD" ]; then 184 | # get download URL 185 | PROPERTIES_FILE=$SCRIPT_HOME/godel/config/godel.properties 186 | if [ ! -f "$PROPERTIES_FILE" ]; then 187 | echo "Properties file must exist at $PROPERTIES_FILE" 188 | exit 1 189 | fi 190 | DOWNLOAD_URL=$(cat "$PROPERTIES_FILE" | sed -E -n "s/^distributionURL=//p") 191 | if [ -z "$DOWNLOAD_URL" ]; then 192 | echo "Value for property \"distributionURL\" was empty in $PROPERTIES_FILE" 193 | exit 1 194 | fi 195 | DOWNLOAD_CHECKSUM=$(cat "$PROPERTIES_FILE" | sed -E -n "s/^distributionSHA256=//p") 196 | 197 | # create downloads directory if it does not already exist 198 | mkdir -p "$GODEL_BASE_DIR/downloads" 199 | 200 | # download tgz and verify its contents 201 | DOWNLOAD_DST=$GODEL_BASE_DIR/downloads/godel-$VERSION.tgz 202 | download "$DOWNLOAD_URL" "$DOWNLOAD_DST" 203 | if [ -n "$DOWNLOAD_CHECKSUM" ]; then 204 | verify_checksum "$DOWNLOAD_DST" "$DOWNLOAD_CHECKSUM" 205 | fi 206 | verify_dist_tgz_valid "$DOWNLOAD_DST" "$VERSION" 207 | 208 | # create temporary directory for unarchiving, unarchive downloaded file and verify directory 209 | TMP_DIST_DIR=$(mktemp -d "$GODEL_BASE_DIR/tmp_XXXXXX" 2>/dev/null || mktemp -d -t "$GODEL_BASE_DIR/tmp_XXXXXX") 210 | trap 'rm -rf "$TMP_DIST_DIR"' EXIT 211 | tar zxvf "$DOWNLOAD_DST" -C "$TMP_DIST_DIR" >/dev/null 2>&1 212 | verify_godel_version "$TMP_DIST_DIR" "$VERSION" "$OS" 213 | 214 | # if destination directory for distribution already exists, remove it 215 | if [ -d "$GODEL_BASE_DIR/dists/godel-$VERSION" ]; then 216 | rm -rf "$GODEL_BASE_DIR/dists/godel-$VERSION" 217 | fi 218 | 219 | # ensure that parent directory of destination exists 220 | mkdir -p "$GODEL_BASE_DIR/dists" 221 | 222 | # move expanded distribution directory to destination location. The location of the unarchived directory is known to 223 | # be in the same directory tree as the destination, so "mv" should always work. 224 | mv "$TMP_DIST_DIR/godel-$VERSION" "$GODEL_BASE_DIR/dists/godel-$VERSION" 225 | fi 226 | 227 | verify_checksum "$CMD" "$EXPECTED_CHECKSUM" 228 | 229 | # execute command 230 | $CMD --wrapper "$SCRIPT_HOME/$(basename "$0")" "$@" 231 | -------------------------------------------------------------------------------- /login.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | import ( 8 | "encoding/json" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | ) 13 | 14 | type LoginRequest struct { 15 | UserHandle string `json:"user_handle"` 16 | Password string `json:"password"` 17 | } 18 | 19 | type LoginResponse struct { 20 | AccessToken string `json:"access_token"` 21 | CreatedAt int `json:"created_at"` 22 | ExpiresAt string `json:"expires_at"` 23 | User User `json:"user"` 24 | } 25 | 26 | func (client *Client) Login(request *LoginRequest) (*LoginResponse, error) { 27 | 28 | resp, err := http.PostForm(client.baseURL+"/auth/users/access_token", 29 | url.Values{ 30 | "user_handle": {request.UserHandle}, 31 | "password": {request.Password}, 32 | }, 33 | ) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | body, err := ioutil.ReadAll(resp.Body) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | response := LoginResponse{} 44 | 45 | if err := json.Unmarshal(body, &response); err != nil { 46 | return nil, err 47 | } 48 | 49 | return &response, nil 50 | } 51 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | type Session struct { 13 | UserID string `json:"user-id"` 14 | Token string `json:"token"` 15 | } 16 | 17 | func NewSession(userID string, token string) *Session { 18 | return &Session{ 19 | userID, 20 | token, 21 | } 22 | } 23 | 24 | func NewEnvSession() (*Session, error) { 25 | 26 | userID, found := os.LookupEnv(UserIDEnvironmentVariable) 27 | if !found { 28 | return nil, fmt.Errorf("no %s found in environment", UserIDEnvironmentVariable) 29 | } 30 | 31 | token, found := os.LookupEnv(TokenEnvironmentVariable) 32 | if !found { 33 | return nil, fmt.Errorf("no %s found in environment", TokenEnvironmentVariable) 34 | } 35 | 36 | return &Session{ 37 | userID, 38 | token, 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /user.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Josh Komoroske. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE.txt file. 4 | 5 | package posh 6 | 7 | type User struct { 8 | ID string `json:"id"` 9 | Email string `json:"email"` 10 | Username string `json:"username"` 11 | DisplayHandle string `json:"display_handle"` 12 | FullName string `json:"full_name"` 13 | FirstName string `json:"first_name"` 14 | LastName string `json:"last_name"` 15 | Gender string `json:"gender"` 16 | } 17 | --------------------------------------------------------------------------------