cinebase 5.1.0 → 5.3.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/ci.yml +1 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +17 -0
- data/README.md +80 -7
- data/cinebase.gemspec +2 -0
- data/lib/cinebase/festival.rb +28 -0
- data/lib/cinebase/performance.rb +11 -0
- data/lib/cinebase/title_sanitizer.rb +39 -0
- data/lib/cinebase/version.rb +1 -1
- data/lib/cinebase/vista/client.rb +55 -0
- data/lib/cinebase/vista/guest_token.rb +77 -0
- data/lib/cinebase/vista/package_task.rb +116 -0
- data/lib/cinebase/vista.rb +14 -0
- data/lib/cinebase.rb +1 -0
- metadata +34 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b497375ced74ea56a85b85027ff086e690432d8296a2b43e4d2d59103579a593
|
|
4
|
+
data.tar.gz: 1b277120417de80d13097b49ac8d5203f9a54bb8214a3842ea25120b2eb71c6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b6c03efd197eba1cfc566839c09b05c7f22c64a05cdade0806417507d4a014befeef4ab0451cbdc360f0df28a810ec7a7843e08be6fcc9c3b413007ed2440c2
|
|
7
|
+
data.tar.gz: da91b411310832dc36e999024a094f87321a4597033ad97a38627e10a4d2be44a6656bda1c392bb8ec2696db22755008d05da0f18859a6fe909b5a14b1e6f6de
|
data/.github/workflows/ci.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
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
|
+
## [5.3.0] - 2026-10-22
|
|
6
|
+
### Added
|
|
7
|
+
- `Performance#film_year` with an optional `film_year:` option, and `TitleSanitizer.film_year` reading a trailing `(1996)` from a title
|
|
8
|
+
- `COMMON_REMOVE` strips that year, `(Re-release)` and Lithuanian versions
|
|
9
|
+
- `Horror Season` title prefixes are festivals, so `Horror Season 2026 From Dusk Till Dawn (1996)` becomes `From Dusk Till Dawn`, year 1996, in the Horror Season 2026
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Indentation of tests added in 5.2.0
|
|
13
|
+
|
|
14
|
+
## [5.2.0] - 2026-10-22
|
|
15
|
+
### Added
|
|
16
|
+
- `Performance#festival`, `#language` and `#subtitled?`, with optional `festival:` and `language:` options
|
|
17
|
+
- `Cinebase::Festival` value object (`name`, `year`) and `Festival.from_title`
|
|
18
|
+
- `TitleSanitizer::COMMON_REMOVE` and `COMMON_REPLACE`: opt-in rules for dub and subtitle versions, language versions, anniversaries, Q&A add-ons, encores, sing-alongs, festival prefixes, opera and ballet seasons, and `WORLD TOUR` capitals
|
|
19
|
+
- `cinebase/vista`, loaded only on request, for chains on Vista OCAPI: `Vista::Client`, a `Vista::GuestToken` template and `Vista::PackageTask` for platform gems bundling curl-impersonate
|
|
20
|
+
- CI on Ruby 4.0
|
|
21
|
+
|
|
5
22
|
## [5.1.0] - 2025-10-31
|
|
6
23
|
### Changed
|
|
7
24
|
- Converted all test files from minitest spec syntax to vanilla minitest syntax
|
data/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# Cinebase
|
|
2
2
|
|
|
3
3
|
[](http://rubygems.org/gems/cinebase)
|
|
4
|
-
[](https://travis-ci.org/andycroll/cinebase)
|
|
5
|
-
[](https://codeclimate.com/github/andycroll/cinebase)
|
|
6
|
-
[](https://codeclimate.com/github/andycroll/cinebase/coverage)
|
|
7
4
|

|
|
8
5
|
|
|
9
6
|
A basic interface for a series of cinema gems.
|
|
@@ -44,9 +41,11 @@ module CinemaChainName
|
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
module CinemaChainName
|
|
47
|
-
class
|
|
44
|
+
class Performance < Cinebase::Performance
|
|
48
45
|
def self.at(cinema_id)
|
|
49
|
-
# parse webpages or apis to get
|
|
46
|
+
# parse webpages or apis to get performances for a cinema, built with
|
|
47
|
+
# booking_url:, cinema_id:, cinema_name:, dimension:, festival:,
|
|
48
|
+
# film_name:, film_year:, language:, starting_at: and variant:
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
51
|
end
|
|
@@ -56,18 +55,92 @@ module CinemaChainName
|
|
|
56
55
|
class TitleSanitizer < Cinebase::TitleSanitizer
|
|
57
56
|
private
|
|
58
57
|
|
|
58
|
+
# Chain patterns first, so markers like trailing sing-alongs are at the
|
|
59
|
+
# end of the title by the time the common rules run
|
|
59
60
|
def remove
|
|
60
|
-
#
|
|
61
|
+
[] + COMMON_REMOVE # chain-specific patterns, then the common ones
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def replace
|
|
64
|
-
# { core title regex => replacement text }
|
|
65
|
+
{}.merge(COMMON_REPLACE) # { core title regex => replacement text }
|
|
65
66
|
end
|
|
66
67
|
end
|
|
67
68
|
end
|
|
68
69
|
end
|
|
69
70
|
```
|
|
70
71
|
|
|
72
|
+
### Festivals
|
|
73
|
+
|
|
74
|
+
A festival is an annual event, so `Cinebase::Festival` holds the name and the
|
|
75
|
+
edition year. Pass it to a performance as `festival:`.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Cinebase::Festival.from_title('Cine-Excess 2026: Scum Of The Earth', year: 2026)
|
|
79
|
+
#=> #<data Cinebase::Festival name="Cine-Excess", year=2026>
|
|
80
|
+
|
|
81
|
+
# No year in the title, so the screening's year is the edition
|
|
82
|
+
Cinebase::Festival.from_title('UKJFF - From Cable Street To Brick Lane', year: 2026)
|
|
83
|
+
#=> #<data Cinebase::Festival name="UKJFF", year=2026>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`COMMON_REMOVE` strips the same prefixes from titles.
|
|
87
|
+
|
|
88
|
+
### Film years
|
|
89
|
+
|
|
90
|
+
Titles often end with the film's year. `COMMON_REMOVE` strips it, and
|
|
91
|
+
`TitleSanitizer.film_year` reads it so it can be passed as `film_year:`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
Cinebase::TitleSanitizer.film_year('Street Fighter (2026)') #=> 2026
|
|
95
|
+
Cinebase::TitleSanitizer.film_year('Kung Fu Soccer (2026) (Dubbed)') #=> 2026
|
|
96
|
+
Cinebase::TitleSanitizer.film_year('Blade Runner 2049') #=> nil
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For a re-release this is the original year, which is what IMDb lists.
|
|
100
|
+
|
|
101
|
+
### Vista OCAPI chains
|
|
102
|
+
|
|
103
|
+
Chains whose websites run on Vista share a client, guest token retrieval and
|
|
104
|
+
packaging. These are not loaded by `require 'cinebase'`:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
require 'cinebase/vista'
|
|
108
|
+
|
|
109
|
+
client = Cinebase::Vista::Client.new(
|
|
110
|
+
base_url: 'https://vwc.odeon.co.uk/WSVistaWebClient/ocapi/v1/',
|
|
111
|
+
region_code: 'UK',
|
|
112
|
+
token: guest_jwt
|
|
113
|
+
)
|
|
114
|
+
client.get('sites')
|
|
115
|
+
|
|
116
|
+
module CinemaChainName
|
|
117
|
+
class GuestToken < Cinebase::Vista::GuestToken
|
|
118
|
+
PAGE_URL = 'https://www.example.co.uk/page-with-the-token/'.freeze
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def bundled_curl
|
|
123
|
+
File.expand_path('../../vendor/curl-impersonate/bin/curl-impersonate', __dir__)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def extract_token(html)
|
|
127
|
+
# return the guest JWT from the page
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`GuestToken#fetch` runs `curl-impersonate` with a Chrome profile, checks the
|
|
134
|
+
token's declared expiry and raises `Cinebase::Vista::AuthenticationError` when
|
|
135
|
+
it cannot get a current token. To ship curl-impersonate inside platform gems,
|
|
136
|
+
add this to the chain gem's Rakefile:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
require 'cinebase/vista/package_task'
|
|
140
|
+
Cinebase::Vista::PackageTask.new('chain_name.gemspec')
|
|
141
|
+
# rake 'package:native[arm64-darwin]' or rake package:all
|
|
142
|
+
```
|
|
143
|
+
|
|
71
144
|
## Contributing
|
|
72
145
|
|
|
73
146
|
1. [Fork it](https://github.com/andycroll/cinebase/fork)
|
data/cinebase.gemspec
CHANGED
|
@@ -18,10 +18,12 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
19
|
spec.require_paths = ['lib']
|
|
20
20
|
|
|
21
|
+
spec.add_development_dependency 'base64'
|
|
21
22
|
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
22
23
|
spec.add_development_dependency 'minitest-reporters'
|
|
23
24
|
spec.add_development_dependency 'rake', '>= 13.0'
|
|
24
25
|
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
26
|
+
spec.add_development_dependency 'webmock'
|
|
25
27
|
|
|
26
28
|
spec.add_runtime_dependency 'tzinfo'
|
|
27
29
|
spec.add_runtime_dependency 'tzinfo-data'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cinebase
|
|
4
|
+
Festival = Data.define(:name, :year)
|
|
5
|
+
|
|
6
|
+
# An annual festival edition a film is screened in
|
|
7
|
+
class Festival
|
|
8
|
+
# Title prefixes naming a festival: the name, then the year when given
|
|
9
|
+
PREFIXES = [
|
|
10
|
+
/\A(Cine-Excess) (20\d{2}): /,
|
|
11
|
+
/\A(Cinemagic) (\d{4}) -/,
|
|
12
|
+
/\A(UKJFF) -/,
|
|
13
|
+
/\A(Horror Season) (20\d{2}) /
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
# @param [String] title raw film title
|
|
17
|
+
# @param [Integer] year the edition when the title gives none, usually the
|
|
18
|
+
# screening's year
|
|
19
|
+
# @return [Cinebase::Festival, nil]
|
|
20
|
+
def self.from_title(title, year:)
|
|
21
|
+
PREFIXES.each do |prefix|
|
|
22
|
+
match = title.match(prefix)
|
|
23
|
+
return new(name: match[1], year: Integer(match[2] || year)) if match
|
|
24
|
+
end
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/cinebase/performance.rb
CHANGED
|
@@ -7,14 +7,20 @@ module Cinebase
|
|
|
7
7
|
attr_reader :dimension
|
|
8
8
|
attr_reader :cinema_id
|
|
9
9
|
attr_reader :cinema_name
|
|
10
|
+
attr_reader :festival
|
|
10
11
|
attr_reader :film_name
|
|
12
|
+
attr_reader :film_year
|
|
13
|
+
attr_reader :language
|
|
11
14
|
|
|
12
15
|
def initialize(options)
|
|
13
16
|
@booking_url = options.fetch(:booking_url, nil)
|
|
14
17
|
@cinema_name = options.fetch(:cinema_name)
|
|
15
18
|
@cinema_id = options.fetch(:cinema_id)
|
|
16
19
|
@dimension = options.fetch(:dimension, '2d')
|
|
20
|
+
@festival = options.fetch(:festival, nil)
|
|
17
21
|
@film_name = options.fetch(:film_name)
|
|
22
|
+
@film_year = options.fetch(:film_year, nil)
|
|
23
|
+
@language = options.fetch(:language, nil)
|
|
18
24
|
@time = options.fetch(:starting_at)
|
|
19
25
|
@variant = options.fetch(:variant, [])
|
|
20
26
|
end
|
|
@@ -41,5 +47,10 @@ module Cinebase
|
|
|
41
47
|
@variant = @variant.split(' ') if @variant.is_a?(String)
|
|
42
48
|
@variant.map(&:downcase).sort.uniq
|
|
43
49
|
end
|
|
50
|
+
|
|
51
|
+
# English subtitles or open captions
|
|
52
|
+
def subtitled?
|
|
53
|
+
variant.include?('subtitled')
|
|
54
|
+
end
|
|
44
55
|
end
|
|
45
56
|
end
|
|
@@ -27,3 +27,42 @@ module Cinebase
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
module Cinebase
|
|
32
|
+
# Rules every chain can share; subclasses opt in after their own patterns,
|
|
33
|
+
# e.g. `[...] + COMMON_REMOVE` in #remove
|
|
34
|
+
class TitleSanitizer
|
|
35
|
+
LANGUAGES = %w(Arabic Cantonese French Hindi Kannada Korean Lithuanian
|
|
36
|
+
Malayalam Mandarin Marathi Polish Punjabi Tamil Telugu
|
|
37
|
+
Turkish).freeze
|
|
38
|
+
|
|
39
|
+
# A trailing "(1996)", allowing other bracketed markers after it
|
|
40
|
+
FILM_YEAR = /\s*\((1[89]\d{2}|20\d{2})\)(?=(?:\s*\([^)]*\))*\s*\z)/
|
|
41
|
+
|
|
42
|
+
COMMON_REMOVE = [
|
|
43
|
+
*Festival::PREFIXES, # festival prefix
|
|
44
|
+
/\s+[23]d\b/i, # dimension
|
|
45
|
+
/\((#{LANGUAGES.join('|')})\)/i, # language version
|
|
46
|
+
/\((Dubbed|Subbed|English Dub|[^)]*subtitles)\)/i, # dub or subtitle version
|
|
47
|
+
/\(\d+(st|nd|rd|th) Anniversary\)/i, # anniversary re-release
|
|
48
|
+
/\(Re-release\)/i, # re-release
|
|
49
|
+
FILM_YEAR, # film year, see .film_year
|
|
50
|
+
/\s*\+\s*(In-Person )?Q&A\z/i, # Q&A add-on
|
|
51
|
+
/:\s*Encore\z/i, # encore
|
|
52
|
+
/\bsing-?a-?long\z/i, # singalong
|
|
53
|
+
'amp;' # html ampersands
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
# @param [String] title raw film title
|
|
57
|
+
# @return [Integer, nil] the film's year from a trailing "(1996)"
|
|
58
|
+
def self.film_year(title)
|
|
59
|
+
title[FILM_YEAR, 1]&.to_i
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
COMMON_REPLACE = {
|
|
63
|
+
/Met Opera 20\d{2}-\d{2}: (.*)/ => 'Met Opera: ',
|
|
64
|
+
/RBO (?:Cinema Season )?20\d{2}-\d{2}: (.*)/ => 'Royal Ballet and Opera: ',
|
|
65
|
+
/WORLD TOUR(.*)/ => 'World Tour'
|
|
66
|
+
}.freeze
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/cinebase/version.rb
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
|
|
6
|
+
module Cinebase
|
|
7
|
+
module Vista
|
|
8
|
+
# Makes guest-token GET requests to a chain's OCAPI
|
|
9
|
+
class Client
|
|
10
|
+
# @param [String] base_url e.g. 'https://vwc.odeon.co.uk/WSVistaWebClient/ocapi/v1/'
|
|
11
|
+
# @param [String] region_code Connect-Region-Code header, e.g. 'UK' or 'GB'
|
|
12
|
+
# @param [String, nil] token raw guest JWT, without the "Bearer " prefix
|
|
13
|
+
def initialize(base_url:, region_code:, token:)
|
|
14
|
+
@base_url = base_url
|
|
15
|
+
@region_code = region_code
|
|
16
|
+
@token = token
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param [String] path relative to the base URL
|
|
20
|
+
# @param [Hash] query parameters; Array values repeat the key
|
|
21
|
+
# @return [Hash] parsed JSON
|
|
22
|
+
# @raise [Cinebase::Vista::AuthenticationError] missing or rejected token
|
|
23
|
+
def get(path, query = {})
|
|
24
|
+
if !@token.is_a?(String) || @token.strip.empty?
|
|
25
|
+
raise AuthenticationError, 'Set a current guest token before fetching data'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
response = request(path, query)
|
|
29
|
+
case response
|
|
30
|
+
when Net::HTTPSuccess
|
|
31
|
+
JSON.parse(response.body)
|
|
32
|
+
when Net::HTTPUnauthorized
|
|
33
|
+
raise AuthenticationError, 'Vista rejected the guest token; supply a fresh one'
|
|
34
|
+
else
|
|
35
|
+
# Omit response bodies and never redirect credentials to another host.
|
|
36
|
+
raise "Vista HTTP error #{response.code}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def request(path, query)
|
|
43
|
+
uri = URI(@base_url + path)
|
|
44
|
+
uri.query = URI.encode_www_form(query) unless query.empty?
|
|
45
|
+
request = Net::HTTP::Get.new(uri)
|
|
46
|
+
request['Authorization'] = "Bearer #{@token}"
|
|
47
|
+
request['Connect-Region-Code'] = @region_code
|
|
48
|
+
request['Accept'] = 'application/json'
|
|
49
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
|
|
50
|
+
http.request(request)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module Cinebase
|
|
7
|
+
module Vista
|
|
8
|
+
# Retrieves a chain website's public guest token with curl-impersonate,
|
|
9
|
+
# without running a browser. Subclasses set PAGE_URL and implement
|
|
10
|
+
# #extract_token(html) and #bundled_curl.
|
|
11
|
+
class GuestToken
|
|
12
|
+
# @param [String, nil] curl curl-impersonate executable; defaults to the
|
|
13
|
+
# one bundled in the chain gem, then the one on PATH
|
|
14
|
+
def initialize(curl: nil)
|
|
15
|
+
@curl = curl || default_curl
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [String] the guest JWT
|
|
19
|
+
# @raise [Cinebase::Vista::AuthenticationError]
|
|
20
|
+
def fetch
|
|
21
|
+
token = extract_token(request_page)
|
|
22
|
+
unless token.is_a?(String) && !token.empty?
|
|
23
|
+
raise AuthenticationError, 'Guest token is missing from the page'
|
|
24
|
+
end
|
|
25
|
+
validate_expiry!(token)
|
|
26
|
+
token
|
|
27
|
+
rescue JSON::ParserError, ArgumentError, IndexError, TypeError, NoMethodError
|
|
28
|
+
raise AuthenticationError, 'Guest token page data is missing or invalid', cause: nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# HTTP statuses whose page still carries the token
|
|
34
|
+
def accepted_statuses
|
|
35
|
+
%w[200]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def bundled_curl
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def default_curl
|
|
43
|
+
bundled_curl && File.executable?(bundled_curl) ? bundled_curl : 'curl-impersonate'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def curl_command
|
|
47
|
+
[@curl, '--disable', '--impersonate', 'chrome150',
|
|
48
|
+
'--compressed', '--silent', '--show-error',
|
|
49
|
+
'--connect-timeout', '10', '--max-time', '30',
|
|
50
|
+
'--write-out', "\n%{http_code}", self.class::PAGE_URL]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def request_page
|
|
54
|
+
output, _stderr, result = Open3.capture3(*curl_command)
|
|
55
|
+
unless result.success?
|
|
56
|
+
raise AuthenticationError, "Guest token request failed (curl exit #{result.exitstatus})"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
html, _, status = output.rpartition("\n")
|
|
60
|
+
unless accepted_statuses.include?(status)
|
|
61
|
+
raise AuthenticationError, "Guest token page returned HTTP #{status}"
|
|
62
|
+
end
|
|
63
|
+
html
|
|
64
|
+
rescue Errno::ENOENT
|
|
65
|
+
raise AuthenticationError, 'Install curl-impersonate or pass its executable path with curl:'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def validate_expiry!(token)
|
|
69
|
+
payload = token.split('.').fetch(1).tr('-_', '+/').unpack1('m')
|
|
70
|
+
claims = JSON.parse(payload)
|
|
71
|
+
if Integer(claims.fetch('exp')) <= Time.now.to_i
|
|
72
|
+
raise AuthenticationError, 'Guest token supplied by the page has expired'
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'rake'
|
|
6
|
+
require 'rubygems/package'
|
|
7
|
+
require 'tmpdir'
|
|
8
|
+
require 'zlib'
|
|
9
|
+
|
|
10
|
+
module Cinebase
|
|
11
|
+
module Vista
|
|
12
|
+
# Defines package:native[platform] and package:all, which build platform
|
|
13
|
+
# gems that bundle curl-impersonate for GuestToken. In a chain gem's
|
|
14
|
+
# Rakefile: `Cinebase::Vista::PackageTask.new('odeon_uk.gemspec')`
|
|
15
|
+
class PackageTask
|
|
16
|
+
include Rake::DSL
|
|
17
|
+
|
|
18
|
+
VERSION = '2.2.3'
|
|
19
|
+
|
|
20
|
+
# Platform => [upstream asset target, SHA-256 of the v2.2.3 release asset]
|
|
21
|
+
RELEASES = {
|
|
22
|
+
'x86_64-linux-gnu' => ['x86_64-linux-gnu', 'ca7c8aae49e33260d3a2400db16c7737aaf04692d606a843502056b42990f989'],
|
|
23
|
+
'aarch64-linux-gnu' => ['aarch64-linux-gnu', '5e71795b971f32be8f3d7785895f4dceb232f04414f5a067db266432a5b08657'],
|
|
24
|
+
'arm64-darwin' => ['arm64-macos', '2569f4139460fcb301484d37938de91b1c220efd15487ee22a9db554262062fe'],
|
|
25
|
+
'x86_64-darwin' => ['x86_64-macos', '4686806d59abea93866a917c3025a049ef3d7a3240de742ad66bd1a4d9890dc7']
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# @param [String] gemspec path to the chain gem's gemspec
|
|
29
|
+
# @param [String] root the chain gem's root directory
|
|
30
|
+
def initialize(gemspec, root: Dir.pwd)
|
|
31
|
+
@gemspec = gemspec
|
|
32
|
+
@root = root
|
|
33
|
+
define
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def define
|
|
39
|
+
namespace :package do
|
|
40
|
+
desc "Build a gem with bundled curl-impersonate #{VERSION} for the given platform"
|
|
41
|
+
task :native, [:platform] do |_task, args|
|
|
42
|
+
build(args.fetch(:platform))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'Build all supported platform gems with bundled curl-impersonate'
|
|
46
|
+
task :all do
|
|
47
|
+
RELEASES.each_key { |platform| build(platform) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build(platform)
|
|
53
|
+
target, digest = RELEASES.fetch(platform)
|
|
54
|
+
asset = "curl-impersonate-v#{VERSION}.#{target}.tar.gz"
|
|
55
|
+
url = "https://github.com/lexiforest/curl-impersonate/releases/download/v#{VERSION}/#{asset}"
|
|
56
|
+
archive = File.join(@root, 'pkg', 'downloads', asset)
|
|
57
|
+
download(url, archive)
|
|
58
|
+
abort "SHA-256 mismatch for #{archive}; delete it and retry" unless Digest::SHA256.file(archive).hexdigest == digest
|
|
59
|
+
|
|
60
|
+
spec = Gem::Specification.load(File.join(@root, @gemspec)).dup
|
|
61
|
+
Dir.mktmpdir('vista-package') do |stage|
|
|
62
|
+
spec.files.each do |file|
|
|
63
|
+
destination = File.join(stage, file)
|
|
64
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
65
|
+
FileUtils.cp(File.join(@root, file), destination)
|
|
66
|
+
end
|
|
67
|
+
vendor(archive, File.join(stage, 'vendor', 'curl-impersonate'), url, digest)
|
|
68
|
+
|
|
69
|
+
Dir.chdir(stage) do
|
|
70
|
+
spec.platform = Gem::Platform.new(platform)
|
|
71
|
+
spec.files += Dir.glob('vendor/**/*').select { |file| File.file?(file) }
|
|
72
|
+
Gem::Package.build(spec, false, false, File.join(@root, 'pkg', spec.file_name))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def download(url, archive)
|
|
78
|
+
FileUtils.mkdir_p(File.dirname(archive))
|
|
79
|
+
sh 'curl', '--fail', '--location', '--retry', '3', '--output', archive, url unless File.exist?(archive)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def vendor(archive, vendor, url, digest)
|
|
83
|
+
Zlib::GzipReader.open(archive) do |gzip|
|
|
84
|
+
Gem::Package::TarReader.new(gzip) do |tar|
|
|
85
|
+
tar.each do |entry|
|
|
86
|
+
next unless entry.file?
|
|
87
|
+
|
|
88
|
+
relative = vendor_path(entry.full_name)
|
|
89
|
+
next unless relative
|
|
90
|
+
|
|
91
|
+
destination = File.join(vendor, relative)
|
|
92
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
93
|
+
File.binwrite(destination, entry.read)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
File.chmod(0o755, File.join(vendor, 'bin', 'curl-impersonate'))
|
|
98
|
+
File.write(File.join(vendor, 'SOURCE'), <<~SOURCE)
|
|
99
|
+
curl-impersonate #{VERSION}
|
|
100
|
+
Binary: #{url}
|
|
101
|
+
SHA-256: #{digest}
|
|
102
|
+
Source and build instructions: https://github.com/lexiforest/curl-impersonate/tree/v#{VERSION}
|
|
103
|
+
Redistribution notices: licenses/
|
|
104
|
+
SOURCE
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def vendor_path(name)
|
|
108
|
+
if name == 'curl-impersonate'
|
|
109
|
+
'bin/curl-impersonate'
|
|
110
|
+
elsif name.start_with?('LICENSE') && File.basename(name) == name
|
|
111
|
+
"licenses/#{name}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cinebase'
|
|
4
|
+
|
|
5
|
+
module Cinebase
|
|
6
|
+
# Shared support for chains whose sites run on Vista's OCAPI. Not loaded by
|
|
7
|
+
# `require 'cinebase'`; Vista-based gems `require 'cinebase/vista'`.
|
|
8
|
+
module Vista
|
|
9
|
+
class AuthenticationError < StandardError; end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'cinebase/vista/client'
|
|
14
|
+
require 'cinebase/vista/guest_token'
|
data/lib/cinebase.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cinebase
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Croll
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: bundler
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,6 +79,20 @@ dependencies:
|
|
|
65
79
|
- - "~>"
|
|
66
80
|
- !ruby/object:Gem::Version
|
|
67
81
|
version: '0.22'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webmock
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
68
96
|
- !ruby/object:Gem::Dependency
|
|
69
97
|
name: tzinfo
|
|
70
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -116,9 +144,14 @@ files:
|
|
|
116
144
|
- cinebase.gemspec
|
|
117
145
|
- lib/cinebase.rb
|
|
118
146
|
- lib/cinebase/cinema.rb
|
|
147
|
+
- lib/cinebase/festival.rb
|
|
119
148
|
- lib/cinebase/performance.rb
|
|
120
149
|
- lib/cinebase/title_sanitizer.rb
|
|
121
150
|
- lib/cinebase/version.rb
|
|
151
|
+
- lib/cinebase/vista.rb
|
|
152
|
+
- lib/cinebase/vista/client.rb
|
|
153
|
+
- lib/cinebase/vista/guest_token.rb
|
|
154
|
+
- lib/cinebase/vista/package_task.rb
|
|
122
155
|
homepage: http://github.com/andycroll/cinebase
|
|
123
156
|
licenses:
|
|
124
157
|
- MIT
|