cineworld_uk 4.0.0 → 4.2.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +26 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +97 -6
  5. data/Rakefile +3 -23
  6. data/cineworld_uk.gemspec +2 -1
  7. data/lib/cineworld_uk/internal/api_response.rb +7 -30
  8. data/lib/cineworld_uk/internal/guest_token.rb +30 -0
  9. data/lib/cineworld_uk/internal/parser/api/film.rb +6 -0
  10. data/lib/cineworld_uk/internal/parser/api/performance.rb +5 -0
  11. data/lib/cineworld_uk/internal/title_sanitizer.rb +2 -10
  12. data/lib/cineworld_uk/performance.rb +4 -1
  13. data/lib/cineworld_uk/version.rb +2 -2
  14. data/lib/cineworld_uk.rb +8 -1
  15. data/test/fixtures/api/showtimes-014.json +67 -0
  16. data/test/fixtures/api/sites.json +52 -0
  17. data/test/lib/cineworld_uk/cinema_test.rb +63 -223
  18. data/test/lib/cineworld_uk/internal/api_response_test.rb +28 -107
  19. data/test/lib/cineworld_uk/internal/fixture_shape_test.rb +14 -0
  20. data/test/lib/cineworld_uk/internal/guest_token_test.rb +87 -0
  21. data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +44 -85
  22. data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +53 -131
  23. data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +27 -45
  24. data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +75 -126
  25. data/test/lib/cineworld_uk/performance_test.rb +153 -180
  26. data/test/lib/cineworld_uk/version_test.rb +3 -3
  27. data/test/live/api_shape_test.rb +22 -0
  28. data/test/live/integration_test.rb +13 -43
  29. data/test/live/live_helper.rb +11 -0
  30. data/test/package_test.rb +56 -0
  31. data/test/support/api_shapes.rb +48 -0
  32. data/test/support/vista_fixtures.rb +58 -0
  33. data/test/test_helper.rb +3 -0
  34. metadata +37 -14
  35. data/test/fixtures/api/cinema-list.json +0 -1
  36. data/test/fixtures/api/dates-14.json +0 -1
  37. data/test/fixtures/api/performances-tomorrow-14.json +0 -1
  38. data/test/fixtures/api/vista-showtimes-brighton.json +0 -1
  39. data/test/support/fixture_reader.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5284f7c7c8b846edbdd724cb3c105b033da4f313eed083d6b691ae6b8714d3b8
4
- data.tar.gz: 1568acd04ed0346ba388509903a17487726c088ff2d280329cfb0fe8f6023eed
3
+ metadata.gz: 97a79daf0c192e1c7a12ab72a96c6fae42433c44761fa7a991cfb0a2d456cf51
4
+ data.tar.gz: dfa8faf8c393df7b2df082d458af26a63aa8171a9a6f45fb964b040b07864baa
5
5
  SHA512:
6
- metadata.gz: faff1f58b5e101ce8185a96f791a27088ed31f7c78d3eebf3f9b332b6931848604726b7e5b3d16f3bb51c1878c4693cb993863cf48dcbbe743436208e5c3186b
7
- data.tar.gz: 2ab7741bafc40d66d104390340fa2e28a1a5b0a6d166f85b3526f8eb576d664b6860c5f03032fc3d71a2c21bb3ece25804d5258aeba3a7f74601027610c161d8
6
+ metadata.gz: 67fcdf358e0f0764b5d0b240999fcbfd1bc313a0ce9a97eb75fc5cc5d413ca7a244c74ef9f5993249fa2496bb33a8c03a3968f048c2a6a9e28a6ba444ed14bec
7
+ data.tar.gz: 19641348e8182a4aefdefc2674b934a08a4a1ac0ffcf109b6c678262400dd78b451845ad807ad7a4ad54a61bce461cfb9fd3e6612a1ac4a8f419427c46925647
@@ -15,3 +15,29 @@ jobs:
15
15
  ruby-version: ${{ matrix.ruby-version }}
16
16
  bundler-cache: true
17
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: '3.4'
37
+ bundler-cache: true
38
+ - run: bundle exec rake 'package:native[${{ matrix.platform }}]'
39
+ - run: CINEWORLD_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/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
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.2.0 - 2026-10-22
6
+
7
+ ### Added
8
+ - `Performance#language` from Vista language attributes such as `Tamil`, `Performance#subtitled?`, and `Performance#festival`, a `Cinebase::Festival` with `name` and `year`.
9
+ - Live API shape tests that check real responses without storing them.
10
+
11
+ ### Changed
12
+ - Require cinebase 5.2. The Vista client, guest token retrieval and platform packaging task now come from `cinebase/vista`; behaviour, errors and pinned curl-impersonate digests are unchanged.
13
+ - `CineworldUk::AuthenticationError` is now `Cinebase::Vista::AuthenticationError`, so existing `rescue` clauses keep working.
14
+ - Film titles also use cinebase's common rules: dub and subtitle versions, more language versions, anniversaries, Q&A add-ons and festival prefixes are removed.
15
+ - Tests use plain `Minitest::Test` classes, with hand-written fixtures in place of recorded API responses.
16
+
17
+ ### Fixed
18
+ - `Sing-Along` inside a real title, such as `Bebefinn Sing-Along Movie`, is no longer removed.
19
+
20
+ ### Removed
21
+ - The `fixtures` rake task, which saved API responses into the repository.
22
+
23
+ ## 4.1.0 - 2026-10-15
24
+
25
+ ### Added
26
+ - Explicit guest token acquisition and renewal with `CineworldUk.acquire_token!`, using the standalone `curl-impersonate` CLI without Python or a browser.
27
+ - Platform-specific gems bundle curl-impersonate 2.2.3 for x86-64/ARM64 glibc Linux and macOS, with pinned archive checksums, upstream notices, and native package CI checks. No separate executable installation is needed on these platforms.
28
+ - Optional executable path for token acquisition. Existing caller-supplied tokens continue to work without the CLI.
29
+ - Expiry checks and authentication errors for unavailable commands, blocked pages, and missing or invalid page tokens; failed acquisition preserves the previous token.
30
+
5
31
  ## 4.0.0 - 2026-10-15
6
32
 
7
33
  ### Changed
data/README.md CHANGED
@@ -27,22 +27,70 @@ Performance titles are sanitized as much as possible, removing 'screening type'
27
27
 
28
28
  ### Authentication
29
29
 
30
- Configure a current guest JWT before retrieving data (without the `Bearer ` prefix):
30
+ Acquire a guest JWT explicitly before retrieving data:
31
31
 
32
32
  ```ruby
33
33
  require 'cineworld_uk'
34
34
 
35
+ CineworldUk.acquire_token!
36
+ CineworldUk::Cinema.all
37
+ ```
38
+
39
+ This shells out to **`curl-impersonate`**, not ordinary system `curl`, using its
40
+ Chrome 150 TLS/HTTP profile. No Python, Chrome, JavaScript execution or browser
41
+ cookies are needed. Platform-specific gem packages include the pinned
42
+ `curl-impersonate` 2.2.3 executable and its upstream licence notices. RubyGems
43
+ and Bundler automatically select the matching package for:
44
+
45
+ - glibc Linux: x86-64 and ARM64 (`x86_64-linux-gnu`, `aarch64-linux-gnu`)
46
+ - macOS: Intel and Apple Silicon (`x86_64-darwin`, `arm64-darwin`)
47
+
48
+ The executable runs directly from the installed gem. There are no separate
49
+ installation commands, PATH changes, or runtime downloads on these platforms.
50
+
51
+ When installing from Git, forcing the source (`ruby`) gem, or using another
52
+ platform (including Alpine/musl and Windows), install a compatible
53
+ [curl-impersonate release](https://github.com/lexiforest/curl-impersonate/releases/tag/v2.2.3)
54
+ with `--impersonate chrome150` support yourself. For an x86-64 Linux checkout:
55
+
56
+ ```sh
57
+ mkdir -p "$HOME/.local/bin"
58
+ curl -fL https://github.com/lexiforest/curl-impersonate/releases/download/v2.2.3/curl-impersonate-v2.2.3.x86_64-linux-gnu.tar.gz \
59
+ | tar -xz -C "$HOME/.local/bin" curl-impersonate
60
+ export PATH="$HOME/.local/bin:$PATH"
61
+ ```
62
+
63
+ Other platforms have separate assets on the release page. You can also specify
64
+ the executable path:
65
+
66
+ ```ruby
67
+ CineworldUk.acquire_token!(curl: '/opt/curl-impersonate/curl-impersonate')
68
+ ```
69
+
70
+ The method returns the token and sets `CineworldUk.token` in memory. It reads
71
+ `__NEXT_DATA__.props.pageProps.environment.gasToken` from the ticketing site's
72
+ root page, even when the page displays a 404. It checks the declared expiry,
73
+ but decoding the JWT does not verify its signature. A blocked page, expired or
74
+ missing token, missing executable, or failed command raises
75
+ `CineworldUk::AuthenticationError` and leaves any previous token unchanged.
76
+ Requests time out after 30 seconds. Certificate verification remains enabled;
77
+ redirects are not followed, and subprocess arguments are passed without a shell.
78
+ Cloudflare may still block requests on other networks or after website changes.
79
+
80
+ Alternatively, supply your own current guest JWT without the `Bearer ` prefix;
81
+ this requires no external executable:
82
+
83
+ ```ruby
35
84
  CineworldUk.token = ENV.fetch('CINEWORLD_TOKEN')
36
85
  ```
37
86
 
38
87
  The gem sends `Authorization: Bearer ...` and `Connect-Region-Code: GB` to
39
- `digital-api.cineworld.co.uk`. It does not acquire or refresh tokens, copy browser
40
- cookies, or bypass Cloudflare. The ticketing website supplies its guest token in
41
- `__NEXT_DATA__.props.pageProps.environment.gasToken`; access to that page may
42
- require an interactive browser.
88
+ `digital-api.cineworld.co.uk`. Normal API calls never acquire or renew a token
89
+ automatically.
43
90
 
44
91
  Missing tokens and HTTP 401 responses raise `CineworldUk::AuthenticationError`.
45
- Set `CineworldUk.token` again with a fresh token before retrying. Other HTTP
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
46
94
  failures raise an error rather than returning an empty schedule. Redirects are
47
95
  not followed, so credentials are not forwarded to another host.
48
96
 
@@ -129,6 +177,15 @@ performance.dimension
129
177
  performance.variant
130
178
  #=> ['audio_described']
131
179
 
180
+ performance.language
181
+ #=> 'Tamil' # audio language, nil when the cinema does not tag one
182
+
183
+ performance.subtitled?
184
+ #=> true
185
+
186
+ performance.festival
187
+ #=> #<data Cinebase::Festival name="Cine-Excess", year=2026> # nil outside festivals
188
+
132
189
  performance.starting_at
133
190
  #=> 2026-10-14 14:40:00 UTC
134
191
 
@@ -145,6 +202,40 @@ performance.cinema_id
145
202
  #=> 14
146
203
  ```
147
204
 
205
+ ## Tests
206
+
207
+ ```sh
208
+ bundle exec rake # unit tests against fixtures
209
+ bundle exec rake live # live API, needs curl-impersonate or CINEWORLD_TOKEN
210
+ ```
211
+
212
+ Fixtures in `test/fixtures/api` are hand-written: invented films and
213
+ addresses in the shape of Vista responses, never recorded API output.
214
+ `test/support/api_shapes.rb` describes the fields the gem reads. The unit suite
215
+ checks the fixtures against it and the live suite checks real responses, held
216
+ only in memory. When the API changes, update the shape first, then the fixtures.
217
+
218
+ ## Building release packages
219
+
220
+ ```sh
221
+ bundle exec rake build # Portable source gem; external curl-impersonate required
222
+ bundle exec rake package:all # Four platform gems with bundled executables
223
+ ```
224
+
225
+ To build and check just one platform on a matching host:
226
+
227
+ ```sh
228
+ bundle exec rake 'package:native[x86_64-linux-gnu]'
229
+ CINEWORLD_PACKAGE=pkg/cineworld_uk-4.2.0-x86_64-linux-gnu.gem bundle exec ruby test/package_test.rb
230
+ ```
231
+
232
+ Native packaging downloads pinned upstream archives at **build time**, verifies
233
+ their SHA-256 digests, and includes the executable, licence files and source
234
+ provenance. Archives and built gems stay in ignored `pkg/`, not Git. CI builds
235
+ and installation-tests each platform on its corresponding runner and saves the
236
+ gems as workflow artifacts; it does not publish them. Publish the source and
237
+ platform gems together for automatic platform selection to work for users.
238
+
148
239
  ## Contributing
149
240
 
150
241
  1. Fork it
data/Rakefile CHANGED
@@ -2,6 +2,9 @@
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('cineworld_uk.gemspec', root: __dir__)
5
8
 
6
9
  Rake::TestTask.new do |t|
7
10
  t.libs << 'lib/cineworld_uk'
@@ -32,27 +35,4 @@ task :console do
32
35
  IRB.start
33
36
  end
34
37
 
35
- desc 'Recreate test fixtures'
36
- task :fixtures do
37
- require 'cineworld_uk'
38
- CineworldUk.token = ENV.fetch("CINEWORLD_TOKEN")
39
-
40
- def js_fixture(name)
41
- File.expand_path("../test/fixtures/#{name}.json", __FILE__)
42
- end
43
-
44
- api = CineworldUk::Internal::ApiResponse.new
45
-
46
- File.open(js_fixture('api/cinema-list'), 'w') do |file|
47
- puts '* API Cinema List'
48
- file.write api.cinema_list
49
- end
50
-
51
- File.open(js_fixture('api/performances-tomorrow-14'), 'w') do |file|
52
- puts '* API Performances in Brighton Tomorrow'
53
- file.write api.performances(14, Date.today + 1)
54
- end
55
-
56
- end
57
-
58
38
  task :default => :test
data/cineworld_uk.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
+ spec.add_development_dependency 'base64'
20
21
  spec.add_development_dependency 'bundler', '>= 2.0'
21
22
  spec.add_development_dependency 'minitest-mock'
22
23
  spec.add_development_dependency 'minitest-reporters'
@@ -24,6 +25,6 @@ Gem::Specification.new do |spec|
24
25
  spec.add_development_dependency 'simplecov', '< 1'
25
26
  spec.add_development_dependency 'webmock'
26
27
 
27
- spec.add_runtime_dependency 'cinebase', '~> 5.0'
28
+ spec.add_runtime_dependency 'cinebase', '~> 5.2'
28
29
  spec.add_runtime_dependency 'nokogiri', '>= 1.11'
29
30
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
3
  require "json"
5
4
  require "date"
6
5
  require "time"
@@ -17,6 +16,7 @@ module CineworldUk
17
16
  "Movies for Juniors" => "junior",
18
17
  "Unlimited Screening" => "unlimited-screening", "Q&A" => "qa"
19
18
  }.freeze
19
+ LANGUAGES = Cinebase::TitleSanitizer::LANGUAGES
20
20
 
21
21
  def cinema_list
22
22
  cinemas = fetch("sites").fetch("sites").map do |site|
@@ -53,6 +53,9 @@ module CineworldUk
53
53
  ATTRIBUTES[attribute.dig("name", "text")]
54
54
  end
55
55
  attrs << "3d" if showtime["requires3dGlasses"]
56
+ language = showtime.fetch("attributeIds", []).filter_map do |id|
57
+ attributes.fetch(id).dig("shortName", "text")
58
+ end.find { |name| LANGUAGES.include?(name) }
56
59
  # Formats belong to a session, not to the film as a whole.
57
60
  id = showtime.fetch("id")
58
61
  normalized_films << {
@@ -62,7 +65,7 @@ module CineworldUk
62
65
  }
63
66
  starts_at = showtime.fetch("schedule").fetch("startsAt")
64
67
  {
65
- "filmId" => id, "attributeIds" => attrs,
68
+ "filmId" => id, "attributeIds" => attrs, "language" => language,
66
69
  "eventDateTime" => Time.iso8601(starts_at).utc.iso8601,
67
70
  "bookingLink" =>
68
71
  "https://web.cineworld.co.uk/order/showtimes/#{id}/seats"
@@ -82,34 +85,8 @@ module CineworldUk
82
85
  private
83
86
 
84
87
  def fetch(path, query = {})
85
- token = CineworldUk.token
86
- if !token.is_a?(String) || token.strip.empty?
87
- raise AuthenticationError,
88
- "Set CineworldUk.token to a current guest JWT " \
89
- "before fetching data"
90
- end
91
- uri = URI(BASE_URL + path)
92
- uri.query = URI.encode_www_form(query) unless query.empty?
93
- request = Net::HTTP::Get.new(uri)
94
- request["Authorization"] = "Bearer #{token}"
95
- request["Connect-Region-Code"] = "GB"
96
- request["Accept"] = "application/json"
97
- response = Net::HTTP.start(
98
- uri.host, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30
99
- ) do |http|
100
- http.request(request)
101
- end
102
- case response
103
- when Net::HTTPSuccess
104
- JSON.parse(response.body)
105
- when Net::HTTPUnauthorized
106
- raise AuthenticationError,
107
- "Vista rejected the guest token; " \
108
- "supply a fresh token with CineworldUk.token="
109
- else
110
- # Omit response bodies and never redirect credentials to another host.
111
- raise "Vista HTTP error #{response.code}"
112
- end
88
+ Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: "GB",
89
+ token: CineworldUk.token).get(path, query)
113
90
  end
114
91
  end
115
92
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "nokogiri"
5
+
6
+ module CineworldUk
7
+ module Internal
8
+ # Reads the public guest token from the ticketing site's page data
9
+ class GuestToken < Cinebase::Vista::GuestToken
10
+ PAGE_URL = "https://web.cineworld.co.uk/"
11
+
12
+ private
13
+
14
+ # The root page carries the token even when it displays a 404.
15
+ def accepted_statuses
16
+ %w[200 404]
17
+ end
18
+
19
+ def bundled_curl
20
+ File.expand_path("../../../vendor/curl-impersonate/bin/curl-impersonate", __dir__)
21
+ end
22
+
23
+ def extract_token(html)
24
+ script = Nokogiri::HTML(html).at_css("script#__NEXT_DATA__")
25
+ data = JSON.parse(script ? script.content : "")
26
+ data.dig("props", "pageProps", "environment", "gasToken")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -21,6 +21,12 @@ module CineworldUk
21
21
  @data['attributeIds']&.include?('3d') ? '3d' : '2d'
22
22
  end
23
23
 
24
+ # @param [Integer] year edition when the title gives none
25
+ # @return [Cinebase::Festival, nil]
26
+ def festival(year)
27
+ Cinebase::Festival.from_title(@data['name'] || '', year: year)
28
+ end
29
+
24
30
  # Sanitized film name
25
31
  # @return [String]
26
32
  def name
@@ -21,6 +21,11 @@ module CineworldUk
21
21
  @data['filmId']
22
22
  end
23
23
 
24
+ # @return [String, nil] audio language, e.g. 'Tamil'
25
+ def language
26
+ @data['language']
27
+ end
28
+
24
29
  # @return [DateTime] in local time
25
30
  def starting_at
26
31
  Time.parse(@data['eventDateTime'])
@@ -18,13 +18,6 @@ module CineworldUk
18
18
  # Strings and regex to be removed
19
19
  def remove
20
20
  [
21
- /\s+[23]d/i, # dimension
22
- /\(Hindi\)/i, # Indian Language
23
- /\(Malayalam\)/i, # Indian Language
24
- /\(Punjabi\)/i, # Indian Language
25
- /\(Tamil\)/i, # Indian Language
26
- /\(Cantonese\)/i, # Chinese
27
- /\(Turkish\)/i, # Foreign Language
28
21
  %r(-? \d{1,2}/\d{1,2}/\d{2,4}), # date
29
22
  /\n/, # newlines
30
23
  /\- Encore/, # encore
@@ -38,9 +31,8 @@ module CineworldUk
38
31
  'SciScreen: ', # SciScreen
39
32
  /\s(-\s)*(With)*\sLive Q And A/i, # Live Q&A
40
33
  /(\s+\-\s+)?MOVIES FOR JUNIORS/i, # movies for juniors
41
- /\bsing\-?a\-?long\b/i, # singalong
42
34
  %r{\s20\d{2}[\/-]20\d{2} Season\b}
43
- ]
35
+ ] + COMMON_REMOVE
44
36
  end
45
37
 
46
38
  # Regexes and their replacements
@@ -61,7 +53,7 @@ module CineworldUk
61
53
  /ROH The Royal Ballet\s*[:-] (.*)/ => 'The Royal Ballet: ',
62
54
  /Royal Ballet Live\s*[:-] (.*)/ => 'The Royal Ballet: ',
63
55
  /San Francisco Ballets (.*)/ => 'San Francisco Ballet: '
64
- }
56
+ }.merge(COMMON_REPLACE)
65
57
  end
66
58
  end
67
59
  end
@@ -71,14 +71,17 @@ module CineworldUk
71
71
  events.map do |event_data|
72
72
  performance = Internal::Parser::Api::Performance.new(event_data)
73
73
  film = films_hash[performance.film_id]
74
+ starting_at = utc(performance.starting_at)
74
75
 
75
76
  {
76
77
  cinema_name: response["body"]["cinemaName"] ||
77
78
  CineworldUk::Cinema.new(cinema_id).name,
78
79
  booking_url: performance.booking_url,
79
80
  dimension: film.dimension,
81
+ festival: film.festival(starting_at.year),
80
82
  film_name: film.name,
81
- starting_at: utc(performance.starting_at),
83
+ language: performance.language,
84
+ starting_at: starting_at,
82
85
  variant: (performance.variant + film.variant).sort
83
86
  }
84
87
  end
@@ -1,6 +1,6 @@
1
1
  # Ruby interface for http://www.cineworld.co.uk
2
- # @version 4.0.0
2
+ # @version 4.2.0
3
3
  module CineworldUk
4
4
  # Gem version
5
- VERSION = "4.0.0".freeze
5
+ VERSION = "4.2.0".freeze
6
6
  end
data/lib/cineworld_uk.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  require 'json'
2
2
 
3
3
  require 'cinebase'
4
+ require 'cinebase/vista'
4
5
 
5
6
  require_relative './cineworld_uk/version'
6
7
 
7
8
  require_relative './cineworld_uk/internal/api_response'
9
+ require_relative './cineworld_uk/internal/guest_token'
8
10
  require_relative './cineworld_uk/internal/parser/api/cinema_address'
9
11
  require_relative './cineworld_uk/internal/parser/api/film'
10
12
  require_relative './cineworld_uk/internal/parser/api/performance'
@@ -16,10 +18,15 @@ require_relative './cineworld_uk/performance'
16
18
 
17
19
  # cineworld_uk gem
18
20
  module CineworldUk
19
- class AuthenticationError < StandardError; end
21
+ AuthenticationError = Cinebase::Vista::AuthenticationError
20
22
 
21
23
  # A raw guest JWT, without the "Bearer " prefix. Never persisted by the gem.
22
24
  class << self
23
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
24
31
  end
25
32
  end
@@ -0,0 +1,67 @@
1
+ {
2
+ "businessDate": "2026-10-24",
3
+ "showtimes": [
4
+ {
5
+ "id": "014-1001",
6
+ "filmId": "FILM-1",
7
+ "schedule": { "startsAt": "2026-10-24T09:00:00+01:00" },
8
+ "attributeIds": ["A-2D", "A-AD"],
9
+ "requires3dGlasses": false
10
+ },
11
+ {
12
+ "id": "014-1002",
13
+ "filmId": "FILM-1",
14
+ "schedule": { "startsAt": "2026-10-24T14:30:00+01:00" },
15
+ "attributeIds": ["A-IMAX"],
16
+ "requires3dGlasses": true
17
+ },
18
+ {
19
+ "id": "014-1003",
20
+ "filmId": "FILM-2",
21
+ "schedule": { "startsAt": "2026-10-24T18:00:00+01:00" },
22
+ "attributeIds": ["A-TAMIL", "A-ST"],
23
+ "requires3dGlasses": false
24
+ },
25
+ {
26
+ "id": "014-1004",
27
+ "filmId": "FILM-3",
28
+ "schedule": { "startsAt": "2026-10-24T10:00:00+01:00" },
29
+ "attributeIds": ["A-M4J"],
30
+ "requires3dGlasses": false
31
+ },
32
+ {
33
+ "id": "014-1005",
34
+ "filmId": "FILM-4",
35
+ "schedule": { "startsAt": "2026-10-24T20:15:00+01:00" },
36
+ "attributeIds": ["A-AC"],
37
+ "requires3dGlasses": false
38
+ },
39
+ {
40
+ "id": "014-1006",
41
+ "filmId": "FILM-1",
42
+ "schedule": { "startsAt": "2026-10-25T00:15:00+01:00" },
43
+ "attributeIds": [],
44
+ "requires3dGlasses": false
45
+ }
46
+ ],
47
+ "relatedData": {
48
+ "films": [
49
+ { "id": "FILM-1", "title": { "text": "The Example Film" } },
50
+ { "id": "FILM-2", "title": { "text": "Imaginary Picture (Tamil)" } },
51
+ { "id": "FILM-3", "title": { "text": "Pretend Pups - Movies For Juniors" } },
52
+ { "id": "FILM-4", "title": { "text": "Cine-Excess 2026: Scum Of The Earth" } }
53
+ ],
54
+ "attributes": [
55
+ { "id": "A-2D", "shortName": { "text": "2D" }, "name": { "text": "2D Film" } },
56
+ { "id": "A-AD", "shortName": { "text": "AD" }, "name": { "text": "Audio Described" } },
57
+ { "id": "A-IMAX", "shortName": { "text": "IMAX" }, "name": { "text": "IMAX Film" } },
58
+ { "id": "A-TAMIL", "shortName": { "text": "Tamil" }, "name": { "text": "Tamil" } },
59
+ { "id": "A-ST", "shortName": { "text": "ST" }, "name": { "text": "Subtitled" } },
60
+ { "id": "A-M4J", "shortName": { "text": "M4J" }, "name": { "text": "Movies for Juniors" } },
61
+ { "id": "A-AC", "shortName": { "text": "AC" }, "name": { "text": "Event Cinema" } }
62
+ ],
63
+ "sites": [
64
+ { "id": "014", "name": { "text": "Brighton" } }
65
+ ]
66
+ }
67
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "sites": [
3
+ {
4
+ "id": "014",
5
+ "name": { "text": "Brighton" },
6
+ "contactDetails": {
7
+ "addressV2": {
8
+ "line1": { "text": "1 Example Marina" },
9
+ "line2": { "text": "Brighton" },
10
+ "line3": { "text": "East Sussex" },
11
+ "line4": { "text": "BN2 0ZZ" }
12
+ }
13
+ }
14
+ },
15
+ {
16
+ "id": "088",
17
+ "name": { "text": "Glasgow Silverburn" },
18
+ "contactDetails": {
19
+ "addressV2": {
20
+ "line1": { "text": "2 Example Centre" },
21
+ "line2": { "text": "Glasgow" },
22
+ "line3": { "text": "Lanarkshire" },
23
+ "line4": { "text": "G53 0ZZ" }
24
+ }
25
+ }
26
+ },
27
+ {
28
+ "id": "103",
29
+ "name": { "text": "Leicester Square" },
30
+ "contactDetails": {
31
+ "addressV2": {
32
+ "line1": { "text": "3 Example Square" },
33
+ "line2": { "text": "London" },
34
+ "line3": null,
35
+ "line4": { "text": "WC2H 0ZZ" }
36
+ }
37
+ }
38
+ },
39
+ {
40
+ "id": "999",
41
+ "name": { "text": "Example - Riverside" },
42
+ "contactDetails": {
43
+ "addressV2": {
44
+ "line1": { "text": "4 Example Quay" },
45
+ "line2": { "text": "Exampleton" },
46
+ "line3": null,
47
+ "line4": { "text": "EX1 0ZZ" }
48
+ }
49
+ }
50
+ }
51
+ ]
52
+ }