odeon_uk 5.0.0-aarch64-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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +43 -0
  3. data/.gitignore +18 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +132 -0
  6. data/COMM-LICENSE +22 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +617 -0
  9. data/README.md +174 -0
  10. data/Rakefile +38 -0
  11. data/lib/odeon_uk/cinema.rb +181 -0
  12. data/lib/odeon_uk/internal/api_response.rb +36 -0
  13. data/lib/odeon_uk/internal/guest_token.rb +23 -0
  14. data/lib/odeon_uk/internal/parser/api/showtime.rb +80 -0
  15. data/lib/odeon_uk/internal/title_sanitizer.rb +42 -0
  16. data/lib/odeon_uk/performance.rb +81 -0
  17. data/lib/odeon_uk/version.rb +6 -0
  18. data/lib/odeon_uk.rb +30 -0
  19. data/odeon_uk.gemspec +31 -0
  20. data/test/fixtures/api/showtimes-824.json +66 -0
  21. data/test/fixtures/api/sites.json +32 -0
  22. data/test/lib/odeon_uk/cinema_test.rb +113 -0
  23. data/test/lib/odeon_uk/internal/api_response_test.rb +66 -0
  24. data/test/lib/odeon_uk/internal/fixture_shape_test.rb +14 -0
  25. data/test/lib/odeon_uk/internal/guest_token_test.rb +84 -0
  26. data/test/lib/odeon_uk/internal/parser/api/showtime_test.rb +68 -0
  27. data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +141 -0
  28. data/test/lib/odeon_uk/performance_test.rb +150 -0
  29. data/test/lib/odeon_uk/version_test.rb +7 -0
  30. data/test/live/api_shape_test.rb +19 -0
  31. data/test/live/integration_test.rb +21 -0
  32. data/test/live/live_helper.rb +11 -0
  33. data/test/package_test.rb +56 -0
  34. data/test/support/api_fixtures_helper.rb +16 -0
  35. data/test/support/api_shapes.rb +46 -0
  36. data/test/support/fake_api_response.rb +20 -0
  37. data/test/test_helper.rb +20 -0
  38. data/vendor/curl-impersonate/SOURCE +5 -0
  39. data/vendor/curl-impersonate/bin/curl-impersonate +0 -0
  40. data/vendor/curl-impersonate/licenses/LICENSE +21 -0
  41. data/vendor/curl-impersonate/licenses/LICENSE_BORINGSSL +238 -0
  42. data/vendor/curl-impersonate/licenses/LICENSE_BROTLI +19 -0
  43. data/vendor/curl-impersonate/licenses/LICENSE_CARES +24 -0
  44. data/vendor/curl-impersonate/licenses/LICENSE_CURL +22 -0
  45. data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_GPL2 +339 -0
  46. data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_GPL3 +35 -0
  47. data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_LGPL3 +165 -0
  48. data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_UNICODE +91 -0
  49. data/vendor/curl-impersonate/licenses/LICENSE_NGHTTP2 +23 -0
  50. data/vendor/curl-impersonate/licenses/LICENSE_NGHTTP3 +22 -0
  51. data/vendor/curl-impersonate/licenses/LICENSE_NGTCP2 +22 -0
  52. data/vendor/curl-impersonate/licenses/LICENSE_ZLIB +22 -0
  53. data/vendor/curl-impersonate/licenses/LICENSE_ZSTD +30 -0
  54. metadata +223 -0
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # OdeonUk
2
+
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
+
5
+ [![Gem Version](https://badge.fury.io/rb/odeon_uk.svg)](https://badge.fury.io/rb/odeon_uk)
6
+ [![Inline docs](http://inch-ci.org/github/andycroll/odeon_uk.svg?branch=master)](http://inch-ci.org/github/andycroll/odeon_uk)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'odeon_uk'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install odeon_uk
21
+
22
+ ## Usage
23
+
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.
25
+
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.
63
+
64
+ ### Cinema
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
+
70
+ ``` ruby
71
+ OdeonUk::Cinema.all
72
+ #=> [<OdeonUk::Cinema ...>, <OdeonUk::Cinema ...>, ...]
73
+
74
+ cinema = OdeonUk::Cinema.new(824)
75
+ #=> <OdeonUk::Cinema ...>
76
+
77
+ cinema.adr
78
+ #=> {
79
+ street_address: 'Kingswest',
80
+ extended_address: 'West Street',
81
+ locality: 'Brighton',
82
+ region: nil,
83
+ postal_code: nil,
84
+ country_name: 'United Kingdom'
85
+ }
86
+
87
+ cinema.brand
88
+ #=> 'Odeon'
89
+
90
+ cinema.full_name
91
+ #=> 'Odeon Brighton'
92
+
93
+ cinema.id
94
+ #=> '824'
95
+
96
+ cinema.url
97
+ #=> 'https://www.odeon.co.uk/cinemas/brighton/'
98
+ ```
99
+
100
+ ### Performances
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
+
106
+ ``` ruby
107
+ OdeonUk::Performance.at(824)
108
+ #=> [<OdeonUk::Performance ...>, <OdeonUk::Performance ...>, ...]
109
+
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
114
+ #=> <OdeonUk::Performance ...>
115
+
116
+ performance.film_name
117
+ #=> 'Street Fighter'
118
+
119
+ performance.dimension
120
+ #=> '2d'
121
+
122
+ performance.variant
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
133
+
134
+ performance.starting_at
135
+ #=> 2026-10-24 08:00:00 UTC
136
+
137
+ performance.showing_on
138
+ #=> #<Date: 2026-10-24 ((2461338j,0s,0n),+0s,2299161j)>
139
+
140
+ performance.booking_url
141
+ #=> 'https://www.odeon.co.uk/ticketing/seat-picker/?showtimeId=824-69206'
142
+
143
+ performance.cinema_name
144
+ #=> 'Brighton'
145
+
146
+ performance.cinema_id
147
+ #=> '824'
148
+ ```
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
+
166
+ ## Contributing
167
+
168
+ 1. Fork it
169
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
170
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
171
+ 4. Push to the branch (`git push origin my-new-feature`)
172
+ 5. Create new Pull Request
173
+
174
+ Note that contributors assign all rights to the owner, Andy Croll ([github](http://github.com/andycroll)), of this gem.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rake/testtask'
5
+ require 'cinebase/vista/package_task'
6
+
7
+ Cinebase::Vista::PackageTask.new('odeon_uk.gemspec', root: __dir__)
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << 'lib/odeon_uk'
11
+ t.test_files = FileList[
12
+ 'test/lib/odeon_uk/*_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'
24
+ ]
25
+ t.verbose = true
26
+ end
27
+
28
+ # http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
29
+ desc 'run gem console'
30
+ task :console do
31
+ require 'irb'
32
+ require 'irb/completion'
33
+ require 'odeon_uk'
34
+ ARGV.clear
35
+ IRB.start
36
+ end
37
+
38
+ task default: :test
@@ -0,0 +1,181 @@
1
+ module OdeonUk
2
+ # The object representing a cinema on the Odeon UK website
3
+ class Cinema < Cinebase::Cinema
4
+ # @!attribute [r] id
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
12
+
13
+ # Constructor
14
+ # @param [Integer, String] id the cinema id of the format 17/'017'
15
+ # @return [OdeonUk::Cinema]
16
+ def initialize(id)
17
+ @id = id.to_s.rjust(3, '0')
18
+ end
19
+
20
+ # Return basic cinema information for all UK cinemas
21
+ # @return [Array<OdeonUk::Cinema>]
22
+ # @example
23
+ # OdeonUk::Cinema.all
24
+ # #=> [<OdeonUk::Cinema>, <OdeonUk::Cinema>, ...]
25
+ def self.all
26
+ sites_hash.keys.map { |cinema_id| new(cinema_id) }
27
+ end
28
+
29
+ # @!method address
30
+ # Address of the cinema
31
+ # @return [Hash] of different address parts
32
+ # @see #adr
33
+
34
+ # Address of the cinema
35
+ # @return [Hash] contains :street_address, :extended_address,
36
+ # :locality, :postal_code, :country
37
+ # @example
38
+ # cinema = OdeonUk::Cinema.new(824)
39
+ # cinema.adr
40
+ # #=> {
41
+ # street_address: 'Kingswest',
42
+ # extended_address: 'West Street',
43
+ # locality: 'Brighton',
44
+ # region: nil,
45
+ # postal_code: nil,
46
+ # country_name: 'United Kingdom'
47
+ # }
48
+ # @note Uses the standard method naming as at http://microformats.org/wiki/adr
49
+ # @note The API does not supply postcodes
50
+ def adr
51
+ address = site_hash.dig('contactDetails', 'address') || {}
52
+ {
53
+ street_address: address['line1'],
54
+ extended_address: address['line2'],
55
+ locality: address['city'],
56
+ region: nil,
57
+ postal_code: nil,
58
+ country_name: 'United Kingdom'.freeze
59
+ }
60
+ end
61
+
62
+ # Brand of the cinema
63
+ # @return [String] which will always be 'Odeon'
64
+ # @example
65
+ # cinema = OdeonUk::Cinema.new(3)
66
+ # cinema.brand
67
+ # #=> 'Odeon'
68
+ def brand
69
+ 'Odeon'.freeze
70
+ end
71
+
72
+ # @!method country_name
73
+ # Country of the cinema
74
+ # @return [String] which will always be 'United Kingdom'
75
+ # @example
76
+ # cinema = CineworldUk::Cinema.new(3)
77
+ # cinema.country_name
78
+ # #=> 'United Kingdom'
79
+
80
+ # @!method extended_address
81
+ # The second address line of the cinema
82
+ # @return [String]
83
+ # @example
84
+ # cinema = CineworldUk::Cinema.new(10)
85
+ # cinema.extended_address
86
+ # #=> 'Chelsea'
87
+ #
88
+ # cinema = CineworldUk::Cinema.new(3)
89
+ # cinema.extended_address
90
+ # #=> ''
91
+
92
+ # @!method full_name
93
+ # The name of the cinema including the brand
94
+ # @return [String]
95
+ # @example
96
+ # cinema = CineworldUk::Cinema.new(88)
97
+ # cinema.full_name
98
+ # #=> 'Cineworld Glasgow: IMAX at GSC'
99
+
100
+ # @!method locality
101
+ # The locality (town) of the cinema
102
+ # @return [String]
103
+ # @example
104
+ # cinema = CineworldUk::Cinema.new(3)
105
+ # cinema.locality
106
+ # #=> 'Brighton'
107
+
108
+ # The name of the cinema
109
+ # @return [String]
110
+ # @example
111
+ # cinema = CineworldUk::Cinema.new(3)
112
+ # cinema.name
113
+ # #=> 'Brighton'
114
+ def name
115
+ @name ||= site_name.gsub(/\ALondon /, '').gsub(' - ', ': ')
116
+ end
117
+
118
+ # @!method postal_code
119
+ # Post code of the cinema
120
+ # @return [String]
121
+ # @example
122
+ # cinema = CineworldUk::Cinema.new(3)
123
+ # cinema.postal_code
124
+ # #=> 'BN2 5UF'
125
+
126
+ # @!method region
127
+ # The region (county) of the cinema if provided
128
+ # @return [String]
129
+ # @example
130
+ # cinema = CineworldUk::Cinema.new(3)
131
+ # cinema.region
132
+ # #=> 'East Sussex'
133
+
134
+ # @!method slug
135
+ # The URL-able slug of the cinema
136
+ # @return [String]
137
+ # @example
138
+ # cinema = CineworldUk::Cinema.new(3)
139
+ # cinema.slug
140
+ # #=> 'odeon-brighton'
141
+
142
+ # @!method street_address
143
+ # The street address of the cinema
144
+ # @return [String]
145
+ # @example
146
+ # cinema = CineworldUk::Cinema.new(3)
147
+ # cinema.street_address
148
+ # #=> 'Brighton Marina'
149
+ # @note Uses the standard method naming as at http://microformats.org/wiki/adr
150
+
151
+ # The url of the cinema on the Cineworld website
152
+ # @return [String]
153
+ def url
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
162
+ end
163
+ private_class_method :sites_hash
164
+
165
+ private
166
+
167
+ def site_hash
168
+ @site_hash ||= self.class.send(:sites_hash).fetch(id, {})
169
+ end
170
+
171
+ def site_name
172
+ site_hash.dig('name', 'text').to_s
173
+ end
174
+
175
+ def url_slug
176
+ URL_SLUGS.fetch(id) do
177
+ site_name.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/\A-|-\z/, '')
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module OdeonUk
6
+ # Internal utility classes: Do not use
7
+ # @api private
8
+ module Internal
9
+ # Fetches cinema and showtime data from the Vista API behind odeon.co.uk
10
+ class ApiResponse
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"
14
+
15
+ # @return [Array<Hash>] UK cinemas
16
+ def sites
17
+ client.get("sites").fetch("sites")
18
+ end
19
+
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)
26
+ end
27
+
28
+ private
29
+
30
+ def client
31
+ Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: REGION_CODE,
32
+ token: OdeonUk.token)
33
+ end
34
+ end
35
+ end
36
+ 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
@@ -0,0 +1,42 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # Sanitize and standardize film titles
6
+ class TitleSanitizer < Cinebase::TitleSanitizer
7
+ # @!method initialize(title)
8
+ # Constructor
9
+ # @param [String] title a film title
10
+ # @return [CineworldUk::Internal::TitleSanitizer]
11
+
12
+ # @!method sanitized
13
+ # sanitized and standardized title
14
+ # @return [String] sanitised title
15
+
16
+ private
17
+
18
+ # strings and regex to be removed
19
+ def remove
20
+ [
21
+ 'Autism Friendly Screening -', # autism screening
22
+ /\(encore.+\)/i, # encore for NT Live
23
+ /\(live\)/i, # live in brackets
24
+ 'Autism Friendly - ' # autism friendly
25
+ ] + COMMON_REMOVE
26
+ end
27
+
28
+ # regexes and their replacements
29
+ def replace
30
+ {
31
+ /Bolshoi - (.*)/ => 'Bolshoi: ',
32
+ /Globe On Screen: (.*)/ => 'Globe: ',
33
+ /Met Opera - (.*)/ => 'Met Opera: ',
34
+ /National Theatre Live - (.*)/ => 'National Theatre: ',
35
+ /NT Live - (.*)/ => 'National Theatre: ',
36
+ /ROH - (.*)/ => 'Royal Opera House: ',
37
+ /(.*)\- RSC Live \d{1,4}/ => 'Royal Shakespeare Company: '
38
+ }.merge(COMMON_REPLACE)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,81 @@
1
+ module OdeonUk
2
+ # The object representing a single screening of a film on the Odeon UK website
3
+ class Performance < Cinebase::Performance
4
+ # @!attribute [r] booking_url
5
+ # @return [String] the booking URL on the cinema website
6
+ # @!attribute [r] cinema_name
7
+ # @return [String] the cinema name
8
+ # @!attribute [r] cinema_id
9
+ # @return [String] the cinema id
10
+ # @!attribute [r] dimension
11
+ # @return [String] 2d or 3d
12
+ # @!attribute [r] film_name
13
+ # @return [String] the film name
14
+
15
+ # @!method initialize(options)
16
+ # @param [Hash] options options hash
17
+ # @option options [String] :booking_url (nil) buying url for the screening
18
+ # @option options [String] :cinema_name name of the cinema
19
+ # @option options [String] :cinema_id website id of the cinema
20
+ # @option options [String] :dimension ('2d') dimension of the screening
21
+ # @option options [String] :film_name name of the film
22
+ # @option options [Time] :starting_at listed start time of the performance
23
+ # @option options [Cinebase::Festival] :festival (nil) festival edition
24
+ # @option options [String] :language (nil) audio language of the screening
25
+
26
+ # Performances within an inclusive range of cinema business dates.
27
+ # @param [Integer, String] cinema_id id of the cinema on the website
28
+ # @param [Date] from first date (defaults to today in London)
29
+ # @param [Date] to last date (defaults to 13 days after from)
30
+ # @return [Array<OdeonUk::Performance>]
31
+ def self.at(
32
+ cinema_id,
33
+ from: TZInfo::Timezone.get('Europe/London').now.to_date,
34
+ to: from + 13
35
+ )
36
+ raise ArgumentError, 'to must not precede from' if to < from
37
+
38
+ api = OdeonUk::Internal::ApiResponse.new
39
+ cinema = Cinema.new(cinema_id)
40
+ (from..to).flat_map do |date|
41
+ performances_on(api, cinema, date)
42
+ end
43
+ end
44
+
45
+ # @!method showing_on
46
+ # The date of the screening
47
+ # @return [Date]
48
+
49
+ # @!method starting_at
50
+ # UTC time of the screening
51
+ # @return [Time]
52
+
53
+ # @!method variant
54
+ # The kinds of screening (IMAX, kids, baby, senior)
55
+ # @return <Array[String]>
56
+
57
+
58
+ # private
59
+
60
+ def self.performances_on(api, cinema, date)
61
+ response = api.showtimes(cinema.id, date)
62
+ related = response.fetch('relatedData')
63
+ films = by_id(related.fetch('films'))
64
+ attributes = by_id(related.fetch('attributes'))
65
+
66
+ response.fetch('showtimes').map do |data|
67
+ showtime = OdeonUk::Internal::Parser::Api::Showtime.new(
68
+ data, films: films, attributes: attributes
69
+ )
70
+ new(showtime.to_hash.merge(cinema_id: cinema.id,
71
+ cinema_name: cinema.name))
72
+ end
73
+ end
74
+ private_class_method :performances_on
75
+
76
+ def self.by_id(items)
77
+ items.to_h { |item| [item.fetch('id'), item] }
78
+ end
79
+ private_class_method :by_id
80
+ end
81
+ end
@@ -0,0 +1,6 @@
1
+ # Ruby interface for https://www.odeon.co.uk
2
+ # @version 5.0.0
3
+ module OdeonUk
4
+ # Gem version
5
+ VERSION = '5.0.0'.freeze
6
+ end
data/lib/odeon_uk.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+
3
+ require 'cinebase'
4
+ require 'cinebase/vista'
5
+
6
+ require_relative './odeon_uk/version'
7
+
8
+ require_relative './odeon_uk/internal/api_response'
9
+ require_relative './odeon_uk/internal/guest_token'
10
+ require_relative './odeon_uk/internal/title_sanitizer'
11
+
12
+ require_relative './odeon_uk/internal/parser/api/showtime'
13
+
14
+ require_relative './odeon_uk/cinema'
15
+ require_relative './odeon_uk/performance'
16
+
17
+ # odeon_uk gem
18
+ module OdeonUk
19
+ AuthenticationError = Cinebase::Vista::AuthenticationError
20
+
21
+ # A raw guest JWT, without the "Bearer " prefix. Never persisted by the gem.
22
+ class << self
23
+ attr_accessor :token
24
+
25
+ # Explicit acquisition/renewal; ordinary API calls never launch a subprocess.
26
+ def acquire_token!(curl: nil)
27
+ self.token = Internal::GuestToken.new(curl: curl).fetch
28
+ end
29
+ end
30
+ end