odeon_uk 5.1.0-x86_64-linux-gnu → 5.2.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -0
- data/README.md +21 -3
- data/lib/odeon_uk/cinema.rb +27 -0
- data/lib/odeon_uk/internal/api_response.rb +17 -4
- data/lib/odeon_uk/version.rb +2 -2
- data/lib/odeon_uk.rb +5 -8
- data/odeon_uk.gemspec +1 -1
- data/test/fixtures/api/sites.json +3 -0
- data/test/lib/odeon_uk/cinema_test.rb +25 -0
- data/test/lib/odeon_uk/internal/api_response_test.rb +36 -0
- data/test/lib/odeon_uk/internal/guest_token_test.rb +7 -0
- data/test/live/integration_test.rb +10 -0
- data/test/support/api_shapes.rb +5 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 789fcaeb757389c60c612896ada9f71c3c3bb9e70db96bccb0f570ea50ebad9f
|
|
4
|
+
data.tar.gz: 5eac1581b656a59102d5ff98e80495a46cdd069e4a3616c2bf268acd4eb63d66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9aacaacabd125b7586e1397f64809ddc13c4b9bde4d863bf4837f469535d663db62d9bdd551762eb5e99e668d09a3c35333a0601052edd41233f950ca5c8c222
|
|
7
|
+
data.tar.gz: 7fdbf726625c78532f00959ba49dbe4c34c29aead88058396508cef02cf7f52725d30052d787070de674e4709817dbbee973ff426108804e60e237b439a994ad
|
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
|
+
## 5.2.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
|
+
- `OdeonUk.ensure_token!` fetches a guest token only when there is none or it expires within an hour, and `OdeonUk.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 `OdeonUk.token`, so the next `ensure_token!` fetches a fresh one.
|
|
13
|
+
- Requires cinebase 5.6.
|
|
14
|
+
|
|
15
|
+
## 5.1.1 - 2026-10-22
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Reuse one Vista client, and so one kept-alive connection, for all requests until the token changes, instead of opening a TLS connection for every request. Requires cinebase 5.5.
|
|
19
|
+
|
|
5
20
|
## 5.1.0 - 2026-10-22
|
|
6
21
|
|
|
7
22
|
### Added
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -57,9 +57,21 @@ OdeonUk.token = ENV.fetch('ODEON_TOKEN')
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Tokens currently last 12 hours. The gem checks the declared expiry but does not
|
|
60
|
-
verify the signature.
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
verify the signature. Normal API calls never acquire or renew a token
|
|
61
|
+
automatically. `OdeonUk.ensure_token!` fetches one only when there is no token,
|
|
62
|
+
or the token expires within an hour, and otherwise keeps the current one. Call
|
|
63
|
+
it before each batch of requests:
|
|
64
|
+
|
|
65
|
+
``` ruby
|
|
66
|
+
OdeonUk.ensure_token!
|
|
67
|
+
OdeonUk.token_expires_at
|
|
68
|
+
#=> 2026-10-31 03:00:00 UTC
|
|
69
|
+
OdeonUk::Cinema.all
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Missing, expired or rejected tokens raise `OdeonUk::AuthenticationError`. A
|
|
73
|
+
token the API rejects is cleared from `OdeonUk.token`, so the next
|
|
74
|
+
`ensure_token!` fetches a fresh one.
|
|
63
75
|
|
|
64
76
|
### Cinema
|
|
65
77
|
|
|
@@ -93,6 +105,12 @@ cinema.full_name
|
|
|
93
105
|
cinema.id
|
|
94
106
|
#=> '824'
|
|
95
107
|
|
|
108
|
+
cinema.latitude
|
|
109
|
+
#=> 50.821 # Float, nil when the API has no location
|
|
110
|
+
|
|
111
|
+
cinema.longitude
|
|
112
|
+
#=> -0.1452
|
|
113
|
+
|
|
96
114
|
cinema.url
|
|
97
115
|
#=> 'https://www.odeon.co.uk/cinemas/brighton/'
|
|
98
116
|
```
|
data/lib/odeon_uk/cinema.rb
CHANGED
|
@@ -105,6 +105,26 @@ module OdeonUk
|
|
|
105
105
|
# cinema.locality
|
|
106
106
|
# #=> 'Brighton'
|
|
107
107
|
|
|
108
|
+
# Latitude of the cinema
|
|
109
|
+
# @return [Float, nil] decimal degrees, or nil when unknown
|
|
110
|
+
# @example
|
|
111
|
+
# cinema = OdeonUk::Cinema.new(824)
|
|
112
|
+
# cinema.latitude
|
|
113
|
+
# #=> 50.821
|
|
114
|
+
def latitude
|
|
115
|
+
coordinates&.first
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Longitude of the cinema
|
|
119
|
+
# @return [Float, nil] decimal degrees, or nil when unknown
|
|
120
|
+
# @example
|
|
121
|
+
# cinema = OdeonUk::Cinema.new(824)
|
|
122
|
+
# cinema.longitude
|
|
123
|
+
# #=> -0.1452
|
|
124
|
+
def longitude
|
|
125
|
+
coordinates&.last
|
|
126
|
+
end
|
|
127
|
+
|
|
108
128
|
# The name of the cinema
|
|
109
129
|
# @return [String]
|
|
110
130
|
# @example
|
|
@@ -164,6 +184,13 @@ module OdeonUk
|
|
|
164
184
|
|
|
165
185
|
private
|
|
166
186
|
|
|
187
|
+
# 0, 0 marks a site whose position was never set
|
|
188
|
+
def coordinates
|
|
189
|
+
location = site_hash['location'] || {}
|
|
190
|
+
pair = [location['latitude'], location['longitude']]
|
|
191
|
+
pair.map(&:to_f) if pair.all?(Numeric) && pair.any?(&:nonzero?)
|
|
192
|
+
end
|
|
193
|
+
|
|
167
194
|
def site_hash
|
|
168
195
|
@site_hash ||= self.class.send(:sites_hash).fetch(id, {})
|
|
169
196
|
end
|
|
@@ -14,22 +14,35 @@ module OdeonUk
|
|
|
14
14
|
|
|
15
15
|
# @return [Array<Hash>] UK cinemas
|
|
16
16
|
def sites
|
|
17
|
-
|
|
17
|
+
get("sites").fetch("sites")
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# @param [String] cinema_id zero-padded site id, e.g. '017'
|
|
21
21
|
# @param [Date] date business date
|
|
22
22
|
# @return [Hash] showtimes plus their related films and attributes
|
|
23
23
|
def showtimes(cinema_id, date)
|
|
24
|
-
|
|
24
|
+
get("showtimes/by-business-date/#{date.iso8601}",
|
|
25
25
|
"siteIds" => cinema_id)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
+
def get(path, query = {})
|
|
31
|
+
client.get(path, query)
|
|
32
|
+
rescue Cinebase::Vista::AuthenticationError
|
|
33
|
+
OdeonUk.reject_token!(@client_token)
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# One client keeps one connection open, so reuse it until the token changes.
|
|
30
38
|
def client
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
token = OdeonUk.token
|
|
40
|
+
if @client.nil? || @client_token != token
|
|
41
|
+
@client_token = token
|
|
42
|
+
@client = Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: REGION_CODE,
|
|
43
|
+
token: token)
|
|
44
|
+
end
|
|
45
|
+
@client
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
end
|
data/lib/odeon_uk/version.rb
CHANGED
data/lib/odeon_uk.rb
CHANGED
|
@@ -18,13 +18,10 @@ require_relative './odeon_uk/performance'
|
|
|
18
18
|
module OdeonUk
|
|
19
19
|
AuthenticationError = Cinebase::Vista::AuthenticationError
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def acquire_token!(curl: nil)
|
|
27
|
-
self.token = Internal::GuestToken.new(curl: curl).fetch
|
|
28
|
-
end
|
|
21
|
+
extend Cinebase::Vista::TokenHolder
|
|
22
|
+
|
|
23
|
+
# @api private
|
|
24
|
+
def self.guest_token_class
|
|
25
|
+
Internal::GuestToken
|
|
29
26
|
end
|
|
30
27
|
end
|
data/odeon_uk.gemspec
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
{
|
|
4
4
|
"id": "017",
|
|
5
5
|
"name": { "text": "Birmingham New Street" },
|
|
6
|
+
"location": { "latitude": 0, "longitude": 0 },
|
|
6
7
|
"contactDetails": {
|
|
7
8
|
"address": { "line1": "1 Example Arcade", "line2": "New Street", "city": "Birmingham" }
|
|
8
9
|
}
|
|
@@ -10,6 +11,7 @@
|
|
|
10
11
|
{
|
|
11
12
|
"id": "153",
|
|
12
13
|
"name": { "text": "London Leicester Square" },
|
|
14
|
+
"location": null,
|
|
13
15
|
"contactDetails": {
|
|
14
16
|
"address": { "line1": "2 Example Square", "line2": "", "city": "London" }
|
|
15
17
|
}
|
|
@@ -17,6 +19,7 @@
|
|
|
17
19
|
{
|
|
18
20
|
"id": "824",
|
|
19
21
|
"name": { "text": "Brighton" },
|
|
22
|
+
"location": { "latitude": 50.8215, "longitude": -0.146 },
|
|
20
23
|
"contactDetails": {
|
|
21
24
|
"address": { "line1": "3 Example Parade", "line2": "Seafront", "city": "Brighton" }
|
|
22
25
|
}
|
|
@@ -66,6 +66,31 @@ class OdeonUk::CinemaTest < Minitest::Test
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
def test_latitude_and_longitude
|
|
70
|
+
with_fake_api do
|
|
71
|
+
cinema = OdeonUk::Cinema.new(824)
|
|
72
|
+
|
|
73
|
+
assert_in_delta 50.8215, cinema.latitude, 0.0001
|
|
74
|
+
assert_in_delta(-0.146, cinema.longitude, 0.0001)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_latitude_and_longitude_are_nil_without_a_location
|
|
79
|
+
with_fake_api do
|
|
80
|
+
[153, 955].each do |id|
|
|
81
|
+
assert_nil OdeonUk::Cinema.new(id).latitude
|
|
82
|
+
assert_nil OdeonUk::Cinema.new(id).longitude
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_latitude_and_longitude_are_nil_at_zero_zero
|
|
88
|
+
with_fake_api do
|
|
89
|
+
assert_nil OdeonUk::Cinema.new(17).latitude
|
|
90
|
+
assert_nil OdeonUk::Cinema.new(17).longitude
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
69
94
|
def test_name_removes_london_prefix
|
|
70
95
|
with_fake_api do
|
|
71
96
|
assert_equal 'Leicester Square', OdeonUk::Cinema.new(153).name
|
|
@@ -50,6 +50,14 @@ class OdeonUk::Internal::ApiResponseTest < Minitest::Test
|
|
|
50
50
|
assert_raises(OdeonUk::AuthenticationError) { api.sites }
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def test_rejected_token_is_cleared
|
|
54
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 401)
|
|
55
|
+
|
|
56
|
+
assert_raises(OdeonUk::AuthenticationError) { api.sites }
|
|
57
|
+
|
|
58
|
+
assert_nil OdeonUk.token
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
def test_server_error_omits_body
|
|
54
62
|
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 500, body: 'secret')
|
|
55
63
|
|
|
@@ -58,8 +66,36 @@ class OdeonUk::Internal::ApiResponseTest < Minitest::Test
|
|
|
58
66
|
assert_equal 'Vista HTTP error 500', error.message
|
|
59
67
|
end
|
|
60
68
|
|
|
69
|
+
def test_reuses_one_client_while_the_token_holds
|
|
70
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 200, body: sites_json)
|
|
71
|
+
shared = api
|
|
72
|
+
|
|
73
|
+
assert_equal 1, count_clients { 3.times { shared.sites } }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_builds_a_new_client_for_a_new_token
|
|
77
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 200, body: sites_json)
|
|
78
|
+
shared = api
|
|
79
|
+
|
|
80
|
+
clients = count_clients do
|
|
81
|
+
shared.sites
|
|
82
|
+
OdeonUk.token = 'renewed-jwt'
|
|
83
|
+
shared.sites
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
assert_equal 2, clients
|
|
87
|
+
end
|
|
88
|
+
|
|
61
89
|
private
|
|
62
90
|
|
|
91
|
+
def count_clients
|
|
92
|
+
clients = 0
|
|
93
|
+
original = Cinebase::Vista::Client.method(:new)
|
|
94
|
+
counting = ->(**options) { clients += 1; original.call(**options) }
|
|
95
|
+
Cinebase::Vista::Client.stub(:new, counting) { yield }
|
|
96
|
+
clients
|
|
97
|
+
end
|
|
98
|
+
|
|
63
99
|
def api
|
|
64
100
|
OdeonUk::Internal::ApiResponse.new
|
|
65
101
|
end
|
|
@@ -51,6 +51,13 @@ class OdeonUk::Internal::GuestTokenTest < Minitest::Test
|
|
|
51
51
|
assert_match(/curl-impersonate/, error.message)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
def test_ensure_token_fetches_only_without_a_current_token
|
|
55
|
+
token = token_expiring_at(Time.now + 7200)
|
|
56
|
+
|
|
57
|
+
assert_equal token, OdeonUk.ensure_token!(curl: fake_curl("#{page_with(token)}\n200"))
|
|
58
|
+
assert_equal token, OdeonUk.ensure_token!(curl: File.join(@directory, 'missing'))
|
|
59
|
+
end
|
|
60
|
+
|
|
54
61
|
def test_acquire_token_sets_token
|
|
55
62
|
token = token_expiring_at(Time.now + 3600)
|
|
56
63
|
|
|
@@ -8,6 +8,16 @@ class LiveCinemaTest < Minitest::Test
|
|
|
8
8
|
assert_operator cinemas.size, :>, 90
|
|
9
9
|
assert_equal 'Brighton', cinemas.detect { |cinema| cinema.id == '824' }.name
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
def test_cinemas_have_uk_coordinates
|
|
13
|
+
located = OdeonUk::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
|
data/test/support/api_shapes.rb
CHANGED
|
@@ -15,6 +15,11 @@ module ApiShapes
|
|
|
15
15
|
%w(line1 line2 city).each do |key|
|
|
16
16
|
assert_kind_of String, address.fetch(key), "site #{site['id']} #{key}"
|
|
17
17
|
end
|
|
18
|
+
location = site['location'] || {}
|
|
19
|
+
%w(latitude longitude).each do |key|
|
|
20
|
+
value = location[key]
|
|
21
|
+
assert_kind_of Numeric, value, "site #{site['id']} #{key}" unless value.nil?
|
|
22
|
+
end
|
|
18
23
|
end
|
|
19
24
|
end
|
|
20
25
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: odeon_uk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.2.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.
|
|
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.
|
|
123
|
+
version: '5.6'
|
|
124
124
|
description: Pull movie & cinema information from the ODEON Vista API
|
|
125
125
|
email:
|
|
126
126
|
- andy@goodscary.com
|