pinterest-dl 1.0.1 → 1.0.3

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +75 -56
  3. data/lib/pinterest-dl.rb +193 -135
  4. metadata +4 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27626810a9014457fd993c95652b1705437df4c008e4df3586bdea8baf0b7e65
4
- data.tar.gz: ddb835faccb168517d3ff36ef36e1d601094d23ebb29b8f36b12473d67ffd62f
3
+ metadata.gz: 73cae48cd87360325168ee188b8a67b25944402f33857a61dee4ea3a13457f91
4
+ data.tar.gz: 313a17fd00772aa8980ee8389dc5bd934b8217225f4e4b11f130ad4882e629c0
5
5
  SHA512:
6
- metadata.gz: c945ee342ddd919339e480708d7d9f88710e56841f7e5d744c878a4c88c68ad53e37383b9f71a2a333a858a5874ed50f0d0c1f77552b60e87ca9d5dbcbf38800
7
- data.tar.gz: 53d172562e998a79ef4931ce716850be86a86ba13cb9b5c875b4bc2e77fdc77a0f4fa7b5f2c80d71b392130b6f7566411793e696ffe2de45c74507a8d5442bbd
6
+ metadata.gz: e08fc0e08bec2a47a91659d10b1ba3a87e6d2c70e81fc27134366768108ebf3e1a5f10bfea2dc3391c63fa038eb9daba58594ffd8c5997338b240dd21fcaa558
7
+ data.tar.gz: 7bcfe71875c82f31ddaf9e27a0f648b646bb6381045830d03d6ee0a8fb561283e8a0eb4e85036915a6d71e6f5865e05f0aa4a5e0658f601b593f8e5a6f42f714
data/README.md CHANGED
@@ -1,57 +1,76 @@
1
- <div align="center">
2
-
3
- # pinterest-dl
4
-
5
- ---
6
-
7
- [![Gem Downloads](https://img.shields.io/gem/dt/pinterest-dl)](https://rubygems.org/gems/pinterest-dl)
8
- [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)
9
- [![Ruby](https://img.shields.io/badge/Ruby-2.5+-red)]()
10
-
11
-
12
- <img src="https://img.icons8.com/color/96/000000/pinterest--v1.png" width="80">
13
-
14
- </div>
15
-
16
- ---
17
-
18
- Get direct image and video URLs from Pinterest:)
19
-
20
- ![Demo](https://github.com/monji024/pinterest-dl/raw/main/test.gif)
21
-
22
- Using a VPN is important for those in Iran!!!
23
-
24
- ## Installation
25
-
26
- ```ruby
27
- gem install pinterest-dl
28
- ```
29
-
30
- ## Usage
31
-
32
- ```ruby
33
- require 'pinterest-dl'
34
- ```
35
-
36
- ### Get image url
37
-
38
- ```ruby
39
- url = PinterestDL.get_image_url('https://www.pinterest.com/pin/example/')
40
- # => "https://i.pinimg.com/originals/8e/2a/..."
41
- ```
42
-
43
- ### Get video url
44
-
45
- ```ruby
46
- url = PinterestDL.get_video_url('https://www.pinterest.com/pin/example/')
47
- # => "https://v.pinimg.com/videos/..."
48
- ```
49
-
50
- ## Links
51
-
52
- - [RubyGem](https://rubygems.org/gems/pinterest-dl)
53
- - [GitHub](https://github.com/monji024/pinterest-dl)
54
-
55
- ## License
56
-
1
+ <div align="center">
2
+
3
+ # pinterest-dl
4
+
5
+ ---
6
+
7
+ [![Gem Downloads](https://img.shields.io/gem/dt/pinterest-dl)](https://rubygems.org/gems/pinterest-dl)
8
+ [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)
9
+ [![Ruby](https://img.shields.io/badge/Ruby-2.5+-red)](https://www.ruby-lang.org/)
10
+
11
+ <img src="https://img.icons8.com/color/96/000000/pinterest--v1.png" width="80">
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ Get direct image and video URLs from Pinterest.
18
+
19
+ ![Demo](https://github.com/monji024/pinterest-dl/raw/main/test.gif)
20
+
21
+ > **Note:** Users in Iran may need a VPN to access Pinterest.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ gem install pinterest-dl
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```ruby
32
+ require 'pinterest-dl'
33
+ ```
34
+
35
+ ### Get Image URL
36
+
37
+ ```ruby
38
+ url = PinterestDL.get_image_url('https://www.pinterest.com/pin/example/')
39
+ ```
40
+
41
+ ### Get Video URL
42
+
43
+ ```ruby
44
+ url = PinterestDL.get_video_url('https://www.pinterest.com/pin/example/')
45
+ ```
46
+
47
+ ### Search Pins
48
+
49
+ ```ruby
50
+ # Search for pins with a specific query
51
+ results = PinterestDL.search('nature landscape', limit: 10)
52
+
53
+ # Returns an array of pin objects with:
54
+ # id, pin_url, title, description, image_url, video_url
55
+
56
+ # Get only image URLs from search results
57
+ images = PinterestDL.search_images('sunset', limit: 5)
58
+ # => ["https://i.pinimg.com/originals/...", ...]
59
+
60
+ # Get only video URLs from search results
61
+ videos = PinterestDL.search_videos('tutorial', limit: 3)
62
+ # => ["https://v.pinimg.com/videos/...", ...]
63
+
64
+ # Get search results as JSON
65
+ json_results = PinterestDL.search_json('anime art', limit: 20)
66
+ # => Returns formatted JSON string
67
+ ```
68
+
69
+ ## Links
70
+
71
+ - [RubyGems](https://rubygems.org/gems/pinterest-dl)
72
+ - [GitHub](https://github.com/monji024/pinterest-dl)
73
+
74
+ ## License
75
+
57
76
  MIT © [monji024](https://github.com/monji024)
data/lib/pinterest-dl.rb CHANGED
@@ -1,135 +1,193 @@
1
- #!/usr/bin/env ruby
2
- # pinterest-dl.rb
3
- # Pinterest downloader
4
- # creator: Monji
5
- # ver: 1.0.0
6
- # github: https://github.com/monji024/pinterest-dl
7
-
8
- require 'net/http'
9
- require 'uri'
10
- require 'json'
11
- require 'openssl'
12
-
13
-
14
- module PinterestDL
15
- ver = '1.0.0'.freeze
16
- github = 'https://github.com/monji024/pinterest-dl'.freeze
17
-
18
- puts "pinterest-dl v#{ver} (#{github})\n\n"
19
-
20
- USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'.freeze
21
-
22
- class << self
23
- def get_image_url(pin_url)
24
- html = fetch_html(pin_url)
25
- return nil unless html
26
-
27
- # 1 -> json-ld
28
- url = extract_json_ld(html, 'ImageObject')
29
- return url if url
30
-
31
- # 2 -> open graph
32
- match = html.match(%r{<meta[^>]*property="og:image"[^>]*content="([^"]+)"})
33
- if match
34
- url = match[1]
35
- puts "image url: #{url}"
36
- return url
37
- end
38
-
39
- # 3 -> direct i.pinimg.com patterns
40
- match = html.match(%r{https://i\.pinimg\.com/(?:originals|474x|236x|736x)/[^"\s]+})
41
- if match
42
- url = match[0]
43
- reset = "\e[0m"
44
- green = "\e[32m"
45
- puts "#{green}image url :#{reset} #{url}"
46
- return url
47
- end
48
-
49
- puts 'No found'
50
- nil
51
- end
52
-
53
-
54
- def get_video_url(pin_url)
55
- html = fetch_html(pin_url)
56
- return nil unless html
57
-
58
- # 1 -> json-ld
59
- url = extract_json_ld(html, 'VideoObject')
60
- if url
61
- url = url.gsub('\\u002F', '/').gsub('\\', '')
62
- puts "ok : #{url}"
63
- return url
64
- end
65
-
66
- # 2 -> Open Graph video
67
- match = html.match(%r{<meta[^>]*property="og:video"[^>]*content="([^"]+)"})
68
- if match
69
- url = match[1].gsub('\\u002F', '/').gsub('\\', '')
70
- puts "video url: #{url}"
71
- return url
72
- end
73
-
74
- # 3 -> contentUrl in page source
75
- match = html.match(/"contentUrl"\s*:\s*"([^"]+\.mp4[^"]*)"/)
76
- if match
77
- url = match[1].gsub('\\u002F', '/').gsub('\\', '')
78
- green = "\e[32m"
79
- reset = "\e[0m"
80
- puts "#{green}video url :#{reset} #{url}"
81
- return url
82
- end
83
-
84
- puts 'No found'
85
- nil
86
- end
87
-
88
- private
89
-
90
- def fetch_html(url)
91
- uri = URI(url)
92
- http = Net::HTTP.new(uri.host, uri.port)
93
- http.use_ssl = (uri.scheme == 'https')
94
- http.max_retries = 2
95
- http.open_timeout = 10
96
- http.read_timeout = 20
97
-
98
- resp = http.request(Net::HTTP::Head.new(uri, 'User-Agent' => USER_AGENT))
99
-
100
- 5.times do
101
- break unless resp.is_a?(Net::HTTPRedirection)
102
- location = resp['location']
103
- location = URI.join(uri.to_s, location) if location.start_with?('/')
104
- uri = URI(location)
105
- http = Net::HTTP.new(uri.host, uri.port)
106
- http.use_ssl = (uri.scheme == 'https')
107
- resp = http.request(Net::HTTP::Head.new(uri, 'User-Agent' => USER_AGENT))
108
- end
109
-
110
- # get final page
111
- req = Net::HTTP::Get.new(uri, 'User-Agent' => USER_AGENT)
112
- resp = http.request(req)
113
-
114
- return resp.body if resp.is_a?(Net::HTTPSuccess)
115
- nil
116
- rescue StandardError => e
117
- red = "\e[31m"
118
- reset = "\e[0m"
119
- puts "#{red}http err!:#{reset} #{e.message}"
120
- nil
121
- end
122
- def extract_json_ld(html, type)
123
- match = html.match(%r{<script[^>]*type="application/ld\+json"[^>]*>(.*?)</script>}m)
124
- return nil unless match
125
-
126
- begin
127
- data = JSON.parse(match[1])
128
- data = data[0] if data.is_a?(Array) && !data.empty?
129
- return data['contentUrl'] if data['@type'] == type && data['contentUrl']
130
- rescue JSON::ParserError
131
- end
132
- nil
133
- end
134
- end
135
- end
1
+ #!/usr/bin/env ruby
2
+ # Pinterest downloader
3
+ # creator: Monji
4
+ # ver: 1.0.3
5
+ # github: https://github.com/monji024/pinterest-dl
6
+
7
+ require 'net/http'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'openssl'
11
+
12
+
13
+ module PinterestDL
14
+ ver = '1.0.3'.freeze
15
+ github = 'https://github.com/monji024/pinterest-dl'.freeze
16
+
17
+ puts "pinterest-dl v#{ver} (#{github})\n\n"
18
+
19
+ USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'.freeze
20
+
21
+ class << self
22
+ def get_image_url(pin_url)
23
+ html = fetch_html(pin_url)
24
+ return nil unless html
25
+
26
+ # 1 -> json-ld
27
+ url = extract_json_ld(html, 'ImageObject')
28
+ return url if url
29
+ # 2 -> open graph
30
+ match = html.match(%r{<meta[^>]*property="og:image"[^>]*content="([^"]+)"})
31
+ if match
32
+ url = match[1]
33
+ puts "image url: #{url}"
34
+ return url
35
+ end
36
+ # 3 -> direct i.pinimg.com patterns
37
+ match = html.match(%r{https://i\.pinimg\.com/(?:originals|474x|236x|736x)/[^"\s]+})
38
+ if match
39
+ url = match[0]
40
+ reset = "\e[0m"
41
+ green = "\e[32m"
42
+ puts "#{green}image url :#{reset} #{url}"
43
+ return url
44
+ end
45
+ puts 'No found:)'
46
+ nil
47
+ end
48
+
49
+
50
+ def get_video_url(pin_url)
51
+ html = fetch_html(pin_url)
52
+ return nil unless html
53
+
54
+ # 1 -> json-ld
55
+ url = extract_json_ld(html, 'VideoObject')
56
+ if url
57
+ url = url.gsub('\\u002F', '/').gsub('\\', '')
58
+ puts "ok : #{url}"
59
+ return url
60
+ end
61
+ # 2 -> Open Graph video
62
+ match = html.match(%r{<meta[^>]*property="og:video"[^>]*content="([^"]+)"})
63
+ if match
64
+ url = match[1].gsub('\\u002F', '/').gsub('\\', '')
65
+ puts "video url: #{url}"
66
+ return url
67
+ end
68
+ # 3 -> contentUrl in page source
69
+ match = html.match(/"contentUrl"\s*:\s*"([^"]+\.mp4[^"]*)"/)
70
+ if match
71
+ url = match[1].gsub('\\u002F', '/').gsub('\\', '')
72
+ green = "\e[32m"
73
+ reset = "\e[0m"
74
+ puts "#{green}video url :#{reset} #{url}"
75
+ return url
76
+ end
77
+
78
+ puts 'No found'
79
+ nil
80
+ end
81
+ def search(query, limit: 25)
82
+ source_path = "/search/pins/?q=#{URI.encode_www_form_component(query)}"
83
+ home_uri = URI("https://www.pinterest.com#{source_path}")
84
+ http = Net::HTTP.new(home_uri.host, home_uri.port)
85
+ http.use_ssl = true
86
+ http.open_timeout = 10
87
+ http.read_timeout = 20
88
+ home_req = Net::HTTP::Get.new(home_uri, 'User-Agent' => USER_AGENT)
89
+ home_resp = http.request(home_req)
90
+ cookies = (home_resp.get_fields('set-cookie') || []).map { |c| c.split(';').first }
91
+ cookie_header = cookies.join('; ')
92
+ csrf = cookies.find { |c| c.start_with?('csrftoken=') }&.split('=', 2)&.last || 'undefined'
93
+ data = {
94
+ options: { query: query, scope: 'pins', page_size: limit },
95
+ context: {}}.to_json
96
+ res_uri = URI('https://www.pinterest.com/resource/BaseSearchResource/get/')
97
+ res_uri.query = URI.encode_www_form('source_url' => source_path, 'data' => data)
98
+ req = Net::HTTP::Get.new(res_uri)
99
+ req['User-Agent'] = USER_AGENT
100
+ req['Accept'] = 'application/json, text/javascript, */*, q=0.01'
101
+ req['X-Requested-With'] = 'XMLHttpRequest'
102
+ req['X-Pinterest-PWS-Handler'] = 'www/search/[scope].js'
103
+ req['X-CSRFToken'] = csrf
104
+ req['Referer'] = home_uri.to_s
105
+ req['Cookie'] = cookie_header
106
+ resp = http.request(req)
107
+ unless resp.is_a?(Net::HTTPSuccess)
108
+ puts "\e[31msearch http err!:\e[0m #{resp.code}"
109
+ return []
110
+ end
111
+ json = begin
112
+ JSON.parse(resp.body)
113
+ rescue JSON::ParserError
114
+ nil
115
+ end
116
+ return [] unless json
117
+ raw_results = (json.dig('resource_response', 'data', 'results') || []).first(limit)
118
+ pins = raw_results.filter_map do |r|
119
+ id = r['id']
120
+ next nil unless id
121
+ orig_image = r.dig('images', 'orig', 'url')
122
+ fallback_image = r['images']&.values&.map { |v| v.is_a?(Hash) ? v['url'] : nil }&.compact&.last
123
+ image_url = orig_image || fallback_image
124
+ video_url = r.dig('videos', 'video_list')&.values&.first&.dig('url')
125
+ {
126
+ id: id,
127
+ pin_url: "https://www.pinterest.com/pin/#{id}/",
128
+ title: r['title'] || r['grid_title'],
129
+ description: r['description'],
130
+ image_url: image_url,
131
+ video_url: video_url
132
+ }
133
+ end
134
+ green = "\e[32m"
135
+ reset = "\e[0m"
136
+ puts "#{green}found #{pins.size} pin(s) for \"#{query}\"#{reset}"
137
+ pins
138
+ rescue StandardError => e
139
+ puts "\e[31msearch err!:\e[0m #{e.message}"
140
+ []
141
+ end
142
+ def search_json(query, limit: 25)
143
+ JSON.pretty_generate(search(query, limit: limit))
144
+ end
145
+ def search_images(query, limit: 25)
146
+ search(query, limit: limit).filter_map { |p| p[:image_url] }
147
+ end
148
+ def search_videos(query, limit: 25)
149
+ search(query, limit: limit).filter_map { |p| p[:video_url] }
150
+ end
151
+ private
152
+
153
+ def fetch_html(url)
154
+ uri = URI(url)
155
+ http = Net::HTTP.new(uri.host, uri.port)
156
+ http.use_ssl = (uri.scheme == 'https')
157
+ http.max_retries = 2
158
+ http.open_timeout = 10
159
+ http.read_timeout = 20
160
+ resp = http.request(Net::HTTP::Head.new(uri, 'User-Agent' => USER_AGENT))
161
+ 5.times do
162
+ break unless resp.is_a?(Net::HTTPRedirection)
163
+ location = resp['location']
164
+ location = URI.join(uri.to_s, location) if location.start_with?('/')
165
+ uri = URI(location)
166
+ http = Net::HTTP.new(uri.host, uri.port)
167
+ http.use_ssl = (uri.scheme == 'https')
168
+ resp = http.request(Net::HTTP::Head.new(uri, 'User-Agent' => USER_AGENT))
169
+ end
170
+ # get final page
171
+ req = Net::HTTP::Get.new(uri, 'User-Agent' => USER_AGENT)
172
+ resp = http.request(req)
173
+ return resp.body if resp.is_a?(Net::HTTPSuccess)
174
+ nil
175
+ rescue StandardError => e
176
+ red = "\e[31m"
177
+ reset = "\e[0m"
178
+ puts "#{red}http err!:#{reset} #{e.message}"
179
+ nil
180
+ end
181
+ def extract_json_ld(html, type)
182
+ match = html.match(%r{<script[^>]*type="application/ld\+json"[^>]*>(.*?)</script>}m)
183
+ return nil unless match
184
+ begin
185
+ data = JSON.parse(match[1])
186
+ data = data[0] if data.is_a?(Array) && !data.empty?
187
+ return data['contentUrl'] if data['@type'] == type && data['contentUrl']
188
+ rescue JSON::ParserError
189
+ end
190
+ nil
191
+ end
192
+ end
193
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinterest-dl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Monji
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-28 00:00:00.000000000 Z
10
+ date: 2026-07-28 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Simple Pinterest Downloader Tool:)
14
13
  email: hoseinmonjiofficial@gmail.com
@@ -24,7 +23,6 @@ licenses:
24
23
  metadata:
25
24
  source_code_uri: https://github.com/monji024/pinterest-dl
26
25
  bug_tracker_uri: https://github.com/monji024/pinterest-dl/issues
27
- post_install_message:
28
26
  rdoc_options: []
29
27
  require_paths:
30
28
  - lib
@@ -39,8 +37,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
39
37
  - !ruby/object:Gem::Version
40
38
  version: '0'
41
39
  requirements: []
42
- rubygems_version: 3.5.22
43
- signing_key:
40
+ rubygems_version: 3.6.3
44
41
  specification_version: 4
45
- summary: Extract direct image/video URLs from Pinterest!!!
42
+ summary: Extract direct image/video/search URLs from Pinterest!!!
46
43
  test_files: []