cineworld_uk 3.0.4 → 4.0.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 +17 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +21 -0
- data/README.md +67 -16
- data/Rakefile +12 -36
- data/cineworld_uk.gemspec +6 -4
- data/lib/cineworld_uk/cinema.rb +7 -4
- data/lib/cineworld_uk/internal/api_response.rb +99 -56
- 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 +6 -2
- 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 +111 -68
- 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 +51 -0
- data/test/support/fixture_reader.rb +2 -14
- data/test/test_helper.rb +3 -2
- metadata +49 -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: 5284f7c7c8b846edbdd724cb3c105b033da4f313eed083d6b691ae6b8714d3b8
|
|
4
|
+
data.tar.gz: 1568acd04ed0346ba388509903a17487726c088ff2d280329cfb0fe8f6023eed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: faff1f58b5e101ce8185a96f791a27088ed31f7c78d3eebf3f9b332b6931848604726b7e5b3d16f3bb51c1878c4693cb993863cf48dcbbe743436208e5c3186b
|
|
7
|
+
data.tar.gz: 2ab7741bafc40d66d104390340fa2e28a1a5b0a6d166f85b3526f8eb576d664b6860c5f03032fc3d71a2c21bb3ece25804d5258aeba3a7f74601027610c161d8
|
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,17 @@
|
|
|
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
|
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.0.0 - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Retrieve cinemas and performances from the Vista API instead of the retired iOS API.
|
|
9
|
+
- Require a caller-supplied guest JWT through `CineworldUk.token`; token acquisition and renewal remain the caller's responsibility.
|
|
10
|
+
- `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.
|
|
11
|
+
- Use one request per cinema per requested day, reusing related film, cinema and attribute data. Share cached cinema data with address lookups.
|
|
12
|
+
- Convert advertised session times to UTC using Vista's explicit offsets and preserve session-specific formats.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `CineworldUk::AuthenticationError` for missing tokens and HTTP 401 responses.
|
|
16
|
+
- Orb setup for the pinned Ruby version and gem dependencies.
|
|
17
|
+
|
|
18
|
+
## 3.1.0 - 2016-09-11
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Use of www2 domain, set to HTTPS and www
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Tests against live server, using `rake live`
|
|
25
|
+
|
|
5
26
|
## 3.0.4 - 2016-04-26
|
|
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,46 @@ 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
|
+
Configure a current guest JWT before retrieving data (without the `Bearer ` prefix):
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require 'cineworld_uk'
|
|
34
|
+
|
|
35
|
+
CineworldUk.token = ENV.fetch('CINEWORLD_TOKEN')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
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.
|
|
43
|
+
|
|
44
|
+
Missing tokens and HTTP 401 responses raise `CineworldUk::AuthenticationError`.
|
|
45
|
+
Set `CineworldUk.token` again with a fresh token before retrying. Other HTTP
|
|
46
|
+
failures raise an error rather than returning an empty schedule. Redirects are
|
|
47
|
+
not followed, so credentials are not forwarded to another host.
|
|
48
|
+
|
|
49
|
+
There is no verified fixed token lifetime. Its declared lifetime is `exp - iat`
|
|
50
|
+
seconds. The website reloads to renew after 90% of that interval. To inspect
|
|
51
|
+
your token locally without printing it (decoding does not verify its signature):
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require 'base64'
|
|
55
|
+
payload = CineworldUk.token.split('.').fetch(1)
|
|
56
|
+
claims = JSON.parse(Base64.urlsafe_decode64(payload))
|
|
57
|
+
puts "Lifetime: #{claims.fetch('exp') - claims.fetch('iat')} seconds"
|
|
58
|
+
puts "Expires: #{Time.at(claims.fetch('exp')).utc}"
|
|
59
|
+
```
|
|
60
|
+
|
|
31
61
|
### Cinema
|
|
32
62
|
|
|
33
63
|
``` ruby
|
|
34
64
|
CineworldUk::Cinema.all
|
|
35
65
|
#=> [<CineworldUK::Cinema ...>, <CineworldUK::Cinema ...>, ...]
|
|
36
66
|
|
|
37
|
-
cinema = CineworldUk::Cinema.new(
|
|
67
|
+
cinema = CineworldUk::Cinema.new(14)
|
|
38
68
|
#=> <CineworldUK::Cinema ...>
|
|
39
69
|
|
|
40
70
|
cinema.adr
|
|
@@ -42,7 +72,7 @@ cinema.adr
|
|
|
42
72
|
street_address: 'Brighton Marina Village',
|
|
43
73
|
extended_address: nil,
|
|
44
74
|
locality: 'Brighton',
|
|
45
|
-
region:
|
|
75
|
+
region: 'East Sussex',
|
|
46
76
|
postal_code: 'BN2 5UF',
|
|
47
77
|
country: 'United Kingdom'
|
|
48
78
|
}
|
|
@@ -54,7 +84,7 @@ cinema.full_name
|
|
|
54
84
|
#=> 'Cineworld Brighton'
|
|
55
85
|
|
|
56
86
|
cinema.id
|
|
57
|
-
#=>
|
|
87
|
+
#=> 14
|
|
58
88
|
|
|
59
89
|
cinema.postal_code
|
|
60
90
|
#=> 'BN2 5UF'
|
|
@@ -62,36 +92,57 @@ cinema.postal_code
|
|
|
62
92
|
|
|
63
93
|
### Performances
|
|
64
94
|
|
|
95
|
+
`at` accepts a numeric Vista cinema ID (`14`, `'14'` or `'014'` for Brighton),
|
|
96
|
+
not a Boxoffice ID such as `X079B`. Dates are inclusive **business dates**.
|
|
97
|
+
Without dates, it fetches today in London and the following 13 days; it does not
|
|
98
|
+
discover every future screening date.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
day = Date.new(2026, 10, 14)
|
|
102
|
+
performances = CineworldUk::Performance.at(14, from: day, to: day)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This makes **one HTTP request per cinema per requested day**, including empty
|
|
106
|
+
days. Cinema names, film titles and attributes come from the same response—no
|
|
107
|
+
extra cinema or film lookups are needed. `Cinema.all` separately fetches and
|
|
108
|
+
caches the cinema list in memory, shared by name and address lookups. Performance
|
|
109
|
+
responses are not cached across calls, so reuse the returned array when possible.
|
|
110
|
+
|
|
111
|
+
Advertised session times (`startsAt`), not feature start times (`filmStartsAt`),
|
|
112
|
+
are converted to UTC using the offsets supplied by Vista. Recognized session
|
|
113
|
+
attributes populate the existing dimension and variant fields; unknown attributes
|
|
114
|
+
are ignored. No seat-availability or booking operations are performed.
|
|
115
|
+
|
|
65
116
|
``` ruby
|
|
66
|
-
CineworldUk::Performance.at(
|
|
117
|
+
CineworldUk::Performance.at(14)
|
|
67
118
|
#=> [<CineworldUK::Performance ...>, <CineworldUK::Performance ...>, ...]
|
|
68
119
|
|
|
69
|
-
performance =
|
|
120
|
+
performance = performances.first
|
|
70
121
|
#=> <CineworldUK::Performance ...>
|
|
71
122
|
|
|
72
123
|
performance.film_name
|
|
73
|
-
#=> '
|
|
124
|
+
#=> 'Other Mommy'
|
|
74
125
|
|
|
75
126
|
performance.dimension
|
|
76
127
|
#=> '2d'
|
|
77
128
|
|
|
78
129
|
performance.variant
|
|
79
|
-
#=> ['
|
|
130
|
+
#=> ['audio_described']
|
|
80
131
|
|
|
81
132
|
performance.starting_at
|
|
82
|
-
#=>
|
|
133
|
+
#=> 2026-10-14 14:40:00 UTC
|
|
83
134
|
|
|
84
135
|
performance.showing_on
|
|
85
|
-
#=>
|
|
136
|
+
#=> Date.new(2026, 10, 14)
|
|
86
137
|
|
|
87
138
|
performance.booking_url
|
|
88
|
-
#=> "
|
|
139
|
+
#=> "https://web.cineworld.co.uk/order/showtimes/014-165507/seats"
|
|
89
140
|
|
|
90
141
|
performance.cinema_name
|
|
91
142
|
#=> 'Brighton'
|
|
92
143
|
|
|
93
144
|
performance.cinema_id
|
|
94
|
-
#=>
|
|
145
|
+
#=> 14
|
|
95
146
|
```
|
|
96
147
|
|
|
97
148
|
## Contributing
|
data/Rakefile
CHANGED
|
@@ -13,6 +13,15 @@ Rake::TestTask.new do |t|
|
|
|
13
13
|
t.verbose = true
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
Rake::TestTask.new do |t|
|
|
17
|
+
t.libs << 'lib/cineworld_uk'
|
|
18
|
+
t.name = :live
|
|
19
|
+
t.test_files = FileList[
|
|
20
|
+
'test/live/*_test.rb'
|
|
21
|
+
]
|
|
22
|
+
t.verbose = true
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
desc 'console'
|
|
17
26
|
# http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
|
|
18
27
|
task :console do
|
|
@@ -26,6 +35,7 @@ end
|
|
|
26
35
|
desc 'Recreate test fixtures'
|
|
27
36
|
task :fixtures do
|
|
28
37
|
require 'cineworld_uk'
|
|
38
|
+
CineworldUk.token = ENV.fetch("CINEWORLD_TOKEN")
|
|
29
39
|
|
|
30
40
|
def js_fixture(name)
|
|
31
41
|
File.expand_path("../test/fixtures/#{name}.json", __FILE__)
|
|
@@ -38,45 +48,11 @@ task :fixtures do
|
|
|
38
48
|
file.write api.cinema_list
|
|
39
49
|
end
|
|
40
50
|
|
|
41
|
-
File.open(js_fixture('api/
|
|
42
|
-
puts '* API Cinema Detail Brighton'
|
|
43
|
-
file.write api.cinema_detail(3)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
File.open(js_fixture('api/cinema-detail-96'), 'w') do |file|
|
|
47
|
-
puts '* API Cinema Detail Birmingham NEC'
|
|
48
|
-
file.write api.cinema_detail(96)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
File.open(js_fixture('api/cinema-detail-21'), 'w') do |file|
|
|
52
|
-
puts '* API Cinema Detail Edinburgh'
|
|
53
|
-
file.write api.cinema_detail(21)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
File.open(js_fixture('api/cinema-detail-10'), 'w') do |file|
|
|
57
|
-
puts '* API Cinema Detail Chelsea'
|
|
58
|
-
file.write api.cinema_detail(10)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
File.open(js_fixture('api/film-list'), 'w') do |file|
|
|
62
|
-
puts '* API Film List'
|
|
63
|
-
file.write api.film_list
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
File.open(js_fixture('api/film-list-comingsoon'), 'w') do |file|
|
|
67
|
-
puts '* API Film List Coming Soon'
|
|
68
|
-
file.write api.film_list_comingsoon
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
File.open(js_fixture('api/performances-tomorrow-3'), 'w') do |file|
|
|
51
|
+
File.open(js_fixture('api/performances-tomorrow-14'), 'w') do |file|
|
|
72
52
|
puts '* API Performances in Brighton Tomorrow'
|
|
73
|
-
file.write api.performances(
|
|
53
|
+
file.write api.performances(14, Date.today + 1)
|
|
74
54
|
end
|
|
75
55
|
|
|
76
|
-
File.open(js_fixture('api/dates-3'), 'w') do |file|
|
|
77
|
-
puts '* API Dates Brighton Tomorrow'
|
|
78
|
-
file.write api.dates(3)
|
|
79
|
-
end
|
|
80
56
|
end
|
|
81
57
|
|
|
82
58
|
task :default => :test
|
data/cineworld_uk.gemspec
CHANGED
|
@@ -12,16 +12,18 @@ 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 'cinebase', '~>
|
|
27
|
+
spec.add_runtime_dependency 'cinebase', '~> 5.0'
|
|
28
|
+
spec.add_runtime_dependency 'nokogiri', '>= 1.11'
|
|
27
29
|
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::HTTP.build(host: 'www2.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
|
|
@@ -7,8 +7,9 @@ module CineworldUk
|
|
|
7
7
|
class CinemaAddress
|
|
8
8
|
# @param [Integer] id the cinema id
|
|
9
9
|
# @return [CineworldUk::Internal::Parser::Api::CinemaAddress]
|
|
10
|
-
def initialize(id)
|
|
10
|
+
def initialize(id, cinemas = nil)
|
|
11
11
|
@id = id
|
|
12
|
+
@cinemas = cinemas
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
# @return [Hash] contains :street_address, :extended_address,
|
|
@@ -27,52 +28,51 @@ module CineworldUk
|
|
|
27
28
|
|
|
28
29
|
private
|
|
29
30
|
|
|
30
|
-
def
|
|
31
|
-
@
|
|
32
|
-
cinema_detail_hash['address'].split(',').map(&:strip).compact
|
|
31
|
+
def address_info
|
|
32
|
+
@address_info ||= cinema_hash['addressInfo'] || {}
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def
|
|
36
|
-
@
|
|
35
|
+
def cinema_hash
|
|
36
|
+
@cinema_hash ||= begin
|
|
37
|
+
cinemas = @cinemas ||
|
|
38
|
+
JSON.parse(cinema_list_response)["body"]["cinemas"]
|
|
39
|
+
formatted_id = @id.to_s.rjust(3, '0')
|
|
40
|
+
cinemas.find { |c| c['id'] == formatted_id } || {}
|
|
41
|
+
end
|
|
37
42
|
end
|
|
38
43
|
|
|
39
|
-
def
|
|
40
|
-
@
|
|
41
|
-
CineworldUk::Internal::ApiResponse.new.
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def ext_array
|
|
45
|
-
@ext_array ||= adr_array[1..-1]
|
|
44
|
+
def cinema_list_response
|
|
45
|
+
@cinema_list_response ||=
|
|
46
|
+
CineworldUk::Internal::ApiResponse.new.cinema_list
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def extended_address
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
50
|
+
addr2 = address_info['address2']
|
|
51
|
+
addr3 = address_info['address3']
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
return nil if london?
|
|
54
|
+
addr2 || addr3
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def locality
|
|
58
|
-
|
|
59
|
-
london? ? ext_array[0] : ext_array[1]
|
|
58
|
+
address_info['city']
|
|
60
59
|
end
|
|
61
60
|
|
|
62
61
|
def london?
|
|
63
|
-
|
|
62
|
+
address_info['city'] == 'London' ||
|
|
63
|
+
cinema_hash['displayName']&.start_with?('London')
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def postal_code
|
|
67
|
-
|
|
67
|
+
address_info['postalCode']
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def region
|
|
71
|
-
'London' if london?
|
|
71
|
+
address_info['state'] || ('London' if london?)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def street_address
|
|
75
|
-
|
|
75
|
+
address_info['address1']
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|