odeon_uk 4.0.1 → 5.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 +5 -5
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +28 -0
- data/README.md +90 -21
- data/Rakefile +14 -15
- data/lib/odeon_uk/cinema.rb +40 -22
- data/lib/odeon_uk/internal/api_response.rb +19 -39
- data/lib/odeon_uk/internal/guest_token.rb +23 -0
- data/lib/odeon_uk/internal/parser/api/showtime.rb +80 -0
- data/lib/odeon_uk/internal/title_sanitizer.rb +3 -8
- data/lib/odeon_uk/performance.rb +34 -41
- data/lib/odeon_uk/version.rb +3 -3
- data/lib/odeon_uk.rb +16 -2
- data/odeon_uk.gemspec +10 -6
- data/test/fixtures/api/showtimes-824.json +66 -0
- data/test/fixtures/api/sites.json +32 -0
- data/test/lib/odeon_uk/cinema_test.rb +66 -215
- data/test/lib/odeon_uk/internal/api_response_test.rb +48 -29
- data/test/lib/odeon_uk/internal/fixture_shape_test.rb +14 -0
- data/test/lib/odeon_uk/internal/guest_token_test.rb +84 -0
- data/test/lib/odeon_uk/internal/parser/api/showtime_test.rb +68 -0
- data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +109 -89
- data/test/lib/odeon_uk/performance_test.rb +124 -146
- data/test/lib/odeon_uk/version_test.rb +3 -3
- data/test/live/api_shape_test.rb +19 -0
- data/test/live/integration_test.rb +21 -0
- data/test/live/live_helper.rb +11 -0
- data/test/package_test.rb +56 -0
- data/test/support/api_fixtures_helper.rb +5 -30
- data/test/support/api_shapes.rb +46 -0
- data/test/support/fake_api_response.rb +9 -6
- data/test/test_helper.rb +6 -3
- metadata +66 -89
- data/.travis.yml +0 -7
- data/lib/odeon_uk/internal/parser/api/film_lookup.rb +0 -34
- data/lib/odeon_uk/internal/parser/api/performance_day.rb +0 -92
- data/rake/fixture_creator.rb +0 -46
- data/test/fixtures/api/all_cinemas.plist +0 -0
- data/test/fixtures/api/app_init.plist +0 -0
- data/test/fixtures/api/film_times/71-14876.plist +0 -0
- data/test/fixtures/api/film_times/71-15408.plist +0 -0
- data/test/fixtures/api/film_times/71-15684.plist +0 -0
- data/test/fixtures/api/film_times/71-15866.plist +0 -0
- data/test/fixtures/api/film_times/71-15868.plist +0 -0
- data/test/fixtures/api/film_times/71-15961.plist +0 -0
- data/test/fixtures/api/film_times/71-15963.plist +0 -0
- data/test/fixtures/api/film_times/71-15965.plist +0 -0
- data/test/fixtures/api/film_times/71-15967.plist +0 -0
- data/test/fixtures/api/film_times/71-15969.plist +0 -0
- data/test/fixtures/api/film_times/71-15977.plist +0 -0
- data/test/fixtures/api/film_times/71-15980.plist +0 -0
- data/test/fixtures/api/film_times/71-16037.plist +0 -0
- data/test/fixtures/api/film_times/71-16136.plist +0 -0
- data/test/fixtures/api/film_times/71-16142.plist +0 -0
- data/test/fixtures/api/film_times/71-16143.plist +0 -0
- data/test/fixtures/api/film_times/71-16187.plist +0 -0
- data/test/fixtures/api/film_times/71-16369.plist +0 -0
- data/test/fixtures/api/film_times/71-16370.plist +0 -0
- data/test/fixtures/api/film_times/71-16374.plist +0 -0
- data/test/fixtures/api/film_times/71-16386.plist +0 -0
- data/test/fixtures/api/film_times/71-16387.plist +0 -0
- data/test/fixtures/api/film_times/71-16388.plist +0 -0
- data/test/fixtures/api/film_times/71-16438.plist +0 -0
- data/test/fixtures/api/film_times/71-16454.plist +0 -0
- data/test/fixtures/api/film_times/71-16457.plist +0 -0
- data/test/fixtures/api/film_times/71-16477.plist +0 -0
- data/test/fixtures/api/film_times/71-16478.plist +0 -0
- data/test/fixtures/api/film_times/71-16480.plist +0 -0
- data/test/fixtures/api/film_times/71-16482.plist +0 -0
- data/test/fixtures/api/film_times/71-16489.plist +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0f8dac2f14a307cda7efcbbe3e2a1806144ae44962cb543a676397beac9128e9
|
|
4
|
+
data.tar.gz: 89240ce98e81acc220c63fcd010e5fbc8468056c41a3d2d1e014dbae5223a4b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c03e61f5667184a6992d22582f6ee67e94f4e163ab0d1c4e82132816e7d4f97d46cb1f0a98712ce5bf60b2477f519e150e2107423a4a5df1ca04bcd6e9822b1d
|
|
7
|
+
data.tar.gz: ead7043b142df55fbafedb7dc3b165c35b2082e250ed1e35419e46eb3b1b0826929c76e1c4256461c94861bf128ac52b4bed5b2bc8c6ae85ce75249346498920
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
ruby-version: ['3.4', '4.0']
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
- run: bundle exec rake test
|
|
18
|
+
|
|
19
|
+
package:
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
include:
|
|
23
|
+
- runner: ubuntu-latest
|
|
24
|
+
platform: x86_64-linux-gnu
|
|
25
|
+
- runner: ubuntu-24.04-arm
|
|
26
|
+
platform: aarch64-linux-gnu
|
|
27
|
+
- runner: macos-15
|
|
28
|
+
platform: arm64-darwin
|
|
29
|
+
- runner: macos-15-intel
|
|
30
|
+
platform: x86_64-darwin
|
|
31
|
+
runs-on: ${{ matrix.runner }}
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: ruby/setup-ruby@v1
|
|
35
|
+
with:
|
|
36
|
+
ruby-version: '4.0'
|
|
37
|
+
bundler-cache: true
|
|
38
|
+
- run: bundle exec rake 'package:native[${{ matrix.platform }}]'
|
|
39
|
+
- run: ODEON_PACKAGE="$(echo pkg/*.gem)" bundle exec ruby test/package_test.rb
|
|
40
|
+
- uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: gem-${{ matrix.platform }}
|
|
43
|
+
path: pkg/*.gem
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
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.0.0 - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Retrieve cinemas and performances from the Vista API behind odeon.co.uk instead of the retired iOS plist API.
|
|
9
|
+
- Cinema ids are Vista site ids, zero-padded strings such as `'017'` and `'824'` (Brighton was `71`).
|
|
10
|
+
- `Cinema.all` returns UK cinemas only.
|
|
11
|
+
- `Cinema#adr` includes the second address line. Postcodes are no longer available.
|
|
12
|
+
- `Cinema#url` points at the current cinema pages.
|
|
13
|
+
- `Performance.at` accepts inclusive `from:` and `to:` business dates, defaulting to today in London and the following 13 days. One request per day.
|
|
14
|
+
- Performance times come from Vista's explicit UTC offsets.
|
|
15
|
+
- Title sanitizer handles Met Opera and RBO season prefixes, language and encore suffixes.
|
|
16
|
+
- Require Ruby 3.4 or newer and cinebase 5.2, which now holds the shared Vista client, guest token retrieval, packaging task and common title rules.
|
|
17
|
+
- Film titles drop dub, subtitle, language, anniversary and Q&A markers, and festival prefixes.
|
|
18
|
+
- Convert tests from minitest spec syntax to plain `Minitest::Test` classes.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `OdeonUk.token` for a caller-supplied guest JWT, and `OdeonUk.acquire_token!` to fetch one with `curl-impersonate`.
|
|
22
|
+
- Platform gems that bundle curl-impersonate 2.2.3.
|
|
23
|
+
- `OdeonUk::AuthenticationError` for missing, expired and rejected tokens.
|
|
24
|
+
- `Performance#booking_url`, `Performance#language`, `Performance#subtitled?` and `Performance#festival` (a `Cinebase::Festival` with `name` and `year`).
|
|
25
|
+
- `audio_described`, `autism_friendly` and `subtitled` variants.
|
|
26
|
+
- Live integration tests and GitHub Actions CI on Ruby 3.4 and 4.0.
|
|
27
|
+
- Live API shape tests that check real responses without storing them.
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
- CFPropertyList dependency, Travis CI and Code Climate.
|
|
31
|
+
- Recorded API fixtures and the `fixtures` rake task. Fixtures are hand-written.
|
|
32
|
+
|
|
5
33
|
## 4.0.1 - 2016-02-11
|
|
6
34
|
|
|
7
35
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# OdeonUk
|
|
2
2
|
|
|
3
|
-
A simple gem to
|
|
3
|
+
A simple gem to pull cinemas and performances from the Vista API behind the [ODEON UK website](https://www.odeon.co.uk) and spit out useful formatted info.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/odeon_uk)
|
|
6
|
-
[](https://codeclimate.com/github/andycroll/odeon_uk)
|
|
7
|
-
[](https://codeclimate.com/github/andycroll/odeon_uk/coverage)
|
|
8
|
-
[](https://travis-ci.org/andycroll/odeon_uk)
|
|
9
6
|
[](http://inch-ci.org/github/andycroll/odeon_uk)
|
|
10
7
|
|
|
11
8
|
## Installation
|
|
@@ -26,25 +23,65 @@ Or install it yourself as:
|
|
|
26
23
|
|
|
27
24
|
The gem conforms to the API set down in the `cinebase` gem, [andycroll/cinebase](https://github.com/andycroll/cinebase), which provides a lot of useful base vocabulary and repetitive code for this series of cinema focussed gems.
|
|
28
25
|
|
|
29
|
-
Performance titles are sanitized as much as possible, removing 'screening type' information and 'dimension' as well as standardising all the theatre/cultural event naming (NT Live, Royal Opera
|
|
26
|
+
Performance titles are sanitized as much as possible, removing 'screening type' information and 'dimension' as well as standardising all the theatre/cultural event naming (NT Live, Royal Ballet and Opera etc).
|
|
27
|
+
|
|
28
|
+
### Guest token
|
|
29
|
+
|
|
30
|
+
Every API call needs the public guest token the ODEON website gives each
|
|
31
|
+
visitor. Fetch one before anything else:
|
|
32
|
+
|
|
33
|
+
``` ruby
|
|
34
|
+
OdeonUk.acquire_token!
|
|
35
|
+
OdeonUk::Cinema.all
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This shells out to **`curl-impersonate`**, not ordinary system `curl`, using its
|
|
39
|
+
Chrome 150 TLS/HTTP profile, and reads `window.initialData.api.authToken` from
|
|
40
|
+
`https://www.odeon.co.uk/my-account/`. That page skips the queue-it waiting room
|
|
41
|
+
every other page redirects to. No browser or JavaScript execution is needed.
|
|
42
|
+
|
|
43
|
+
Platform-specific gem packages include the pinned `curl-impersonate` 2.2.3
|
|
44
|
+
executable for glibc Linux (x86-64, ARM64) and macOS (Intel, Apple Silicon).
|
|
45
|
+
On other platforms, or when installing from Git, install a
|
|
46
|
+
[curl-impersonate release](https://github.com/lexiforest/curl-impersonate/releases/tag/v2.2.3)
|
|
47
|
+
yourself and put it on your `PATH` or pass its path:
|
|
48
|
+
|
|
49
|
+
``` ruby
|
|
50
|
+
OdeonUk.acquire_token!(curl: '/opt/curl-impersonate/curl-impersonate')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or supply your own current guest JWT, without the `Bearer ` prefix:
|
|
54
|
+
|
|
55
|
+
``` ruby
|
|
56
|
+
OdeonUk.token = ENV.fetch('ODEON_TOKEN')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Tokens currently last 12 hours. The gem checks the declared expiry but does not
|
|
60
|
+
verify the signature. Missing, expired or rejected tokens raise
|
|
61
|
+
`OdeonUk::AuthenticationError`; call `OdeonUk.acquire_token!` again. Normal API
|
|
62
|
+
calls never acquire or renew a token automatically.
|
|
30
63
|
|
|
31
64
|
### Cinema
|
|
32
65
|
|
|
66
|
+
Only UK cinemas are returned; ODEON's Irish cinemas are left out. Cinema ids
|
|
67
|
+
changed with the move to the Vista API (Brighton was 71 and is now 824). The API
|
|
68
|
+
does not supply postcodes, so `postal_code` is empty.
|
|
69
|
+
|
|
33
70
|
``` ruby
|
|
34
71
|
OdeonUk::Cinema.all
|
|
35
72
|
#=> [<OdeonUk::Cinema ...>, <OdeonUk::Cinema ...>, ...]
|
|
36
73
|
|
|
37
|
-
cinema = OdeonUk::Cinema.new(
|
|
74
|
+
cinema = OdeonUk::Cinema.new(824)
|
|
38
75
|
#=> <OdeonUk::Cinema ...>
|
|
39
76
|
|
|
40
77
|
cinema.adr
|
|
41
78
|
#=> {
|
|
42
79
|
street_address: 'Kingswest',
|
|
43
|
-
extended_address:
|
|
80
|
+
extended_address: 'West Street',
|
|
44
81
|
locality: 'Brighton',
|
|
45
82
|
region: nil,
|
|
46
|
-
postal_code:
|
|
47
|
-
|
|
83
|
+
postal_code: nil,
|
|
84
|
+
country_name: 'United Kingdom'
|
|
48
85
|
}
|
|
49
86
|
|
|
50
87
|
cinema.brand
|
|
@@ -54,46 +91,78 @@ cinema.full_name
|
|
|
54
91
|
#=> 'Odeon Brighton'
|
|
55
92
|
|
|
56
93
|
cinema.id
|
|
57
|
-
#=>
|
|
94
|
+
#=> '824'
|
|
58
95
|
|
|
59
|
-
cinema.
|
|
60
|
-
#=> '
|
|
96
|
+
cinema.url
|
|
97
|
+
#=> 'https://www.odeon.co.uk/cinemas/brighton/'
|
|
61
98
|
```
|
|
62
99
|
|
|
63
100
|
### Performances
|
|
64
101
|
|
|
102
|
+
`Performance.at` returns performances for an inclusive range of business dates,
|
|
103
|
+
defaulting to today in London and the following 13 days. It makes one request per
|
|
104
|
+
day.
|
|
105
|
+
|
|
65
106
|
``` ruby
|
|
66
|
-
OdeonUk::Performance.at(
|
|
107
|
+
OdeonUk::Performance.at(824)
|
|
67
108
|
#=> [<OdeonUk::Performance ...>, <OdeonUk::Performance ...>, ...]
|
|
68
109
|
|
|
69
|
-
|
|
110
|
+
OdeonUk::Performance.at(824, from: Date.today, to: Date.today + 2)
|
|
111
|
+
#=> [<OdeonUk::Performance ...>, <OdeonUk::Performance ...>, ...]
|
|
112
|
+
|
|
113
|
+
performance = OdeonUk::Performance.at(824).first
|
|
70
114
|
#=> <OdeonUk::Performance ...>
|
|
71
115
|
|
|
72
116
|
performance.film_name
|
|
73
|
-
#=> '
|
|
117
|
+
#=> 'Street Fighter'
|
|
74
118
|
|
|
75
119
|
performance.dimension
|
|
76
120
|
#=> '2d'
|
|
77
121
|
|
|
78
122
|
performance.variant
|
|
79
|
-
#=> ['
|
|
123
|
+
#=> ['audio_described', 'imax']
|
|
124
|
+
|
|
125
|
+
performance.language
|
|
126
|
+
#=> 'Korean' # audio language, nil when the cinema does not tag one
|
|
127
|
+
|
|
128
|
+
performance.subtitled?
|
|
129
|
+
#=> true # English subtitles or open captions
|
|
130
|
+
|
|
131
|
+
performance.festival
|
|
132
|
+
#=> #<data Cinebase::Festival name="Cine-Excess", year=2026> # nil outside festivals
|
|
80
133
|
|
|
81
134
|
performance.starting_at
|
|
82
|
-
#=>
|
|
135
|
+
#=> 2026-10-24 08:00:00 UTC
|
|
83
136
|
|
|
84
137
|
performance.showing_on
|
|
85
|
-
#=> #<Date:
|
|
138
|
+
#=> #<Date: 2026-10-24 ((2461338j,0s,0n),+0s,2299161j)>
|
|
86
139
|
|
|
87
|
-
performance.booking_url
|
|
88
|
-
#=>
|
|
140
|
+
performance.booking_url
|
|
141
|
+
#=> 'https://www.odeon.co.uk/ticketing/seat-picker/?showtimeId=824-69206'
|
|
89
142
|
|
|
90
143
|
performance.cinema_name
|
|
91
144
|
#=> 'Brighton'
|
|
92
145
|
|
|
93
146
|
performance.cinema_id
|
|
94
|
-
#=>
|
|
147
|
+
#=> '824'
|
|
95
148
|
```
|
|
96
149
|
|
|
150
|
+
Variants are `arts`, `audio_described`, `autism_friendly`, `baby`, `imax`,
|
|
151
|
+
`kids`, `senior` and `subtitled`.
|
|
152
|
+
|
|
153
|
+
## Tests
|
|
154
|
+
|
|
155
|
+
``` sh
|
|
156
|
+
bundle exec rake # unit tests against fixtures
|
|
157
|
+
bundle exec rake live # live API, needs curl-impersonate or ODEON_TOKEN
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Fixtures in `test/fixtures/api` are hand-written: invented films and
|
|
161
|
+
addresses in the shape of Vista responses, never recorded API output.
|
|
162
|
+
`test/support/api_shapes.rb` describes the fields the gem reads. The unit suite
|
|
163
|
+
checks the fixtures against it and the live suite checks real responses, held
|
|
164
|
+
only in memory. When the API changes, update the shape first, then the fixtures.
|
|
165
|
+
|
|
97
166
|
## Contributing
|
|
98
167
|
|
|
99
168
|
1. Fork it
|
data/Rakefile
CHANGED
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
require 'bundler/gem_tasks'
|
|
3
3
|
|
|
4
4
|
require 'rake/testtask'
|
|
5
|
+
require 'cinebase/vista/package_task'
|
|
6
|
+
|
|
7
|
+
Cinebase::Vista::PackageTask.new('odeon_uk.gemspec', root: __dir__)
|
|
5
8
|
|
|
6
9
|
Rake::TestTask.new do |t|
|
|
7
10
|
t.libs << 'lib/odeon_uk'
|
|
8
11
|
t.test_files = FileList[
|
|
9
12
|
'test/lib/odeon_uk/*_test.rb',
|
|
10
|
-
'test/lib/odeon_uk/
|
|
11
|
-
'test/lib/odeon_uk/internal/*_test.rb'
|
|
13
|
+
'test/lib/odeon_uk/internal/*_test.rb',
|
|
14
|
+
'test/lib/odeon_uk/internal/parser/api/*_test.rb'
|
|
15
|
+
]
|
|
16
|
+
t.verbose = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Rake::TestTask.new do |t|
|
|
20
|
+
t.libs << 'lib/odeon_uk'
|
|
21
|
+
t.name = :live
|
|
22
|
+
t.test_files = FileList[
|
|
23
|
+
'test/live/*_test.rb'
|
|
12
24
|
]
|
|
13
25
|
t.verbose = true
|
|
14
26
|
end
|
|
@@ -23,17 +35,4 @@ task :console do
|
|
|
23
35
|
IRB.start
|
|
24
36
|
end
|
|
25
37
|
|
|
26
|
-
desc 'recreate test fixtures'
|
|
27
|
-
task :fixtures do
|
|
28
|
-
require 'odeon_uk'
|
|
29
|
-
require_relative 'rake/fixture_creator'
|
|
30
|
-
require 'fileutils'
|
|
31
|
-
|
|
32
|
-
FixtureCreator::Api.new.app_init!
|
|
33
|
-
FixtureCreator::Api.new.all_cinemas!
|
|
34
|
-
puts 'Clean folder'
|
|
35
|
-
FileUtils.rm FileList['test/fixtures/api/film_times/*.plist']
|
|
36
|
-
FixtureCreator::Api.new.film_times!(71)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
38
|
task default: :test
|
data/lib/odeon_uk/cinema.rb
CHANGED
|
@@ -2,22 +2,28 @@ module OdeonUk
|
|
|
2
2
|
# The object representing a cinema on the Odeon UK website
|
|
3
3
|
class Cinema < Cinebase::Cinema
|
|
4
4
|
# @!attribute [r] id
|
|
5
|
-
# @return [
|
|
5
|
+
# @return [String] the zero-padded id of the cinema, e.g. '017'
|
|
6
|
+
|
|
7
|
+
# Vista site ids whose page slug differs from the cinema name
|
|
8
|
+
URL_SLUGS = {
|
|
9
|
+
'915' => 'dudley-merry-hill',
|
|
10
|
+
'955' => 'manchester-trafford-centre'
|
|
11
|
+
}.freeze
|
|
6
12
|
|
|
7
13
|
# Constructor
|
|
8
|
-
# @param [Integer, String] id the cinema id of the format
|
|
14
|
+
# @param [Integer, String] id the cinema id of the format 17/'017'
|
|
9
15
|
# @return [OdeonUk::Cinema]
|
|
10
16
|
def initialize(id)
|
|
11
|
-
@id = id.to_s
|
|
17
|
+
@id = id.to_s.rjust(3, '0')
|
|
12
18
|
end
|
|
13
19
|
|
|
14
|
-
# Return basic cinema information for all cinemas
|
|
20
|
+
# Return basic cinema information for all UK cinemas
|
|
15
21
|
# @return [Array<OdeonUk::Cinema>]
|
|
16
22
|
# @example
|
|
17
23
|
# OdeonUk::Cinema.all
|
|
18
24
|
# #=> [<OdeonUk::Cinema>, <OdeonUk::Cinema>, ...]
|
|
19
25
|
def self.all
|
|
20
|
-
|
|
26
|
+
sites_hash.keys.map { |cinema_id| new(cinema_id) }
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
# @!method address
|
|
@@ -29,23 +35,26 @@ module OdeonUk
|
|
|
29
35
|
# @return [Hash] contains :street_address, :extended_address,
|
|
30
36
|
# :locality, :postal_code, :country
|
|
31
37
|
# @example
|
|
32
|
-
# cinema = OdeonUk::Cinema.new(
|
|
38
|
+
# cinema = OdeonUk::Cinema.new(824)
|
|
33
39
|
# cinema.adr
|
|
34
40
|
# #=> {
|
|
35
|
-
# street_address: '
|
|
36
|
-
# extended_address: '
|
|
41
|
+
# street_address: 'Kingswest',
|
|
42
|
+
# extended_address: 'West Street',
|
|
37
43
|
# locality: 'Brighton',
|
|
38
|
-
#
|
|
44
|
+
# region: nil,
|
|
45
|
+
# postal_code: nil,
|
|
39
46
|
# country_name: 'United Kingdom'
|
|
40
47
|
# }
|
|
41
48
|
# @note Uses the standard method naming as at http://microformats.org/wiki/adr
|
|
49
|
+
# @note The API does not supply postcodes
|
|
42
50
|
def adr
|
|
51
|
+
address = site_hash.dig('contactDetails', 'address') || {}
|
|
43
52
|
{
|
|
44
|
-
street_address:
|
|
45
|
-
extended_address:
|
|
46
|
-
locality:
|
|
53
|
+
street_address: address['line1'],
|
|
54
|
+
extended_address: address['line2'],
|
|
55
|
+
locality: address['city'],
|
|
47
56
|
region: nil,
|
|
48
|
-
postal_code:
|
|
57
|
+
postal_code: nil,
|
|
49
58
|
country_name: 'United Kingdom'.freeze
|
|
50
59
|
}
|
|
51
60
|
end
|
|
@@ -103,7 +112,7 @@ module OdeonUk
|
|
|
103
112
|
# cinema.name
|
|
104
113
|
# #=> 'Brighton'
|
|
105
114
|
def name
|
|
106
|
-
@name ||=
|
|
115
|
+
@name ||= site_name.gsub(/\ALondon /, '').gsub(' - ', ': ')
|
|
107
116
|
end
|
|
108
117
|
|
|
109
118
|
# @!method postal_code
|
|
@@ -142,22 +151,31 @@ module OdeonUk
|
|
|
142
151
|
# The url of the cinema on the Cineworld website
|
|
143
152
|
# @return [String]
|
|
144
153
|
def url
|
|
145
|
-
"
|
|
154
|
+
"https://www.odeon.co.uk/cinemas/#{url_slug}/"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @api private
|
|
158
|
+
def self.sites_hash
|
|
159
|
+
@sites_hash ||= OdeonUk::Internal::ApiResponse.new.sites.to_h do |site|
|
|
160
|
+
[site.fetch('id'), site]
|
|
161
|
+
end
|
|
146
162
|
end
|
|
163
|
+
private_class_method :sites_hash
|
|
147
164
|
|
|
148
165
|
private
|
|
149
166
|
|
|
150
|
-
def
|
|
151
|
-
@
|
|
152
|
-
OdeonUk::Internal::ApiResponse.new.all_cinemas.fetch('sites', {})
|
|
167
|
+
def site_hash
|
|
168
|
+
@site_hash ||= self.class.send(:sites_hash).fetch(id, {})
|
|
153
169
|
end
|
|
154
170
|
|
|
155
|
-
def
|
|
156
|
-
|
|
171
|
+
def site_name
|
|
172
|
+
site_hash.dig('name', 'text').to_s
|
|
157
173
|
end
|
|
158
174
|
|
|
159
|
-
def
|
|
160
|
-
|
|
175
|
+
def url_slug
|
|
176
|
+
URL_SLUGS.fetch(id) do
|
|
177
|
+
site_name.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/\A-|-\z/, '')
|
|
178
|
+
end
|
|
161
179
|
end
|
|
162
180
|
end
|
|
163
181
|
end
|
|
@@ -1,55 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
3
4
|
|
|
4
5
|
module OdeonUk
|
|
5
6
|
# Internal utility classes: Do not use
|
|
6
7
|
# @api private
|
|
7
8
|
module Internal
|
|
8
|
-
#
|
|
9
|
+
# Fetches cinema and showtime data from the Vista API behind odeon.co.uk
|
|
9
10
|
class ApiResponse
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# cinemas information
|
|
14
|
-
# @return [Hash] decoded response of api containing cinema details
|
|
15
|
-
def all_cinemas
|
|
16
|
-
parse(all_cinemas_raw)
|
|
17
|
-
end
|
|
11
|
+
BASE_URL = "https://vwc.odeon.co.uk/WSVistaWebClient/ocapi/v1/"
|
|
12
|
+
# Limits sites to the UK, leaving out ODEON's Irish cinemas.
|
|
13
|
+
REGION_CODE = "UK"
|
|
18
14
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
parse(app_init_raw)
|
|
15
|
+
# @return [Array<Hash>] UK cinemas
|
|
16
|
+
def sites
|
|
17
|
+
client.get("sites").fetch("sites")
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
#
|
|
26
|
-
# @
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
# @param [String] cinema_id zero-padded site id, e.g. '017'
|
|
21
|
+
# @param [Date] date business date
|
|
22
|
+
# @return [Hash] showtimes plus their related films and attributes
|
|
23
|
+
def showtimes(cinema_id, date)
|
|
24
|
+
client.get("showtimes/by-business-date/#{date.iso8601}",
|
|
25
|
+
"siteIds" => cinema_id)
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
private
|
|
32
29
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def app_init_raw
|
|
38
|
-
post('app-init').body
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def film_times_raw(cinema_id, film_id)
|
|
42
|
-
post('film-times', { s: cinema_id, m: film_id }).body
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def post(path, request_body={})
|
|
46
|
-
uri = URI("https://api.odeon.co.uk/#{VERSION}/api/#{path}")
|
|
47
|
-
Net::HTTP.post_form(uri, request_body)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def parse(content)
|
|
51
|
-
plist = CFPropertyList::List.new(data: content)
|
|
52
|
-
CFPropertyList.native_types(plist.value).fetch('data', {})
|
|
30
|
+
def client
|
|
31
|
+
Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: REGION_CODE,
|
|
32
|
+
token: OdeonUk.token)
|
|
53
33
|
end
|
|
54
34
|
end
|
|
55
35
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module OdeonUk
|
|
6
|
+
module Internal
|
|
7
|
+
# Reads the public guest token from the ODEON website's page data
|
|
8
|
+
class GuestToken < Cinebase::Vista::GuestToken
|
|
9
|
+
# Every other page redirects to the queue-it waiting room first.
|
|
10
|
+
PAGE_URL = "https://www.odeon.co.uk/my-account/"
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def bundled_curl
|
|
15
|
+
File.expand_path("../../../vendor/curl-impersonate/bin/curl-impersonate", __dir__)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_token(html)
|
|
19
|
+
JSON.parse(html[/window\.initialData\s*=\s*(.+?);\s*$/, 1].to_s).dig("api", "authToken")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module OdeonUk
|
|
6
|
+
# @api private
|
|
7
|
+
module Internal
|
|
8
|
+
module Parser
|
|
9
|
+
module Api
|
|
10
|
+
# Parses a single Vista showtime into performance attributes
|
|
11
|
+
class Showtime
|
|
12
|
+
BOOKING_URL = "https://www.odeon.co.uk/ticketing/seat-picker/?showtimeId="
|
|
13
|
+
|
|
14
|
+
# Vista attribute short names
|
|
15
|
+
VARIANTS = {
|
|
16
|
+
"AudioDes" => "audio_described",
|
|
17
|
+
"AutFriend" => "autism_friendly",
|
|
18
|
+
"EnglishSub" => "subtitled",
|
|
19
|
+
"EventCinem" => "arts",
|
|
20
|
+
"IMAX" => "imax",
|
|
21
|
+
"Newbies" => "baby",
|
|
22
|
+
"ODEON Kids" => "kids",
|
|
23
|
+
"OpenCapt" => "subtitled",
|
|
24
|
+
"SilverCine" => "senior"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# @param [Hash] data one showtime
|
|
28
|
+
# @param [Hash{String => Hash}] films related films keyed by id
|
|
29
|
+
# @param [Hash{String => Hash}] attributes related attributes keyed by id
|
|
30
|
+
def initialize(data, films:, attributes:)
|
|
31
|
+
@data = data
|
|
32
|
+
@films = films
|
|
33
|
+
@attributes = attributes
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Hash] options for OdeonUk::Performance.new
|
|
37
|
+
def to_hash
|
|
38
|
+
{
|
|
39
|
+
booking_url: BOOKING_URL + @data.fetch("id"),
|
|
40
|
+
dimension: @data["requires3dGlasses"] ? "3d" : "2d",
|
|
41
|
+
festival: Cinebase::Festival.from_title(title, year: starting_at.year),
|
|
42
|
+
film_name: film_name,
|
|
43
|
+
language: language,
|
|
44
|
+
starting_at: starting_at,
|
|
45
|
+
variant: variant
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def attribute_names(key)
|
|
52
|
+
@data.fetch("attributeIds", []).filter_map do |id|
|
|
53
|
+
@attributes.dig(id, key, "text")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def starting_at
|
|
58
|
+
Time.iso8601(@data.dig("schedule", "startsAt")).utc
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def film_name
|
|
62
|
+
TitleSanitizer.new(title).sanitized
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def title
|
|
66
|
+
@films.fetch(@data.fetch("filmId")).dig("title", "text")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def language
|
|
70
|
+
attribute_names("name").filter_map { |name| name[/\A(.+) \(Audio\)\z/, 1] }.first
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def variant
|
|
74
|
+
attribute_names("shortName").filter_map { |name| VARIANTS[name] }.uniq
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -18,16 +18,11 @@ module OdeonUk
|
|
|
18
18
|
# strings and regex to be removed
|
|
19
19
|
def remove
|
|
20
20
|
[
|
|
21
|
-
/\s+[23][dD]/, # dimension
|
|
22
21
|
'Autism Friendly Screening -', # autism screening
|
|
23
|
-
/\ACinemagic \d{1,4} \-/, # cinemagic
|
|
24
22
|
/\(encore.+\)/i, # encore for NT Live
|
|
25
23
|
/\(live\)/i, # live in brackets
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
'Autism Friendly - ', # autism friendly
|
|
29
|
-
'amp;', # html ampersands
|
|
30
|
-
]
|
|
24
|
+
'Autism Friendly - ' # autism friendly
|
|
25
|
+
] + COMMON_REMOVE
|
|
31
26
|
end
|
|
32
27
|
|
|
33
28
|
# regexes and their replacements
|
|
@@ -40,7 +35,7 @@ module OdeonUk
|
|
|
40
35
|
/NT Live - (.*)/ => 'National Theatre: ',
|
|
41
36
|
/ROH - (.*)/ => 'Royal Opera House: ',
|
|
42
37
|
/(.*)\- RSC Live \d{1,4}/ => 'Royal Shakespeare Company: '
|
|
43
|
-
}
|
|
38
|
+
}.merge(COMMON_REPLACE)
|
|
44
39
|
end
|
|
45
40
|
end
|
|
46
41
|
end
|