kickstapi 0.0.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: