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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +26 -0
- data/CHANGELOG.md +26 -0
- data/README.md +97 -6
- data/Rakefile +3 -23
- data/cineworld_uk.gemspec +2 -1
- data/lib/cineworld_uk/internal/api_response.rb +7 -30
- data/lib/cineworld_uk/internal/guest_token.rb +30 -0
- data/lib/cineworld_uk/internal/parser/api/film.rb +6 -0
- data/lib/cineworld_uk/internal/parser/api/performance.rb +5 -0
- data/lib/cineworld_uk/internal/title_sanitizer.rb +2 -10
- data/lib/cineworld_uk/performance.rb +4 -1
- data/lib/cineworld_uk/version.rb +2 -2
- data/lib/cineworld_uk.rb +8 -1
- data/test/fixtures/api/showtimes-014.json +67 -0
- data/test/fixtures/api/sites.json +52 -0
- data/test/lib/cineworld_uk/cinema_test.rb +63 -223
- data/test/lib/cineworld_uk/internal/api_response_test.rb +28 -107
- data/test/lib/cineworld_uk/internal/fixture_shape_test.rb +14 -0
- data/test/lib/cineworld_uk/internal/guest_token_test.rb +87 -0
- data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +44 -85
- data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +53 -131
- data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +27 -45
- data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +75 -126
- data/test/lib/cineworld_uk/performance_test.rb +153 -180
- data/test/lib/cineworld_uk/version_test.rb +3 -3
- data/test/live/api_shape_test.rb +22 -0
- data/test/live/integration_test.rb +13 -43
- data/test/live/live_helper.rb +11 -0
- data/test/package_test.rb +56 -0
- data/test/support/api_shapes.rb +48 -0
- data/test/support/vista_fixtures.rb +58 -0
- data/test/test_helper.rb +3 -0
- metadata +37 -14
- data/test/fixtures/api/cinema-list.json +0 -1
- data/test/fixtures/api/dates-14.json +0 -1
- data/test/fixtures/api/performances-tomorrow-14.json +0 -1
- data/test/fixtures/api/vista-showtimes-brighton.json +0 -1
- data/test/support/fixture_reader.rb +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97a79daf0c192e1c7a12ab72a96c6fae42433c44761fa7a991cfb0a2d456cf51
|
|
4
|
+
data.tar.gz: dfa8faf8c393df7b2df082d458af26a63aa8171a9a6f45fb964b040b07864baa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67fcdf358e0f0764b5d0b240999fcbfd1bc313a0ce9a97eb75fc5cc5d413ca7a244c74ef9f5993249fa2496bb33a8c03a3968f048c2a6a9e28a6ba444ed14bec
|
|
7
|
+
data.tar.gz: 19641348e8182a4aefdefc2674b934a08a4a1ac0ffcf109b6c678262400dd78b451845ad807ad7a4ad54a61bce461cfb9fd3e6612a1ac4a8f419427c46925647
|
data/.github/workflows/test.yml
CHANGED
|
@@ -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
|
-
|
|
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`.
|
|
40
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
86
|
-
|
|
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
|
|
@@ -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
|
-
|
|
83
|
+
language: performance.language,
|
|
84
|
+
starting_at: starting_at,
|
|
82
85
|
variant: (performance.variant + film.variant).sort
|
|
83
86
|
}
|
|
84
87
|
end
|
data/lib/cineworld_uk/version.rb
CHANGED
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
|
-
|
|
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
|
+
}
|