vue_uk 1.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +21 -0
  3. data/.github/workflows/release.yml +19 -0
  4. data/.gitignore +18 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +10 -0
  7. data/COMM-LICENSE +22 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +617 -0
  10. data/README.md +141 -0
  11. data/Rakefile +35 -0
  12. data/lib/vue_uk/cinema.rb +115 -0
  13. data/lib/vue_uk/internal/api_response.rb +51 -0
  14. data/lib/vue_uk/internal/http.rb +42 -0
  15. data/lib/vue_uk/internal/parser/api/showtime.rb +73 -0
  16. data/lib/vue_uk/internal/title_sanitizer.rb +31 -0
  17. data/lib/vue_uk/internal/website.rb +22 -0
  18. data/lib/vue_uk/performance.rb +70 -0
  19. data/lib/vue_uk/version.rb +6 -0
  20. data/lib/vue_uk.rb +19 -0
  21. data/test/fixtures/api/cinemas.json +42 -0
  22. data/test/fixtures/api/films-10051.json +103 -0
  23. data/test/fixtures/website/cinema-10051.html +8 -0
  24. data/test/lib/vue_uk/cinema_test.rb +110 -0
  25. data/test/lib/vue_uk/internal/api_response_test.rb +68 -0
  26. data/test/lib/vue_uk/internal/fixture_shape_test.rb +18 -0
  27. data/test/lib/vue_uk/internal/parser/api/showtime_test.rb +44 -0
  28. data/test/lib/vue_uk/internal/title_sanitizer_test.rb +30 -0
  29. data/test/lib/vue_uk/internal/website_test.rb +22 -0
  30. data/test/lib/vue_uk/performance_test.rb +108 -0
  31. data/test/lib/vue_uk/version_test.rb +7 -0
  32. data/test/live/api_shape_test.rb +17 -0
  33. data/test/live/integration_test.rb +25 -0
  34. data/test/live/live_helper.rb +9 -0
  35. data/test/support/api_shapes.rb +38 -0
  36. data/test/support/fakes.rb +31 -0
  37. data/test/support/fixtures_helper.rb +21 -0
  38. data/test/test_helper.rb +20 -0
  39. data/vue_uk.gemspec +30 -0
  40. metadata +195 -0
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # VueUk
2
+
3
+ A simple gem to pull cinemas and performances from the API behind the [Vue cinemas website](https://www.myvue.com) and spit out useful formatted info.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'vue_uk'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install vue_uk
18
+
19
+ ## Usage
20
+
21
+ The gem conforms to the API set down in the `cinebase` gem, [andycroll/cinebase](https://github.com/andycroll/cinebase), which provides a lot of useful base vocabulary and repetitive code for this series of cinema focussed gems.
22
+
23
+ Performance titles are sanitized as much as possible, removing language versions, years, screening dates and festival prefixes.
24
+
25
+ No token or setup is needed. The API answers uncached requests only within the
26
+ guest session the website starts for every visitor, so the gem starts one with
27
+ `POST /api/microservice/auth/token` and sends its cookies, starting a fresh one
28
+ once if the API rejects it.
29
+
30
+ ### Cinema
31
+
32
+ Only UK cinemas are returned; Vue's euro-priced cinemas are left out. The API
33
+ has no addresses, so the first call to an address method reads the cinema's page
34
+ on myvue.com.
35
+
36
+ ``` ruby
37
+ VueUk::Cinema.all
38
+ #=> [<VueUk::Cinema ...>, <VueUk::Cinema ...>, ...]
39
+
40
+ cinema = VueUk::Cinema.new(10051)
41
+ #=> <VueUk::Cinema ...>
42
+
43
+ cinema.adr
44
+ #=> {
45
+ street_address: 'The Viaduct',
46
+ extended_address: 'Hyndburn Road',
47
+ locality: 'Accrington',
48
+ region: nil,
49
+ postal_code: 'BB5 1QF',
50
+ country_name: 'United Kingdom'
51
+ }
52
+
53
+ cinema.brand
54
+ #=> 'Vue'
55
+
56
+ cinema.full_name
57
+ #=> 'Vue Accrington'
58
+
59
+ cinema.id
60
+ #=> '10051'
61
+
62
+ cinema.url
63
+ #=> 'https://www.myvue.com/cinema/accrington'
64
+ ```
65
+
66
+ ### Performances
67
+
68
+ `Performance.at` returns performances for an inclusive range of showing dates,
69
+ defaulting to today in London and the following 13 days. It makes one request
70
+ for all of the cinema's advertised dates and keeps those in the range.
71
+
72
+ ``` ruby
73
+ VueUk::Performance.at(10051, from: Date.today, to: Date.today + 2)
74
+ #=> [<VueUk::Performance ...>, <VueUk::Performance ...>, ...]
75
+
76
+ performance = VueUk::Performance.at(10051).first
77
+ #=> <VueUk::Performance ...>
78
+
79
+ performance.film_name
80
+ #=> 'Clayface'
81
+
82
+ performance.language
83
+ #=> 'English' # Vue tags English audio too
84
+
85
+ performance.film_year
86
+ #=> nil # from the title, e.g. 2026 for 'Street Fighter (2026)'
87
+
88
+ performance.dimension
89
+ #=> '2d'
90
+
91
+ performance.variant
92
+ #=> ['audio_described']
93
+
94
+ performance.subtitled?
95
+ #=> false
96
+
97
+ performance.festival
98
+ #=> nil # e.g. #<data Cinebase::Festival name="CFF", year=2026>
99
+
100
+ performance.starting_at
101
+ #=> 2026-10-23 16:00:00 UTC
102
+
103
+ performance.booking_url
104
+ #=> 'https://www.myvue.com/book-tickets/summary/10051/HO00020288/139988'
105
+
106
+ performance.cinema_name
107
+ #=> 'Accrington'
108
+
109
+ performance.cinema_id
110
+ #=> '10051'
111
+ ```
112
+
113
+ Variants are `arts`, `audio_described`, `autism_friendly`, `kids` and
114
+ `subtitled`.
115
+
116
+ ## Tests
117
+
118
+ ```sh
119
+ bundle exec rake # unit tests against fixtures
120
+ bundle exec rake live # live myvue.com
121
+ ```
122
+
123
+ Fixtures in `test/fixtures` are hand-written: invented films and addresses in
124
+ the shape of Vue's responses, never recorded output. `test/support/api_shapes.rb`
125
+ describes the fields the gem reads. The unit suite checks the fixtures against
126
+ it and the live suite checks real responses, held only in memory. When the API
127
+ changes, update the shape first, then the fixtures.
128
+
129
+ ## Releasing
130
+
131
+ Bump the version, merge, then run the **Release Gem** workflow in GitHub
132
+ Actions. It runs the tests, tags the version and publishes the gem through
133
+ RubyGems trusted publishing.
134
+
135
+ ## Contributing
136
+
137
+ 1. Fork it
138
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
139
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
140
+ 4. Push to the branch (`git push origin my-new-feature`)
141
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib/vue_uk'
8
+ t.test_files = FileList[
9
+ 'test/lib/vue_uk/*_test.rb',
10
+ 'test/lib/vue_uk/internal/*_test.rb',
11
+ 'test/lib/vue_uk/internal/parser/api/*_test.rb'
12
+ ]
13
+ t.verbose = true
14
+ end
15
+
16
+ Rake::TestTask.new do |t|
17
+ t.libs << 'lib/vue_uk'
18
+ t.name = :live
19
+ t.test_files = FileList[
20
+ 'test/live/*_test.rb'
21
+ ]
22
+ t.verbose = true
23
+ end
24
+
25
+ # http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
26
+ desc 'run gem console'
27
+ task :console do
28
+ require 'irb'
29
+ require 'irb/completion'
30
+ require 'vue_uk'
31
+ ARGV.clear
32
+ IRB.start
33
+ end
34
+
35
+ task default: :test
@@ -0,0 +1,115 @@
1
+ module VueUk
2
+ # The object representing a cinema on the Vue website
3
+ class Cinema < Cinebase::Cinema
4
+ # @!attribute [r] id
5
+ # @return [String] the Vue id of the cinema, e.g. '10051'
6
+
7
+ # UK postcode, the last line of an address
8
+ POSTCODE = /\A[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}\z/i.freeze
9
+
10
+ # A county and postcode on one line, e.g. 'Essex CO4 5UP'
11
+ REGION_AND_POSTCODE = /\A(.+?)\s+([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2})\z/i.freeze
12
+
13
+ # Map coordinates some cinemas list in place of a postcode
14
+ COORDINATES = /\A-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?\z/.freeze
15
+
16
+ # Constructor
17
+ # @param [Integer, String] id the cinema id of the format 10051/'10051'
18
+ # @return [VueUk::Cinema]
19
+ def initialize(id)
20
+ @id = id.to_s
21
+ end
22
+
23
+ # Return basic cinema information for all UK cinemas
24
+ # @return [Array<VueUk::Cinema>]
25
+ # @example
26
+ # VueUk::Cinema.all
27
+ # #=> [<VueUk::Cinema>, <VueUk::Cinema>, ...]
28
+ def self.all
29
+ cinemas_hash.keys.map { |id| new(id) }
30
+ end
31
+
32
+ # @!method address
33
+ # Address of the cinema
34
+ # @return [Hash] of different address parts
35
+ # @see #adr
36
+
37
+ # Address of the cinema, read from its page on the Vue website
38
+ # @return [Hash] contains :street_address, :extended_address,
39
+ # :locality, :region, :postal_code, :country_name
40
+ # @example
41
+ # cinema = VueUk::Cinema.new(10051)
42
+ # cinema.adr
43
+ # #=> {
44
+ # street_address: 'The Viaduct',
45
+ # extended_address: 'Hyndburn Road',
46
+ # locality: 'Accrington',
47
+ # region: nil,
48
+ # postal_code: 'BB5 1QF',
49
+ # country_name: 'United Kingdom'
50
+ # }
51
+ # @note Uses method naming as at http://microformats.org/wiki/adr
52
+ def adr
53
+ lines = address_lines
54
+ if lines.last&.match?(POSTCODE)
55
+ postal_code = lines.pop
56
+ elsif (match = lines.last&.match(REGION_AND_POSTCODE))
57
+ lines.pop
58
+ region, postal_code = match.captures
59
+ end
60
+ street_address = lines.shift
61
+ locality = lines.pop
62
+ {
63
+ street_address: street_address,
64
+ extended_address: lines.empty? ? nil : lines.join(', '),
65
+ locality: locality,
66
+ region: region,
67
+ postal_code: postal_code,
68
+ country_name: 'United Kingdom'.freeze
69
+ }
70
+ end
71
+
72
+ # Brand of the cinema
73
+ # @return [String] which will always be 'Vue'
74
+ def brand
75
+ 'Vue'.freeze
76
+ end
77
+
78
+ # The name of the cinema
79
+ # @return [String]
80
+ # @example
81
+ # VueUk::Cinema.new(10051).name
82
+ # #=> 'Accrington'
83
+ def name
84
+ cinema_hash['cinemaName'].to_s
85
+ end
86
+
87
+ # The url of the cinema on the Vue website
88
+ # @return [String]
89
+ def url
90
+ cinema_hash['whatsOnUrl'].to_s.delete_suffix('/whats-on')
91
+ end
92
+
93
+ # @api private
94
+ # Leaves out Vue's euro-priced cinemas outside the UK.
95
+ def self.cinemas_hash
96
+ @cinemas_hash ||= VueUk::Internal::ApiResponse.new.cinemas
97
+ .select { |cinema| cinema['venueCurrency'] == 'GBP' }
98
+ .to_h { |cinema| [cinema.fetch('cinemaId'), cinema] }
99
+ end
100
+ private_class_method :cinemas_hash
101
+
102
+ private
103
+
104
+ def address_lines
105
+ @page ||= VueUk::Internal::Website.new.cinema(cinema_hash.fetch('whatsOnUrl'))
106
+ @page.dig('cinemaAddress', 'value').to_s.split(/\r?\n/)
107
+ .map { |line| line.strip.sub(/,\s*(UK|United Kingdom)\z/i, '') }
108
+ .reject { |line| line.empty? || line.match?(COORDINATES) }
109
+ end
110
+
111
+ def cinema_hash
112
+ @cinema_hash ||= self.class.send(:cinemas_hash).fetch(id, {})
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module VueUk
6
+ # Internal utility classes: Do not use
7
+ # @api private
8
+ module Internal
9
+ # Fetches cinema and showing data from the API behind myvue.com
10
+ class ApiResponse
11
+ include Http
12
+
13
+ BASE_URL = "https://www.myvue.com/api/microservice/"
14
+ # The query the website itself sends; embargo level 3 is public listings
15
+ FILM_QUERY = {
16
+ "minEmbargoLevel" => 3, "includesSession" => true, "includeSessionAttributes" => true
17
+ }.freeze
18
+
19
+ # @return [Array<Hash>] cinemas
20
+ def cinemas
21
+ fetch("showings/cinemas").fetch("result").flat_map { |group| group.fetch("cinemas") }
22
+ end
23
+
24
+ # @param [String] cinema_id e.g. '10051'
25
+ # @return [Array<Hash>] films with their sessions on every advertised date
26
+ def films(cinema_id)
27
+ fetch("showings/cinemas/#{cinema_id}/films", FILM_QUERY).fetch("result")
28
+ end
29
+
30
+ private
31
+
32
+ # Uncached responses need the guest session the website starts for
33
+ # every visitor; start a fresh one once if it is rejected.
34
+ def fetch(path, query = {})
35
+ url = BASE_URL + path
36
+ url += "?#{URI.encode_www_form(query)}" unless query.empty?
37
+ retried = false
38
+ begin
39
+ @session ||= post_for_cookies("#{BASE_URL}auth/token", accept: "application/json")
40
+ JSON.parse(get(url, accept: "application/json", cookie: @session))
41
+ rescue Unauthorized
42
+ raise if retried
43
+
44
+ retried = true
45
+ @session = nil
46
+ retry
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+
5
+ module VueUk
6
+ # Internal utility classes: Do not use
7
+ # @api private
8
+ module Internal
9
+ # Requests to myvue.com
10
+ module Http
11
+ class Unauthorized < StandardError; end
12
+
13
+ private
14
+
15
+ def get(url, accept:, cookie: nil)
16
+ request = Net::HTTP::Get.new(URI(url))
17
+ request["Cookie"] = cookie if cookie
18
+ perform(request, accept: accept).body
19
+ end
20
+
21
+ # @return [String] the response's cookies as a Cookie header value
22
+ def post_for_cookies(url, accept:)
23
+ response = perform(Net::HTTP::Post.new(URI(url)), accept: accept)
24
+ response.get_fields("Set-Cookie").to_a.map { |cookie| cookie.split(";").first }.join("; ")
25
+ end
26
+
27
+ def perform(request, accept:)
28
+ request["Accept"] = accept
29
+ uri = request.uri
30
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
31
+ open_timeout: 10, read_timeout: 30) do |http|
32
+ http.request(request)
33
+ end
34
+ # Omit response bodies from errors, and never follow redirects.
35
+ raise Unauthorized, "Vue HTTP error 401" if response.is_a?(Net::HTTPUnauthorized)
36
+ raise "Vue HTTP error #{response.code}" unless response.is_a?(Net::HTTPSuccess)
37
+
38
+ response
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module VueUk
6
+ # @api private
7
+ module Internal
8
+ module Parser
9
+ module Api
10
+ # Parses a single Vue session into performance attributes
11
+ class Showtime
12
+ BOOKING_HOST = "https://www.myvue.com"
13
+
14
+ # Vue attribute names
15
+ VARIANTS = {
16
+ "Audio Description (AD)" => "audio_described",
17
+ "Autism Friendly" => "autism_friendly",
18
+ "Big Screen Events" => "arts",
19
+ "Big Screen Music" => "arts",
20
+ "Big Screen Musicals" => "arts",
21
+ "Big Shorts" => "kids",
22
+ "Event" => "arts",
23
+ "Mighty Mornings" => "kids",
24
+ "Open Captioned" => "subtitled"
25
+ }.freeze
26
+
27
+ # Vue tags English audio too, unlike the other chains
28
+ LANGUAGES = (Cinebase::TitleSanitizer::LANGUAGES + ["English"]).freeze
29
+
30
+ # @param [Hash] session one session
31
+ # @param [Hash] film the film the session shows
32
+ def initialize(session, film:)
33
+ @session = session
34
+ @film = film
35
+ end
36
+
37
+ # @return [Hash] options for VueUk::Performance.new
38
+ def to_hash
39
+ {
40
+ booking_url: booking_url,
41
+ dimension: attribute_names.any? { |name| name.match?(/\b3D\b/i) } ? "3d" : "2d",
42
+ festival: Cinebase::Festival.from_title(title, year: starting_at.year),
43
+ film_name: TitleSanitizer.new(title).sanitized,
44
+ film_year: Cinebase::TitleSanitizer.film_year(title),
45
+ language: attribute_names.find { |name| LANGUAGES.include?(name) },
46
+ starting_at: starting_at,
47
+ variant: attribute_names.filter_map { |name| VARIANTS[name] }.uniq
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def attribute_names
54
+ @attribute_names ||= @session.fetch("attributes", []).map { |attribute| attribute["name"].to_s.strip }
55
+ end
56
+
57
+ def booking_url
58
+ path = @session["bookingUrl"]
59
+ URI.join(BOOKING_HOST, path).to_s if path
60
+ end
61
+
62
+ def starting_at
63
+ Time.iso8601(@session.fetch("showTimeWithTimeZone")).utc
64
+ end
65
+
66
+ def title
67
+ @film.fetch("filmTitle")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ module VueUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # Sanitize and standardize film titles
6
+ class TitleSanitizer < Cinebase::TitleSanitizer
7
+ # @!method initialize(title)
8
+ # Constructor
9
+ # @param [String] title a film title
10
+ # @return [VueUk::Internal::TitleSanitizer]
11
+
12
+ # @!method sanitized
13
+ # sanitized and standardized title
14
+ # @return [String] sanitised title
15
+
16
+ private
17
+
18
+ # strings and regex to be removed
19
+ def remove
20
+ [
21
+ /\s*\(\d{1,2} [A-Z][a-z]+ \d{4}\)/ # screening date
22
+ ] + COMMON_REMOVE
23
+ end
24
+
25
+ # regexes and their replacements
26
+ def replace
27
+ COMMON_REPLACE
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module VueUk
6
+ # Internal utility classes: Do not use
7
+ # @api private
8
+ module Internal
9
+ # Reads cinema details the API doesn't give from a cinema's page data
10
+ class Website
11
+ include Http
12
+
13
+ # @param [String] url a cinema's what's on page
14
+ # @return [Hash] the page's cinema details, e.g. its cinemaAddress
15
+ def cinema(url)
16
+ html = get(url, accept: "text/html")
17
+ script = html[%r{<script id="__NEXT_DATA__"[^>]*>(.*?)</script>}m, 1].to_s
18
+ JSON.parse(script).dig("props", "pageProps", "layoutData", "sitecore", "context", "cinema") || {}
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ module VueUk
2
+ # A single screening of a film on the Vue website
3
+ class Performance < Cinebase::Performance
4
+ # @!attribute [r] booking_url
5
+ # @return [String] the booking URL on the cinema website
6
+ # @!attribute [r] cinema_name
7
+ # @return [String] the cinema name
8
+ # @!attribute [r] cinema_id
9
+ # @return [String] the cinema id
10
+ # @!attribute [r] dimension
11
+ # @return [String] 2d or 3d
12
+ # @!attribute [r] film_name
13
+ # @return [String] the film name
14
+
15
+ # @!method initialize(options)
16
+ # @param [Hash] options options hash
17
+ # @option options [String] :booking_url (nil) buying url for the screening
18
+ # @option options [String] :cinema_name name of the cinema
19
+ # @option options [String] :cinema_id website id of the cinema
20
+ # @option options [String] :dimension ('2d') dimension of the screening
21
+ # @option options [Cinebase::Festival] :festival (nil) festival edition
22
+ # @option options [String] :film_name name of the film
23
+ # @option options [Integer] :film_year (nil) year from the film's title
24
+ # @option options [String] :language (nil) audio language, e.g. 'English'
25
+ # @option options [Time] :starting_at listed start time of the performance
26
+
27
+ # Performances within an inclusive range of showing dates, from one request
28
+ # for all of the cinema's advertised dates.
29
+ # @param [Integer, String] cinema_id id of the cinema
30
+ # @param [Date] from first date (defaults to today in London)
31
+ # @param [Date] to last date (defaults to 13 days after from)
32
+ # @return [Array<VueUk::Performance>]
33
+ def self.at(
34
+ cinema_id,
35
+ from: TZInfo::Timezone.get('Europe/London').now.to_date,
36
+ to: from + 13
37
+ )
38
+ raise ArgumentError, 'to must not precede from' if to < from
39
+
40
+ cinema = Cinema.new(cinema_id)
41
+ VueUk::Internal::ApiResponse.new.films(cinema.id).flat_map do |film|
42
+ sessions_between(film, from, to).map do |session|
43
+ showtime = VueUk::Internal::Parser::Api::Showtime.new(session, film: film)
44
+ new(showtime.to_hash.merge(cinema_id: cinema.id, cinema_name: cinema.name))
45
+ end
46
+ end
47
+ end
48
+
49
+ # @!method showing_on
50
+ # The date of the screening
51
+ # @return [Date]
52
+
53
+ # @!method starting_at
54
+ # UTC time of the screening
55
+ # @return [Time]
56
+
57
+ # @!method variant
58
+ # The kinds of screening (arts, audio_described, kids, subtitled ...)
59
+ # @return <Array[String]>
60
+
61
+ # private
62
+
63
+ def self.sessions_between(film, from, to)
64
+ film.fetch('showingGroups', [])
65
+ .select { |group| (from..to).cover?(Date.iso8601(group.fetch('date')[0, 10])) }
66
+ .flat_map { |group| group.fetch('sessions', []) }
67
+ end
68
+ private_class_method :sessions_between
69
+ end
70
+ end
@@ -0,0 +1,6 @@
1
+ # Ruby interface for https://www.myvue.com
2
+ # @version 1.0.0
3
+ module VueUk
4
+ # Gem version
5
+ VERSION = '1.0.0'.freeze
6
+ end
data/lib/vue_uk.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ require 'cinebase'
4
+
5
+ require_relative './vue_uk/version'
6
+
7
+ require_relative './vue_uk/internal/http'
8
+ require_relative './vue_uk/internal/api_response'
9
+ require_relative './vue_uk/internal/website'
10
+ require_relative './vue_uk/internal/title_sanitizer'
11
+
12
+ require_relative './vue_uk/internal/parser/api/showtime'
13
+
14
+ require_relative './vue_uk/cinema'
15
+ require_relative './vue_uk/performance'
16
+
17
+ # vue_uk gem
18
+ module VueUk
19
+ end
@@ -0,0 +1,42 @@
1
+ {
2
+ "result": [
3
+ {
4
+ "alpha": "A",
5
+ "cinemas": [
6
+ {
7
+ "cinemaId": "10051",
8
+ "cinemaName": "Accrington",
9
+ "fullName": "Accrington - Example Viaduct",
10
+ "venueCurrency": "GBP",
11
+ "whatsOnUrl": "https://www.myvue.com/cinema/accrington/whats-on"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "alpha": "B",
17
+ "cinemas": [
18
+ {
19
+ "cinemaId": "10015",
20
+ "cinemaName": "Birmingham (Star City)",
21
+ "fullName": "Birmingham - Example Park",
22
+ "venueCurrency": "GBP",
23
+ "whatsOnUrl": "https://www.myvue.com/cinema/birmingham-star-city/whats-on"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "alpha": "E",
29
+ "cinemas": [
30
+ {
31
+ "cinemaId": "90001",
32
+ "cinemaName": "Example Euro",
33
+ "fullName": "Example Euro - Elsewhere",
34
+ "venueCurrency": "EUR",
35
+ "whatsOnUrl": "https://www.myvue.com/cinema/example-euro/whats-on"
36
+ }
37
+ ]
38
+ }
39
+ ],
40
+ "responseCode": 0,
41
+ "errorMessage": null
42
+ }