kickstapi 0.0.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0fa7c8fb3235af0c4bcb98666500470c7f6eefcc
4
- data.tar.gz: 59ca695e6b72d9a670e1840482b53a207bbaa478
3
+ metadata.gz: e7a27eb2fdb171795742943d1dcdf6f7dcc13856
4
+ data.tar.gz: d2c7e82ac82d04e2399274f8dc5c2ad5c202e559
5
5
  SHA512:
6
- metadata.gz: 0f7c87564e5c0bbe1db9c07a14c33b241af76ef2ed1dcaa515277eb6dbfcf155d4e19b368d29ef1fff923fc99443e54f45b8bca31c9be737796becf1df7b9d49
7
- data.tar.gz: 6e58f01a22e1dc046fb1091eb578f8b88dcb9990686b7b69ba6fa4d2abbbd5cd9a4a5361c53db79e9885803ddf5390c99d024d700e90feb1c1fedc681a34b056
6
+ metadata.gz: 38c7392691d07678ade9e2400bb67a7d1c2fb1e422121f20457a774cf309f62757b510fef1e06daed6fc9230ead26ed8369f785b6ff654d433223bcb2929690c
7
+ data.tar.gz: f4bb5fb43a88e151a782f99fe780e3c05ce13d4e4ac33cf29df7e166cc1b41d95ce891e41c2ad592b3fec66928311799cf2f2108676fcc06aba4f68dd2101b6a
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Kickstapi [![Build Status](https://travis-ci.org/kvannotten/kickstapi.png?branch=master)](https://travis-ci.org/kvannotten/kickstapi)
1
+ # Kickstapi [![Build Status](https://travis-ci.org/kvannotten/kickstapi.png?branch=master)](https://travis-ci.org/kvannotten/kickstapi) [![Gem Version](https://badge.fury.io/rb/kickstapi.svg)](http://badge.fury.io/rb/kickstapi)
2
2
 
3
3
  This gem scrapes Kickstarter to create an API that facilitates the creation of applications querying Kickstarter.
4
4
 
@@ -24,20 +24,28 @@ Or install it yourself as:
24
24
  require 'kickstapi'
25
25
 
26
26
  # this gives you an array of Project objects containing 'Planetary Annihilation'
27
- projects = Kickstapi.search_projects "Planetary Annihilation"
27
+ projects = Kickstapi.find_projects_with_filter "Planetary Annihilation"
28
28
 
29
29
  # lets take the first project
30
30
  project = projects.first
31
31
 
32
32
  # we can then perform the following methods on it
33
- project.name
34
- project.url
35
- project.about
36
- project.creator
37
- project.pledged
38
- project.percentage_funded
39
- project.backers
40
- project.status
33
+ project.name # returns the name of the project
34
+ project.id # returns the id of the project
35
+ project.url # returns the url of the project
36
+ project.creator # returns the name of the creator
37
+ # The methods above are always available, the methods below
38
+ # will be lazy loaded when requested (this requires an additional
39
+ # HTML request)
40
+ project.about # returns the about section
41
+ project.pledged # returns the total amount pledged
42
+ project.percentage_funded # returns the percentage of funds that have been achieved so far
43
+ project.backers # returns the amount of backers
44
+ project.status # returns if the project is cancelled or succesful or still running
45
+ project.currency # returns the currency type (USD, CAD, GBP, EUR, ...)
46
+ project.goal # returns the fund goal
47
+ project.end_date # returns the date that the project (will) end(s)
48
+ project.hours_left # returns how many hours are still left on the project
41
49
 
42
50
  ```
43
51
 
@@ -0,0 +1,32 @@
1
+ module Kickstapi
2
+ module Ghostly
3
+ module Macros
4
+ private
5
+ def lazy_accessor(*names)
6
+ names.each do |name|
7
+ attr_writer name
8
+ define_method(name) do
9
+ load
10
+ instance_variable_get("@#{name}")
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.included(other)
17
+ other.extend(Macros)
18
+ end
19
+
20
+ attr_accessor :data_source
21
+ attr_writer :load_state
22
+
23
+ def load_state
24
+ @load_state ||= :ghost
25
+ end
26
+
27
+ def load
28
+ return if load_state == :loaded
29
+ data_source.load(self)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ require 'mechanize'
2
+ require 'json'
3
+
4
+ module Kickstapi
5
+ class KickstarterGateway
6
+
7
+ def projects_with_name(filter, offset = 1)
8
+ search_url = "http://www.kickstarter.com/projects/search?page=#{offset}utf8=%E2%9C%93&term=#{URI::encode filter}"
9
+
10
+ projects = []
11
+ should_page = true
12
+
13
+ agent.get(search_url) do |page|
14
+ page.search("div.project-card-wrap").each do |project|
15
+ p = Hash.new(0)
16
+
17
+ bb_card = project.search("h2.bbcard_name")
18
+ bb_card_link = bb_card.search("a").first
19
+
20
+ p[:name] = bb_card_link.content
21
+ p[:url] = "https://www.kickstarter.com#{bb_card_link.attributes["href"].value.split('?').first}"
22
+ p[:id] = JSON.parse(project.attributes["data-project"])["id"].to_i
23
+ p[:creator] = project.search(".bbcard_name span").text.gsub(/\n|by/, '')
24
+
25
+ projects << p
26
+ end
27
+ should_page = projects.count > 0
28
+ end
29
+
30
+ [projects, should_page]
31
+ end
32
+
33
+ def project_by_url(url)
34
+ project = {}
35
+ agent.get(url) do |page|
36
+ project[:name] = page.search(%Q{//h2[@class='mb1']//a}).text
37
+ project[:creator] = page.search(%Q{//span[@class='creator']//a}).text
38
+ project[:url] = url
39
+ project[:id] = page.search('div').select { |div| div[:class] =~ /^Project\d+/ }.map { |div| div[:class].to_s }.uniq.first.scan(/(\d+)/).flatten.first.to_i
40
+ project[:backers] = page.search(%Q{//data[@itemprop='Project[backers_count]']}).first.attributes["data-value"].value.to_i
41
+ project[:pledged] = page.search(%Q{//data[@itemprop='Project[pledged]']}).first.attributes["data-value"].value.to_f
42
+ project[:goal] = page.search(%Q{//div[@id='pledged']}).first.attributes['data-goal'].value.to_f
43
+ project[:currency] = page.search(%Q{//data[@itemprop='Project[pledged]']}).first.attributes["data-currency"].value
44
+ project[:percentage_funded] = page.search(%Q{//div[@id='pledged']}).first.attributes['data-percent-raised'].value.to_f * 100
45
+ project[:end_date] = DateTime.parse page.search(%Q{//span[@id='project_duration_data']}).first.attributes['data-end_time'].value
46
+ project[:hours_left] = page.search(%Q{//span[@id='project_duration_data']}).first.attributes['data-hours-remaining'].value.to_f
47
+ end
48
+ project
49
+ end
50
+
51
+ private
52
+
53
+ def agent
54
+ @agent ||= Mechanize.new
55
+ end
56
+
57
+ end
58
+ end
@@ -1,15 +1,37 @@
1
1
  require 'json'
2
+ require 'kickstapi/ghostly'
2
3
 
3
4
  module Kickstapi
4
5
  class Project
5
- attr_accessor :id, :name, :url, :creator, :about,
6
- :pledged, :goal, :currency, :percentage_funded, :backers,
7
- :status, :end_date
6
+ include Ghostly
7
+
8
+ attr_accessor :id, :name, :url, :creator
9
+ lazy_accessor :about, :pledged, :goal,
10
+ :currency, :percentage_funded, :backers,
11
+ :status, :end_date, :hours_left
8
12
 
13
+ def initialize(attributes = {})
14
+ complete(attributes)
15
+ end
16
+
17
+ def complete(attributes)
18
+ attributes.each do |key, value|
19
+ public_send("#{key}=", value)
20
+ end
21
+ end
22
+
23
+ def to_s
24
+ inspect
25
+ end
26
+
27
+ def ==(other)
28
+ other.is_a?(Project) && other.id == id
29
+ end
30
+
9
31
  def to_hash
10
32
  hash = {}
11
33
  self.instance_variables.each do |var|
12
- sym = var.to_s.gsub /@/, ''
34
+ sym = var.to_s.gsub(/@/, '')
13
35
  hash[sym.to_sym] = self.instance_variable_get var
14
36
  end
15
37
  hash
@@ -0,0 +1,69 @@
1
+ require 'kickstapi/project'
2
+
3
+ module Kickstapi
4
+ class ProjectMapper
5
+ attr_reader :gateway
6
+
7
+ def initialize(gateway)
8
+ @gateway = gateway
9
+ end
10
+
11
+ def projects_by_filter(filter, max_results = :all)
12
+ page = 1
13
+ should_page = true
14
+ projects = []
15
+
16
+ loop do
17
+ break unless should_page
18
+ break unless max_results == :all || max_results.to_i >= projects.size
19
+
20
+ projects_hashes, should_page = @gateway.projects_with_name(filter, page)
21
+ page += 1
22
+
23
+ projects_hashes.each do |project_hash|
24
+ project = Project.new(data_source: self)
25
+
26
+ project.complete(project_hash)
27
+
28
+ projects << project
29
+ end
30
+
31
+ end
32
+ projects = projects[0...max_results] unless max_results == :all
33
+ projects
34
+ end
35
+
36
+ def project_by_url(url)
37
+ project_hash = @gateway.project_by_url(url)
38
+ project = Project.new(data_source: self)
39
+ project.url = project_hash[:url]
40
+
41
+ fill_project(project, project_hash)
42
+ project
43
+ end
44
+
45
+ def load(project)
46
+ fill_project(project, @gateway.project_by_url(project.url))
47
+ end
48
+
49
+ private
50
+
51
+ def fill_project(project, project_hash = {})
52
+ project.complete(project_hash)
53
+ project.load_state = :loaded
54
+ if project.pledged < project.goal
55
+ if project.end_date < DateTime.now
56
+ project.status = :failed
57
+ else
58
+ project.status = :running_not_yet_achieved
59
+ end
60
+ else
61
+ if project.end_date < DateTime.now
62
+ project.status = :succesful
63
+ else
64
+ project.status = :running_already_achieved
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module Kickstapi
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.5"
3
3
  end
data/lib/kickstapi.rb CHANGED
@@ -1,74 +1,25 @@
1
1
  require "kickstapi/version"
2
2
  require "kickstapi/project"
3
+ require "kickstapi/kickstarter_gateway"
4
+ require "kickstapi/project_mapper"
5
+ require "kickstapi/ghostly"
3
6
 
4
7
  require 'open-uri'
5
8
  require 'mechanize'
6
9
 
7
10
  module Kickstapi
8
- def self.get_agent
9
- @agent ||= Mechanize.new
10
- end
11
-
12
- def self.search_projects( filter = "", offset = 1)
13
- search_url = "http://www.kickstarter.com/projects/search?page=#{offset}utf8=%E2%9C%93&term=#{URI::encode filter}"
11
+ def self.find_projects_with_filter(filter, max_results = :all)
12
+ gw = Kickstapi::KickstarterGateway.new
13
+ mapper = Kickstapi::ProjectMapper.new(gw)
14
14
 
15
- projects = []
16
- Kickstapi.get_agent.get(search_url) do |page|
17
- page.search("div.project-card").each do |project|
18
- p = Project.new
19
- bb_card = project.search("h2.bbcard_name")
20
- bb_card_link = bb_card.search("a").first
15
+ mapper.projects_by_filter filter, max_results
16
+ end
21
17
 
22
- p.name = bb_card_link.content
23
- p.url = bb_card_link.attributes["href"].value.split('?').first
24
- p.id = p.url.scan(/\/(\d+)\//).flatten.first.to_i
25
- bb_card_author = bb_card.search("span").first
26
- p.creator = bb_card_author.content.gsub(/\nby\n/, '').gsub(/\n/, '')
27
- p.about = project.search("p.bbcard_blurb").first.content.strip
28
-
29
- if project.search(".project-failed").first.nil? then
30
- pledged = project.search("strong.project-pledged-amount").first.content.gsub(/\n/, '').gsub(/pledged/, '')
31
-
32
- p.pledged = pledged[1, pledged.length].gsub(',', '').to_f
33
- p.currency = pledged[0, 1]
34
- p.percentage_funded = project.search("strong.project-pledged-percent").first.content.gsub(/\n/, '').gsub(/funded/, '')
35
- p.backers = project.search("li.middle").first.search("strong").first.content.gsub(/\n/, '')
36
- if project.search("li.ksr_page_timer").first.nil? then
37
- p.status = project.search("li.last").first.search("strong").first.content
38
- else
39
- p.end_date = DateTime.parse(project.search("li.ksr_page_timer").first.attributes["data-end_time"].value)
40
- p.status = "Running"
41
- end
42
- else
43
- p.status = "Failed"
44
- end
45
- projects << p
46
- end
47
- end
18
+ def self.find_project_by_url(url)
19
+ gw = Kickstapi::KickstarterGateway.new
20
+ mapper = Kickstapi::ProjectMapper.new(gw)
48
21
 
49
- projects
50
- end
51
-
52
- def self.get_project name
53
- return Kickstapi.search_projects(name).first
22
+ mapper.project_by_url url
54
23
  end
55
-
56
- def self.get_project_by_url url
57
- p = Project.new
58
- Kickstapi.get_agent.get(url) do |page|
59
- p.name = page.search(%Q{//h2[@class='mb1']//a}).text
60
- p.creator = page.search(%Q{//span[@class='creator']//a}).text
61
- p.url = url
62
- p.id = p.id = p.url.scan(/\/(\d+)\//).flatten.first.to_i
63
- p.backers = page.search(%Q{//data[@itemprop='Project[backers_count]']}).first.attributes["data-value"].value.to_i
64
- p.pledged = page.search(%Q{//data[@itemprop='Project[pledged]']}).first.attributes["data-value"].value.to_f
65
- p.goal = page.search(%Q{//div[@id='pledged']}).first.attributes['data-goal'].value
66
- p.currency = page.search(%Q{//data[@itemprop='Project[pledged]']}).first.attributes["data-currency"].value
67
- p.percentage_funded = page.search(%Q{//div[@id='pledged']}).first.attributes['data-percent-raised'].value.to_f * 100
68
- p.end_date = DateTime.parse page.search(%Q{//span[@id='project_duration_data']}).first.attributes['data-end_time'].value
69
- end
70
-
71
- p
72
- end
73
-
24
+
74
25
  end
@@ -6,9 +6,8 @@ describe Kickstapi do
6
6
  end
7
7
 
8
8
  before :all do
9
- @projects = Kickstapi.search_projects "Planetary Annihilation" # succesful project
10
- @failure = Kickstapi.get_project "Quala" # failing project
11
- @single_project = Kickstapi.get_project_by_url "http://www.kickstarter.com/projects/659943965/planetary-annihilation-a-next-generation-rts?ref=live"
9
+ @projects = Kickstapi.find_projects_with_filter "Planetary Annihilation" # succesful project
10
+ @failure = Kickstapi.find_projects_with_filter("Quala").first # failing project
12
11
  end
13
12
 
14
13
  context 'projects' do
@@ -24,44 +23,55 @@ describe Kickstapi do
24
23
 
25
24
  its(:to_hash) { should be_kind_of Hash }
26
25
 
27
- [ :id, :name, :url, :creator,
28
- :about, :pledged, :currency,
29
- :percentage_funded, :backers,
30
- :status].each do |method|
31
- it { should respond_to method }
32
- its(method) { should_not be_nil }
33
- its(:to_hash) { should have_key method }
34
- end
35
-
36
26
  its(:id) { should be_kind_of Fixnum }
37
- its(:id) { should > 0 }
38
27
 
39
28
  its(:pledged) { should be_kind_of Float }
40
29
 
41
30
  it "returns valid json" do
42
- expect { JSON.parse @projects.first.to_json }.to_not raise_error JSON::ParserError
31
+ expect { JSON.parse @projects.first.to_json }.not_to raise_error
43
32
  end
44
- end
45
-
46
- context 'project by URL' do
47
- subject { @single_project }
48
-
49
- [ :id, :name, :url, :creator,
50
- :pledged, :currency,
51
- :percentage_funded, :backers,
52
- :goal].each do |method|
53
- it { should respond_to method }
54
- its(method) { should_not be_nil }
55
- its(:to_hash) { should have_key method }
33
+
34
+ it "should be in ghost state when first fetched" do
35
+ temp_project = Kickstapi.find_projects_with_filter("Ewe Topia").first
36
+ temp_project.load_state.should be_eql :ghost
56
37
  end
57
- end
58
-
59
- context 'failed project' do
60
- subject { @failure }
61
-
62
- its(:status) { should eql "Failed" }
63
- [:pledged, :percentage_funded, :backers].each do |method|
64
- its(method) { should be_nil }
38
+
39
+ it "should do an initial fetch of some fields, while in ghost state" do
40
+ temp_project = Kickstapi.find_projects_with_filter("Ewe Topia").first
41
+ temp_project.id
42
+ temp_project.creator
43
+ temp_project.url
44
+ temp_project.name
45
+ temp_project.load_state.should be_eql :ghost
46
+ end
47
+
48
+ it "should lazily load all the arguments" do
49
+ temp_project = Kickstapi.find_projects_with_filter("Ewe Topia").first
50
+ temp_project.backers # fetch an item that is lazily loaded
51
+ temp_project.load_state.should be_eql :loaded
52
+ end
53
+
54
+ it "should be retrievable from it's URL" do
55
+ temp_project = Kickstapi.find_project_by_url(@failure.url)
56
+ temp_project.name.should be_eql @failure.name
57
+ end
58
+
59
+ it "should be comparable" do
60
+ temp_project = Kickstapi.find_project_by_url(@projects.first.url)
61
+ (temp_project == @projects.first).should be_true
62
+ end
63
+
64
+ it "should be different from another project" do
65
+ (@projects.first == @failure).should be_false
66
+ end
67
+
68
+ it "should mark a successful project" do
69
+ @projects.first.status.should be_eql :succesful
70
+ end
71
+
72
+ it "should mark an unsuccessful project" do
73
+ @failure.status.should be_eql :failed
65
74
  end
66
75
  end
76
+
67
77
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kickstapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristof Vannotten
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-23 00:00:00.000000000 Z
11
+ date: 2014-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -96,7 +96,10 @@ files:
96
96
  - Rakefile
97
97
  - kickstapi.gemspec
98
98
  - lib/kickstapi.rb
99
+ - lib/kickstapi/ghostly.rb
100
+ - lib/kickstapi/kickstarter_gateway.rb
99
101
  - lib/kickstapi/project.rb
102
+ - lib/kickstapi/project_mapper.rb
100
103
  - lib/kickstapi/version.rb
101
104
  - spec/kickstapi_spec.rb
102
105
  - spec/spec_helper.rb
@@ -127,3 +130,4 @@ summary: This gem offers an API for Kickstarter
127
130
  test_files:
128
131
  - spec/kickstapi_spec.rb
129
132
  - spec/spec_helper.rb
133
+ has_rdoc: