kickscraper 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +74 -0
- data/CONTRIBUTORS.md +14 -0
- data/Gemfile +9 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +144 -0
- data/Rakefile +18 -0
- data/examples/my_projects.rb +18 -0
- data/kickscraper.gemspec +31 -0
- data/lib/kickscraper.rb +19 -0
- data/lib/kickscraper/api.rb +32 -0
- data/lib/kickscraper/client.rb +225 -0
- data/lib/kickscraper/client/category.rb +18 -0
- data/lib/kickscraper/client/comment.rb +5 -0
- data/lib/kickscraper/client/location.rb +0 -0
- data/lib/kickscraper/client/notifications.rb +0 -0
- data/lib/kickscraper/client/project.rb +71 -0
- data/lib/kickscraper/client/update.rb +5 -0
- data/lib/kickscraper/client/user.rb +34 -0
- data/lib/kickscraper/configure.rb +11 -0
- data/lib/kickscraper/connection.rb +63 -0
- data/lib/kickscraper/response.rb +15 -0
- data/lib/kickscraper/version.rb +3 -0
- data/spec/category_spec.rb +26 -0
- data/spec/client_spec.rb +84 -0
- data/spec/kickscraper_spec.rb +16 -0
- data/spec/no_email_password_client_spec.rb +73 -0
- data/spec/no_email_password_project_spec.rb +76 -0
- data/spec/project_spec.rb +57 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/shared_examples.rb +103 -0
- data/spec/test_constants.rb +18 -0
- data/spec/user_spec.rb +33 -0
- metadata +216 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 87aea1accb9c5d850a4e99e590e430eec8cce9c0
|
4
|
+
data.tar.gz: 72f2f4a49a7a6a102c0e850912e43e62549255a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e992d00b4be9af29e8b3ad4c836cafb30265d5a003370df363c3516e692061c4be2f93d5d8b06804019b57c7d2a6c38b19f32492a0cb04648c8a1be5005b9615
|
7
|
+
data.tar.gz: 4f50b09cd04a405e4b80e43867590e7c3add968b68bf8fca74f42106a6c02066554b2f7636dd14958f2c0fce253bde677c670b4df5895c78c006f837806545a5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
### 0.2.0
|
2
|
+
|
3
|
+
* Changing the minor version because we've rewritten several things to accommodate Kickstarter's change to public search
|
4
|
+
* This encapsulates the changes starting at v 0.1.2
|
5
|
+
|
6
|
+
### 0.1.4
|
7
|
+
|
8
|
+
* changes
|
9
|
+
* Using ssl (https) for all public www.kickstarter.com searches
|
10
|
+
|
11
|
+
### 0.1.3
|
12
|
+
|
13
|
+
* changes
|
14
|
+
* Using the public url for categories instead of the api url
|
15
|
+
* Fixing project comments and updates so that they are now pulled in when a project didn't already have them
|
16
|
+
* Adding more tests to cover the new scenarios for pulling public info from Kickstarter without API credentials
|
17
|
+
|
18
|
+
### 0.1.2
|
19
|
+
|
20
|
+
* changes
|
21
|
+
* Using the public search instead of the authenticated API for project searches
|
22
|
+
* Allowing configuration with no email/pass (which now works for some features)
|
23
|
+
* NOTE: These changes were made in response to a change in Kickstarter's API, and there are currently some broken features
|
24
|
+
|
25
|
+
### 0.1.1
|
26
|
+
|
27
|
+
* enhancements
|
28
|
+
* Adding a created_projects method to user
|
29
|
+
|
30
|
+
* bug fixes
|
31
|
+
* Fixing the backed_projects and starred_projects methods for user
|
32
|
+
* Fixes an error when searching for projects with spaces in the name
|
33
|
+
|
34
|
+
### 0.1.0
|
35
|
+
|
36
|
+
* enhancements
|
37
|
+
* adding support for Kickstarter categories!
|
38
|
+
* basic pagination when searching projects
|
39
|
+
|
40
|
+
* optimize
|
41
|
+
* huge refactor of tests to DRY them up
|
42
|
+
|
43
|
+
* deprecate
|
44
|
+
* client.newest_projects (replaced by client.recently_launched_projects)
|
45
|
+
* client.can_load_more_projects (replaced by client.more_projects_available?)
|
46
|
+
|
47
|
+
* bug fixes
|
48
|
+
* a little bit more error handling
|
49
|
+
|
50
|
+
|
51
|
+
### 0.0.3
|
52
|
+
|
53
|
+
* enhancements
|
54
|
+
* added tests!
|
55
|
+
* a little bit of basic error checking
|
56
|
+
|
57
|
+
* optimize
|
58
|
+
* DRYing up the API calls
|
59
|
+
|
60
|
+
* bug fixes
|
61
|
+
* updating the search for "newest" projects, which was renamed by Kickstarter
|
62
|
+
* handling special characters in searches
|
63
|
+
|
64
|
+
|
65
|
+
### 0.0.2
|
66
|
+
|
67
|
+
* enhancements
|
68
|
+
* Project searches (popular, recent, ending soon)
|
69
|
+
* User biography
|
70
|
+
|
71
|
+
|
72
|
+
### 0.0.1
|
73
|
+
|
74
|
+
* Basic working API (getting projects and users)
|
data/CONTRIBUTORS.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mark Olson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# Kickscraper
|
2
|
+
|
3
|
+
Kickscraper is a library for interfacing with Kickstarter's undocumented/unannounced API. With it, you can get a lot of data about projects, whether you've backed them or not, including reward levels, amount pledged, links to the videos, updates, if it's been successful or not, and details about the creator.
|
4
|
+
|
5
|
+
Much of the of data available is documented in the [wiki for this project](https://github.com/markolson/kickscraper/wiki/_pages).
|
6
|
+
|
7
|
+
|
8
|
+
## Status Update
|
9
|
+
|
10
|
+
This version now uses Kickstarter's public search for all project searches. It should be a drop-in replacement for the
|
11
|
+
previous version.
|
12
|
+
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
$ gem install kickscraper
|
17
|
+
|
18
|
+
Or for use in another app, add it to your Gemfile
|
19
|
+
|
20
|
+
# use the gem
|
21
|
+
gem 'kickscraper'
|
22
|
+
|
23
|
+
# or stay up to date with the repo (which should be stable)
|
24
|
+
gem 'kickscraper', :git => 'git://github.com/markolson/kickscraper.git'
|
25
|
+
|
26
|
+
|
27
|
+
## Quick way to get started in the console
|
28
|
+
|
29
|
+
1. Put your real Kickstarter user credentials in spec/test_constants.rb (or leave blank, which will still allow some kickscraper features)
|
30
|
+
2. Enter the console with "rake console"
|
31
|
+
3. Start using kickscraper with any of the examples below (starting with "c = Kickscraper.client")
|
32
|
+
|
33
|
+
|
34
|
+
## Sample Usage
|
35
|
+
|
36
|
+
Provided with your user credentials this will list the first 20 or so projects you've backed, along with if the project is still active and if it has met it's funding goal.
|
37
|
+
|
38
|
+
require 'kickscraper'
|
39
|
+
|
40
|
+
Kickscraper.configure do |config|
|
41
|
+
config.email = 'your-kickstarter-email-address@domain.com'
|
42
|
+
config.password = 'This is not my real password. Seriously.'
|
43
|
+
end
|
44
|
+
|
45
|
+
client = Kickscraper.client
|
46
|
+
puts " A | C |"
|
47
|
+
puts "------------------------"
|
48
|
+
client.user.backed_projects.each {|x|
|
49
|
+
print (x.active? ? ' X |' : ' |')
|
50
|
+
print (x.successful? ? ' X | ' : ' | ')
|
51
|
+
puts x.name
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
## API Examples
|
56
|
+
c = Kickscraper.client
|
57
|
+
|
58
|
+
c.user.class
|
59
|
+
=> Kickscraper::User
|
60
|
+
|
61
|
+
# List what data is available on an object
|
62
|
+
c.user.keys
|
63
|
+
=> ["id", "name", "slug", "avatar", "urls", "location",
|
64
|
+
"biography", "backed_projects_count", "created_projects_count",
|
65
|
+
"unread_messages_count", "unanswered_surveys_count",
|
66
|
+
"starred_projects_count", "social", "send_newsletters",
|
67
|
+
"category_wheel", "notify_of_backings", "notify_of_updates",
|
68
|
+
"notify_of_follower", "notify_of_friend_activity",
|
69
|
+
"notify_of_comments", "notify_mobile_of_backings",
|
70
|
+
"notify_mobile_of_updates", "notify_mobile_of_follower",
|
71
|
+
"notify_mobile_of_friend_activity", "notify_mobile_of_comments",
|
72
|
+
"updated_at", "created_at"]
|
73
|
+
|
74
|
+
|
75
|
+
# In addition, User types have "backed_projects" and "starred_projects" available.
|
76
|
+
c.user.backed_projects
|
77
|
+
=> [<Project: 'The Veronica Mars Movie Project'>, <Project: 'RiffTrax Wants to Riff TWILIGHT Live in Theaters Nationwide!'>, <Project: 'To Be Or Not To Be: That Is The Adventure'>, <Project: 'Planetary Annihilation - A Next Generation RTS'>, <Project: 'OUYA: A New Kind of Video Game Console'>, <Project: 'Gotham Knight Terrors: Comedic Batman Short'>, <Project: 'Internet Meme Playing Cards'>, <Project: 'GOOD JOB, BRAIN! - A Trivia & Quiz Show Podcast'>, <Project: 'Elevation Dock: The Best Dock For iPhone'>, <Project: 'Trebuchette - the snap-together, desktop trebuchet'>]
|
78
|
+
|
79
|
+
# Some values are already converted to their appropriate types. Some aren't yet.
|
80
|
+
c.user.backed_projects.first.creator.class
|
81
|
+
=> Kickscraper::User
|
82
|
+
|
83
|
+
# You can chain methods together and dig into the objects.
|
84
|
+
# So get the thumbnail of the person that created the latest project we backed
|
85
|
+
c.user.backed_projects.first.creator.avatar.thumb
|
86
|
+
|
87
|
+
# You can search for projects
|
88
|
+
c.search_projects('veronica mars')
|
89
|
+
=> [<Project: 'The Veronica Mars Movie Project'>, <Project: 'Prodigal Daughter - TV Show Pilot'>]
|
90
|
+
|
91
|
+
# and view their rewards or updates, in addition to any of the data found in the "keys"
|
92
|
+
vm = c.search_projects('veronica mars').first
|
93
|
+
vm.updates
|
94
|
+
=> [...]
|
95
|
+
vm.successful?
|
96
|
+
=> true
|
97
|
+
vm.video.high
|
98
|
+
=> "https://d2pq0u4uni88oo.cloudfront.net/projects/56284/video-217182-h264_high.mp4?2013"
|
99
|
+
|
100
|
+
# When searching for projects (or using convenient methods like recently_launched_projects()),
|
101
|
+
# you can continue the search until Kickstarter returns no more results
|
102
|
+
c.recently_launched_projects
|
103
|
+
=> [ _array of projects_ ]
|
104
|
+
c.load_more_projects if c.more_projects_available?
|
105
|
+
=> [ _array of additional projects_ ]
|
106
|
+
|
107
|
+
# Print all the updates for all the current user's projects
|
108
|
+
c.user.backed_projects.each { |project|
|
109
|
+
puts project.name.upcase
|
110
|
+
project.updates.reverse.each { |update|
|
111
|
+
# strip the HTML out of the body, since this outputs to a terminal
|
112
|
+
puts "Update #{update.sequence}: #{update.body.gsub(/<\/?[^>]*>/, "")}\n\n"
|
113
|
+
}
|
114
|
+
puts "\n\n"
|
115
|
+
}
|
116
|
+
|
117
|
+
# Get info about Kickstarter categories or find projects by category
|
118
|
+
client.categories
|
119
|
+
=> [<Category: 'Art'>, <Category: 'Conceptual Art'>, <Category: 'Crafts'>, <Category: 'Animation'>, <Category: 'Tabletop Games'>, <Category: 'Classical Music'>, <Category: 'Art Book'>, <Category: 'Hardware'>, <Category: 'Comics'>, <Category: 'Digital Art'>, <Category: 'Graphic Design'>, <Category: 'Documentary'>, <Category: 'Video Games'>, <Category: 'Country & Folk'>, <Category: 'Children's Book'>, <Category: 'Open Software'>, <Category: 'Dance'>, <Category: 'Illustration'>, <Category: 'Product Design'>, <Category: 'Narrative Film'>, <Category: 'Electronic Music'>, <Category: 'Fiction'>, <Category: 'Design'>, <Category: 'Journalism'>, <Category: 'Painting'>, <Category: 'Short Film'>, <Category: 'Hip-Hop'>, <Category: 'Fashion'>, <Category: 'Performance Art'>, <Category: 'Webseries'>, <Category: 'Indie Rock'>, <Category: 'Nonfiction'>, <Category: 'Film & Video'>, <Category: 'Jazz'>, <Category: 'Periodical'>, <Category: 'Food'>, <Category: 'Pop'>, <Category: 'Poetry'>, <Category: 'Mixed Media'>, <Category: 'Games'>, <Category: 'Rock'>, <Category: 'Public Art'>, <Category: 'Radio & Podcast'>, <Category: 'Music'>, <Category: 'Sculpture'>, <Category: 'World Music'>, <Category: 'Photography'>, <Category: 'Publishing'>, <Category: 'Technology'>, <Category: 'Theater'>]
|
120
|
+
|
121
|
+
category = client.category('product design')
|
122
|
+
=> <Category: 'Product Design'>
|
123
|
+
|
124
|
+
category.keys
|
125
|
+
=> ["id", "name", "position", "parent_id", "parent", "projects_count", "urls"]
|
126
|
+
|
127
|
+
category.projects_count
|
128
|
+
=> 234
|
129
|
+
|
130
|
+
ps = category.projects
|
131
|
+
=> [<Project: 'The Skinny Mirror - The Mirror That Compliments You'>, <Project: 'Rustic Fort'>, <Project: 'pushXpro: Revolutionizing the push-up exercise'>, <Project: 'Introducing Nubrella, the world's first hands-free umbrella.'>, <Project: 'Stream: The Pebble Smartwatch Dock'>, <Project: 'Get iPatched - Protect Yourself From Big Brother & Hackers'>, <Project: 'TrueRec introduces the DFP Kayak - Dive Fish Paddle'>, <Project: 'MagCozy: A Leash for Your MagSafe 2 Adapter'>, <Project: 'ECO Friendly Bamboo Party Trays'>, <Project: 'PJ reefs Miniature Saltwater Aquarium'>]
|
132
|
+
|
133
|
+
|
134
|
+
## Contributing
|
135
|
+
|
136
|
+
There are two good ways to contribute:
|
137
|
+
|
138
|
+
First, by using it and creating Issues as you find problems or rough spots. Second, by taking matters into your own hands:
|
139
|
+
|
140
|
+
1. Fork it
|
141
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
142
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
143
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
144
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
task :console do
|
4
|
+
require 'irb'
|
5
|
+
require 'irb/completion'
|
6
|
+
require 'kickscraper'
|
7
|
+
|
8
|
+
require './spec/test_constants'
|
9
|
+
|
10
|
+
Kickscraper.configure do |config|
|
11
|
+
config.email = KICKSCRAPER_TEST_API_EMAIL
|
12
|
+
config.password = KICKSCRAPER_TEST_API_PASSWORD
|
13
|
+
config.proxy = LOCAL_PROXY if defined? LOCAL_PROXY
|
14
|
+
end
|
15
|
+
|
16
|
+
ARGV.clear
|
17
|
+
IRB.start
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'kickscraper'
|
2
|
+
|
3
|
+
p "configuring.."
|
4
|
+
Kickscraper.configure do |config|
|
5
|
+
config.email = 'your-kickstarter-email-address@domain.com'
|
6
|
+
config.password = 'This is not my real password. Seriously.'
|
7
|
+
end
|
8
|
+
|
9
|
+
p "logging in.."
|
10
|
+
c = Kickscraper.client
|
11
|
+
p "got access token #{Kickscraper.token.gsub(/\w{30}$/, "X" * 30)}"
|
12
|
+
puts " Active | Met Goal |"
|
13
|
+
puts "--------------------"
|
14
|
+
c.user.backed_projects.each {|x|
|
15
|
+
print (x.active? ? ' X |' : ' |')
|
16
|
+
print (x.successful? ? ' X | ' : ' | ')
|
17
|
+
puts x.name
|
18
|
+
}
|
data/kickscraper.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kickscraper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kickscraper"
|
8
|
+
spec.version = Kickscraper::VERSION
|
9
|
+
spec.authors = ["Mark Olson", "Ben Rugg", "James Allen", "Mark Anderson"]
|
10
|
+
spec.email = ["theothermarkolson@gmail.com"]
|
11
|
+
spec.description = %q{Interact with Kickstarter through their API}
|
12
|
+
spec.summary = %q{API library for Kickstarter}
|
13
|
+
spec.homepage = "https://github.com/markolson/kickscraper"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec-core"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency('faraday', ['>= 0.7', '< 0.9'])
|
26
|
+
spec.add_runtime_dependency('faraday_middleware', '~> 0.8')
|
27
|
+
spec.add_runtime_dependency('multi_json', '>= 1.0.3', '~> 1.0')
|
28
|
+
spec.add_runtime_dependency('hashie', '~> 2')
|
29
|
+
spec.add_runtime_dependency('uri-query_params')
|
30
|
+
|
31
|
+
end
|
data/lib/kickscraper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require "kickscraper/version"
|
5
|
+
require "kickscraper/configure"
|
6
|
+
require "kickscraper/response"
|
7
|
+
require "kickscraper/connection"
|
8
|
+
require "kickscraper/client"
|
9
|
+
require "kickscraper/api"
|
10
|
+
|
11
|
+
|
12
|
+
module Kickscraper
|
13
|
+
extend Configure
|
14
|
+
attr_accessor :client
|
15
|
+
|
16
|
+
def self.client
|
17
|
+
@client ||= Kickscraper::Client.new
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kickscraper
|
2
|
+
class Api
|
3
|
+
extend Connection
|
4
|
+
include Hashie::Extensions::Coercion
|
5
|
+
attr_accessor :raw
|
6
|
+
|
7
|
+
def initialize(blob)
|
8
|
+
@raw = blob
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(name)
|
12
|
+
@raw.send(name) if @raw.respond_to? name
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.coerce(raw)
|
16
|
+
a = self.new(raw)
|
17
|
+
self::do_coercion(a)
|
18
|
+
a
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.do_coercion(instance)
|
22
|
+
self.key_coercions.each{ |k,v| instance.raw[k] = v.coerce(instance.raw[k]) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def uid
|
26
|
+
self.id == Kickscraper.client.user.id ? 'self' : self.id
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Dir[File.expand_path('../client/*.rb', __FILE__)].each{|f| load f}
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Kickscraper
|
4
|
+
class Client
|
5
|
+
include Connection
|
6
|
+
|
7
|
+
attr_accessor :user
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@more_projects_available = false
|
11
|
+
|
12
|
+
if Kickscraper.email.nil?
|
13
|
+
@user = nil
|
14
|
+
else
|
15
|
+
if Kickscraper.token.nil?
|
16
|
+
token_response = connection.post('xauth/access_token?client_id=2II5GGBZLOOZAA5XBU1U0Y44BU57Q58L8KOGM7H0E0YFHP3KTG', {'email' => Kickscraper.email, 'password' => Kickscraper.password }.to_json)
|
17
|
+
if token_response.body.error_messages
|
18
|
+
raise token_response.body.error_messages.join("\n")
|
19
|
+
return
|
20
|
+
end
|
21
|
+
Kickscraper.token = token_response.body.access_token
|
22
|
+
@user = User.coerce(token_response.body.user)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_user(id)
|
28
|
+
self::process_api_call "user", id.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_project(id_or_slug)
|
32
|
+
self::process_api_call "project", id_or_slug.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def search_projects(search_terms, page = nil)
|
36
|
+
self::process_api_call "projects", "advanced", search_terms, page
|
37
|
+
end
|
38
|
+
|
39
|
+
def ending_soon_projects(page = nil)
|
40
|
+
self::process_api_call "projects", "ending-soon", "", page
|
41
|
+
end
|
42
|
+
|
43
|
+
def popular_projects(page = nil)
|
44
|
+
self::process_api_call "projects", "popular", "", page
|
45
|
+
end
|
46
|
+
|
47
|
+
def recently_launched_projects(page = nil)
|
48
|
+
self::process_api_call "projects", "recently-launched", "", page
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :newest_projects, :recently_launched_projects
|
52
|
+
|
53
|
+
def more_projects_available?
|
54
|
+
@more_projects_available
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :can_load_more_projects, :more_projects_available?
|
58
|
+
|
59
|
+
def load_more_projects
|
60
|
+
if self::more_projects_available?
|
61
|
+
self::process_api_call @last_api_call_params[:request_for], @last_api_call_params[:additional_path], @last_api_call_params[:search_terms], (@last_api_call_params[:page] + 1)
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def categories
|
68
|
+
self::process_api_call "categories", ""
|
69
|
+
end
|
70
|
+
|
71
|
+
def category(id_or_name = nil)
|
72
|
+
return categories.find{|i| i.name.downcase.start_with? id_or_name.downcase} if id_or_name.is_a? String
|
73
|
+
self::process_api_call "category", id_or_name.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def process_api_call(request_for, additional_path, search_terms = "", page = nil)
|
78
|
+
|
79
|
+
# save the parameters for this call, so we can repeat it to get the next page of results
|
80
|
+
@last_api_call_params = {
|
81
|
+
request_for: request_for,
|
82
|
+
additional_path: additional_path,
|
83
|
+
search_terms: search_terms,
|
84
|
+
page: page.nil? ? 1 : page
|
85
|
+
}
|
86
|
+
|
87
|
+
# make the api call (to the API resource we want)
|
88
|
+
response = self::make_api_call(request_for, additional_path, search_terms, page)
|
89
|
+
|
90
|
+
# handle the response, returning an object with the results
|
91
|
+
self::coerce_api_response(request_for, response)
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def process_api_url(request_for, api_url, coerce_response = true)
|
96
|
+
|
97
|
+
# make the api call to whatever url we specified
|
98
|
+
response = connection.get(api_url)
|
99
|
+
|
100
|
+
|
101
|
+
# if we want to coerce the response, do it now
|
102
|
+
if coerce_response
|
103
|
+
|
104
|
+
self::coerce_api_response(request_for, response)
|
105
|
+
|
106
|
+
# else, just return the raw body
|
107
|
+
else
|
108
|
+
|
109
|
+
response.body
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def coerce_api_response(expected_type, response)
|
115
|
+
|
116
|
+
# define what we should return as an empty response, based on the expected type
|
117
|
+
types_that_should_return_an_array = ["projects", "comments", "updates", "categories"]
|
118
|
+
empty_response = (types_that_should_return_an_array.include? expected_type) ? [] : nil
|
119
|
+
|
120
|
+
|
121
|
+
# get the body from the response
|
122
|
+
body = response.body
|
123
|
+
|
124
|
+
|
125
|
+
# if we got an error response back, stop here and return an empty response
|
126
|
+
return empty_response if response.headers['status'].to_i >= 400 || !response.headers['content-type'].start_with?('application/json')
|
127
|
+
return empty_response if (body.respond_to?("error_messages") && !body.error_messages.empty?) || (body.respond_to?("http_code") && body.http_code == 404)
|
128
|
+
|
129
|
+
|
130
|
+
# otherwise, take the response from the api and coerce it to the type we want
|
131
|
+
case expected_type.downcase
|
132
|
+
when "user"
|
133
|
+
|
134
|
+
User.coerce body
|
135
|
+
|
136
|
+
when "project"
|
137
|
+
|
138
|
+
Project.coerce body
|
139
|
+
|
140
|
+
when "projects"
|
141
|
+
|
142
|
+
# if the body is just an array of projects, with no root keys, then coerce
|
143
|
+
# the array
|
144
|
+
if body.is_a?(Array)
|
145
|
+
|
146
|
+
@more_projects_available = false
|
147
|
+
body.map { |project| Project.coerce project }
|
148
|
+
|
149
|
+
|
150
|
+
# else, if the body doesn't have any projects, return an empty array
|
151
|
+
elsif body.projects.nil?
|
152
|
+
|
153
|
+
@more_projects_available = false
|
154
|
+
return empty_response
|
155
|
+
|
156
|
+
|
157
|
+
# else, determine if we can load more projects and then return an array of projects
|
158
|
+
else
|
159
|
+
|
160
|
+
if @last_api_call_params && !body.total_hits.nil?
|
161
|
+
@more_projects_available = @last_api_call_params[:page] * 20 < body.total_hits # (there is a huge assumption here that Kickstarter will always return 20 projects per page!)
|
162
|
+
end
|
163
|
+
|
164
|
+
return body.projects.map { |project| Project.coerce project }
|
165
|
+
end
|
166
|
+
|
167
|
+
when "comments"
|
168
|
+
|
169
|
+
return empty_response if body.comments.nil?
|
170
|
+
body.comments.map { |comment| Comment.coerce comment }
|
171
|
+
|
172
|
+
when "updates"
|
173
|
+
|
174
|
+
return empty_response if body.updates.nil?
|
175
|
+
body.updates.map { |update| Update.coerce update }
|
176
|
+
|
177
|
+
when "categories"
|
178
|
+
|
179
|
+
return empty_response if body.categories.nil?
|
180
|
+
body.categories.map { |category| Category.coerce category }
|
181
|
+
|
182
|
+
when "category"
|
183
|
+
|
184
|
+
Category.coerce body
|
185
|
+
|
186
|
+
else
|
187
|
+
raise ArgumentError, "invalid api request type"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def make_api_call(request_for, additional_path = "", search_terms = "", page = nil)
|
193
|
+
|
194
|
+
# set the url/path differently for each type of request
|
195
|
+
case request_for.downcase
|
196
|
+
when "user"
|
197
|
+
api_or_search = "api"
|
198
|
+
path = "/v1/users"
|
199
|
+
when "project"
|
200
|
+
api_or_search = "api"
|
201
|
+
path = "/v1/projects"
|
202
|
+
when "projects"
|
203
|
+
api_or_search = "search"
|
204
|
+
path = "/discover"
|
205
|
+
when "category", "categories"
|
206
|
+
api_or_search = "api"
|
207
|
+
path = "/v1/categories"
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
# add the additional path if we have it
|
212
|
+
path += "/" + CGI.escape(additional_path) unless additional_path.empty?
|
213
|
+
|
214
|
+
|
215
|
+
# create the params hash and add the params we want
|
216
|
+
params = {}
|
217
|
+
params[:term] = search_terms unless search_terms.empty?
|
218
|
+
params[:page] = page unless page.nil?
|
219
|
+
|
220
|
+
|
221
|
+
# make the connection and return the response
|
222
|
+
connection(api_or_search).get(path, params)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|