pinterest-dl 1.0.3 → 2.0.0
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 +4 -4
- data/CHANGELOG.md +40 -0
- data/LICENSE +13 -0
- data/README.md +142 -31
- data/bin/pinterest-dl +107 -0
- data/lib/pinterest-dl.rb +15 -191
- data/lib/pinterest_dl/api.rb +84 -0
- data/lib/pinterest_dl/board.rb +106 -0
- data/lib/pinterest_dl/client.rb +111 -0
- data/lib/pinterest_dl/configuration.rb +46 -0
- data/lib/pinterest_dl/downloader.rb +57 -0
- data/lib/pinterest_dl/errors.rb +49 -0
- data/lib/pinterest_dl/extractors/image.rb +47 -0
- data/lib/pinterest_dl/extractors/video.rb +61 -0
- data/lib/pinterest_dl/progress_bar.rb +21 -0
- data/lib/pinterest_dl/search.rb +142 -0
- data/lib/pinterest_dl/version.rb +6 -0
- metadata +31 -8
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module PinterestDL
|
|
8
|
+
class Search
|
|
9
|
+
RESOURCE_URL = 'https://www.pinterest.com/resource/BaseSearchResource/get/'
|
|
10
|
+
|
|
11
|
+
def initialize(client: PinterestDL::Client.new, config: PinterestDL.config)
|
|
12
|
+
@client = client
|
|
13
|
+
@config = config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(query, limit: 25, pages: 5)
|
|
17
|
+
results = []
|
|
18
|
+
bookmarks = nil
|
|
19
|
+
pages_fetched = 0
|
|
20
|
+
|
|
21
|
+
loop do
|
|
22
|
+
page = fetch_page(query, bookmarks: bookmarks)
|
|
23
|
+
pins = page[:pins]
|
|
24
|
+
results.concat(pins)
|
|
25
|
+
bookmarks = page[:bookmarks]
|
|
26
|
+
pages_fetched += 1
|
|
27
|
+
|
|
28
|
+
break if results.size >= limit
|
|
29
|
+
break if bookmarks.nil? || bookmarks.empty? || bookmarks == ['-end-']
|
|
30
|
+
break if pages_fetched >= pages
|
|
31
|
+
break if pins.empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
results.first(limit)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def fetch_page(query, bookmarks: nil)
|
|
40
|
+
session = establish_session(query)
|
|
41
|
+
body = @client.get(
|
|
42
|
+
build_search_url(session[:source_path], query, bookmarks),
|
|
43
|
+
use_cache: false,
|
|
44
|
+
headers: search_headers(session)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
json = safe_parse(body)
|
|
48
|
+
raise PinterestDL::NotFoundError, "No search results for \"#{query}\"" unless json
|
|
49
|
+
|
|
50
|
+
parse_search_response(json)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def establish_session(query)
|
|
54
|
+
source_path = "/search/pins/?q=#{URI.encode_www_form_component(query)}"
|
|
55
|
+
home_uri = URI("https://www.pinterest.com#{source_path}")
|
|
56
|
+
home_response = @client.raw_get(home_uri.to_s)
|
|
57
|
+
cookies = (home_response.get_fields('set-cookie') || []).map { |c| c.split(';').first }
|
|
58
|
+
|
|
59
|
+
{ source_path: source_path, home_uri: home_uri, cookies: cookies, csrf: csrf_token(cookies) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def csrf_token(cookies)
|
|
63
|
+
token_cookie = cookies.find { |c| c.start_with?('csrftoken=') }
|
|
64
|
+
token_cookie ? token_cookie.split('=', 2).last : 'undefined'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_search_url(source_path, query, bookmarks)
|
|
68
|
+
options = { query: query, scope: 'pins', page_size: 25 }
|
|
69
|
+
options[:bookmarks] = bookmarks if bookmarks && !bookmarks.empty?
|
|
70
|
+
data = { options: options, context: {} }.to_json
|
|
71
|
+
|
|
72
|
+
res_uri = URI(RESOURCE_URL)
|
|
73
|
+
res_uri.query = URI.encode_www_form('source_url' => source_path, 'data' => data)
|
|
74
|
+
res_uri.to_s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def search_headers(session)
|
|
78
|
+
cookie_header = [@config.cookies, session[:cookies].join('; ')].compact.reject(&:empty?).join('; ')
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
'Accept' => 'application/json, text/javascript, */*, q=0.01',
|
|
82
|
+
'X-Requested-With' => 'XMLHttpRequest',
|
|
83
|
+
'X-Pinterest-PWS-Handler' => 'www/search/[scope].js',
|
|
84
|
+
'X-CSRFToken' => session[:csrf],
|
|
85
|
+
'Referer' => session[:home_uri].to_s,
|
|
86
|
+
'Cookie' => cookie_header
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_search_response(json)
|
|
91
|
+
data_node = json.dig('resource_response', 'data') || {}
|
|
92
|
+
raw_results = data_node['results'] || []
|
|
93
|
+
next_bookmarks = json.dig('resource_response', 'bookmark') ||
|
|
94
|
+
json.dig('resource', 'options', 'bookmarks')
|
|
95
|
+
|
|
96
|
+
{ pins: raw_results.filter_map { |r| build_pin(r) }, bookmarks: Array(next_bookmarks) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_pin(raw)
|
|
100
|
+
id = raw['id']
|
|
101
|
+
return nil unless id
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
id: id,
|
|
105
|
+
pin_url: "https://www.pinterest.com/pin/#{id}/",
|
|
106
|
+
title: raw['title'] || raw['grid_title'],
|
|
107
|
+
description: raw['description'],
|
|
108
|
+
creator: pin_creator(raw),
|
|
109
|
+
image_url: pin_image_url(raw),
|
|
110
|
+
video_url: first_video_url(raw)
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def pin_creator(raw)
|
|
115
|
+
raw.dig('pinner', 'username') || raw.dig('pinner', 'full_name')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def pin_image_url(raw)
|
|
119
|
+
raw.dig('images', 'orig', 'url') || fallback_image_url(raw)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def fallback_image_url(raw)
|
|
123
|
+
images = raw['images']
|
|
124
|
+
return nil unless images
|
|
125
|
+
|
|
126
|
+
images.values.filter_map { |v| v['url'] if v.is_a?(Hash) }.last
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def first_video_url(raw)
|
|
130
|
+
video_list = raw.dig('videos', 'video_list')
|
|
131
|
+
return nil unless video_list
|
|
132
|
+
|
|
133
|
+
video_list.values.first&.dig('url')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def safe_parse(body)
|
|
137
|
+
JSON.parse(body)
|
|
138
|
+
rescue JSON::ParserError, TypeError
|
|
139
|
+
nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
metadata
CHANGED
|
@@ -1,28 +1,50 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pinterest-dl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Monji
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
11
12
|
dependencies: []
|
|
12
|
-
description:
|
|
13
|
+
description: A Ruby toolkit for getting direct image and video URLs from Pinterest
|
|
14
|
+
pins, searching pins, fetching board contents, and downloading media to disk — as
|
|
15
|
+
a library or a CLI.
|
|
13
16
|
email: hoseinmonjiofficial@gmail.com
|
|
14
|
-
executables:
|
|
17
|
+
executables:
|
|
18
|
+
- pinterest-dl
|
|
15
19
|
extensions: []
|
|
16
20
|
extra_rdoc_files: []
|
|
17
21
|
files:
|
|
22
|
+
- CHANGELOG.md
|
|
23
|
+
- LICENSE
|
|
18
24
|
- README.md
|
|
25
|
+
- bin/pinterest-dl
|
|
19
26
|
- lib/pinterest-dl.rb
|
|
27
|
+
- lib/pinterest_dl/api.rb
|
|
28
|
+
- lib/pinterest_dl/board.rb
|
|
29
|
+
- lib/pinterest_dl/client.rb
|
|
30
|
+
- lib/pinterest_dl/configuration.rb
|
|
31
|
+
- lib/pinterest_dl/downloader.rb
|
|
32
|
+
- lib/pinterest_dl/errors.rb
|
|
33
|
+
- lib/pinterest_dl/extractors/image.rb
|
|
34
|
+
- lib/pinterest_dl/extractors/video.rb
|
|
35
|
+
- lib/pinterest_dl/progress_bar.rb
|
|
36
|
+
- lib/pinterest_dl/search.rb
|
|
37
|
+
- lib/pinterest_dl/version.rb
|
|
20
38
|
homepage: https://github.com/monji024/pinterest-dl
|
|
21
39
|
licenses:
|
|
22
|
-
-
|
|
40
|
+
- WTFPL
|
|
23
41
|
metadata:
|
|
24
42
|
source_code_uri: https://github.com/monji024/pinterest-dl
|
|
25
43
|
bug_tracker_uri: https://github.com/monji024/pinterest-dl/issues
|
|
44
|
+
changelog_uri: https://github.com/monji024/pinterest-dl/blob/main/CHANGELOG.md
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
46
|
+
documentation_uri: https://rubydoc.info/gems/pinterest-dl
|
|
47
|
+
post_install_message:
|
|
26
48
|
rdoc_options: []
|
|
27
49
|
require_paths:
|
|
28
50
|
- lib
|
|
@@ -30,14 +52,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
30
52
|
requirements:
|
|
31
53
|
- - ">="
|
|
32
54
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2.
|
|
55
|
+
version: 2.7.0
|
|
34
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
57
|
requirements:
|
|
36
58
|
- - ">="
|
|
37
59
|
- !ruby/object:Gem::Version
|
|
38
60
|
version: '0'
|
|
39
61
|
requirements: []
|
|
40
|
-
rubygems_version: 3.
|
|
62
|
+
rubygems_version: 3.3.15
|
|
63
|
+
signing_key:
|
|
41
64
|
specification_version: 4
|
|
42
|
-
summary: Extract direct image/video
|
|
65
|
+
summary: Extract direct image/video URLs, search, and download from Pinterest
|
|
43
66
|
test_files: []
|