odeon_uk 3.0.6 → 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.
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