pinterest-dl 2.0.3 → 2.0.4

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
  SHA256:
3
- metadata.gz: 2ba41b3cdd44c6463cfffde77fc4b9db8185f47b1d62e3a9555b9408bfd4f197
4
- data.tar.gz: b032e121ffb9750ed128b0a0c6ce5d0266dea8db4fbc99ee1bd6579e8f680d1a
3
+ metadata.gz: ab49a0f8fc2500835b07ec8abf935c4a643d7a9fa8f6e2de9bdb5250f10ef24c
4
+ data.tar.gz: 9fb506ca599f3a1625989ab178c7ed4f0c0e5ddad236f416f514666c90741bf0
5
5
  SHA512:
6
- metadata.gz: 11fe875c2a1eabda28fb7f7b4f342b33565dc141b5fd7ff06cac800595a834c9156abd9fe61544f75eed944f0553fc180183cad5ac7fdef6ca13a03023e179ae
7
- data.tar.gz: dc3600f15608c7717f2010cceef198e636e423b3788ee5763ff4e49fdfd19f49cef545e5206d1a7a0981d658194a97745aecbc0f373da038062f381b2ad74ec6
6
+ metadata.gz: e3962e2ea256111c1c9178947a0aa026daec6fed0c1cfefb2f81d92dd77299eebc9c2634f2ecf9e74ede3edfacb327ce28381c9aeffa951f9960c81f8883dddd
7
+ data.tar.gz: '09407f8541a4042be42584393524b8aee0147654d46c0abdc1d0c17ad9467bbaa17406fd260099fe214db43aa946553f20a696524616e66e2817477d6cd897b8'
data/CHANGELOG.md CHANGED
@@ -3,7 +3,7 @@
3
3
  All notable changes to this project are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
- ## [2.0.2]
6
+ ## [2.0.3]
7
7
 
8
8
  ### Fixed
9
9
  - **`PinterestDL::Board` was broken.** `lib/pinterest_dl/board.rb` accidentally
@@ -11,6 +11,11 @@ This project follows [Semantic Versioning](https://semver.org/).
11
11
  search logic), so it silently collided with `PinterestDL::Search` at load
12
12
  time and `PinterestDL.board(...)` never worked correctly. Restored the
13
13
  correct `Board` class backed by Pinterest's `BoardFeedResource`.
14
+ - `PinterestDL.board` requests were missing a session cookie and CSRF token,
15
+ which Pinterest's internal resource API requires — every board request
16
+ came back as HTTP 403. `Board` now loads the board page first to obtain
17
+ a session cookie + CSRF token before calling the resource API, the same
18
+ way `Search` already did.
14
19
  - `pin.it/...` short links were rejected by `get_image_url` / `get_video_url` /
15
20
  `get_media` even though Pinterest itself redirects them to a normal pin
16
21
  page. `PIN_URL_PATTERN` now accepts `pin.it/...` links too.
@@ -74,7 +74,6 @@ module PinterestDL
74
74
  end
75
75
 
76
76
  private
77
-
78
77
  def extract_pin_image(html, quality:)
79
78
  pin = Extractors::PinData.extract(html)
80
79
  url = pin && pin[:image_url]
@@ -40,7 +40,13 @@ module PinterestDL
40
40
  private
41
41
 
42
42
  def fetch_page(match, bookmarks:)
43
- body = @client.get(build_request_url(match, bookmarks), use_cache: false, headers: request_headers)
43
+ session = establish_session(match)
44
+ body = @client.get(
45
+ build_request_url(match, bookmarks),
46
+ use_cache: false,
47
+ headers: board_headers(session)
48
+ )
49
+
44
50
  json = safe_parse(body)
45
51
  raise PinterestDL::NotFoundError, "Board not found: #{match[0]}" unless json
46
52
 
@@ -50,6 +56,20 @@ module PinterestDL
50
56
  { pins: Array(data_node).filter_map { |r| build_pin(r) }, bookmarks: Array(next_bookmarks) }
51
57
  end
52
58
 
59
+ def establish_session(match)
60
+ board_path = "/#{match[:username]}/#{match[:slug]}/"
61
+ board_uri = URI("https://www.pinterest.com#{board_path}")
62
+ home_response = @client.raw_get(board_uri.to_s)
63
+ cookies = (home_response.get_fields('set-cookie') || []).map { |c| c.split(';').first }
64
+
65
+ { board_path: board_path, board_uri: board_uri, cookies: cookies, csrf: csrf_token(cookies) }
66
+ end
67
+
68
+ def csrf_token(cookies)
69
+ token_cookie = cookies.find { |c| c.start_with?('csrftoken=') }
70
+ token_cookie ? token_cookie.split('=', 2).last : 'undefined'
71
+ end
72
+
53
73
  def build_request_url(match, bookmarks)
54
74
  options = {
55
75
  board_url: "/#{match[:username]}/#{match[:slug]}/",
@@ -64,10 +84,16 @@ module PinterestDL
64
84
  res_uri.to_s
65
85
  end
66
86
 
67
- def request_headers
87
+ def board_headers(session)
88
+ cookie_header = [@config.cookies, session[:cookies].join('; ')].compact.reject(&:empty?).join('; ')
89
+
68
90
  {
69
91
  'Accept' => 'application/json, text/javascript, */*, q=0.01',
70
- 'X-Requested-With' => 'XMLHttpRequest'
92
+ 'X-Requested-With' => 'XMLHttpRequest',
93
+ 'X-Pinterest-PWS-Handler' => 'www/[username]/[slug].js',
94
+ 'X-CSRFToken' => session[:csrf],
95
+ 'Referer' => session[:board_uri].to_s,
96
+ 'Cookie' => cookie_header
71
97
  }
72
98
  end
73
99
 
@@ -71,14 +71,7 @@ module PinterestDL
71
71
  raise PinterestDL::NotFoundError, "Redirected without a location header (#{url})" unless location
72
72
 
73
73
  location = URI.join(url, location).to_s if location.start_with?('/')
74
-
75
- uri = URI(location)
76
- if uri.host&.match?(/^(?:www\.)?pinterest\.(?!com$)[a-z.]+$/i) || uri.host == 'pin.it'
77
- uri.host = 'www.pinterest.com'
78
- location = uri.to_s
79
- end
80
-
81
- get_or_raw(location, raw: raw)
74
+ get_or_raw(location, raw: raw)
82
75
  when Net::HTTPForbidden
83
76
  raise PinterestDL::RateLimitError, "Request to #{url} was blocked (HTTP 403)"
84
77
  else
@@ -6,13 +6,16 @@ module PinterestDL
6
6
  module Extractors
7
7
  module PinData
8
8
  SCRIPT_PATTERN = %r{<script[^>]*id=["']__PWS_DATA__["'][^>]*>(.*?)</script>}m.freeze
9
+
9
10
  module_function
11
+
10
12
  def extract(html)
11
13
  data = parse_embedded_json(html)
12
14
  return nil unless data
13
15
 
14
16
  pin = find_pin_node(data)
15
17
  return nil unless pin
18
+
16
19
  {
17
20
  image_url: pin.dig('images', 'orig', 'url'),
18
21
  video_url: first_video_url(pin),
@@ -3,7 +3,6 @@
3
3
  # Monji024
4
4
  module PinterestDL
5
5
  module Extractors
6
- # Extracts the direct video URL for a pin from raw HTML.
7
6
  module Video
8
7
  module_function
9
8
 
@@ -50,7 +50,8 @@ module PinterestDL
50
50
  parse_search_response(json)
51
51
  end
52
52
 
53
-
53
+ # Pinterest's search resource requires a session cookie + CSRF token
54
+ # obtained by first loading the human-facing search page.
54
55
  def establish_session(query)
55
56
  source_path = "/search/pins/?q=#{URI.encode_www_form_component(query)}"
56
57
  home_uri = URI("https://www.pinterest.com#{source_path}")
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Monji024
4
4
  module PinterestDL
5
- VERSION = '2.0.3'
5
+ VERSION = '2.0.4'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinterest-dl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Monji
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-29 00:00:00.000000000 Z
11
+ date: 2026-08-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby toolkit for getting direct image and video URLs from Pinterest
14
14
  pins, searching pins, fetching board contents, and downloading media to disk — as