john_stamos 0.0.1
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 +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +132 -0
- data/Rakefile +1 -0
- data/john_stamos.gemspec +32 -0
- data/lib/john_stamos/client.rb +57 -0
- data/lib/john_stamos/pin.rb +78 -0
- data/lib/john_stamos/pin_search.rb +150 -0
- data/lib/john_stamos/pinner.rb +81 -0
- data/lib/john_stamos/version.rb +3 -0
- data/lib/john_stamos.rb +20 -0
- data/spec/lib/john_stamos/client_spec.rb +147 -0
- data/spec/lib/john_stamos/pin_search_spec.rb +298 -0
- data/spec/lib/john_stamos/pin_spec.rb +100 -0
- data/spec/lib/john_stamos/pinner_spec.rb +123 -0
- data/spec/lib/john_stamos_spec.rb +8 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/vcr_cassettes/JohnStamos_Client/_page_content/returns_a_Mechanize_Page.yml +221 -0
- data/spec/support/vcr_cassettes/JohnStamos_Client/_search_pins/returns_the_correct_number_of_pins.yml +1022 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/_pinner/returns_a_Pinner_object.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/_pinner/shows_my_fake_account_as_the_owning_Pinner.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/belongs_to_the_correct_board.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/has_the_correct_description.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/has_the_correct_image.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/has_the_correct_like_count.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/has_the_correct_repin_count.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/has_the_correct_source_url.yml +330 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pin/scraped_data/is_not_a_video.yml +329 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_execute_/when_using_a_search_term_that_yields_thousands_of_pins/with_a_limit_higher_than_the_default/collects_the_correct_number_of_pin_ids.yml +2732 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_execute_/when_using_a_search_term_that_yields_thousands_of_pins/with_a_limit_lower_than_the_default/collects_the_correct_number_of_pin_ids.yml +1199 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_execute_/when_using_a_search_term_that_yields_thousands_of_pins/with_a_limit_lower_than_the_default/never_calls_subsequent_retrieval_.yml +1199 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_execute_/when_using_a_search_term_that_yields_thousands_of_pins/with_no_limit_specified/limits_the_pin_ids_collected_to_50.yml +1697 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_execute_/when_using_a_search_term_that_yields_thousands_of_pins/with_no_limit_specified/limits_to_50_the_default.yml +1697 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_first_retrieval_/with_results_found/sets_next_bookmark.yml +1035 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_first_retrieval_/with_results_found/sets_next_bookmark_with_a_hash_for_the_next_50_results.yml +1035 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_first_retrieval_/with_results_found/should_set_pin_ids.yml +1035 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_first_retrieval_/without_any_results_found/has_a_nil_next_bookmark.yml +253 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_first_retrieval_/without_any_results_found/leaves_the_pin_ids_array_empty.yml +254 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_subsequent_retrieval/with_results_found/sets_next_bookmark_with_a_hash_for_the_next_50_results.yml +1772 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_subsequent_retrieval/with_results_found/sets_the_next_bookmark.yml +1772 -0
- data/spec/support/vcr_cassettes/JohnStamos_PinSearch/_subsequent_retrieval/with_results_found/should_append_the_new_pin_ids.yml +1772 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_about/returns_the_correct_about_text.yml +287 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_avatar/returns_the_correct_avatar_URL.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_board_count/returns_the_correct_number_of_boards.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_follower_count/returns_a_follower_count_of_0.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_location/returns_the_correct_location.yml +287 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_pin_count/returns_the_correct_number_of_pins.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/_website_url/returns_the_correct_website_url.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/name_related_attributes/returns_the_correct_first_name.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/name_related_attributes/returns_the_correct_full_name.yml +286 -0
- data/spec/support/vcr_cassettes/JohnStamos_Pinner/name_related_attributes/returns_the_correct_last_name.yml +286 -0
- metadata +279 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c66ac4a130bded5d5012b17412f1a77cc975f7bb
|
4
|
+
data.tar.gz: 007cbf2771d8fb6ec1a96f78c47b863ddfd431ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60d24054779e9da576348520c20919f0f5f33c34574860615b71c82103781fce890ed059635f0e1b285f3584dc6a14d028a1cc5efc043ad9fc454f643f5e8395
|
7
|
+
data.tar.gz: ad0169ed7a5024733155002f2a6cec5b817c5f90aed770b0bd0ed77e959c734439bfeb6f060d71fcea4a6fb43b8b97b1a577c2133b56a5fd5f37435326a0b17f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 mariozig
|
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,132 @@
|
|
1
|
+
# JohnStamos
|
2
|
+
|
3
|
+
The greasiest Pinterest client. JohnStamos exposes an interface you can use to query Pinterest for data about individual Pins, Pinners and Pin searches. Have mercy.
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/mariozig/john_stamos.png?branch=master)](https://travis-ci.org/mariozig/john_stamos)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'john_stamos'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install john_stamos
|
20
|
+
|
21
|
+
Or finally, if none of that stuff applies to you, do what it takes to install the gem and add this to your script:
|
22
|
+
|
23
|
+
require 'john_stamos'
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Searching for Pins
|
28
|
+
|
29
|
+
Here's a typical use-case: You want to search Pinterest for some pins. Once you have those pins, you want to access information about each individual pin as well as information about the person who pinned it.
|
30
|
+
|
31
|
+
client = JohnStamos::Client.new
|
32
|
+
|
33
|
+
# Search for some sweet Pins of "pallet gardens"
|
34
|
+
pins = client.search_pins("pallet gardens", limit: 10)
|
35
|
+
# Returns an Array of Pin objects
|
36
|
+
|
37
|
+
# The first Pin will be fun to investigate.
|
38
|
+
first_pallet_garden_pin = pallet_search.first
|
39
|
+
|
40
|
+
# Print some info about the first Pin description
|
41
|
+
puts first_pallet_garden_pin.description
|
42
|
+
# => "A cool pallet garden"
|
43
|
+
|
44
|
+
# the full sized image
|
45
|
+
puts first_pallet_garden_pin.image
|
46
|
+
# => "http://media-cache-ak0.pinimg.com/originals....whatever.jpg"
|
47
|
+
|
48
|
+
# Pinterest's ID for this Pin
|
49
|
+
puts first_pallet_garden_pin.id
|
50
|
+
# => "123456789"
|
51
|
+
|
52
|
+
# URL to the Pin
|
53
|
+
puts first_pallet_garden_pin.url
|
54
|
+
# => "http://pinterest.com/pin/123456789"
|
55
|
+
|
56
|
+
# We can even get at the user who Pinned this Pin
|
57
|
+
first_pallet_garden_pin.pinner
|
58
|
+
|
59
|
+
# Print the Pinner's full name
|
60
|
+
puts first_pallet_garden_pin.pinner.full_name
|
61
|
+
# => "Little Bobby Tables"
|
62
|
+
|
63
|
+
### Using the Client
|
64
|
+
|
65
|
+
The Client is the single entry point into all things JohnStamos.
|
66
|
+
|
67
|
+
# Create a new Client
|
68
|
+
client = JohnStamos::Client.new
|
69
|
+
|
70
|
+
# The Client also supports using a proxy
|
71
|
+
client_with_proxy = JohnStamos::Client.new(proxy: 'http://127.1.1.1:4747')
|
72
|
+
|
73
|
+
# Note: If you're hosted on Heroku, you will want to use a proxy.
|
74
|
+
|
75
|
+
|
76
|
+
### Accessing Individual Pin Data
|
77
|
+
|
78
|
+
There are times you might want to access an individual Pin's information. All you need is the ID assigned to the Pin by Pinterest to get going.
|
79
|
+
|
80
|
+
client = JohnStamos::Client.new
|
81
|
+
|
82
|
+
# Get a magnificent Pin using it's ID
|
83
|
+
perfect_strangers_pin = client.pin(161848180328631541)
|
84
|
+
|
85
|
+
puts perfect_strangers_pin.description
|
86
|
+
# => "Balki Bartokomous and Cousin Larry!!! PERFECT STRANGERS...loved that show!"
|
87
|
+
|
88
|
+
puts perfect_strangers_pin.image
|
89
|
+
# => "http://media-cache-ec0.pinimg.com/originals/4b/10/d3/4b10d3bd1c172c52934eb2d79ff0031f.jpg"
|
90
|
+
|
91
|
+
# If you didn't look at that image above, please do it before reading further
|
92
|
+
|
93
|
+
puts perfect_strangers_pin.url
|
94
|
+
# => "http://pinterest.com/pin/161848180328631541/"
|
95
|
+
|
96
|
+
puts perfect_strangers_pin.repin_count
|
97
|
+
# => 1
|
98
|
+
|
99
|
+
### Accessing Pinner Data
|
100
|
+
|
101
|
+
You can also query Pinterest for information about specific Pinners. Just pass in the Pinner's user name and you will be good to go.
|
102
|
+
|
103
|
+
client = JohnStamos::Client.new
|
104
|
+
|
105
|
+
# Get Michelle Obama's account
|
106
|
+
michelle_obama = client.pinner("michelleobama")
|
107
|
+
|
108
|
+
# Print some of the Pinner's information
|
109
|
+
puts michelle_obama.last_name
|
110
|
+
# => "Obama"
|
111
|
+
|
112
|
+
puts michelle_obama.first_name
|
113
|
+
# => "Michelle"
|
114
|
+
|
115
|
+
puts michelle_obama.pin_count
|
116
|
+
# => "81"
|
117
|
+
|
118
|
+
puts michelle_obama.website_url
|
119
|
+
# => "http://www.barackobama.com"
|
120
|
+
|
121
|
+
## Contributing
|
122
|
+
|
123
|
+
1. Fork it
|
124
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
125
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
126
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
127
|
+
5. Create new Pull Request
|
128
|
+
|
129
|
+
## Notes
|
130
|
+
|
131
|
+
- Currently tested against [Ruby 2.0 only](https://github.com/mariozig/john_stamos/blob/master/.travis.yml).
|
132
|
+
- If you want to run tests locally I suggest you delete the [VCR cassettes](https://github.com/mariozig/john_stamos/tree/master/spec/support/vcr_cassettes) and start from scratch. Pinterest changes OFTEN.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/john_stamos.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'john_stamos/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "john_stamos"
|
8
|
+
gem.version = JohnStamos::VERSION
|
9
|
+
gem.authors = ["mariozig"]
|
10
|
+
gem.email = ["mariozig@gmail.com"]
|
11
|
+
gem.description = %q{A Pinterest client}
|
12
|
+
gem.summary = %q{An interface into Pinterest that exposes Pinners, Pins and searching. }
|
13
|
+
gem.homepage = "http://github.com/mariozig/john_stamos/"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "rspec", "~> 2.6"
|
24
|
+
gem.add_development_dependency "webmock", "~> 1.8"
|
25
|
+
gem.add_development_dependency "vcr"
|
26
|
+
gem.add_development_dependency "guard-rspec"
|
27
|
+
|
28
|
+
gem.add_dependency "mechanize"
|
29
|
+
gem.add_dependency "rest-client"
|
30
|
+
gem.add_dependency "json"
|
31
|
+
gem.add_dependency "launchy"
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class JohnStamos::Client
|
2
|
+
attr_accessor :proxy
|
3
|
+
|
4
|
+
def initialize(options={})
|
5
|
+
default_options = { proxy: nil }
|
6
|
+
options = default_options.merge(options)
|
7
|
+
@proxy = options[:proxy]
|
8
|
+
end
|
9
|
+
|
10
|
+
def search_pins(search_text, options={})
|
11
|
+
search_scraper = JohnStamos::PinSearch.new(self, search_text, options)
|
12
|
+
search_scraper.execute!
|
13
|
+
|
14
|
+
search_scraper.pins
|
15
|
+
end
|
16
|
+
|
17
|
+
def pin(pinterest_pin_id)
|
18
|
+
JohnStamos::Pin.new(self, pinterest_pin_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pinner(username)
|
22
|
+
JohnStamos::Pinner.new(self, username)
|
23
|
+
end
|
24
|
+
|
25
|
+
def page_content(url)
|
26
|
+
begin
|
27
|
+
page = mechanize_agent.get(url)
|
28
|
+
rescue Mechanize::ResponseReadError => e
|
29
|
+
$stderr.puts "#{e.class}: #{e.message}"
|
30
|
+
$stderr.puts "An error occured; attempting to force_parse"
|
31
|
+
page = e.force_parse
|
32
|
+
end
|
33
|
+
|
34
|
+
page
|
35
|
+
end
|
36
|
+
|
37
|
+
def json_content(url, params)
|
38
|
+
RestClient.proxy = @proxy
|
39
|
+
response = RestClient.get(url, params: params, accept: :json, "X-Requested-With" => "XMLHttpRequest")
|
40
|
+
|
41
|
+
JSON.parse(response)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def mechanize_agent
|
46
|
+
agent = Mechanize.new
|
47
|
+
agent.user_agent_alias = 'Mac Safari'
|
48
|
+
|
49
|
+
if @proxy
|
50
|
+
proxy_uri = URI.parse(@proxy)
|
51
|
+
agent.set_proxy(proxy_uri.hostname, proxy_uri.port)
|
52
|
+
end
|
53
|
+
|
54
|
+
agent
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class JohnStamos::Pin
|
2
|
+
attr_reader :id
|
3
|
+
|
4
|
+
def initialize(client, pinterest_pin_id)
|
5
|
+
@id = pinterest_pin_id
|
6
|
+
@client = client
|
7
|
+
end
|
8
|
+
|
9
|
+
def image
|
10
|
+
embedded_pin_data["images"]["orig"]["url"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def video?
|
14
|
+
embedded_pin_data("is_video")
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
embedded_pin_data("description")
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO: Make this a Board object... not just a url
|
22
|
+
def board
|
23
|
+
embedded_pin_data["board"]["url"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def like_count
|
27
|
+
embedded_pin_data("like_count")
|
28
|
+
end
|
29
|
+
|
30
|
+
def repin_count
|
31
|
+
embedded_pin_data("repin_count")
|
32
|
+
end
|
33
|
+
|
34
|
+
def source_url
|
35
|
+
embedded_pin_data("link")
|
36
|
+
end
|
37
|
+
|
38
|
+
def pinner
|
39
|
+
JohnStamos::Pinner.new(@client, embedded_pin_data["pinner"]["username"])
|
40
|
+
end
|
41
|
+
|
42
|
+
def url
|
43
|
+
"http://pinterest.com/pin/#{@id}/"
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
def page
|
50
|
+
@page ||= @client.page_content(url)
|
51
|
+
end
|
52
|
+
|
53
|
+
def embedded_pin_json
|
54
|
+
embedded_script = page.search('script').select do |script|
|
55
|
+
script['src'].nil? && script.content.include?('Pc.startArgs')
|
56
|
+
end
|
57
|
+
|
58
|
+
embedded_script_content = embedded_script.first.content
|
59
|
+
# This regex used in the range snatches the parameter Pinterest uses to
|
60
|
+
# start their app... This parameter happens to be a JSON representation of
|
61
|
+
# the page.
|
62
|
+
raw_json = embedded_script_content[/Pc.startArgs = (.*);/, 1]
|
63
|
+
embedded_script_json = JSON.parse(raw_json)
|
64
|
+
|
65
|
+
embedded_script_json
|
66
|
+
end
|
67
|
+
|
68
|
+
def embedded_pin_data(attribute=nil)
|
69
|
+
value = embedded_pin_json["tree"]["options"]["module"]["data"]
|
70
|
+
|
71
|
+
if !attribute.nil?
|
72
|
+
value = value[attribute]
|
73
|
+
end
|
74
|
+
|
75
|
+
value
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
class JohnStamos::PinSearch
|
2
|
+
attr_accessor :next_bookmark, :search_text, :pin_ids, :limit
|
3
|
+
|
4
|
+
def initialize(client, search_text=nil, options={})
|
5
|
+
default_options = { limit: 50 }
|
6
|
+
options = default_options.merge(options)
|
7
|
+
@limit = options.fetch(:limit)
|
8
|
+
|
9
|
+
@client = client
|
10
|
+
@search_text = search_text
|
11
|
+
|
12
|
+
@pins, @pin_ids = [], []
|
13
|
+
@next_bookmark = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute!
|
17
|
+
raise JohnStamos::MissingSearchText if @search_text.nil?
|
18
|
+
|
19
|
+
first_retrieval!
|
20
|
+
|
21
|
+
until limit_reached? do
|
22
|
+
subsequent_retrieval!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def first_retrieval_url
|
27
|
+
raise JohnStamos::MissingSearchText if @search_text.nil?
|
28
|
+
"http://pinterest.com/search/pins/?q=#{URI::encode(@search_text)}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def subsequent_retrieval_url
|
32
|
+
'http://pinterest.com/resource/SearchResource/get/'
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_retrieval!
|
36
|
+
page = @client.page_content(first_retrieval_url)
|
37
|
+
|
38
|
+
embedded_script = page.search('script').select do |script|
|
39
|
+
script['src'].nil? && script.content.include?('Pc.startArgs')
|
40
|
+
end
|
41
|
+
|
42
|
+
embedded_script_content = embedded_script.first.content
|
43
|
+
# This regex used in the range snatches the parameter Pinterest uses to
|
44
|
+
# start their app... This parameter happens to be a JSON representation of
|
45
|
+
# the page.
|
46
|
+
raw_json = embedded_script_content[/Pc.startArgs = (.*);/, 1]
|
47
|
+
embedded_script_json = JSON.parse(raw_json)
|
48
|
+
|
49
|
+
pin_ids_from_embedded_script_json = pin_ids_from_first_retrieval(embedded_script_json)
|
50
|
+
|
51
|
+
pin_ids_up_to_limit(pin_ids_from_embedded_script_json)
|
52
|
+
@next_bookmark = next_bookmark_from_first_retrieval(embedded_script_json)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subsequent_retrieval!
|
56
|
+
raise JohnStamos::MissingNextBookmark if @next_bookmark.nil?
|
57
|
+
raise JohnStamos::MissingSearchText if @search_text.nil?
|
58
|
+
|
59
|
+
pins_json = @client.json_content(subsequent_retrieval_url, build_url_params)
|
60
|
+
pin_ids_from_json = pin_ids_from_subsequent_retrieval(pins_json)
|
61
|
+
pin_ids_up_to_limit(pin_ids_from_json)
|
62
|
+
|
63
|
+
@next_bookmark = next_bookmark_from_subsequent_retrieval(pins_json)
|
64
|
+
end
|
65
|
+
|
66
|
+
def more_results?
|
67
|
+
raise JohnStamos::MissingNextBookmark if @next_bookmark.nil?
|
68
|
+
@next_bookmark != "-end-"
|
69
|
+
end
|
70
|
+
|
71
|
+
def limit_reached?
|
72
|
+
@pin_ids.length == @limit.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
def pins
|
76
|
+
@pin_ids.map do |pinterest_pin_id|
|
77
|
+
JohnStamos::Pin.new(@client, pinterest_pin_id)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def pin_ids_from_first_retrieval(json)
|
83
|
+
json["tree"]["options"]["module"]["data"]["results"].map{ |pin| pin["id"] }
|
84
|
+
end
|
85
|
+
|
86
|
+
def pin_ids_from_subsequent_retrieval(json)
|
87
|
+
json["module"]["tree"]["children"].map{ |pin| pin["resource"]["options"]["id"] }
|
88
|
+
end
|
89
|
+
|
90
|
+
def next_bookmark_from_first_retrieval(json)
|
91
|
+
base_search_child = json["tree"]["children"].select do |child|
|
92
|
+
next if child["resource"].nil? || child["resource"]["name"].nil?
|
93
|
+
child["resource"]["name"] == "BaseSearchResource"
|
94
|
+
end
|
95
|
+
next_bookmark = base_search_child.first["resource"]["options"]["bookmarks"].first
|
96
|
+
|
97
|
+
next_bookmark
|
98
|
+
end
|
99
|
+
|
100
|
+
def next_bookmark_from_subsequent_retrieval(json)
|
101
|
+
json["module"]["tree"]["resource"]["options"]["bookmarks"].first
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_url_params
|
105
|
+
data_json = {
|
106
|
+
options: {
|
107
|
+
query: search_text,
|
108
|
+
bookmarks: [@next_bookmark],
|
109
|
+
show_scope_selector: true,
|
110
|
+
scope: "pins"
|
111
|
+
},
|
112
|
+
context: {
|
113
|
+
app_version: "e26ef9c"
|
114
|
+
},
|
115
|
+
"module" => {
|
116
|
+
name: "GridItems",
|
117
|
+
options: {
|
118
|
+
scrollable: true,
|
119
|
+
show_grid_footer: true,
|
120
|
+
centered: true,
|
121
|
+
reflow_all: true,
|
122
|
+
virtualize: true,
|
123
|
+
item_options: {
|
124
|
+
show_pinner: true,
|
125
|
+
show_pinned_from: false,
|
126
|
+
show_board: true
|
127
|
+
},
|
128
|
+
layout: "variable_height"
|
129
|
+
},
|
130
|
+
append: true,
|
131
|
+
error_strategy: 1
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
url_params = {
|
136
|
+
source_url: "/search/pins/?q=#{URI::encode(search_text)}",
|
137
|
+
"_" => 1375127302668,
|
138
|
+
data: data_json.to_json
|
139
|
+
}
|
140
|
+
url_params
|
141
|
+
end
|
142
|
+
|
143
|
+
def pin_ids_up_to_limit(ids)
|
144
|
+
ids.each do |id|
|
145
|
+
break if limit_reached?
|
146
|
+
@pin_ids << id
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class JohnStamos::Pinner
|
4
|
+
attr_reader :username
|
5
|
+
|
6
|
+
def initialize(client, username)
|
7
|
+
@client = client
|
8
|
+
@username = username
|
9
|
+
end
|
10
|
+
|
11
|
+
def url
|
12
|
+
"http://pinterest.com/#{@username}/"
|
13
|
+
end
|
14
|
+
|
15
|
+
def pin_count
|
16
|
+
embedded_pinner_data("pin_count")
|
17
|
+
end
|
18
|
+
|
19
|
+
def about
|
20
|
+
embedded_pinner_data("about")
|
21
|
+
end
|
22
|
+
|
23
|
+
def follower_count
|
24
|
+
embedded_pinner_data("follower_count")
|
25
|
+
end
|
26
|
+
|
27
|
+
def board_count
|
28
|
+
embedded_pinner_data("board_count")
|
29
|
+
end
|
30
|
+
|
31
|
+
def avatar
|
32
|
+
embedded_pinner_data("image_large_url")
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_name
|
36
|
+
embedded_pinner_data("first_name")
|
37
|
+
end
|
38
|
+
|
39
|
+
def last_name
|
40
|
+
embedded_pinner_data("last_name")
|
41
|
+
end
|
42
|
+
|
43
|
+
def full_name
|
44
|
+
embedded_pinner_data("full_name")
|
45
|
+
end
|
46
|
+
|
47
|
+
def website_url
|
48
|
+
embedded_pinner_data("website_url")
|
49
|
+
end
|
50
|
+
|
51
|
+
def location
|
52
|
+
embedded_pinner_data("location")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def page
|
57
|
+
@page ||= @client.page_content(url)
|
58
|
+
end
|
59
|
+
|
60
|
+
def embedded_pinner_json
|
61
|
+
embedded_script = page.search('script').select do |script|
|
62
|
+
script['src'].nil? && script.content.include?('Pc.startArgs')
|
63
|
+
end
|
64
|
+
|
65
|
+
embedded_script_content = embedded_script.first.content
|
66
|
+
# This regex used in the range snatches the parameter Pinterest uses to
|
67
|
+
# start their app... This parameter happens to be a JSON representation of
|
68
|
+
# the page.
|
69
|
+
raw_json = embedded_script_content[/Pc.startArgs = (.*);/, 1]
|
70
|
+
embedded_script_json = JSON.parse(raw_json)
|
71
|
+
|
72
|
+
embedded_script_json
|
73
|
+
end
|
74
|
+
|
75
|
+
def embedded_pinner_data(attribute)
|
76
|
+
value = embedded_pinner_json["tree"]["options"]["module"]["data"][attribute]
|
77
|
+
value = "" if value.nil?
|
78
|
+
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end
|
data/lib/john_stamos.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'mechanize'
|
2
|
+
require 'json'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'launchy'
|
5
|
+
|
6
|
+
require 'john_stamos/version'
|
7
|
+
require 'john_stamos/client'
|
8
|
+
require 'john_stamos/pin_search'
|
9
|
+
require 'john_stamos/pin'
|
10
|
+
require 'john_stamos/pinner'
|
11
|
+
|
12
|
+
module JohnStamos
|
13
|
+
class MissingSearchText < Exception; end
|
14
|
+
class MissingNextBookmark < Exception; end
|
15
|
+
|
16
|
+
private
|
17
|
+
def self.uncle_jesse
|
18
|
+
Launchy.open('http://www.youtube.com/watch?v=bLqAqIj8Rdc')
|
19
|
+
end
|
20
|
+
end
|