odeon_uk 3.0.6 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -3
  3. data/CHANGELOG.md +14 -0
  4. data/{LICENSE.txt → COMM-LICENSE} +0 -0
  5. data/LICENSE +617 -0
  6. data/README.md +58 -29
  7. data/Rakefile +7 -21
  8. data/lib/odeon_uk.rb +5 -14
  9. data/lib/odeon_uk/cinema.rb +123 -75
  10. data/lib/odeon_uk/{api/response.rb → internal/api_response.rb} +3 -3
  11. data/lib/odeon_uk/internal/parser/api/film_lookup.rb +34 -0
  12. data/lib/odeon_uk/internal/parser/api/performance_day.rb +92 -0
  13. data/lib/odeon_uk/internal/title_sanitizer.rb +34 -39
  14. data/lib/odeon_uk/performance.rb +87 -0
  15. data/lib/odeon_uk/version.rb +2 -2
  16. data/odeon_uk.gemspec +19 -21
  17. data/rake/fixture_creator.rb +5 -57
  18. data/test/fixtures/api/all_cinemas.plist +0 -0
  19. data/test/fixtures/api/app_init.plist +0 -0
  20. data/test/fixtures/api/film_times/71-14876.plist +0 -0
  21. data/test/fixtures/api/film_times/71-15408.plist +0 -0
  22. data/test/fixtures/api/film_times/71-15684.plist +0 -0
  23. data/test/fixtures/api/film_times/71-15866.plist +0 -0
  24. data/test/fixtures/api/film_times/71-15868.plist +0 -0
  25. data/test/fixtures/api/film_times/71-15961.plist +0 -0
  26. data/test/fixtures/api/film_times/71-15963.plist +0 -0
  27. data/test/fixtures/api/film_times/71-15965.plist +0 -0
  28. data/test/fixtures/api/film_times/71-15967.plist +0 -0
  29. data/test/fixtures/api/film_times/71-15969.plist +0 -0
  30. data/test/fixtures/api/film_times/71-15977.plist +0 -0
  31. data/test/fixtures/api/film_times/71-15980.plist +0 -0
  32. data/test/fixtures/api/film_times/71-16037.plist +0 -0
  33. data/test/fixtures/api/film_times/71-16136.plist +0 -0
  34. data/test/fixtures/api/film_times/71-16142.plist +0 -0
  35. data/test/fixtures/api/film_times/71-16143.plist +0 -0
  36. data/test/fixtures/api/film_times/71-16187.plist +0 -0
  37. data/test/fixtures/api/film_times/71-16369.plist +0 -0
  38. data/test/fixtures/api/film_times/71-16370.plist +0 -0
  39. data/test/fixtures/api/film_times/71-16374.plist +0 -0
  40. data/test/fixtures/api/film_times/71-16386.plist +0 -0
  41. data/test/fixtures/api/film_times/71-16387.plist +0 -0
  42. data/test/fixtures/api/film_times/71-16388.plist +0 -0
  43. data/test/fixtures/api/film_times/71-16438.plist +0 -0
  44. data/test/fixtures/api/film_times/71-16454.plist +0 -0
  45. data/test/fixtures/api/film_times/71-16457.plist +0 -0
  46. data/test/fixtures/api/film_times/71-16477.plist +0 -0
  47. data/test/fixtures/api/film_times/71-16478.plist +0 -0
  48. data/test/fixtures/api/film_times/71-16480.plist +0 -0
  49. data/test/fixtures/api/film_times/71-16482.plist +0 -0
  50. data/test/fixtures/api/film_times/71-16489.plist +0 -0
  51. data/test/lib/odeon_uk/cinema_test.rb +154 -373
  52. data/test/lib/odeon_uk/{api/response_test.rb → internal/api_response_test.rb} +7 -4
  53. data/test/lib/odeon_uk/performance_test.rb +149 -0
  54. data/test/lib/odeon_uk/version_test.rb +3 -5
  55. data/test/support/api_fixtures_helper.rb +9 -3
  56. data/test/support/fake_api_response.rb +17 -0
  57. data/test/test_helper.rb +9 -3
  58. metadata +89 -145
  59. data/.rdoc_options +0 -16
  60. data/lib/odeon_uk/api/cinema.rb +0 -67
  61. data/lib/odeon_uk/api/screenings.rb +0 -103
  62. data/lib/odeon_uk/configuration.rb +0 -21
  63. data/lib/odeon_uk/html/cinema.rb +0 -101
  64. data/lib/odeon_uk/html/parser/film_with_screenings.rb +0 -160
  65. data/lib/odeon_uk/html/screenings.rb +0 -38
  66. data/lib/odeon_uk/html/website.rb +0 -38
  67. data/lib/odeon_uk/screening.rb +0 -71
  68. data/test/fixtures/api/film_times/71-100747.plist +0 -0
  69. data/test/fixtures/api/film_times/71-100750.plist +0 -0
  70. data/test/fixtures/api/film_times/71-100790.plist +0 -0
  71. data/test/fixtures/api/film_times/71-14646.plist +0 -0
  72. data/test/fixtures/api/film_times/71-14725.plist +0 -0
  73. data/test/fixtures/api/film_times/71-15086.plist +0 -0
  74. data/test/fixtures/api/film_times/71-15096.plist +0 -0
  75. data/test/fixtures/api/film_times/71-15122.plist +0 -0
  76. data/test/fixtures/api/film_times/71-15130.plist +0 -0
  77. data/test/fixtures/api/film_times/71-15141.plist +0 -0
  78. data/test/fixtures/api/film_times/71-15142.plist +0 -0
  79. data/test/fixtures/api/film_times/71-15143.plist +0 -0
  80. data/test/fixtures/api/film_times/71-15144.plist +0 -0
  81. data/test/fixtures/api/film_times/71-15145.plist +0 -0
  82. data/test/fixtures/api/film_times/71-15170.plist +0 -0
  83. data/test/fixtures/api/film_times/71-15172.plist +0 -0
  84. data/test/fixtures/api/film_times/71-15177.plist +0 -0
  85. data/test/fixtures/api/film_times/71-15179.plist +0 -0
  86. data/test/fixtures/api/film_times/71-15182.plist +0 -0
  87. data/test/fixtures/api/film_times/71-15286.plist +0 -0
  88. data/test/fixtures/api/film_times/71-15360.plist +0 -0
  89. data/test/fixtures/api/film_times/71-15586.plist +0 -0
  90. data/test/fixtures/api/film_times/71-15700.plist +0 -0
  91. data/test/fixtures/api/film_times/71-15718.plist +0 -0
  92. data/test/fixtures/api/film_times/71-15768.plist +0 -0
  93. data/test/fixtures/api/film_times/71-15788.plist +0 -0
  94. data/test/fixtures/api/film_times/71-15793.plist +0 -0
  95. data/test/fixtures/api/film_times/71-15794.plist +0 -0
  96. data/test/fixtures/api/film_times/71-15795.plist +0 -0
  97. data/test/fixtures/api/film_times/71-15796.plist +0 -0
  98. data/test/fixtures/api/film_times/71-15799.plist +0 -0
  99. data/test/fixtures/api/film_times/71-15802.plist +0 -0
  100. data/test/fixtures/api/film_times/71-15814.plist +0 -0
  101. data/test/fixtures/api/film_times/71-15817.plist +0 -0
  102. data/test/fixtures/api/film_times/71-15840.plist +0 -0
  103. data/test/fixtures/html/cinema/105.html +0 -2622
  104. data/test/fixtures/html/cinema/211.html +0 -3241
  105. data/test/fixtures/html/cinema/71.html +0 -3448
  106. data/test/fixtures/html/showtimes/11-imax.html +0 -170
  107. data/test/fixtures/html/showtimes/171-d-box.html +0 -203
  108. data/test/fixtures/html/showtimes/71-0.html +0 -59
  109. data/test/fixtures/html/showtimes/71.html +0 -2395
  110. data/test/fixtures/html/sitemap.html +0 -1303
  111. data/test/lib/odeon_uk/api/cinema_test.rb +0 -165
  112. data/test/lib/odeon_uk/api/screenings_test.rb +0 -58
  113. data/test/lib/odeon_uk/configuration_test.rb +0 -29
  114. data/test/lib/odeon_uk/html/cinema_test.rb +0 -149
  115. data/test/lib/odeon_uk/html/parser/film_with_screenings_test.rb +0 -97
  116. data/test/lib/odeon_uk/html/screenings_test.rb +0 -44
  117. data/test/lib/odeon_uk/html/website_test.rb +0 -67
  118. data/test/lib/odeon_uk/screening_test.rb +0 -92
  119. data/test/support/website_fixtures_helper.rb +0 -30
data/.rdoc_options DELETED
@@ -1,16 +0,0 @@
1
- --- !ruby/object:RDoc::Options
2
- encoding: UTF-8
3
- static_path: []
4
- rdoc_include:
5
- - .
6
- charset: UTF-8
7
- exclude:
8
- hyperlink_all: false
9
- line_numbers: false
10
- main_page:
11
- markup: tomdoc
12
- show_hash: false
13
- tab_width: 8
14
- title:
15
- visibility: :protected
16
- webcvs:
@@ -1,67 +0,0 @@
1
- module OdeonUk
2
- # Internal utility classes: Do not use
3
- # @api private
4
- module Api
5
- # The object representing a cinema on the Odeon UK website
6
- class Cinema
7
- # @return [Integer] the numeric id of the cinema via the API
8
- attr_reader :id
9
-
10
- # @param [Integer, String] id cinema id
11
- # @return [OdeonUk::Cinema]
12
- def initialize(id)
13
- @id = id.to_i
14
- end
15
-
16
- # Return basic cinema information for all cinemas
17
- # @return [Array<OdeonUk::Cinema>]
18
- def self.ids
19
- cinemas_hash.keys.map(&:to_i)
20
- end
21
-
22
- # The locality (town) of the cinema
23
- # @return [String]
24
- def locality
25
- cinema_hash['siteAddress2']
26
- end
27
-
28
- # The name of the cinema
29
- # @return [String]
30
- def name
31
- cinema_hash['siteName']
32
- end
33
-
34
- # The url of the cinema
35
- # @return [Nil]
36
- def url
37
- "http://www.odeon.co.uk/cinemas/#{urlized_name}/#{@id}/"
38
- end
39
-
40
- # Post code of the cinema
41
- # @return [String]
42
- def postal_code
43
- cinema_hash['sitePostcode']
44
- end
45
-
46
- # The street adress of the cinema
47
- # @return [String]
48
- def street_address
49
- cinema_hash['siteAddress1']
50
- end
51
-
52
- private
53
-
54
- def self.cinemas_hash
55
- @@cinemas_hash ||= Api::Response.new.all_cinemas['sites']
56
- end
57
-
58
- def cinema_hash
59
- @cinema_hash ||= self.class.cinemas_hash[id.to_s]
60
- end
61
-
62
- def urlized_name
63
- name.downcase.gsub(/[^a-z0-9]/, '_')
64
- end
65
- end
66
- end
67
- end
@@ -1,103 +0,0 @@
1
- module OdeonUk
2
- # Internal utility classes: Do not use
3
- # @api private
4
- module Api
5
- # The object representing a single screening of a film via the Odeon UK api
6
- class Screenings
7
- # All currently listed screenings at a cinema
8
- # @param [Integer] cinema_id id of the cinema
9
- # @return [Array<Hash>]
10
- def self.at(cinema_id)
11
- film_ids(cinema_id).flat_map do |film_id|
12
- times_response = response.film_times(cinema_id, film_id)
13
-
14
- times_response.flat_map do |day|
15
- date = day['date']
16
- screenings = day['attributes']
17
-
18
- screenings.flat_map do |screening|
19
- performances = screening['showtimes']
20
-
21
- performances.flat_map do |p|
22
- {
23
- dimension: screening['attribute'].include?('3D') ? '3d' : '2d',
24
- film_name: films_map(cinema_id)[film_id],
25
- time: TimeParser.new(date, p[0]['performanceTime']).to_utc,
26
- variant: VariantParser.new(screening['attribute']).to_a
27
- }
28
- end
29
- end
30
- end
31
- end
32
- end
33
-
34
- private
35
-
36
- def self.app_init
37
- @@app_init ||= Response.new.app_init
38
- end
39
-
40
- def self.film_ids(cinema_id)
41
- films_map(cinema_id).keys
42
- end
43
-
44
- def self.films_map(cinema_id)
45
- films_at(cinema_id).each_with_object({}) do |f, result|
46
- result[f['filmMasterId']] = f['title']
47
- end
48
- end
49
-
50
- def self.films_at(cinema_id)
51
- app_init['films'].select { |f| f['sites'].include?(cinema_id) }
52
- end
53
-
54
- def self.response
55
- @@response ||= Response.new
56
- end
57
- end
58
-
59
- TimeParser = Struct.new(:date, :time) do
60
- def to_utc
61
- tz.local_to_utc(parsed_time)
62
- end
63
-
64
- private
65
-
66
- def early_morning_screening?
67
- !!time.match(/\A0/)
68
- end
69
-
70
- def parsed_date
71
- case date
72
- when 'Today' then Time.now.strftime '%Y-%m-%d'
73
- when 'Tomorrow' then (Time.now + 60 * 60 * 24).strftime '%Y-%m-%d'
74
- else date.gsub('.', '').match(/\d+\s\w+\z/)[0]
75
- end
76
- end
77
-
78
- def parsed_time
79
- parsed_time = Time.parse("#{parsed_date} #{time} UTC")
80
- # Odeon deliberately mislabel their 00:01 screenings as the day before
81
- early_morning_screening? ? parsed_time + 60 * 60 * 24 : parsed_time
82
- end
83
-
84
- def tz
85
- @tz ||= TZInfo::Timezone.get('Europe/London')
86
- end
87
- end
88
-
89
- VariantParser = Struct.new(:text) do
90
- TRANSLATOR = {
91
- 'Culture' => 'arts',
92
- 'Kids' => 'kids',
93
- 'IMAX' => 'imax',
94
- 'Newbies' => 'baby',
95
- 'Silver Cinema' => 'senior'
96
- }
97
-
98
- def to_a
99
- TRANSLATOR.select { |k, _| text.include?(k) }.values.uniq
100
- end
101
- end
102
- end
103
- end
@@ -1,21 +0,0 @@
1
- module OdeonUk
2
- class << self
3
- attr_accessor :configuration
4
- end
5
-
6
- def self.configuration
7
- @configuration ||= Configuration.new
8
- end
9
-
10
- def self.configure
11
- yield(configuration)
12
- end
13
-
14
- class Configuration
15
- attr_accessor :method
16
-
17
- def initialize
18
- @method = :html
19
- end
20
- end
21
- end
@@ -1,101 +0,0 @@
1
- module OdeonUk
2
- # Internal utility classes: Do not use
3
- # @api private
4
- module Html
5
- # The object representing a cinema on the Odeon UK website
6
- class Cinema
7
- ADDRESS_REGEX = '.gethere .span4 .description'
8
- ALL_LINKS = '.sitemap a[href*=cinemas]'
9
-
10
- # @return [Integer] the numeric id of the cinema on the Odeon website
11
- attr_reader :id
12
-
13
- # @param [Integer, String] id cinema id
14
- # @return [OdeonUk::Cinema]
15
- def initialize(id)
16
- @id = id.to_i
17
- end
18
-
19
- # Return basic cinema information for all cinemas
20
- # @return [Array<OdeonUk::Cinema>]
21
- def self.ids
22
- cinema_links.map { |link| id_from(link) }
23
- end
24
-
25
- # The locality (town) of the cinema
26
- # @return [String]
27
- def locality
28
- return unless address_node
29
- address_node.text.match(/\w+(\s\w+){0,}\s+(\w+(\s\w+){0,})/)[2]
30
- end
31
-
32
- # The name of the cinema
33
- # @return [String]
34
- def name
35
- cinema_link_doc.children.first.to_s
36
- end
37
-
38
- # The web url of the cinema
39
- # @return [String]
40
- def url
41
- @url ||= begin
42
- link = cinema_link_doc.get_attribute('href')
43
- (link[0] == '/') ? "http://www.odeon.co.uk#{link}" : link
44
- end
45
- end
46
-
47
- # Post code of the cinema
48
- # @return [String]
49
- def postal_code
50
- return unless address_node
51
- address_node.text.match(/[A-Z]{1,2}\d{1,2}[A-Z]?\s\d{1,2}[A-Z]{1,2}/)[0]
52
- end
53
-
54
- # The street adress of the cinema
55
- # @return [String]
56
- def street_address
57
- return unless address_node
58
- address_node.text.match(/\A\s+(\w+(\s\w+){0,})/)[1]
59
- end
60
-
61
- private
62
-
63
- def self.id_from(link)
64
- link.get_attribute('href').match(/\/(\d+)\/$/)[1].to_i
65
- end
66
-
67
- def self.cinema_links
68
- @@cinema_links ||= begin
69
- links = sitemap_doc.css(ALL_LINKS)
70
- links.select { |link| link.get_attribute('href').match(/\/\d+\/$/) }
71
- end
72
- end
73
-
74
- def self.sitemap_doc
75
- @@sitemap_doc ||= Nokogiri::HTML(sitemap_response)
76
- end
77
-
78
- def self.sitemap_response
79
- @@sitemap_response ||= Html::Website.new.sitemap
80
- end
81
-
82
- def address_node
83
- cinema_doc.css(ADDRESS_REGEX)[0]
84
- end
85
-
86
- def cinema_response
87
- @cinema_response ||= Html::Website.new.cinema(id)
88
- end
89
-
90
- def cinema_doc
91
- @cinema_doc ||= Nokogiri::HTML(cinema_response)
92
- end
93
-
94
- def cinema_link_doc
95
- self.class.cinema_links.select do |link|
96
- link.get_attribute('href').include?("/#{id}/")
97
- end.first
98
- end
99
- end
100
- end
101
- end
@@ -1,160 +0,0 @@
1
- module OdeonUk
2
- # Internal utility classes: Do not use
3
- # @api private
4
- module Html
5
- # Parses HTML with Nokogiri
6
- module Parser
7
- # Parses a chunk of HTML to derive movie showing data
8
- class FilmWithScreenings
9
- # CSS selector for a film title inside an individual film HTML
10
- NAME_CSS = '.presentation-info h4 a'
11
- # CSS selector for a group of showtimes inside an individual film HTML
12
- SHOWTIMES_CSS = '.times-all.accordion-group'
13
-
14
- # @param [String] html a chunk of html
15
- def initialize(html)
16
- @html = html
17
- end
18
-
19
- # The film name
20
- # @return [String]
21
- def film_name
22
- @film_name ||= Internal::TitleSanitizer.new(raw_film_name).sanitized
23
- end
24
-
25
- # array containing hashes of screening attributes
26
- # @return [Array<Hash>]
27
- def to_a
28
- doc.css(SHOWTIMES_CSS).map do |node|
29
- dimension_parser(node).to_a.map do |hash|
30
- hash.merge(film_name: film_name)
31
- end
32
- end.flatten
33
- end
34
-
35
- private
36
-
37
- def doc
38
- @doc ||= Nokogiri::HTML(@html)
39
- end
40
-
41
- def raw_film_name
42
- @raw_film_name ||= doc.css(NAME_CSS).children.first.to_s
43
- end
44
-
45
- def dimension_parser(node)
46
- DimensionNodeParser.new(node)
47
- end
48
- end
49
-
50
- # parses chunk of screenings for a particular screening dimension
51
- class DimensionNodeParser
52
- # CSS selector for a single screening inside a 'group of showtimes' HTML
53
- SCREENING_CSS = '.show'
54
- # CSS selector for showing technology for a 'group of showtimes' HTML
55
- TECH_CSS = '.tech a'
56
-
57
- # @param [Nokigiri::Node] node a Nokogiri node object
58
- def initialize(node)
59
- @node = node
60
- end
61
-
62
- # array containing hashes of screening attributes for dimension
63
- # @return [Array<Hash>]
64
- def to_a
65
- screening_hashes.map do |hash|
66
- hash.merge(dimension: dimension,
67
- variant: add_imax(hash[:variant]))
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
- dimension_from_tech || '2d'
79
- end
80
-
81
- def dimension_from_tech
82
- tech.match(/[23]d/i) { |data| data[0] }
83
- end
84
-
85
- def imax?
86
- !!tech.match(/imax/)
87
- end
88
-
89
- def screening_hashes
90
- screening_nodes.map { |node| ScreeningNodeParser.new(node).to_hash }
91
- end
92
-
93
- def screening_nodes
94
- @node.css(SCREENING_CSS)
95
- end
96
-
97
- def tech
98
- @tech ||= @node.css(TECH_CSS).text.gsub('in ', '').downcase
99
- end
100
- end
101
-
102
- # parses a single screening
103
- class ScreeningNodeParser
104
- # regex for time format
105
- TIME_REGEX = %r(\d+/\d+/\d+ \d{2}\:\d{2})
106
- # regex for D-Box screenings
107
- DBOX_REGEX = /D-Box/
108
-
109
- # @param [Nokigiri::Node] node a Nokogiri node object
110
- def initialize(node)
111
- @node = node
112
- end
113
-
114
- # hashes of screening attributes
115
- # @return [Hash]
116
- def to_hash
117
- {
118
- booking_url: booking_url,
119
- time: utc_time,
120
- variant: variant
121
- }
122
- end
123
-
124
- private
125
-
126
- def booking_url
127
- link_attr('href').to_s
128
- end
129
-
130
- def link_attr(attribute)
131
- @node.css('a').attribute(attribute)
132
- end
133
-
134
- def info
135
- @node.css('i').to_s
136
- end
137
-
138
- def time
139
- Time.parse(time_string)
140
- end
141
-
142
- def time_string
143
- link_attr('title').to_s.match(TIME_REGEX).to_s + ' UTC'
144
- end
145
-
146
- def tz
147
- @tz ||= TZInfo::Timezone.get('Europe/London')
148
- end
149
-
150
- def utc_time
151
- tz.local_to_utc(time)
152
- end
153
-
154
- def variant
155
- info.match(DBOX_REGEX) ? 'd-box' : ''
156
- end
157
- end
158
- end
159
- end
160
- end