cineworld_uk 4.3.1-x86_64-linux-gnu → 4.4.0-x86_64-linux-gnu

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: 9c445f90a89f599972722f5a60105516629dad8ce1815cc1f6ac62c66cd96fa5
4
- data.tar.gz: 004be778b3c64ae3d6d1cca58b4107d5cbaa4e5cc80ffcb26fb435c3523dff8a
3
+ metadata.gz: bda5e3524fc495d9cd2299b5a8563d1a0a06c1268565282dbc9f861b5902c173
4
+ data.tar.gz: f63b13ab73cd926c9954181a3270b24312bcf92635d283ac8f4517495f1bfec7
5
5
  SHA512:
6
- metadata.gz: e0c862d5c7b251717551c6cff2b6e0c209caf465353f8ab264f1d9d564466b258427574130b30008bcd22311e76b9a10c8079c02358f0ad698a02376c5dc6c8e
7
- data.tar.gz: 31341179ff88509fc1f164998108f70fd77cc0dc8de2363a498326f6aebddf2a222855d0a5e07dd87599e55ca45d6d61aaf646f6611a5d7f7b725def628bc52e
6
+ metadata.gz: 0242c5dd32fc1240940a8311b1772e4994288bb7ca4179b6ad1216b48dec12cb9b1e6f99347df0d35c73ccefa88725cd55a71a081ff72b6f7b3df076024ceb32
7
+ data.tar.gz: dd2f8ca6902de3884be7b8688738d264092fff5148060e4cad33e7f4c15c52e080d0def03d93c4ce749251738d105d209d538fe04ae42b0646683a3999c3fd41
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
+ ## 4.4.0 - 2026-10-31
6
+
7
+ ### Added
8
+ - `Cinema#latitude` and `Cinema#longitude` as Floats in decimal degrees, from the Vista sites response. They are `nil` when a site has no location or sits at 0, 0.
9
+ - `CineworldUk.ensure_token!` fetches a guest token only when there is none or it expires within an hour, and `CineworldUk.token_expires_at` reads its expiry. Both come from cinebase's `Vista::TokenHolder`.
10
+
11
+ ### Changed
12
+ - A token the API rejects is cleared from `CineworldUk.token`, so the next `ensure_token!` fetches a fresh one.
13
+ - Requires cinebase 5.6.
14
+
15
+ ## 4.3.2 - 2026-10-22
16
+
17
+ ### Changed
18
+ - Reuse one Vista client, and so one kept-alive connection, per thread instead of opening a TLS connection for every request. Requires cinebase 5.5. A cinema day now takes about 0.19s rather than 0.37s.
19
+
5
20
  ## 4.3.1 - 2026-10-22
6
21
 
7
22
  ### Fixed
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in cineworld_uk.gemspec
4
4
  gemspec
5
+
data/README.md CHANGED
@@ -36,6 +36,16 @@ CineworldUk.acquire_token!
36
36
  CineworldUk::Cinema.all
37
37
  ```
38
38
 
39
+ `acquire_token!` always fetches a new token. `ensure_token!` fetches one only
40
+ when there is no token, or the token expires within an hour, and otherwise
41
+ keeps the current one. Call it before each batch of requests:
42
+
43
+ ```ruby
44
+ CineworldUk.ensure_token!
45
+ CineworldUk.token_expires_at
46
+ #=> 2026-10-31 03:00:00 UTC
47
+ ```
48
+
39
49
  This shells out to **`curl-impersonate`**, not ordinary system `curl`, using its
40
50
  Chrome 150 TLS/HTTP profile. No Python, Chrome, JavaScript execution or browser
41
51
  cookies are needed. Platform-specific gem packages include the pinned
@@ -89,8 +99,9 @@ The gem sends `Authorization: Bearer ...` and `Connect-Region-Code: GB` to
89
99
  automatically.
90
100
 
91
101
  Missing tokens and HTTP 401 responses raise `CineworldUk::AuthenticationError`.
92
- Call `CineworldUk.acquire_token!` again, or set `CineworldUk.token` with a fresh
93
- token, before retrying. Tokens are never persisted by the gem. Other HTTP
102
+ A token the API rejects is cleared from `CineworldUk.token`, so call
103
+ `CineworldUk.ensure_token!`, or set `CineworldUk.token` with a fresh token,
104
+ before retrying. Tokens are never persisted by the gem. Other HTTP
94
105
  failures raise an error rather than returning an empty schedule. Redirects are
95
106
  not followed, so credentials are not forwarded to another host.
96
107
 
@@ -134,6 +145,12 @@ cinema.full_name
134
145
  cinema.id
135
146
  #=> 14
136
147
 
148
+ cinema.latitude
149
+ #=> 50.8125991821289
150
+
151
+ cinema.longitude
152
+ #=> -0.104699999094009
153
+
137
154
  cinema.postal_code
138
155
  #=> 'BN2 5UF'
139
156
  ```
@@ -229,7 +246,7 @@ To build and check just one platform on a matching host:
229
246
 
230
247
  ```sh
231
248
  bundle exec rake 'package:native[x86_64-linux-gnu]'
232
- CINEWORLD_PACKAGE=pkg/cineworld_uk-4.3.1-x86_64-linux-gnu.gem bundle exec ruby test/package_test.rb
249
+ CINEWORLD_PACKAGE=pkg/cineworld_uk-4.3.2-x86_64-linux-gnu.gem bundle exec ruby test/package_test.rb
233
250
  ```
234
251
 
235
252
  Native packaging downloads pinned upstream archives at **build time**, verifies
data/cineworld_uk.gemspec CHANGED
@@ -25,6 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency 'simplecov', '< 1'
26
26
  spec.add_development_dependency 'webmock'
27
27
 
28
- spec.add_runtime_dependency 'cinebase', '~> 5.4'
28
+ spec.add_runtime_dependency 'cinebase', '~> 5.6'
29
29
  spec.add_runtime_dependency 'nokogiri', '>= 1.11'
30
30
  end
@@ -101,6 +101,26 @@ module CineworldUk
101
101
  # cinema.locality
102
102
  # #=> 'Brighton'
103
103
 
104
+ # Latitude of the cinema
105
+ # @return [Float, nil] decimal degrees, or nil when unknown
106
+ # @example
107
+ # cinema = CineworldUk::Cinema.new(14)
108
+ # cinema.latitude
109
+ # #=> 50.8125991821289
110
+ def latitude
111
+ coordinates&.first
112
+ end
113
+
114
+ # Longitude of the cinema
115
+ # @return [Float, nil] decimal degrees, or nil when unknown
116
+ # @example
117
+ # cinema = CineworldUk::Cinema.new(14)
118
+ # cinema.longitude
119
+ # #=> -0.104699999094009
120
+ def longitude
121
+ coordinates&.last
122
+ end
123
+
104
124
  # The name of the cinema
105
125
  # @return [String]
106
126
  # @example
@@ -164,5 +184,19 @@ module CineworldUk
164
184
  JSON.parse(api.cinema_list)['body']['cinemas']
165
185
  end
166
186
  private_class_method :cinema_list_json
187
+
188
+ private
189
+
190
+ def cinema_hash
191
+ formatted_id = @id.to_s.rjust(3, '0')
192
+ self.class.send(:cinema_list_json).find { |cinema| cinema['id'] == formatted_id } || {}
193
+ end
194
+
195
+ # 0, 0 marks a site whose position was never set
196
+ def coordinates
197
+ location = cinema_hash['location'] || {}
198
+ pair = [location['latitude'], location['longitude']]
199
+ pair.map(&:to_f) if pair.all?(Numeric) && pair.any?(&:nonzero?)
200
+ end
167
201
  end
168
202
  end
@@ -25,6 +25,7 @@ module CineworldUk
25
25
  {
26
26
  "id" => site.fetch("id"),
27
27
  "displayName" => site.fetch("name").fetch("text"),
28
+ "location" => site["location"],
28
29
  "addressInfo" => {
29
30
  "address1" => address.dig("line1", "text"),
30
31
  "city" => address.dig("line2", "text"),
@@ -86,8 +87,20 @@ module CineworldUk
86
87
  private
87
88
 
88
89
  def fetch(path, query = {})
89
- Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: "GB",
90
- token: CineworldUk.token).get(path, query)
90
+ client.get(path, query)
91
+ rescue Cinebase::Vista::AuthenticationError
92
+ CineworldUk.reject_token!(@client_token)
93
+ raise
94
+ end
95
+
96
+ # One client keeps one connection open, so reuse it until the token changes.
97
+ def client
98
+ token = CineworldUk.token
99
+ if @client.nil? || @client_token != token
100
+ @client_token = token
101
+ @client = Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: "GB", token: token)
102
+ end
103
+ @client
91
104
  end
92
105
  end
93
106
  end
@@ -56,9 +56,13 @@ module CineworldUk
56
56
 
57
57
  # private
58
58
 
59
+ API = :cineworld_uk_performance_api
60
+
59
61
  # @api private
62
+ # One API, and so one kept-alive connection, per thread: job runners
63
+ # import several cinemas at once.
60
64
  def self.api
61
- @api ||= Internal::ApiResponse.new
65
+ Thread.current[API] ||= Internal::ApiResponse.new
62
66
  end
63
67
  private_class_method :api
64
68
 
@@ -1,6 +1,6 @@
1
1
  # Ruby interface for http://www.cineworld.co.uk
2
- # @version 4.3.1
2
+ # @version 4.4.0
3
3
  module CineworldUk
4
4
  # Gem version
5
- VERSION = "4.3.1".freeze
5
+ VERSION = "4.4.0".freeze
6
6
  end
data/lib/cineworld_uk.rb CHANGED
@@ -20,13 +20,10 @@ require_relative './cineworld_uk/performance'
20
20
  module CineworldUk
21
21
  AuthenticationError = Cinebase::Vista::AuthenticationError
22
22
 
23
- # A raw guest JWT, without the "Bearer " prefix. Never persisted by the gem.
24
- class << self
25
- attr_accessor :token
26
-
27
- # Explicit acquisition/renewal; ordinary API calls never launch a subprocess.
28
- def acquire_token!(curl: nil)
29
- self.token = Internal::GuestToken.new(curl: curl).fetch
30
- end
23
+ extend Cinebase::Vista::TokenHolder
24
+
25
+ # @api private
26
+ def self.guest_token_class
27
+ Internal::GuestToken
31
28
  end
32
29
  end
@@ -3,6 +3,7 @@
3
3
  {
4
4
  "id": "014",
5
5
  "name": { "text": "Brighton" },
6
+ "location": { "latitude": 50.8125991821289, "longitude": -0.104699999094009 },
6
7
  "contactDetails": {
7
8
  "addressV2": {
8
9
  "line1": { "text": "1 Example Marina" },
@@ -15,6 +16,7 @@
15
16
  {
16
17
  "id": "088",
17
18
  "name": { "text": "Glasgow Silverburn" },
19
+ "location": { "latitude": 0, "longitude": 0 },
18
20
  "contactDetails": {
19
21
  "addressV2": {
20
22
  "line1": { "text": "2 Example Centre" },
@@ -27,6 +29,7 @@
27
29
  {
28
30
  "id": "103",
29
31
  "name": { "text": "Leicester Square" },
32
+ "location": null,
30
33
  "contactDetails": {
31
34
  "addressV2": {
32
35
  "line1": { "text": "3 Example Square" },
@@ -61,6 +61,25 @@ class CineworldUk::CinemaTest < Minitest::Test
61
61
  assert_equal 'Example: Riverside', CineworldUk::Cinema.new(999).name
62
62
  end
63
63
 
64
+ def test_latitude_and_longitude
65
+ cinema = CineworldUk::Cinema.new(14)
66
+
67
+ assert_in_delta 50.8126, cinema.latitude, 0.0001
68
+ assert_in_delta(-0.1047, cinema.longitude, 0.0001)
69
+ end
70
+
71
+ def test_latitude_and_longitude_are_nil_without_a_location
72
+ [103, 999].each do |id|
73
+ assert_nil CineworldUk::Cinema.new(id).latitude
74
+ assert_nil CineworldUk::Cinema.new(id).longitude
75
+ end
76
+ end
77
+
78
+ def test_latitude_and_longitude_are_nil_at_zero_zero
79
+ assert_nil CineworldUk::Cinema.new(88).latitude
80
+ assert_nil CineworldUk::Cinema.new(88).longitude
81
+ end
82
+
64
83
  def test_locality
65
84
  assert_equal 'Brighton', CineworldUk::Cinema.new(14).locality
66
85
  assert_equal 'London', CineworldUk::Cinema.new(103).locality
@@ -14,6 +14,45 @@ class CineworldUk::Internal::ApiResponseTest < Minitest::Test
14
14
  super
15
15
  end
16
16
 
17
+ def test_reuses_one_client_while_the_token_holds
18
+ stub_request(:get, "#{BASE_URL}sites").to_return(body: '{"sites":[]}')
19
+ api = CineworldUk::Internal::ApiResponse.new
20
+
21
+ clients = count_clients { 3.times { api.cinema_list } }
22
+
23
+ assert_equal 1, clients
24
+ end
25
+
26
+ def test_builds_a_new_client_for_a_new_token
27
+ stub_request(:get, "#{BASE_URL}sites").to_return(body: '{"sites":[]}')
28
+ api = CineworldUk::Internal::ApiResponse.new
29
+
30
+ clients = count_clients do
31
+ api.cinema_list
32
+ CineworldUk.token = "renewed-token"
33
+ api.cinema_list
34
+ end
35
+
36
+ assert_equal 2, clients
37
+ end
38
+
39
+ def test_each_thread_has_its_own_performance_api
40
+ main = CineworldUk::Performance.send(:api)
41
+
42
+ other = Thread.new { CineworldUk::Performance.send(:api) }.value
43
+
44
+ assert_same main, CineworldUk::Performance.send(:api)
45
+ refute_same main, other
46
+ end
47
+
48
+ def count_clients
49
+ clients = 0
50
+ original = Cinebase::Vista::Client.method(:new)
51
+ counting = ->(**options) { clients += 1; original.call(**options) }
52
+ Cinebase::Vista::Client.stub(:new, counting) { yield }
53
+ clients
54
+ end
55
+
17
56
  def test_rejects_missing_token
18
57
  CineworldUk.token = nil
19
58
 
@@ -29,6 +68,15 @@ class CineworldUk::Internal::ApiResponseTest < Minitest::Test
29
68
  refute_includes error.message, "expired-secret"
30
69
  end
31
70
 
71
+ def test_rejected_token_is_cleared
72
+ CineworldUk.token = "rejected-token"
73
+ stub_request(:get, "#{BASE_URL}sites").to_return(status: 401)
74
+
75
+ assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
76
+
77
+ assert_nil CineworldUk.token
78
+ end
79
+
32
80
  def test_uses_replacement_token
33
81
  CineworldUk.token = "replacement"
34
82
  stub_request(:get, "#{BASE_URL}sites")
@@ -27,6 +27,16 @@ class CineworldUk::Internal::GuestTokenTest < Minitest::Test
27
27
  end
28
28
  end
29
29
 
30
+ def test_ensure_token_fetches_only_without_a_current_token
31
+ CineworldUk.token = nil
32
+ html, token = token_page(Time.now.to_i + 7200)
33
+ serve_token_page(html)
34
+
35
+ assert_equal token, CineworldUk.ensure_token!(curl: @curl)
36
+ serve_token_page("", exit_code: 7)
37
+ assert_equal token, CineworldUk.ensure_token!(curl: @curl)
38
+ end
39
+
30
40
  def test_rejects_expired_tokens_including_boundary_and_keeps_current_token
31
41
  now = Time.at(1_800_000_000)
32
42
  Time.stub(:now, now) do
@@ -8,6 +8,16 @@ class LiveCinemaTest < Minitest::Test
8
8
  assert_operator cinemas.size, :>, 18
9
9
  assert_equal 'Brighton', cinemas.detect { |cinema| cinema.id == 14 }.name
10
10
  end
11
+
12
+ def test_cinemas_have_uk_coordinates
13
+ located = CineworldUk::Cinema.all.select(&:latitude)
14
+
15
+ refute_empty located
16
+ located.each do |cinema|
17
+ assert_includes 49.0..61.0, cinema.latitude, "#{cinema.id} #{cinema.name}"
18
+ assert_includes(-9.0..2.0, cinema.longitude, "#{cinema.id} #{cinema.name}")
19
+ end
20
+ end
11
21
  end
12
22
 
13
23
  class LivePerformanceTest < Minitest::Test
@@ -16,6 +16,11 @@ module ApiShapes
16
16
  line = address.fetch(key)
17
17
  assert_kind_of String, line.fetch('text'), "site #{site['id']} #{key}" unless line.nil?
18
18
  end
19
+ location = site['location'] || {}
20
+ %w(latitude longitude).each do |key|
21
+ value = location[key]
22
+ assert_kind_of Numeric, value, "site #{site['id']} #{key}" unless value.nil?
23
+ end
19
24
  end
20
25
  end
21
26
 
@@ -53,6 +53,7 @@ module Support
53
53
  [CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
54
54
  klass.instance_variables.each { |name| klass.remove_instance_variable(name) }
55
55
  end
56
+ Thread.current[CineworldUk::Performance::API] = nil if defined?(CineworldUk::Performance::API)
56
57
  end
57
58
  end
58
59
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cineworld_uk
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.1
4
+ version: 4.4.0
5
5
  platform: x86_64-linux-gnu
6
6
  authors:
7
7
  - Andy Croll
@@ -113,14 +113,14 @@ dependencies:
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '5.4'
116
+ version: '5.6'
117
117
  type: :runtime
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '5.4'
123
+ version: '5.6'
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: nokogiri
126
126
  requirement: !ruby/object:Gem::Requirement