cineworld_uk 4.1.0-aarch64-linux-gnu → 4.3.1-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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +73 -0
  3. data/CHANGELOG.md +33 -0
  4. data/README.md +31 -1
  5. data/Rakefile +3 -24
  6. data/cineworld_uk.gemspec +2 -2
  7. data/lib/cineworld_uk/internal/api_response.rb +9 -31
  8. data/lib/cineworld_uk/internal/guest_token.rb +9 -52
  9. data/lib/cineworld_uk/internal/parser/api/film.rb +14 -1
  10. data/lib/cineworld_uk/internal/parser/api/performance.rb +5 -0
  11. data/lib/cineworld_uk/internal/title_sanitizer.rb +5 -10
  12. data/lib/cineworld_uk/performance.rb +5 -1
  13. data/lib/cineworld_uk/version.rb +2 -2
  14. data/lib/cineworld_uk.rb +2 -1
  15. data/test/fixtures/api/showtimes-014.json +76 -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 +27 -182
  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 +65 -127
  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 +94 -120
  25. data/test/lib/cineworld_uk/performance_test.rb +165 -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 +14 -7
  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 +35 -29
  35. data/tasks/package.rake +0 -84
  36. data/test/fixtures/api/cinema-list.json +0 -1
  37. data/test/fixtures/api/dates-14.json +0 -1
  38. data/test/fixtures/api/performances-tomorrow-14.json +0 -1
  39. data/test/fixtures/api/vista-showtimes-brighton.json +0 -1
  40. data/test/support/fixture_reader.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14c904b5bd7004874a4a618f8949ae4ba3cad07df581375b62e7b6688c0e3796
4
- data.tar.gz: 876c22ef9a522f3284043afff272975fae09fe261cbd72f1bf08624f48e174db
3
+ metadata.gz: 20a7a31fc492ed6b31e0047b5c41ff199ebf519c92629e7093a3dce5239e8982
4
+ data.tar.gz: '05893e4d311d0fe22a5f4b79279fbf8c9ce880be15ef33ccc2786bd8f14d5240'
5
5
  SHA512:
6
- metadata.gz: aaac5e8935f3984009de1fad4881e3df180d53819b730abf3f442e353044f521cf7a15c274650413f97136d2a3c6d4db10685902694b3f055413558f9645fe04
7
- data.tar.gz: 9f632c21833cea828591d267a4ac638aace0cdca8d704d9a8c37e3911563dbd33a7ccf4421ff1366e83eb03fd204973e734edb9357b9bc202beb24b375c3e495
6
+ metadata.gz: 0a95217187527f30820ec7f0c6b562809a4d7d75ef1413eeeef2cbeccf850ad31caa83a1d13908e787d5d2e772fc74bb4bcead1d4ec8d793a513627f17ccbeab
7
+ data.tar.gz: b95d72f18095738d9f6cbdba38b695f2d3a4274ced24735629c25b0bd05c9b7feb9785831ffe705144e7229dfb909605912adca7147ff12e4805faa36081515b
@@ -0,0 +1,73 @@
1
+ name: Release Gem
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ package:
8
+ strategy:
9
+ matrix:
10
+ include:
11
+ - runner: ubuntu-latest
12
+ platform: x86_64-linux-gnu
13
+ - runner: ubuntu-24.04-arm
14
+ platform: aarch64-linux-gnu
15
+ - runner: macos-15
16
+ platform: arm64-darwin
17
+ - runner: macos-15-intel
18
+ platform: x86_64-darwin
19
+ runs-on: ${{ matrix.runner }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: '4.0'
25
+ bundler-cache: true
26
+ - run: bundle exec rake 'package:native[${{ matrix.platform }}]'
27
+ - run: CINEWORLD_PACKAGE="$(echo pkg/*.gem)" bundle exec ruby test/package_test.rb
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: gem-${{ matrix.platform }}
31
+ path: pkg/*.gem
32
+
33
+ release:
34
+ needs: package
35
+ runs-on: ubuntu-latest
36
+ permissions:
37
+ id-token: write
38
+ contents: write
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: ruby/setup-ruby@v1
42
+ with:
43
+ ruby-version: '4.0'
44
+ bundler-cache: true
45
+ - run: bundle exec rake test
46
+ - run: bundle exec rake build
47
+ - uses: actions/download-artifact@v4
48
+ with:
49
+ path: pkg
50
+ pattern: gem-*
51
+ merge-multiple: true
52
+ - uses: rubygems/configure-rubygems-credentials@v1.0.0
53
+ - name: Push the source and platform gems
54
+ run: |
55
+ for gem in pkg/*.gem; do
56
+ if output=$(gem push "$gem" 2>&1); then
57
+ echo "$output"
58
+ elif echo "$output" | grep -q "Repushing of gem versions is not allowed"; then
59
+ echo "Already published: $gem"
60
+ else
61
+ echo "$output"
62
+ exit 1
63
+ fi
64
+ done
65
+ - name: Tag the release
66
+ run: |
67
+ tag="v$(ruby -e 'print Gem::Specification.load(Dir["*.gemspec"].first).version')"
68
+ if git ls-remote --exit-code --tags origin "$tag" >/dev/null; then
69
+ echo "$tag already exists"
70
+ else
71
+ git tag "$tag"
72
+ git push origin "$tag"
73
+ fi
data/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
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.3.1 - 2026-10-22
6
+
7
+ ### Fixed
8
+ - Publish the platform gems that bundle curl-impersonate. 4.3.0 shipped only the source gem. No code changes.
9
+
10
+ ## 4.3.0 - 2026-10-22
11
+
12
+ ### Added
13
+ - `Performance#film_year`, read from a trailing `(1996)` in the title, so a re-release gives the original year.
14
+ - `4dx` and `screenx` variants from Vista's `4DX`, `4DX 3D` and `ScreenX` attributes.
15
+
16
+ ### Changed
17
+ - Require cinebase 5.4. Titles also lose `+ ... Q&A` and `+ Discussion` add-ons, `(35mm)`, `(70mm)` and `4K` markers, and `CFF:` festival prefixes.
18
+ - Film titles lose their year suffix and the `(4DX)`, `(ScreenX)`, `M4J:`, `£2 Family Films :`, `Horror Season 2026` and `(Re-release)` markers, and Lithuanian versions. For example `Horror Season 2026 From Dusk Till Dawn (1996)` is now `From Dusk Till Dawn` with `film_year` 1996 and the Horror Season 2026 festival. Consumers matching films by name will see these names change.
19
+
20
+ ## 4.2.0 - 2026-10-22
21
+
22
+ ### Added
23
+ - `Performance#language` from Vista language attributes such as `Tamil`, `Performance#subtitled?`, and `Performance#festival`, a `Cinebase::Festival` with `name` and `year`.
24
+ - Live API shape tests that check real responses without storing them.
25
+
26
+ ### Changed
27
+ - 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.
28
+ - `CineworldUk::AuthenticationError` is now `Cinebase::Vista::AuthenticationError`, so existing `rescue` clauses keep working.
29
+ - 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.
30
+ - Tests use plain `Minitest::Test` classes, with hand-written fixtures in place of recorded API responses.
31
+
32
+ ### Fixed
33
+ - `Sing-Along` inside a real title, such as `Bebefinn Sing-Along Movie`, is no longer removed.
34
+
35
+ ### Removed
36
+ - The `fixtures` rake task, which saved API responses into the repository.
37
+
5
38
  ## 4.1.0 - 2026-10-15
6
39
 
7
40
  ### Added
data/README.md CHANGED
@@ -177,6 +177,18 @@ performance.dimension
177
177
  performance.variant
178
178
  #=> ['audio_described']
179
179
 
180
+ performance.film_year
181
+ #=> 1996 # from the title, nil when it has no year
182
+
183
+ performance.language
184
+ #=> 'Tamil' # audio language, nil when the cinema does not tag one
185
+
186
+ performance.subtitled?
187
+ #=> true
188
+
189
+ performance.festival
190
+ #=> #<data Cinebase::Festival name="Cine-Excess", year=2026> # nil outside festivals
191
+
180
192
  performance.starting_at
181
193
  #=> 2026-10-14 14:40:00 UTC
182
194
 
@@ -193,6 +205,19 @@ performance.cinema_id
193
205
  #=> 14
194
206
  ```
195
207
 
208
+ ## Tests
209
+
210
+ ```sh
211
+ bundle exec rake # unit tests against fixtures
212
+ bundle exec rake live # live API, needs curl-impersonate or CINEWORLD_TOKEN
213
+ ```
214
+
215
+ Fixtures in `test/fixtures/api` are hand-written: invented films and
216
+ addresses in the shape of Vista responses, never recorded API output.
217
+ `test/support/api_shapes.rb` describes the fields the gem reads. The unit suite
218
+ checks the fixtures against it and the live suite checks real responses, held
219
+ only in memory. When the API changes, update the shape first, then the fixtures.
220
+
196
221
  ## Building release packages
197
222
 
198
223
  ```sh
@@ -204,7 +229,7 @@ To build and check just one platform on a matching host:
204
229
 
205
230
  ```sh
206
231
  bundle exec rake 'package:native[x86_64-linux-gnu]'
207
- CINEWORLD_PACKAGE=pkg/cineworld_uk-4.1.0-x86_64-linux-gnu.gem bundle exec ruby test/package_test.rb
232
+ CINEWORLD_PACKAGE=pkg/cineworld_uk-4.3.1-x86_64-linux-gnu.gem bundle exec ruby test/package_test.rb
208
233
  ```
209
234
 
210
235
  Native packaging downloads pinned upstream archives at **build time**, verifies
@@ -214,6 +239,11 @@ and installation-tests each platform on its corresponding runner and saves the
214
239
  gems as workflow artifacts; it does not publish them. Publish the source and
215
240
  platform gems together for automatic platform selection to work for users.
216
241
 
242
+ To release, bump the version, merge, then run the **Release Gem** workflow in
243
+ GitHub Actions. It builds the source and four platform gems, publishes them
244
+ through RubyGems trusted publishing (no MFA code needed) and tags the release.
245
+ Versions already on RubyGems are skipped, so a failed run can be rerun.
246
+
217
247
  ## Contributing
218
248
 
219
249
  1. Fork it
data/Rakefile CHANGED
@@ -2,7 +2,9 @@
2
2
  require "bundler/gem_tasks"
3
3
 
4
4
  require 'rake/testtask'
5
- load File.expand_path('tasks/package.rake', __dir__)
5
+ require 'cinebase/vista/package_task'
6
+
7
+ Cinebase::Vista::PackageTask.new('cineworld_uk.gemspec', root: __dir__)
6
8
 
7
9
  Rake::TestTask.new do |t|
8
10
  t.libs << 'lib/cineworld_uk'
@@ -33,27 +35,4 @@ task :console do
33
35
  IRB.start
34
36
  end
35
37
 
36
- desc 'Recreate test fixtures'
37
- task :fixtures do
38
- require 'cineworld_uk'
39
- CineworldUk.token = ENV.fetch("CINEWORLD_TOKEN")
40
-
41
- def js_fixture(name)
42
- File.expand_path("../test/fixtures/#{name}.json", __FILE__)
43
- end
44
-
45
- api = CineworldUk::Internal::ApiResponse.new
46
-
47
- File.open(js_fixture('api/cinema-list'), 'w') do |file|
48
- puts '* API Cinema List'
49
- file.write api.cinema_list
50
- end
51
-
52
- File.open(js_fixture('api/performances-tomorrow-14'), 'w') do |file|
53
- puts '* API Performances in Brighton Tomorrow'
54
- file.write api.performances(14, Date.today + 1)
55
- end
56
-
57
- end
58
-
59
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,7 +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 'base64', '~> 0.3'
28
- spec.add_runtime_dependency 'cinebase', '~> 5.0'
28
+ spec.add_runtime_dependency 'cinebase', '~> 5.4'
29
29
  spec.add_runtime_dependency 'nokogiri', '>= 1.11'
30
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"
@@ -15,8 +14,10 @@ module CineworldUk
15
14
  "Subtitled" => "subbed", "Subtitles" => "subbed",
16
15
  "Autism Friendly" => "autism-friendly",
17
16
  "Movies for Juniors" => "junior",
18
- "Unlimited Screening" => "unlimited-screening", "Q&A" => "qa"
17
+ "Unlimited Screening" => "unlimited-screening", "Q&A" => "qa",
18
+ "4DX" => "4dx", "4DX 3D" => "4dx", "ScreenX" => "screenx"
19
19
  }.freeze
20
+ LANGUAGES = Cinebase::TitleSanitizer::LANGUAGES
20
21
 
21
22
  def cinema_list
22
23
  cinemas = fetch("sites").fetch("sites").map do |site|
@@ -53,6 +54,9 @@ module CineworldUk
53
54
  ATTRIBUTES[attribute.dig("name", "text")]
54
55
  end
55
56
  attrs << "3d" if showtime["requires3dGlasses"]
57
+ language = showtime.fetch("attributeIds", []).filter_map do |id|
58
+ attributes.fetch(id).dig("shortName", "text")
59
+ end.find { |name| LANGUAGES.include?(name) }
56
60
  # Formats belong to a session, not to the film as a whole.
57
61
  id = showtime.fetch("id")
58
62
  normalized_films << {
@@ -62,7 +66,7 @@ module CineworldUk
62
66
  }
63
67
  starts_at = showtime.fetch("schedule").fetch("startsAt")
64
68
  {
65
- "filmId" => id, "attributeIds" => attrs,
69
+ "filmId" => id, "attributeIds" => attrs, "language" => language,
66
70
  "eventDateTime" => Time.iso8601(starts_at).utc.iso8601,
67
71
  "bookingLink" =>
68
72
  "https://web.cineworld.co.uk/order/showtimes/#{id}/seats"
@@ -82,34 +86,8 @@ module CineworldUk
82
86
  private
83
87
 
84
88
  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
89
+ Cinebase::Vista::Client.new(base_url: BASE_URL, region_code: "GB",
90
+ token: CineworldUk.token).get(path, query)
113
91
  end
114
92
  end
115
93
  end
@@ -1,72 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
3
  require "json"
5
4
  require "nokogiri"
6
- require "open3"
7
5
 
8
6
  module CineworldUk
9
7
  module Internal
10
- # Retrieves the public guest token without running a browser.
11
- class GuestToken
12
- def initialize(curl:)
13
- @curl = curl || default_curl
14
- end
15
-
16
- def fetch
17
- token = extract_token(request_page)
18
- validate_expiry!(token)
19
- token
20
- rescue JSON::ParserError, ArgumentError, IndexError, TypeError, NoMethodError
21
- raise AuthenticationError, "Guest token page data is missing or invalid", cause: nil
22
- end
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/"
23
11
 
24
12
  private
25
13
 
26
- def default_curl
27
- bundled = File.expand_path("../../../vendor/curl-impersonate/bin/curl-impersonate", __dir__)
28
- File.executable?(bundled) ? bundled : "curl-impersonate"
29
- end
30
-
31
- def curl_command
32
- [@curl, "--disable", "--impersonate", "chrome150",
33
- "--compressed", "--silent", "--show-error",
34
- "--connect-timeout", "10", "--max-time", "30",
35
- "--write-out", "\n%{http_code}", "https://web.cineworld.co.uk/"]
14
+ # The root page carries the token even when it displays a 404.
15
+ def accepted_statuses
16
+ %w[200 404]
36
17
  end
37
18
 
38
- def request_page
39
- output, _stderr, result = Open3.capture3(*curl_command)
40
- unless result.success?
41
- raise AuthenticationError,
42
- "Guest token request failed (curl exit #{result.exitstatus})"
43
- end
44
-
45
- html, _, status = output.rpartition("\n")
46
- unless %w[200 404].include?(status)
47
- raise AuthenticationError, "Guest token page returned HTTP #{status}"
48
- end
49
- html
50
- rescue Errno::ENOENT
51
- raise AuthenticationError,
52
- "Install curl-impersonate or pass its executable path with curl:"
19
+ def bundled_curl
20
+ File.expand_path("../../../vendor/curl-impersonate/bin/curl-impersonate", __dir__)
53
21
  end
54
22
 
55
23
  def extract_token(html)
56
24
  script = Nokogiri::HTML(html).at_css("script#__NEXT_DATA__")
57
25
  data = JSON.parse(script ? script.content : "")
58
- token = data.dig("props", "pageProps", "environment", "gasToken")
59
- unless token.is_a?(String) && !token.empty?
60
- raise AuthenticationError, "Guest token is missing from the page"
61
- end
62
- token
63
- end
64
-
65
- def validate_expiry!(token)
66
- claims = JSON.parse(Base64.urlsafe_decode64(token.split(".").fetch(1)))
67
- if Integer(claims.fetch("exp")) <= Time.now.to_i
68
- raise AuthenticationError, "Guest token supplied by the page has expired"
69
- end
26
+ data.dig("props", "pageProps", "environment", "gasToken")
70
27
  end
71
28
  end
72
29
  end
@@ -21,6 +21,17 @@ 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
+
30
+ # @return [Integer, nil] the year at the end of the title
31
+ def film_year
32
+ Cinebase::TitleSanitizer.film_year(@data['name'] || '')
33
+ end
34
+
24
35
  # Sanitized film name
25
36
  # @return [String]
26
37
  def name
@@ -34,11 +45,13 @@ module CineworldUk
34
45
  def variant
35
46
  attrs = @data['attributeIds'] || []
36
47
  [
48
+ attrs.include?('4dx') ? '4dx' : nil,
37
49
  attrs.include?('autism-friendly') ? 'autism_friendly' : nil,
38
50
  attrs.include?('imax') ? 'imax' : nil,
39
51
  attrs.include?('junior') ? 'kids' : nil,
40
52
  attrs.include?('unlimited-screening') ? 'members' : nil,
41
- attrs.include?('qa') ? 'q&a' : nil
53
+ attrs.include?('qa') ? 'q&a' : nil,
54
+ attrs.include?('screenx') ? 'screenx' : nil
42
55
  ].compact
43
56
  end
44
57
  end
@@ -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
@@ -35,12 +28,14 @@ module CineworldUk
35
28
  /\ATake 2 -/, # take 2
36
29
  / - (Subtitled) Movies for Juniors/i, # movies for juniors
37
30
  '(IMAX)', # IMAX
31
+ /\A\((4DX|ScreenX)\)\s*/, # format, kept as a variant
32
+ /\AM4J: /, # movies for juniors
33
+ /\A£2 Family Films : /, # family pricing
38
34
  'SciScreen: ', # SciScreen
39
35
  /\s(-\s)*(With)*\sLive Q And A/i, # Live Q&A
40
36
  /(\s+\-\s+)?MOVIES FOR JUNIORS/i, # movies for juniors
41
- /\bsing\-?a\-?long\b/i, # singalong
42
37
  %r{\s20\d{2}[\/-]20\d{2} Season\b}
43
- ]
38
+ ] + COMMON_REMOVE
44
39
  end
45
40
 
46
41
  # Regexes and their replacements
@@ -61,7 +56,7 @@ module CineworldUk
61
56
  /ROH The Royal Ballet\s*[:-] (.*)/ => 'The Royal Ballet: ',
62
57
  /Royal Ballet Live\s*[:-] (.*)/ => 'The Royal Ballet: ',
63
58
  /San Francisco Ballets (.*)/ => 'San Francisco Ballet: '
64
- }
59
+ }.merge(COMMON_REPLACE)
65
60
  end
66
61
  end
67
62
  end
@@ -71,14 +71,18 @@ 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
+ film_year: film.film_year,
84
+ language: performance.language,
85
+ starting_at: starting_at,
82
86
  variant: (performance.variant + film.variant).sort
83
87
  }
84
88
  end
@@ -1,6 +1,6 @@
1
1
  # Ruby interface for http://www.cineworld.co.uk
2
- # @version 4.1.0
2
+ # @version 4.3.1
3
3
  module CineworldUk
4
4
  # Gem version
5
- VERSION = "4.1.0".freeze
5
+ VERSION = "4.3.1".freeze
6
6
  end
data/lib/cineworld_uk.rb CHANGED
@@ -1,6 +1,7 @@
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
 
@@ -17,7 +18,7 @@ require_relative './cineworld_uk/performance'
17
18
 
18
19
  # cineworld_uk gem
19
20
  module CineworldUk
20
- class AuthenticationError < StandardError; end
21
+ AuthenticationError = Cinebase::Vista::AuthenticationError
21
22
 
22
23
  # A raw guest JWT, without the "Bearer " prefix. Never persisted by the gem.
23
24
  class << self
@@ -0,0 +1,76 @@
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-1007",
41
+ "filmId": "FILM-5",
42
+ "schedule": { "startsAt": "2026-10-24T21:00:00+01:00" },
43
+ "attributeIds": ["A-4DX"],
44
+ "requires3dGlasses": false
45
+ },
46
+ {
47
+ "id": "014-1006",
48
+ "filmId": "FILM-1",
49
+ "schedule": { "startsAt": "2026-10-25T00:15:00+01:00" },
50
+ "attributeIds": [],
51
+ "requires3dGlasses": false
52
+ }
53
+ ],
54
+ "relatedData": {
55
+ "films": [
56
+ { "id": "FILM-1", "title": { "text": "The Example Film" } },
57
+ { "id": "FILM-2", "title": { "text": "Imaginary Picture (Tamil)" } },
58
+ { "id": "FILM-3", "title": { "text": "Pretend Pups - Movies For Juniors" } },
59
+ { "id": "FILM-4", "title": { "text": "Cine-Excess 2026: Scum Of The Earth" } },
60
+ { "id": "FILM-5", "title": { "text": "(4DX) Wildwood (2026)" } }
61
+ ],
62
+ "attributes": [
63
+ { "id": "A-2D", "shortName": { "text": "2D" }, "name": { "text": "2D Film" } },
64
+ { "id": "A-AD", "shortName": { "text": "AD" }, "name": { "text": "Audio Described" } },
65
+ { "id": "A-IMAX", "shortName": { "text": "IMAX" }, "name": { "text": "IMAX Film" } },
66
+ { "id": "A-TAMIL", "shortName": { "text": "Tamil" }, "name": { "text": "Tamil" } },
67
+ { "id": "A-ST", "shortName": { "text": "ST" }, "name": { "text": "Subtitled" } },
68
+ { "id": "A-M4J", "shortName": { "text": "M4J" }, "name": { "text": "Movies for Juniors" } },
69
+ { "id": "A-AC", "shortName": { "text": "AC" }, "name": { "text": "Event Cinema" } },
70
+ { "id": "A-4DX", "shortName": { "text": "4DX" }, "name": { "text": "4DX" } }
71
+ ],
72
+ "sites": [
73
+ { "id": "014", "name": { "text": "Brighton" } }
74
+ ]
75
+ }
76
+ }
@@ -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
+ }