odeon_uk 1.1.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +8 -1
- data/README.md +1 -0
- data/Rakefile +13 -9
- data/lib/odeon_uk/cinema.rb +14 -42
- data/lib/odeon_uk/film.rb +23 -5
- data/lib/odeon_uk/internal/film_with_screenings_parser.rb +133 -46
- data/lib/odeon_uk/internal/showtimes_page.rb +36 -0
- data/lib/odeon_uk/internal/title_sanitizer.rb +49 -0
- data/lib/odeon_uk/internal/website.rb +36 -0
- data/lib/odeon_uk/screening.rb +56 -22
- data/lib/odeon_uk/version.rb +2 -2
- data/lib/odeon_uk.rb +4 -2
- data/odeon_uk.gemspec +0 -1
- data/test/fixture_updater.rb +72 -0
- data/test/fixtures/{odeon-bfi-imax.html → cinema/bfi_imax.html} +988 -1130
- data/test/fixtures/{odeon-brighton.html → cinema/brighton.html} +1035 -761
- data/test/fixtures/{odeon-london-leicester-square.html → cinema/leicester_square.html} +470 -766
- data/test/fixtures/showtimes/brighton/film_first.html +92 -0
- data/test/fixtures/showtimes/brighton/film_last.html +100 -0
- data/test/fixtures/showtimes/brighton.html +2071 -0
- data/test/fixtures/showtimes/liverpool_one/film_first_dbox.html +188 -0
- data/test/fixtures/showtimes/manchester/film_first_imax.html +182 -0
- data/test/fixtures/{odeon-sitemap.html → sitemap.html} +572 -444
- data/test/lib/odeon_uk/cinema_test.rb +129 -231
- data/test/lib/odeon_uk/film_test.rb +50 -3
- data/test/lib/odeon_uk/internal/film_with_screenings_parser_test.rb +79 -123
- data/test/lib/odeon_uk/internal/showtimes_page_test.rb +51 -0
- data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +105 -0
- data/test/lib/odeon_uk/internal/website_test.rb +59 -0
- data/test/lib/odeon_uk/screening_test.rb +66 -32
- metadata +32 -53
- data/test/fixtures/brighton-showtimes/about-time.html +0 -175
- data/test/fixtures/brighton-showtimes/autism-friendly-planes.html +0 -75
- data/test/fixtures/brighton-showtimes/bolshoi-spartacus-live.html +0 -67
- data/test/fixtures/brighton-showtimes/cinemagic-echo-planet.html +0 -67
- data/test/fixtures/brighton-showtimes/globe-on-screen-twelfth-night.html +0 -67
- data/test/fixtures/brighton-showtimes/met-opera-eugene-onegin.html +0 -67
- data/test/fixtures/brighton-showtimes/national-theatre-live-frankenstein.html +0 -78
- data/test/fixtures/brighton-showtimes/nt-live-war-horse.html +0 -67
- data/test/fixtures/brighton-showtimes/royal-opera-house-turandot.html +0 -66
- data/test/fixtures/brighton-showtimes/rsc-richard-ii.html +0 -67
- data/test/fixtures/brighton-showtimes/star-trek-into-darkness-2d.html +0 -83
- data/test/fixtures/brighton-showtimes/ukjff-from-cable-street-to-brick-lane.html +0 -67
- data/test/fixtures/manchester-showtimes/thor-the-dark-world.html +0 -300
- data/test/fixtures/odeon-brighton-showtimes.html +0 -1238
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59c9821b04dd9e8262c25557b82d95b51a9fdf7d
|
4
|
+
data.tar.gz: b1950e1710b9a4bcda1dbe85611cb26ad3604c48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc7ef841b0748d993a87b43e77cebf818cefa48698a3cd461bb57178f96dc677b5f0014d488d49857e76512912034a5e7c37092dcf1109ee5eb5fddb6d5a81aa
|
7
|
+
data.tar.gz: 6be57bd2533423a9895ec6f9a4f6aed25c45721886ac244cbed0ab13720f3a31899d045f3d5fd09b936d556d732ece8f5636948b5787565fcb025e28506bc839
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
|
1
|
+
## 2.0.0, _22nd September 2014_
|
2
|
+
|
3
|
+
- add website utility class
|
4
|
+
- break up film parsing into internal classes
|
5
|
+
- Film#at builds films not cinema
|
6
|
+
- fixture update script
|
7
|
+
- title sanitizer class
|
8
|
+
- updated fixtures
|
2
9
|
- Added changelog
|
3
10
|
|
4
11
|
## 1.1.3, _8th Nov 2013_
|
data/README.md
CHANGED
@@ -5,6 +5,7 @@ A simple gem to parse the [Odeon UK website](http://odeon.co.uk) and spit out us
|
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/odeon_uk.png)](http://badge.fury.io/rb/odeon_uk)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/andycroll/odeon_uk.png)](https://codeclimate.com/github/andycroll/odeon_uk)
|
7
7
|
[![Build Status](https://travis-ci.org/andycroll/odeon_uk.png?branch=master)](https://travis-ci.org/andycroll/odeon_uk)
|
8
|
+
[![Inline docs](http://inch-ci.org/github/andycroll/odeon_uk.png)](http://inch-ci.org/github/andycroll/odeon_uk)
|
8
9
|
|
9
10
|
## Installation
|
10
11
|
|
data/Rakefile
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
-
require
|
2
|
+
require 'bundler/gem_tasks'
|
3
3
|
|
4
4
|
require 'rake/testtask'
|
5
5
|
|
6
6
|
Rake::TestTask.new do |t|
|
7
7
|
t.libs << 'lib/odeon_uk'
|
8
|
-
t.test_files = FileList[
|
8
|
+
t.test_files = FileList[
|
9
|
+
'test/lib/odeon_uk/*_test.rb',
|
10
|
+
'test/lib/odeon_uk/internal/*_test.rb'
|
11
|
+
]
|
9
12
|
t.verbose = true
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
# http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
|
16
|
+
task :console do
|
17
|
+
require 'irb'
|
18
|
+
require 'irb/completion'
|
19
|
+
require 'odeon_uk'
|
20
|
+
ARGV.clear
|
21
|
+
IRB.start
|
14
22
|
end
|
15
23
|
|
16
|
-
task :
|
17
|
-
system "gem push odeon_uk-#{OdeonUk::VERSION}"
|
18
|
-
end
|
19
|
-
|
20
|
-
task :default => :test
|
24
|
+
task default: :test
|
data/lib/odeon_uk/cinema.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
module OdeonUk
|
2
|
-
|
3
2
|
# The object representing a cinema on the Odeon UK website
|
4
3
|
class Cinema
|
5
|
-
|
6
4
|
# @return [String] the brand of the cinema
|
7
5
|
attr_reader :brand
|
8
6
|
# @return [Integer] the numeric id of the cinema on the Odeon website
|
@@ -21,16 +19,13 @@ module OdeonUk
|
|
21
19
|
def initialize(id, name, url)
|
22
20
|
@brand = 'Odeon'
|
23
21
|
@id = id.to_i
|
24
|
-
@name = name.gsub('London - ','')
|
25
|
-
@slug = @name.downcase.gsub(/[^0-9a-z ]/,'').gsub(/\s+/, '-')
|
22
|
+
@name = name.gsub('London - ', '').gsub(' - ', ': ')
|
23
|
+
@slug = @name.downcase.gsub(/[^0-9a-z ]/, '').gsub(/\s+/, '-')
|
26
24
|
@url = (url[0] == '/') ? "http://www.odeon.co.uk#{url}" : url
|
27
25
|
end
|
28
26
|
|
29
27
|
# Return basic cinema information for all cinemas
|
30
28
|
# @return [Array<OdeonUk::Cinema>]
|
31
|
-
# @example
|
32
|
-
# OdeonUk::Cinema.all
|
33
|
-
# #=> [<OdeonUk::Cinema brand="Odeon" name="Odeon Tunbridge Wells" slug="odeon-tunbridge-wells" id=23 url="...">, #=> <OdeonUk::Cinema brand="Odeon" name="Odeon Brighton" slug="odeon-brighton" chain_id="71" url="...">, ...]
|
34
29
|
def self.all
|
35
30
|
cinema_links.map do |link|
|
36
31
|
new_from_link link
|
@@ -38,11 +33,12 @@ module OdeonUk
|
|
38
33
|
end
|
39
34
|
|
40
35
|
# Find a single cinema
|
41
|
-
# @param [Integer, String] id the cinema id of the format 71/'71' as used on
|
36
|
+
# @param [Integer, String] id the cinema id of the format 71/'71' as used on
|
37
|
+
# the odeon.co.uk website
|
42
38
|
# @return [OdeonUk::Cinema, nil]
|
43
39
|
# @example
|
44
40
|
# OdeonUk::Cinema.find('71')
|
45
|
-
# #=> <OdeonUk::Cinema brand="Odeon" name="Brighton" slug="brighton"
|
41
|
+
# #=> <OdeonUk::Cinema brand="Odeon" name="Brighton" slug="brighton" ...>
|
46
42
|
def self.find(id)
|
47
43
|
id = id.to_i
|
48
44
|
return nil unless id > 0
|
@@ -55,7 +51,10 @@ module OdeonUk
|
|
55
51
|
# @example
|
56
52
|
# cinema = OdeonUk::Cinema.find('71')
|
57
53
|
# cinema.adr
|
58
|
-
# #=> { street_address: 'Kingswest',
|
54
|
+
# #=> { street_address: 'Kingswest',
|
55
|
+
# locality: 'Brighton',
|
56
|
+
# postal_code: 'BN1 2RE',
|
57
|
+
# country_name: 'United Kingdom' }
|
59
58
|
def adr
|
60
59
|
{
|
61
60
|
street_address: street_address,
|
@@ -68,15 +67,8 @@ module OdeonUk
|
|
68
67
|
|
69
68
|
# Films with showings scheduled at this cinema
|
70
69
|
# @return [Array<OdeonUk::Film>]
|
71
|
-
# @example
|
72
|
-
# cinema = OdeonUk::Cinema.find('71')
|
73
|
-
# cinema.films
|
74
|
-
# #=> [<OdeonUk::Film name="Iron Man 3">, <OdeonUk::Film name="Star Trek Into Darkness">]
|
75
70
|
def films
|
76
|
-
|
77
|
-
parser = OdeonUk::Internal::FilmWithScreeningsParser.new node.to_s
|
78
|
-
OdeonUk::Film.new parser.film_name
|
79
|
-
end.uniq
|
71
|
+
Film.at(id)
|
80
72
|
end
|
81
73
|
|
82
74
|
# The name of the cinema including the brand
|
@@ -99,7 +91,6 @@ module OdeonUk
|
|
99
91
|
address_node.text.match(/\w+(\s\w+){0,}\s+(\w+(\s\w+){0,})/)[2]
|
100
92
|
end
|
101
93
|
|
102
|
-
|
103
94
|
# Post code of the cinema
|
104
95
|
# @return [String]
|
105
96
|
# @example
|
@@ -117,14 +108,7 @@ module OdeonUk
|
|
117
108
|
# cinema.screenings
|
118
109
|
# #=> [<OdeonUk::Screening film_name="Iron Man 3" cinema_name="Brighton" when="..." variant="...">, <OdeonUk::Screening ...>]
|
119
110
|
def screenings
|
120
|
-
|
121
|
-
parser = OdeonUk::Internal::FilmWithScreeningsParser.new node.to_s
|
122
|
-
parser.showings.map do |screening_type, times_urls|
|
123
|
-
times_urls.map do |array|
|
124
|
-
OdeonUk::Screening.new parser.film_name, self.name, array[0], array[1], screening_type
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end.flatten
|
111
|
+
Screening.at(id)
|
128
112
|
end
|
129
113
|
|
130
114
|
# Screenings for particular film
|
@@ -155,7 +139,7 @@ module OdeonUk
|
|
155
139
|
private
|
156
140
|
|
157
141
|
def self.cinema_links
|
158
|
-
links = parsed_sitemap.css('.sitemap
|
142
|
+
links = parsed_sitemap.css('.sitemap a[href*=cinemas]')
|
159
143
|
links.select { |link| link.get_attribute('href').match(/\/\d+\/$/) }
|
160
144
|
end
|
161
145
|
|
@@ -171,7 +155,7 @@ module OdeonUk
|
|
171
155
|
end
|
172
156
|
|
173
157
|
def self.sitemap_response
|
174
|
-
@sitemap_response ||=
|
158
|
+
@sitemap_response ||= OdeonUk::Internal::Website.new.sitemap
|
175
159
|
end
|
176
160
|
|
177
161
|
def address_node
|
@@ -179,23 +163,11 @@ module OdeonUk
|
|
179
163
|
end
|
180
164
|
|
181
165
|
def cinema_response
|
182
|
-
@cinema_response ||=
|
166
|
+
@cinema_response ||= OdeonUk::Internal::Website.new.cinema(@id)
|
183
167
|
end
|
184
168
|
|
185
169
|
def parsed_cinema
|
186
170
|
Nokogiri::HTML(cinema_response)
|
187
171
|
end
|
188
|
-
|
189
|
-
def film_nodes
|
190
|
-
parsed_showtimes.css('.film-detail')
|
191
|
-
end
|
192
|
-
|
193
|
-
def parsed_showtimes
|
194
|
-
Nokogiri::HTML(showtimes_response)
|
195
|
-
end
|
196
|
-
|
197
|
-
def showtimes_response
|
198
|
-
@showtimes_response ||= HTTParty.get("http://www.odeon.co.uk/showtimes/week/#{@id}?siteId=#{@id}")
|
199
|
-
end
|
200
172
|
end
|
201
173
|
end
|
data/lib/odeon_uk/film.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module OdeonUk
|
2
|
-
|
3
2
|
# The object representing a film on the Odeon UK website
|
4
3
|
class Film
|
5
4
|
include Comparable
|
@@ -16,11 +15,20 @@ module OdeonUk
|
|
16
15
|
@slug = name.downcase.gsub(/[^0-9a-z ]/,'').gsub(/\s+/, '-')
|
17
16
|
end
|
18
17
|
|
18
|
+
# All currently listed films showing at a cinema
|
19
|
+
# @param [Integer] cinema_id id of the cinema on the website
|
20
|
+
# @return [Array<OdeonUk::Film>]
|
21
|
+
def self.at(cinema_id)
|
22
|
+
showtimes_page(cinema_id).to_a.map do |html|
|
23
|
+
new(screenings_parser(html).film_name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
# Allows sort on objects
|
20
28
|
# @param [OdeonUk::Film] other another film object
|
21
29
|
# @return [Integer] -1, 0 or 1
|
22
|
-
def <=>
|
23
|
-
|
30
|
+
def <=>(other)
|
31
|
+
slug <=> other.slug
|
24
32
|
end
|
25
33
|
|
26
34
|
# Check an object is the same as another object.
|
@@ -28,7 +36,7 @@ module OdeonUk
|
|
28
36
|
# @return [Boolean] True if both objects are the same exact object, or if
|
29
37
|
# they are of the same type and share an equal slug
|
30
38
|
# @note Guided by http://woss.name/2011/01/20/equality-comparison-and-ordering-in-ruby/
|
31
|
-
def eql?
|
39
|
+
def eql?(other)
|
32
40
|
self.class == other.class && self == other
|
33
41
|
end
|
34
42
|
|
@@ -41,7 +49,17 @@ module OdeonUk
|
|
41
49
|
# @return [Integer] hash of slug
|
42
50
|
# @note Guided by http://woss.name/2011/01/20/equality-comparison-and-ordering-in-ruby/
|
43
51
|
def hash
|
44
|
-
|
52
|
+
slug.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.screenings_parser(html)
|
58
|
+
OdeonUk::Internal::FilmWithScreeningsParser.new(html)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.showtimes_page(cinema_id)
|
62
|
+
OdeonUk::Internal::ShowtimesPage.new(cinema_id)
|
45
63
|
end
|
46
64
|
end
|
47
65
|
end
|
@@ -1,67 +1,154 @@
|
|
1
1
|
module OdeonUk
|
2
|
-
|
3
2
|
# Internal utility classes: Do not use
|
4
3
|
# @api private
|
5
4
|
module Internal
|
6
|
-
|
7
5
|
# Parses a chunk of HTML to derive movie showing data
|
8
6
|
class FilmWithScreeningsParser
|
7
|
+
# CSS selector for a film title inside an individual film HTML
|
8
|
+
NAME_CSS = '.presentation-info h4 a'
|
9
|
+
# CSS selector for a group of showtimes inside an individual film HTML
|
10
|
+
SHOWTIMES_CSS = '.times-all.accordion-group'
|
9
11
|
|
10
|
-
# @param [String]
|
11
|
-
def initialize(
|
12
|
-
@
|
12
|
+
# @param [String] html a chunk of html
|
13
|
+
def initialize(html)
|
14
|
+
@html = html
|
13
15
|
end
|
14
16
|
|
15
17
|
# The film name
|
16
18
|
# @return [String]
|
17
19
|
def film_name
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
@film_name ||= TitleSanitizer.new(raw_film_name).sanitized
|
21
|
+
end
|
22
|
+
|
23
|
+
# array containing hashes of screening attributes
|
24
|
+
# @return [Array<Hash>]
|
25
|
+
def to_a
|
26
|
+
doc.css(SHOWTIMES_CSS).map do |node|
|
27
|
+
dimension_parser(node).to_a.map do |hash|
|
28
|
+
hash.merge(film_name: film_name)
|
29
|
+
end
|
30
|
+
end.flatten
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def doc
|
36
|
+
@doc ||= Nokogiri::HTML(@html)
|
37
|
+
end
|
38
|
+
|
39
|
+
def raw_film_name
|
40
|
+
@raw_film_name ||= doc.css(NAME_CSS).children.first.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def dimension_parser(node)
|
44
|
+
DimensionNodeParser.new(node)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# parses chunk of screenings for a particular screening dimension
|
49
|
+
class DimensionNodeParser
|
50
|
+
# CSS selector for a single screening inside a 'group of showtimes' HTML
|
51
|
+
SCREENING_CSS = '.show'
|
52
|
+
# CSS selector for showing technology for a 'group of showtimes' HTML
|
53
|
+
TECH_CSS = '.tech a'
|
54
|
+
|
55
|
+
# @param [Nokigiri::Node] node a Nokogiri node object
|
56
|
+
def initialize(node)
|
57
|
+
@node = node
|
58
|
+
end
|
59
|
+
|
60
|
+
# array containing hashes of screening attributes for dimension
|
61
|
+
# @return [Array<Hash>]
|
62
|
+
def to_a
|
63
|
+
screening_hashes.map do |hash|
|
64
|
+
hash.merge(
|
65
|
+
dimension: dimension,
|
66
|
+
variant: add_imax(hash[:variant])
|
67
|
+
)
|
37
68
|
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def add_imax(original)
|
74
|
+
imax? ? "#{original} imax".strip : original
|
75
|
+
end
|
76
|
+
|
77
|
+
def dimension
|
78
|
+
tech.match(/[23]d/)[0]
|
79
|
+
end
|
80
|
+
|
81
|
+
def imax?
|
82
|
+
!!tech.match(/imax/)
|
83
|
+
end
|
38
84
|
|
39
|
-
|
40
|
-
|
41
|
-
name = name.gsub /\s+\z/, '' # remove trailing spaces
|
85
|
+
def screening_hashes
|
86
|
+
screening_nodes.map { |node| ScreeningNodeParser.new(node).to_hash }
|
42
87
|
end
|
43
88
|
|
44
|
-
|
89
|
+
def screening_nodes
|
90
|
+
@node.css(SCREENING_CSS)
|
91
|
+
end
|
92
|
+
|
93
|
+
def tech
|
94
|
+
@tech ||= @node.css(TECH_CSS).text.gsub('in ', '').downcase
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# parses a single screening
|
99
|
+
class ScreeningNodeParser
|
100
|
+
# regex for time format
|
101
|
+
TIME_REGEX = %r(\d+/\d+/\d+ \d{2}\:\d{2})
|
102
|
+
# regex for D-Box screenings
|
103
|
+
DBOX_REGEX = /D-Box/
|
104
|
+
|
105
|
+
# @param [Nokigiri::Node] node a Nokogiri node object
|
106
|
+
def initialize(node)
|
107
|
+
@node = node
|
108
|
+
end
|
109
|
+
|
110
|
+
# hashes of screening attributes
|
45
111
|
# @return [Hash]
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@nokogiri_html.css('.times-all.accordion-group').inject({}) do |result, variant_node|
|
54
|
-
variant = variant_node.css('.tech a').text.gsub('in ', '').upcase
|
55
|
-
|
56
|
-
times_url = variant_node.css('.performance-detail').map do |screening_node|
|
57
|
-
[
|
58
|
-
tz.local_to_utc(Time.parse(screening_node['title'].match(/\d+\/\d+\/\d+ \d{2}\:\d{2}/).to_s + ' UTC')),
|
59
|
-
"http://www.odeon.co.uk#{screening_node['href']}"
|
60
|
-
]
|
61
|
-
end
|
112
|
+
def to_hash
|
113
|
+
{
|
114
|
+
booking_url: booking_url,
|
115
|
+
time: utc_time,
|
116
|
+
variant: variant
|
117
|
+
}
|
118
|
+
end
|
62
119
|
|
63
|
-
|
64
|
-
|
120
|
+
private
|
121
|
+
|
122
|
+
def booking_url
|
123
|
+
link_attr('href').to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
def link_attr(attribute)
|
127
|
+
@node.css('a').attribute(attribute)
|
128
|
+
end
|
129
|
+
|
130
|
+
def info
|
131
|
+
@node.css('i').to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
def time
|
135
|
+
Time.parse(time_string)
|
136
|
+
end
|
137
|
+
|
138
|
+
def time_string
|
139
|
+
link_attr('title').to_s.match(TIME_REGEX).to_s + ' UTC'
|
140
|
+
end
|
141
|
+
|
142
|
+
def tz
|
143
|
+
@tz ||= TZInfo::Timezone.get('Europe/London')
|
144
|
+
end
|
145
|
+
|
146
|
+
def utc_time
|
147
|
+
tz.local_to_utc(time)
|
148
|
+
end
|
149
|
+
|
150
|
+
def variant
|
151
|
+
info.match(DBOX_REGEX) ? 'd-box' : ''
|
65
152
|
end
|
66
153
|
end
|
67
154
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module OdeonUk
|
2
|
+
# Internal utility classes: Do not use
|
3
|
+
# @api private
|
4
|
+
module Internal
|
5
|
+
# Parses a chunk of HTML to derive showing data for a single films
|
6
|
+
class ShowtimesPage
|
7
|
+
# css selector for film html chunks
|
8
|
+
FILM_CSS = '.film-detail'
|
9
|
+
|
10
|
+
# @param [Integer] cinema_id cineworld cinema id
|
11
|
+
def initialize(cinema_id)
|
12
|
+
@cinema_id = cinema_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# break up the showtimes page into individual chunks for each film
|
16
|
+
# @return [Array<String>] html chunks for a film and it's screenings
|
17
|
+
def to_a
|
18
|
+
film_nodes.map { |node| node.to_s.gsub(/^\s+/, '') }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def film_nodes
|
24
|
+
@film_nodes ||= showtimes_doc.css(FILM_CSS)
|
25
|
+
end
|
26
|
+
|
27
|
+
def showtimes
|
28
|
+
@showtimes ||= OdeonUk::Internal::Website.new.showtimes(@cinema_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def showtimes_doc
|
32
|
+
@showtimes_doc ||= Nokogiri::HTML(showtimes)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module OdeonUk
|
2
|
+
# Internal utility classes: Do not use
|
3
|
+
# @api private
|
4
|
+
module Internal
|
5
|
+
# Sanitize and standardize film titles
|
6
|
+
class TitleSanitizer
|
7
|
+
# strings and regex to be removed
|
8
|
+
REMOVE = [
|
9
|
+
/\s+[23][dD]/, # dimension
|
10
|
+
'Autism Friendly Screening -', # autism screening
|
11
|
+
/\ACinemagic \d{1,4} \-/, # cinemagic
|
12
|
+
/\(encore.+\)/i, # encore for NT Live
|
13
|
+
/\(live\)/i, # live in brackets
|
14
|
+
'UKJFF -', # UK Jewish festival prefix
|
15
|
+
]
|
16
|
+
|
17
|
+
# regexes and their replacements
|
18
|
+
REPLACE = {
|
19
|
+
/Bolshoi - (.*)/ => 'Bolshoi: ',
|
20
|
+
/Globe On Screen: (.*)/ => 'Globe: ',
|
21
|
+
/Met Opera - (.*)/ => 'Met Opera: ',
|
22
|
+
/National Theatre Live - (.*)/ => 'National Theatre: ',
|
23
|
+
/NT Live - (.*)/ => 'National Theatre: ',
|
24
|
+
/ROH - (.*)/ => 'Royal Opera House: ',
|
25
|
+
/(.*)\- RSC Live \d{1,4}/ => 'Royal Shakespeare Company: '
|
26
|
+
}
|
27
|
+
|
28
|
+
# @param [String] title a film title
|
29
|
+
def initialize(title)
|
30
|
+
@title = title
|
31
|
+
end
|
32
|
+
|
33
|
+
# sanitized and standardized title
|
34
|
+
# @return [String] title
|
35
|
+
def sanitized
|
36
|
+
@sanitzed ||= begin
|
37
|
+
sanitized = @title
|
38
|
+
REMOVE.each do |pattern|
|
39
|
+
sanitized.gsub! pattern, ''
|
40
|
+
end
|
41
|
+
REPLACE.each do |pattern, prefix|
|
42
|
+
sanitized.gsub!(pattern) { |_| prefix + $1 }
|
43
|
+
end
|
44
|
+
sanitized.squeeze(' ').strip
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module OdeonUk
|
4
|
+
# Internal utility classes: Do not use
|
5
|
+
# @api private
|
6
|
+
module Internal
|
7
|
+
# Utility class to make calls to the odeon website
|
8
|
+
class Website
|
9
|
+
# cinema page
|
10
|
+
# @param [Integer] cinema_id website id of the cinema
|
11
|
+
# @return [String] html of the page
|
12
|
+
def cinema(cinema_id)
|
13
|
+
get("cinemas/odeon/#{cinema_id}/")
|
14
|
+
end
|
15
|
+
|
16
|
+
# showtimes page for a single cinema
|
17
|
+
# @param [Integer] cinema_id website id of the cinema
|
18
|
+
# @return [String] html of the page
|
19
|
+
def showtimes(cinema_id)
|
20
|
+
get("showtimes/week/#{cinema_id}/?siteId=#{cinema_id}")
|
21
|
+
end
|
22
|
+
|
23
|
+
# sitemap page
|
24
|
+
# @return [String] html of the page
|
25
|
+
def sitemap
|
26
|
+
get('sitemap/')
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def get(path)
|
32
|
+
URI("http://www.odeon.co.uk/#{path}").read
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|