cineworld_uk 3.1.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.agents/setup +51 -0
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +21 -0
- data/README.md +136 -16
- data/Rakefile +5 -37
- data/cineworld_uk.gemspec +7 -4
- data/lib/cineworld_uk/cinema.rb +7 -4
- data/lib/cineworld_uk/internal/api_response.rb +99 -56
- data/lib/cineworld_uk/internal/guest_token.rb +73 -0
- data/lib/cineworld_uk/internal/parser/api/cinema_address.rb +24 -24
- data/lib/cineworld_uk/internal/parser/api/film.rb +10 -8
- data/lib/cineworld_uk/internal/parser/api/performance.rb +6 -11
- data/lib/cineworld_uk/performance.rb +42 -23
- data/lib/cineworld_uk/version.rb +2 -2
- data/lib/cineworld_uk.rb +12 -2
- data/tasks/package.rake +84 -0
- data/test/fixtures/api/cinema-list.json +1 -1
- data/test/fixtures/api/dates-14.json +1 -0
- data/test/fixtures/api/performances-tomorrow-14.json +1 -0
- data/test/fixtures/api/vista-showtimes-brighton.json +1 -0
- data/test/lib/cineworld_uk/cinema_test.rb +47 -47
- data/test/lib/cineworld_uk/internal/api_response_test.rb +177 -58
- data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +14 -14
- data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +43 -55
- data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +11 -12
- data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +18 -18
- data/test/lib/cineworld_uk/performance_test.rb +31 -32
- data/test/lib/cineworld_uk/version_test.rb +1 -1
- data/test/live/integration_test.rb +9 -9
- data/test/package_test.rb +49 -0
- data/test/support/fixture_reader.rb +2 -14
- data/test/test_helper.rb +3 -2
- metadata +65 -36
- data/.travis.yml +0 -10
- data/lib/cineworld_uk/internal/parser/api/film_lookup.rb +0 -38
- data/lib/cineworld_uk/internal/parser/api/performances_by_day.rb +0 -54
- data/test/fixtures/api/cinema-detail-10.json +0 -1
- data/test/fixtures/api/cinema-detail-21.json +0 -1
- data/test/fixtures/api/cinema-detail-3.json +0 -1
- data/test/fixtures/api/cinema-detail-96.json +0 -1
- data/test/fixtures/api/dates-3.json +0 -1
- data/test/fixtures/api/film-list-comingsoon.json +0 -1
- data/test/fixtures/api/film-list.json +0 -1
- data/test/fixtures/api/performances-tomorrow-3.json +0 -1
- data/test/lib/cineworld_uk/internal/parser/api/film_lookup_test.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: edfbb0e8f7d6238e878f4018a643befca62b662fffc47cad72367c2e404e9ee7
|
|
4
|
+
data.tar.gz: a0d59a9aa4d3227794fda2f78c00de9e59c62321b3cba3a50dbbd8611856cc82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aefe630b3ad344c2fbced398fc49c5fb9686387331f2cdea072f6dc96ef0893db42af90a6d277117cc7cd1b6ab4505b2fff3e64e859f79896503a71462934a89
|
|
7
|
+
data.tar.gz: cd3ad38fb9b9404504e5379ae03943424d56370b6ce2b0bcda069c9f64fa33a2802ce54dcb567740689408e882bda8e86cba3127193fb510f46fc73b2610c030
|
data/.agents/setup
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$repo_root"
|
|
6
|
+
|
|
7
|
+
echo 'Checking native gem build prerequisites'
|
|
8
|
+
missing=()
|
|
9
|
+
for package in ca-certificates curl build-essential pkg-config libyaml-dev libssl-dev zlib1g-dev; do
|
|
10
|
+
if ! dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q 'install ok installed'; then
|
|
11
|
+
missing+=("$package")
|
|
12
|
+
fi
|
|
13
|
+
done
|
|
14
|
+
if ((${#missing[@]})); then
|
|
15
|
+
sudo apt-get update -qq
|
|
16
|
+
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing[@]}"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
20
|
+
if ! command -v mise >/dev/null; then
|
|
21
|
+
echo 'Installing mise'
|
|
22
|
+
curl --fail --silent --show-error --location https://mise.run | sh
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
ruby_version="$(tr -d '[:space:]' < .ruby-version)"
|
|
26
|
+
echo "Installing Ruby $ruby_version"
|
|
27
|
+
MISE_RUBY_COMPILE=false mise install "ruby@$ruby_version"
|
|
28
|
+
|
|
29
|
+
# Make the pinned Ruby available to later Amp commands in login shells.
|
|
30
|
+
if [[ ! -f "$HOME/.bash_profile" ]]; then
|
|
31
|
+
printf '[ ! -f "$HOME/.profile" ] || . "$HOME/.profile"\n' > "$HOME/.bash_profile"
|
|
32
|
+
fi
|
|
33
|
+
marker="# Cineworld orb Ruby environment: $repo_root"
|
|
34
|
+
if ! grep -Fqx "$marker" "$HOME/.bash_profile"; then
|
|
35
|
+
cat >> "$HOME/.bash_profile" <<EOF
|
|
36
|
+
|
|
37
|
+
$marker
|
|
38
|
+
case "\$PWD" in
|
|
39
|
+
"$repo_root"|"$repo_root"/*)
|
|
40
|
+
export PATH="\$HOME/.local/share/mise/shims:\$HOME/.local/bin:\$PATH"
|
|
41
|
+
export MISE_IDIOMATIC_VERSION_FILE_ENABLE_TOOLS=ruby
|
|
42
|
+
;;
|
|
43
|
+
esac
|
|
44
|
+
EOF
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo 'Installing gem dependencies (no Cineworld token required)'
|
|
48
|
+
mise exec "ruby@$ruby_version" -- bundle config set --local path "$HOME/.cache/cineworld_uk/bundle"
|
|
49
|
+
mise exec "ruby@$ruby_version" -- bundle check || mise exec "ruby@$ruby_version" -- bundle install --jobs 4 --retry 3
|
|
50
|
+
mise reshim
|
|
51
|
+
mise exec "ruby@$ruby_version" -- ruby --version
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
ruby-version: ['3.3', '3.4', '4.0']
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
- run: bundle exec rake test
|
|
18
|
+
|
|
19
|
+
package:
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
include:
|
|
23
|
+
- runner: ubuntu-latest
|
|
24
|
+
platform: x86_64-linux-gnu
|
|
25
|
+
- runner: ubuntu-24.04-arm
|
|
26
|
+
platform: aarch64-linux-gnu
|
|
27
|
+
- runner: macos-15
|
|
28
|
+
platform: arm64-darwin
|
|
29
|
+
- runner: macos-15-intel
|
|
30
|
+
platform: x86_64-darwin
|
|
31
|
+
runs-on: ${{ matrix.runner }}
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: ruby/setup-ruby@v1
|
|
35
|
+
with:
|
|
36
|
+
ruby-version: '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/.gitignore
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
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.1.0 - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Explicit guest token acquisition and renewal with `CineworldUk.acquire_token!`, using the standalone `curl-impersonate` CLI without Python or a browser.
|
|
9
|
+
- 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.
|
|
10
|
+
- Optional executable path for token acquisition. Existing caller-supplied tokens continue to work without the CLI.
|
|
11
|
+
- Expiry checks and authentication errors for unavailable commands, blocked pages, and missing or invalid page tokens; failed acquisition preserves the previous token.
|
|
12
|
+
|
|
13
|
+
## 4.0.0 - 2026-10-15
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Retrieve cinemas and performances from the Vista API instead of the retired iOS API.
|
|
17
|
+
- Require a caller-supplied guest JWT through `CineworldUk.token`; token acquisition and renewal remain the caller's responsibility.
|
|
18
|
+
- `Performance.at` accepts inclusive `from:` and `to:` business dates, defaulting to today in London and the following 13 days rather than discovering all screening dates.
|
|
19
|
+
- Use one request per cinema per requested day, reusing related film, cinema and attribute data. Share cached cinema data with address lookups.
|
|
20
|
+
- Convert advertised session times to UTC using Vista's explicit offsets and preserve session-specific formats.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `CineworldUk::AuthenticationError` for missing tokens and HTTP 401 responses.
|
|
24
|
+
- Orb setup for the pinned Ruby version and gem dependencies.
|
|
25
|
+
|
|
5
26
|
## 3.1.0 - 2016-09-11
|
|
6
27
|
|
|
7
28
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# CineworldUk
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A gem to retrieve [Cineworld UK](https://www.cineworld.co.uk) cinemas and showtimes using the Vista API.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/cineworld_uk)
|
|
6
|
-
[](https://codeclimate.com/github/andycroll/cineworld_uk/coverage)
|
|
8
|
-
[](https://travis-ci.org/andycroll/cineworld_uk)
|
|
9
|
-
[](http://inch-ci.org/github/andycroll/cineworld_uk)
|
|
6
|
+
[](https://github.com/andycroll/cineworld_uk/actions)
|
|
10
7
|
|
|
11
8
|
## Installation
|
|
12
9
|
|
|
@@ -28,13 +25,94 @@ The gem conforms to the API set down in the `cinebase` gem, [andycroll/cinebase]
|
|
|
28
25
|
|
|
29
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 Opera House etc).
|
|
30
27
|
|
|
28
|
+
### Authentication
|
|
29
|
+
|
|
30
|
+
Acquire a guest JWT explicitly before retrieving data:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require 'cineworld_uk'
|
|
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
|
|
84
|
+
CineworldUk.token = ENV.fetch('CINEWORLD_TOKEN')
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The gem sends `Authorization: Bearer ...` and `Connect-Region-Code: GB` to
|
|
88
|
+
`digital-api.cineworld.co.uk`. Normal API calls never acquire or renew a token
|
|
89
|
+
automatically.
|
|
90
|
+
|
|
91
|
+
Missing tokens and HTTP 401 responses raise `CineworldUk::AuthenticationError`.
|
|
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
|
|
94
|
+
failures raise an error rather than returning an empty schedule. Redirects are
|
|
95
|
+
not followed, so credentials are not forwarded to another host.
|
|
96
|
+
|
|
97
|
+
There is no verified fixed token lifetime. Its declared lifetime is `exp - iat`
|
|
98
|
+
seconds. The website reloads to renew after 90% of that interval. To inspect
|
|
99
|
+
your token locally without printing it (decoding does not verify its signature):
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
require 'base64'
|
|
103
|
+
payload = CineworldUk.token.split('.').fetch(1)
|
|
104
|
+
claims = JSON.parse(Base64.urlsafe_decode64(payload))
|
|
105
|
+
puts "Lifetime: #{claims.fetch('exp') - claims.fetch('iat')} seconds"
|
|
106
|
+
puts "Expires: #{Time.at(claims.fetch('exp')).utc}"
|
|
107
|
+
```
|
|
108
|
+
|
|
31
109
|
### Cinema
|
|
32
110
|
|
|
33
111
|
``` ruby
|
|
34
112
|
CineworldUk::Cinema.all
|
|
35
113
|
#=> [<CineworldUK::Cinema ...>, <CineworldUK::Cinema ...>, ...]
|
|
36
114
|
|
|
37
|
-
cinema = CineworldUk::Cinema.new(
|
|
115
|
+
cinema = CineworldUk::Cinema.new(14)
|
|
38
116
|
#=> <CineworldUK::Cinema ...>
|
|
39
117
|
|
|
40
118
|
cinema.adr
|
|
@@ -42,7 +120,7 @@ cinema.adr
|
|
|
42
120
|
street_address: 'Brighton Marina Village',
|
|
43
121
|
extended_address: nil,
|
|
44
122
|
locality: 'Brighton',
|
|
45
|
-
region:
|
|
123
|
+
region: 'East Sussex',
|
|
46
124
|
postal_code: 'BN2 5UF',
|
|
47
125
|
country: 'United Kingdom'
|
|
48
126
|
}
|
|
@@ -54,7 +132,7 @@ cinema.full_name
|
|
|
54
132
|
#=> 'Cineworld Brighton'
|
|
55
133
|
|
|
56
134
|
cinema.id
|
|
57
|
-
#=>
|
|
135
|
+
#=> 14
|
|
58
136
|
|
|
59
137
|
cinema.postal_code
|
|
60
138
|
#=> 'BN2 5UF'
|
|
@@ -62,38 +140,80 @@ cinema.postal_code
|
|
|
62
140
|
|
|
63
141
|
### Performances
|
|
64
142
|
|
|
143
|
+
`at` accepts a numeric Vista cinema ID (`14`, `'14'` or `'014'` for Brighton),
|
|
144
|
+
not a Boxoffice ID such as `X079B`. Dates are inclusive **business dates**.
|
|
145
|
+
Without dates, it fetches today in London and the following 13 days; it does not
|
|
146
|
+
discover every future screening date.
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
day = Date.new(2026, 10, 14)
|
|
150
|
+
performances = CineworldUk::Performance.at(14, from: day, to: day)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This makes **one HTTP request per cinema per requested day**, including empty
|
|
154
|
+
days. Cinema names, film titles and attributes come from the same response—no
|
|
155
|
+
extra cinema or film lookups are needed. `Cinema.all` separately fetches and
|
|
156
|
+
caches the cinema list in memory, shared by name and address lookups. Performance
|
|
157
|
+
responses are not cached across calls, so reuse the returned array when possible.
|
|
158
|
+
|
|
159
|
+
Advertised session times (`startsAt`), not feature start times (`filmStartsAt`),
|
|
160
|
+
are converted to UTC using the offsets supplied by Vista. Recognized session
|
|
161
|
+
attributes populate the existing dimension and variant fields; unknown attributes
|
|
162
|
+
are ignored. No seat-availability or booking operations are performed.
|
|
163
|
+
|
|
65
164
|
``` ruby
|
|
66
|
-
CineworldUk::Performance.at(
|
|
165
|
+
CineworldUk::Performance.at(14)
|
|
67
166
|
#=> [<CineworldUK::Performance ...>, <CineworldUK::Performance ...>, ...]
|
|
68
167
|
|
|
69
|
-
performance =
|
|
168
|
+
performance = performances.first
|
|
70
169
|
#=> <CineworldUK::Performance ...>
|
|
71
170
|
|
|
72
171
|
performance.film_name
|
|
73
|
-
#=> '
|
|
172
|
+
#=> 'Other Mommy'
|
|
74
173
|
|
|
75
174
|
performance.dimension
|
|
76
175
|
#=> '2d'
|
|
77
176
|
|
|
78
177
|
performance.variant
|
|
79
|
-
#=> ['
|
|
178
|
+
#=> ['audio_described']
|
|
80
179
|
|
|
81
180
|
performance.starting_at
|
|
82
|
-
#=>
|
|
181
|
+
#=> 2026-10-14 14:40:00 UTC
|
|
83
182
|
|
|
84
183
|
performance.showing_on
|
|
85
|
-
#=>
|
|
184
|
+
#=> Date.new(2026, 10, 14)
|
|
86
185
|
|
|
87
186
|
performance.booking_url
|
|
88
|
-
#=> "
|
|
187
|
+
#=> "https://web.cineworld.co.uk/order/showtimes/014-165507/seats"
|
|
89
188
|
|
|
90
189
|
performance.cinema_name
|
|
91
190
|
#=> 'Brighton'
|
|
92
191
|
|
|
93
192
|
performance.cinema_id
|
|
94
|
-
#=>
|
|
193
|
+
#=> 14
|
|
95
194
|
```
|
|
96
195
|
|
|
196
|
+
## Building release packages
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
bundle exec rake build # Portable source gem; external curl-impersonate required
|
|
200
|
+
bundle exec rake package:all # Four platform gems with bundled executables
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
To build and check just one platform on a matching host:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
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
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Native packaging downloads pinned upstream archives at **build time**, verifies
|
|
211
|
+
their SHA-256 digests, and includes the executable, licence files and source
|
|
212
|
+
provenance. Archives and built gems stay in ignored `pkg/`, not Git. CI builds
|
|
213
|
+
and installation-tests each platform on its corresponding runner and saves the
|
|
214
|
+
gems as workflow artifacts; it does not publish them. Publish the source and
|
|
215
|
+
platform gems together for automatic platform selection to work for users.
|
|
216
|
+
|
|
97
217
|
## Contributing
|
|
98
218
|
|
|
99
219
|
1. Fork it
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
require "bundler/gem_tasks"
|
|
3
3
|
|
|
4
4
|
require 'rake/testtask'
|
|
5
|
+
load File.expand_path('tasks/package.rake', __dir__)
|
|
5
6
|
|
|
6
7
|
Rake::TestTask.new do |t|
|
|
7
8
|
t.libs << 'lib/cineworld_uk'
|
|
@@ -14,7 +15,7 @@ Rake::TestTask.new do |t|
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
Rake::TestTask.new do |t|
|
|
17
|
-
t.libs << 'lib/
|
|
18
|
+
t.libs << 'lib/cineworld_uk'
|
|
18
19
|
t.name = :live
|
|
19
20
|
t.test_files = FileList[
|
|
20
21
|
'test/live/*_test.rb'
|
|
@@ -35,6 +36,7 @@ end
|
|
|
35
36
|
desc 'Recreate test fixtures'
|
|
36
37
|
task :fixtures do
|
|
37
38
|
require 'cineworld_uk'
|
|
39
|
+
CineworldUk.token = ENV.fetch("CINEWORLD_TOKEN")
|
|
38
40
|
|
|
39
41
|
def js_fixture(name)
|
|
40
42
|
File.expand_path("../test/fixtures/#{name}.json", __FILE__)
|
|
@@ -47,45 +49,11 @@ task :fixtures do
|
|
|
47
49
|
file.write api.cinema_list
|
|
48
50
|
end
|
|
49
51
|
|
|
50
|
-
File.open(js_fixture('api/
|
|
51
|
-
puts '* API Cinema Detail Brighton'
|
|
52
|
-
file.write api.cinema_detail(3)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
File.open(js_fixture('api/cinema-detail-96'), 'w') do |file|
|
|
56
|
-
puts '* API Cinema Detail Birmingham NEC'
|
|
57
|
-
file.write api.cinema_detail(96)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
File.open(js_fixture('api/cinema-detail-21'), 'w') do |file|
|
|
61
|
-
puts '* API Cinema Detail Edinburgh'
|
|
62
|
-
file.write api.cinema_detail(21)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
File.open(js_fixture('api/cinema-detail-10'), 'w') do |file|
|
|
66
|
-
puts '* API Cinema Detail Chelsea'
|
|
67
|
-
file.write api.cinema_detail(10)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
File.open(js_fixture('api/film-list'), 'w') do |file|
|
|
71
|
-
puts '* API Film List'
|
|
72
|
-
file.write api.film_list
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
File.open(js_fixture('api/film-list-comingsoon'), 'w') do |file|
|
|
76
|
-
puts '* API Film List Coming Soon'
|
|
77
|
-
file.write api.film_list_comingsoon
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
File.open(js_fixture('api/performances-tomorrow-3'), 'w') do |file|
|
|
52
|
+
File.open(js_fixture('api/performances-tomorrow-14'), 'w') do |file|
|
|
81
53
|
puts '* API Performances in Brighton Tomorrow'
|
|
82
|
-
file.write api.performances(
|
|
54
|
+
file.write api.performances(14, Date.today + 1)
|
|
83
55
|
end
|
|
84
56
|
|
|
85
|
-
File.open(js_fixture('api/dates-3'), 'w') do |file|
|
|
86
|
-
puts '* API Dates Brighton Tomorrow'
|
|
87
|
-
file.write api.dates(3)
|
|
88
|
-
end
|
|
89
57
|
end
|
|
90
58
|
|
|
91
59
|
task :default => :test
|
data/cineworld_uk.gemspec
CHANGED
|
@@ -12,16 +12,19 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.homepage = 'https://github.com/andycroll/cineworld_uk'
|
|
13
13
|
spec.licenses = %w(AGPL MIT)
|
|
14
14
|
|
|
15
|
-
spec.files = `git ls-files`.split(
|
|
15
|
+
spec.files = `git ls-files`.split("\n")
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
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 'bundler', '
|
|
21
|
-
spec.add_development_dependency '
|
|
20
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
21
|
+
spec.add_development_dependency 'minitest-mock'
|
|
22
22
|
spec.add_development_dependency 'minitest-reporters'
|
|
23
23
|
spec.add_development_dependency 'rake'
|
|
24
|
+
spec.add_development_dependency 'simplecov', '< 1'
|
|
24
25
|
spec.add_development_dependency 'webmock'
|
|
25
26
|
|
|
26
|
-
spec.add_runtime_dependency '
|
|
27
|
+
spec.add_runtime_dependency 'base64', '~> 0.3'
|
|
28
|
+
spec.add_runtime_dependency 'cinebase', '~> 5.0'
|
|
29
|
+
spec.add_runtime_dependency 'nokogiri', '>= 1.11'
|
|
27
30
|
end
|
data/lib/cineworld_uk/cinema.rb
CHANGED
|
@@ -25,8 +25,9 @@ module CineworldUk
|
|
|
25
25
|
# @return [Hash<Integer => String>]
|
|
26
26
|
def self.id_names_hash
|
|
27
27
|
@id_names_hash ||= cinema_list_json.each_with_object({}) do |hash, result|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
name = hash['displayName']
|
|
29
|
+
id_key = hash['id'].to_i
|
|
30
|
+
result[id_key] = name.gsub('London - ', '').gsub(' - ', ': ') if name
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
|
|
@@ -49,7 +50,9 @@ module CineworldUk
|
|
|
49
50
|
# }
|
|
50
51
|
# @note Uses the standard method naming as at http://microformats.org/wiki/adr
|
|
51
52
|
def adr
|
|
52
|
-
CineworldUk::Internal::Parser::Api::CinemaAddress.new(
|
|
53
|
+
CineworldUk::Internal::Parser::Api::CinemaAddress.new(
|
|
54
|
+
@id, self.class.send(:cinema_list_json)
|
|
55
|
+
).to_hash
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
# Brand of the cinema
|
|
@@ -158,7 +161,7 @@ module CineworldUk
|
|
|
158
161
|
# @api private
|
|
159
162
|
def self.cinema_list_json
|
|
160
163
|
@cinema_list_json ||=
|
|
161
|
-
JSON.parse(api.cinema_list)['cinemas']
|
|
164
|
+
JSON.parse(api.cinema_list)['body']['cinemas']
|
|
162
165
|
end
|
|
163
166
|
private_class_method :cinema_list_json
|
|
164
167
|
end
|
|
@@ -1,72 +1,115 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "date"
|
|
6
|
+
require "time"
|
|
2
7
|
|
|
3
8
|
module CineworldUk
|
|
4
|
-
# @api private
|
|
5
9
|
module Internal
|
|
6
|
-
#
|
|
10
|
+
# Adapts Vista responses to the parsers' cinema/film/event vocabulary.
|
|
7
11
|
class ApiResponse
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# @return [String] JSON encoded
|
|
17
|
-
def dates(id)
|
|
18
|
-
response('dates', cinema: id)
|
|
19
|
-
end
|
|
12
|
+
BASE_URL = "https://digital-api.cineworld.co.uk/ocapi/v1/"
|
|
13
|
+
ATTRIBUTES = {
|
|
14
|
+
"3D" => "3d", "IMAX" => "imax", "AD" => "audio-described",
|
|
15
|
+
"Subtitled" => "subbed", "Subtitles" => "subbed",
|
|
16
|
+
"Autism Friendly" => "autism-friendly",
|
|
17
|
+
"Movies for Juniors" => "junior",
|
|
18
|
+
"Unlimited Screening" => "unlimited-screening", "Q&A" => "qa"
|
|
19
|
+
}.freeze
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def film_list_comingsoon
|
|
37
|
-
response('film/list/comingsoon', full: true)
|
|
21
|
+
def cinema_list
|
|
22
|
+
cinemas = fetch("sites").fetch("sites").map do |site|
|
|
23
|
+
address = site.fetch("contactDetails", {}).fetch("addressV2", {})
|
|
24
|
+
{
|
|
25
|
+
"id" => site.fetch("id"),
|
|
26
|
+
"displayName" => site.fetch("name").fetch("text"),
|
|
27
|
+
"addressInfo" => {
|
|
28
|
+
"address1" => address.dig("line1", "text"),
|
|
29
|
+
"city" => address.dig("line2", "text"),
|
|
30
|
+
"state" => address.dig("line3", "text"),
|
|
31
|
+
"postalCode" => address.dig("line4", "text")
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
JSON.generate("body" => { "cinemas" => cinemas })
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
# All screenings from a specific cinema on a specific date
|
|
41
|
-
# @param [Fixnum] cinema_id the id of the cinema
|
|
42
|
-
# @param [Date] date a single date in the future
|
|
43
|
-
# @return [String] JSON encoded
|
|
44
38
|
def performances(cinema_id, date)
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
site_id = cinema_id.to_s.rjust(3, "0")
|
|
40
|
+
response = fetch("showtimes/by-business-date/#{date.iso8601}",
|
|
41
|
+
"siteIds" => site_id)
|
|
42
|
+
related = response.fetch("relatedData")
|
|
43
|
+
films = related.fetch("films").to_h { |film| [film.fetch("id"), film] }
|
|
44
|
+
attributes = related.fetch("attributes").to_h do |attr|
|
|
45
|
+
[attr.fetch("id"), attr]
|
|
46
|
+
end
|
|
47
|
+
normalized_films = []
|
|
48
|
+
events = response.fetch("showtimes").map do |showtime|
|
|
49
|
+
film = films.fetch(showtime.fetch("filmId"))
|
|
50
|
+
attrs = showtime.fetch("attributeIds", []).filter_map do |id|
|
|
51
|
+
attribute = attributes.fetch(id)
|
|
52
|
+
ATTRIBUTES[attribute.dig("shortName", "text")] ||
|
|
53
|
+
ATTRIBUTES[attribute.dig("name", "text")]
|
|
54
|
+
end
|
|
55
|
+
attrs << "3d" if showtime["requires3dGlasses"]
|
|
56
|
+
# Formats belong to a session, not to the film as a whole.
|
|
57
|
+
id = showtime.fetch("id")
|
|
58
|
+
normalized_films << {
|
|
59
|
+
"id" => id,
|
|
60
|
+
"name" => film.fetch("title").fetch("text"),
|
|
61
|
+
"attributeIds" => attrs
|
|
62
|
+
}
|
|
63
|
+
starts_at = showtime.fetch("schedule").fetch("startsAt")
|
|
64
|
+
{
|
|
65
|
+
"filmId" => id, "attributeIds" => attrs,
|
|
66
|
+
"eventDateTime" => Time.iso8601(starts_at).utc.iso8601,
|
|
67
|
+
"bookingLink" =>
|
|
68
|
+
"https://web.cineworld.co.uk/order/showtimes/#{id}/seats"
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
site = related.fetch("sites").detect do |item|
|
|
72
|
+
item.fetch("id") == site_id
|
|
73
|
+
end
|
|
74
|
+
JSON.generate(
|
|
75
|
+
"body" => {
|
|
76
|
+
"films" => normalized_films, "events" => events,
|
|
77
|
+
"cinemaName" => site&.dig("name", "text")
|
|
78
|
+
}
|
|
79
|
+
)
|
|
47
80
|
end
|
|
48
81
|
|
|
49
82
|
private
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
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}"
|
|
62
112
|
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def response(path, hash)
|
|
66
|
-
uri = URI::HTTPS.build(host: 'www.cineworld.co.uk',
|
|
67
|
-
path: "/api/#{path}",
|
|
68
|
-
query: URI.encode_www_form(hash.merge(DEFAULTS)))
|
|
69
|
-
fetch(uri)
|
|
70
113
|
end
|
|
71
114
|
end
|
|
72
115
|
end
|