picturehouse_uk 4.0.0 → 5.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.
- checksums.yaml +5 -5
 - data/.github/workflows/ci.yml +26 -0
 - data/.github/workflows/release.yml +26 -0
 - data/.gitignore +1 -0
 - data/.ruby-version +1 -0
 - data/CHANGELOG.md +25 -0
 - data/Rakefile +2 -2
 - data/lib/picturehouse_uk/cinema.rb +30 -14
 - data/lib/picturehouse_uk/internal/api.rb +42 -0
 - data/lib/picturehouse_uk/internal/parser/address.rb +21 -6
 - data/lib/picturehouse_uk/internal/parser/screenings.rb +85 -133
 - data/lib/picturehouse_uk/internal/website.rb +10 -12
 - data/lib/picturehouse_uk/version.rb +2 -2
 - data/lib/picturehouse_uk.rb +1 -0
 - data/picturehouse_uk.gemspec +2 -3
 - data/rake/fixture_creator.rb +21 -1
 - data/test/fixtures/cinemas.html +4262 -0
 - data/test/fixtures/duke-of-york-s-picturehouse/cinema.html +6187 -0
 - data/test/fixtures/duke-of-york-s-picturehouse/get_movies.json +120552 -0
 - data/test/fixtures/duke-of-york-s-picturehouse/information.html +3725 -0
 - data/test/fixtures/duke-s-at-komedia/cinema.html +6138 -0
 - data/test/fixtures/duke-s-at-komedia/get_movies.json +112 -0
 - data/test/fixtures/duke-s-at-komedia/information.html +3690 -0
 - data/test/fixtures/home.html +6235 -555
 - data/test/fixtures/phoenix-picturehouse/cinema.html +6089 -0
 - data/test/fixtures/phoenix-picturehouse/get_movies.json +120552 -0
 - data/test/fixtures/phoenix-picturehouse/information.html +3630 -0
 - data/test/lib/picturehouse_uk/cinema_test.rb +34 -34
 - data/test/lib/picturehouse_uk/internal/api_test.rb +92 -0
 - data/test/lib/picturehouse_uk/internal/parser/address_test.rb +8 -8
 - data/test/lib/picturehouse_uk/internal/parser/screenings_test.rb +99 -45
 - data/test/lib/picturehouse_uk/internal/title_sanitizer_test.rb +48 -48
 - data/test/lib/picturehouse_uk/internal/website_test.rb +12 -31
 - data/test/lib/picturehouse_uk/performance_test.rb +63 -23
 - data/test/lib/picturehouse_uk/version_test.rb +1 -1
 - data/test/live/integration_test.rb +8 -8
 - data/test/support/fake_api.rb +16 -0
 - data/test/support/fake_website.rb +6 -6
 - data/test/test_helper.rb +3 -2
 - metadata +34 -50
 - data/.travis.yml +0 -8
 - data/test/fixtures/Duke_Of_Yorks/cinema.html +0 -3408
 - data/test/fixtures/Duke_Of_Yorks/info.html +0 -556
 - data/test/fixtures/Duke_Of_Yorks/whats_on.html +0 -3159
 - data/test/fixtures/Dukes_At_Komedia/cinema.html +0 -4764
 - data/test/fixtures/Dukes_At_Komedia/info.html +0 -526
 - data/test/fixtures/Dukes_At_Komedia/whats_on.html +0 -4429
 - data/test/fixtures/National_Media_Museum/cinema.html +0 -9200
 - data/test/fixtures/National_Media_Museum/info.html +0 -606
 - data/test/fixtures/National_Media_Museum/whats_on.html +0 -8850
 - data/test/fixtures/Phoenix_Picturehouse/cinema.html +0 -8274
 - data/test/fixtures/Phoenix_Picturehouse/info.html +0 -542
 - data/test/fixtures/Phoenix_Picturehouse/whats_on.html +0 -7986
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 5c4c71062363f2efdabda5696692051f79c7915f250367ff8c16536e058c821b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: a76bf492b267b7ae89cbbe829b4269dea9ac0207add6b41fe47685d1df76fbb9
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: b1722850d1b2f161725a961f420b8122c404161050c794b02d93a8f735c5d7053745c2eb35de8d0200e5cc20cd2525024ed94a4cc63357fe2157fc9e149fdc11
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: d5225d4815b31cd31d072820f55e257e72cc415e619c8cbb3eaa6b5c7609d81b624107554d367036164f07da58d83bc5656d5e0ad42df154d955af02411d06fd
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            name: CI
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            on:
         
     | 
| 
      
 4 
     | 
    
         
            +
              push:
         
     | 
| 
      
 5 
     | 
    
         
            +
                branches: [ main ]
         
     | 
| 
      
 6 
     | 
    
         
            +
              pull_request:
         
     | 
| 
      
 7 
     | 
    
         
            +
                branches: [ main ]
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            jobs:
         
     | 
| 
      
 10 
     | 
    
         
            +
              test:
         
     | 
| 
      
 11 
     | 
    
         
            +
                runs-on: ubuntu-latest
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                steps:
         
     | 
| 
      
 14 
     | 
    
         
            +
                - uses: actions/checkout@v4
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                - name: Set up Ruby
         
     | 
| 
      
 17 
     | 
    
         
            +
                  uses: ruby/setup-ruby@v1
         
     | 
| 
      
 18 
     | 
    
         
            +
                  with:
         
     | 
| 
      
 19 
     | 
    
         
            +
                    ruby-version: '3.4'
         
     | 
| 
      
 20 
     | 
    
         
            +
                    bundler-cache: true
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                - name: Run tests
         
     | 
| 
      
 23 
     | 
    
         
            +
                  run: bundle exec rake
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                - name: Run live tests
         
     | 
| 
      
 26 
     | 
    
         
            +
                  run: bundle exec rake live
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            name: Release Gem
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            on:
         
     | 
| 
      
 4 
     | 
    
         
            +
              workflow_dispatch:
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            jobs:
         
     | 
| 
      
 7 
     | 
    
         
            +
              release:
         
     | 
| 
      
 8 
     | 
    
         
            +
                runs-on: ubuntu-latest
         
     | 
| 
      
 9 
     | 
    
         
            +
                permissions:
         
     | 
| 
      
 10 
     | 
    
         
            +
                  id-token: write
         
     | 
| 
      
 11 
     | 
    
         
            +
                  contents: write
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                steps:
         
     | 
| 
      
 14 
     | 
    
         
            +
                - uses: actions/checkout@v4
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                - name: Set up Ruby
         
     | 
| 
      
 17 
     | 
    
         
            +
                  uses: ruby/setup-ruby@v1
         
     | 
| 
      
 18 
     | 
    
         
            +
                  with:
         
     | 
| 
      
 19 
     | 
    
         
            +
                    ruby-version: '3.4'
         
     | 
| 
      
 20 
     | 
    
         
            +
                    bundler-cache: true
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                - name: Run tests
         
     | 
| 
      
 23 
     | 
    
         
            +
                  run: bundle exec rake test
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                - name: Release gem
         
     | 
| 
      
 26 
     | 
    
         
            +
                  uses: rubygems/release-gem@v1
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/.ruby-version
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            3.4.7
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -2,6 +2,31 @@ 
     | 
|
| 
       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.0.0] - 2025-10-31
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 8 
     | 
    
         
            +
            - JSON API client for fetching performance data from `/api/get-movies-ajax` endpoint
         
     | 
| 
      
 9 
     | 
    
         
            +
            - `PicturehouseUk::Internal::Api` for making API requests
         
     | 
| 
      
 10 
     | 
    
         
            +
            - `PicturehouseUk::Internal::Parser::Screenings` for parsing JSON responses (replaced old HTML parser)
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Comprehensive tests for JSON API functionality
         
     | 
| 
      
 12 
     | 
    
         
            +
            - Sample JSON fixtures for testing
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 15 
     | 
    
         
            +
            - `Performance.at()` now uses JSON API instead of HTML parsing
         
     | 
| 
      
 16 
     | 
    
         
            +
            - Improved variant detection (arts, baby, kids, senior, imax)
         
     | 
| 
      
 17 
     | 
    
         
            +
            - Booking URLs now use Vista format: `https://web.picturehouses.com/order/showtimes/{cinema_id}-{session_id}/seats`
         
     | 
| 
      
 18 
     | 
    
         
            +
            - Enhanced 3D film detection from both title and attributes
         
     | 
| 
      
 19 
     | 
    
         
            +
            - Increased test coverage to 98.65%
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            ### Removed
         
     | 
| 
      
 22 
     | 
    
         
            +
            - Old HTML parser `Parser::Screenings` (completely replaced with JSON API version)
         
     | 
| 
      
 23 
     | 
    
         
            +
            - Test file for old HTML parser
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ### Technical Details
         
     | 
| 
      
 26 
     | 
    
         
            +
            - The Picturehouse website now dynamically populates screening data via AJAX calls to their JSON API
         
     | 
| 
      
 27 
     | 
    
         
            +
            - The `#show_all_date_list` div is populated client-side with data from the API endpoint
         
     | 
| 
      
 28 
     | 
    
         
            +
            - HTML parsing approach replaced with direct API consumption for better reliability
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
       5 
30 
     | 
    
         
             
            ## 4.0.0 - 2016-02-10
         
     | 
| 
       6 
31 
     | 
    
         | 
| 
       7 
32 
     | 
    
         
             
            The cinebase standardisation release.
         
     | 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -37,8 +37,8 @@ task :fixtures do 
     | 
|
| 
       37 
37 
     | 
    
         
             
              require_relative 'rake/fixture_creator'
         
     | 
| 
       38 
38 
     | 
    
         | 
| 
       39 
39 
     | 
    
         
             
              FixtureCreator.new.home
         
     | 
| 
       40 
     | 
    
         
            -
               
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 40 
     | 
    
         
            +
              FixtureCreator.new.cinemas
         
     | 
| 
      
 41 
     | 
    
         
            +
              %w(duke-of-york-s-picturehouse duke-s-at-komedia phoenix-picturehouse).each do |cinema_id|
         
     | 
| 
       42 
42 
     | 
    
         
             
                FixtureCreator.new.cinema(cinema_id)
         
     | 
| 
       43 
43 
     | 
    
         
             
              end
         
     | 
| 
       44 
44 
     | 
    
         
             
            end
         
     | 
| 
         @@ -2,9 +2,9 @@ module PicturehouseUk 
     | 
|
| 
       2 
2 
     | 
    
         
             
              # The object representing a cinema on the Picturehouse UK website
         
     | 
| 
       3 
3 
     | 
    
         
             
              class Cinema < Cinebase::Cinema
         
     | 
| 
       4 
4 
     | 
    
         
             
                # address css
         
     | 
| 
       5 
     | 
    
         
            -
                ADDRESS_CSS = '. 
     | 
| 
       6 
     | 
    
         
            -
                # cinema link css
         
     | 
| 
       7 
     | 
    
         
            -
                CINEMA_LINKS_CSS = ' 
     | 
| 
      
 5 
     | 
    
         
            +
                ADDRESS_CSS = '.cinemaAdrass:not(.openingTime)'.freeze
         
     | 
| 
      
 6 
     | 
    
         
            +
                # cinema link css on the /cinema page
         
     | 
| 
      
 7 
     | 
    
         
            +
                CINEMA_LINKS_CSS = 'a[href*="/cinema/"]'.freeze
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
                # @!attribute [r] id
         
     | 
| 
       10 
10 
     | 
    
         
             
                #   @return [Integer] the numeric id of the cinema on the Cineworld website
         
     | 
| 
         @@ -147,15 +147,15 @@ module PicturehouseUk 
     | 
|
| 
       147 
147 
     | 
    
         
             
                private
         
     | 
| 
       148 
148 
     | 
    
         | 
| 
       149 
149 
     | 
    
         
             
                def self.cinema_links
         
     | 
| 
       150 
     | 
    
         
            -
                   
     | 
| 
      
 150 
     | 
    
         
            +
                  cinemas_doc.css(CINEMA_LINKS_CSS)
         
     | 
| 
       151 
151 
     | 
    
         
             
                end
         
     | 
| 
       152 
152 
     | 
    
         
             
                private_class_method :cinema_links
         
     | 
| 
       153 
153 
     | 
    
         | 
| 
       154 
     | 
    
         
            -
                def self. 
     | 
| 
       155 
     | 
    
         
            -
                  @ 
     | 
| 
       156 
     | 
    
         
            -
                    Nokogiri::HTML(PicturehouseUk::Internal::Website.new. 
     | 
| 
      
 154 
     | 
    
         
            +
                def self.cinemas_doc
         
     | 
| 
      
 155 
     | 
    
         
            +
                  @cinemas_doc ||=
         
     | 
| 
      
 156 
     | 
    
         
            +
                    Nokogiri::HTML(PicturehouseUk::Internal::Website.new.cinemas)
         
     | 
| 
       157 
157 
     | 
    
         
             
                end
         
     | 
| 
       158 
     | 
    
         
            -
                private_class_method : 
     | 
| 
      
 158 
     | 
    
         
            +
                private_class_method :cinemas_doc
         
     | 
| 
       159 
159 
     | 
    
         | 
| 
       160 
160 
     | 
    
         
             
                def address_node
         
     | 
| 
       161 
161 
     | 
    
         
             
                  @address_node ||= info_doc.css(ADDRESS_CSS)
         
     | 
| 
         @@ -163,11 +163,11 @@ module PicturehouseUk 
     | 
|
| 
       163 
163 
     | 
    
         | 
| 
       164 
164 
     | 
    
         
             
                def info_doc
         
     | 
| 
       165 
165 
     | 
    
         
             
                  @info_doc ||=
         
     | 
| 
       166 
     | 
    
         
            -
                    Nokogiri::HTML(PicturehouseUk::Internal::Website.new. 
     | 
| 
      
 166 
     | 
    
         
            +
                    Nokogiri::HTML(PicturehouseUk::Internal::Website.new.information(id))
         
     | 
| 
       167 
167 
     | 
    
         
             
                end
         
     | 
| 
       168 
168 
     | 
    
         | 
| 
       169 
169 
     | 
    
         
             
                # @api private
         
     | 
| 
       170 
     | 
    
         
            -
                # Utility class to parse the links  
     | 
| 
      
 170 
     | 
    
         
            +
                # Utility class to parse the links from the cinemas page
         
     | 
| 
       171 
171 
     | 
    
         
             
                class ListParser
         
     | 
| 
       172 
172 
     | 
    
         
             
                  def initialize(nodes)
         
     | 
| 
       173 
173 
     | 
    
         
             
                    @nodes = nodes
         
     | 
| 
         @@ -175,22 +175,38 @@ module PicturehouseUk 
     | 
|
| 
       175 
175 
     | 
    
         | 
| 
       176 
176 
     | 
    
         
             
                  def to_hash
         
     | 
| 
       177 
177 
     | 
    
         
             
                    @nodes.each_with_object({}) do |node, result|
         
     | 
| 
       178 
     | 
    
         
            -
                       
     | 
| 
      
 178 
     | 
    
         
            +
                      cinema_id = id(node)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      cinema_name = name(node)
         
     | 
| 
      
 180 
     | 
    
         
            +
                      # Skip links that don't have valid cinema data
         
     | 
| 
      
 181 
     | 
    
         
            +
                      next unless cinema_id && cinema_name
         
     | 
| 
      
 182 
     | 
    
         
            +
                      # Skip duplicate entries
         
     | 
| 
      
 183 
     | 
    
         
            +
                      next if result.key?(cinema_id)
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                      result[cinema_id] = { name: cinema_name, url: url(node) }
         
     | 
| 
       179 
186 
     | 
    
         
             
                    end
         
     | 
| 
       180 
187 
     | 
    
         
             
                  end
         
     | 
| 
       181 
188 
     | 
    
         | 
| 
       182 
189 
     | 
    
         
             
                  private
         
     | 
| 
       183 
190 
     | 
    
         | 
| 
       184 
191 
     | 
    
         
             
                  def id(node)
         
     | 
| 
       185 
     | 
    
         
            -
                    url(node) 
     | 
| 
      
 192 
     | 
    
         
            +
                    href = url(node)
         
     | 
| 
      
 193 
     | 
    
         
            +
                    return nil unless href
         
     | 
| 
      
 194 
     | 
    
         
            +
                    match = href.match(%r{/cinema/([^/?#]+)})
         
     | 
| 
      
 195 
     | 
    
         
            +
                    match ? match[1] : nil
         
     | 
| 
       186 
196 
     | 
    
         
             
                  end
         
     | 
| 
       187 
197 
     | 
    
         | 
| 
       188 
198 
     | 
    
         
             
                  def name(node)
         
     | 
| 
       189 
     | 
    
         
            -
                     
     | 
| 
      
 199 
     | 
    
         
            +
                    # Cinema name is in a <p> tag within the link
         
     | 
| 
      
 200 
     | 
    
         
            +
                    p_tag = node.css('p').first
         
     | 
| 
      
 201 
     | 
    
         
            +
                    return nil unless p_tag
         
     | 
| 
      
 202 
     | 
    
         
            +
                    # Get text and remove the location span
         
     | 
| 
      
 203 
     | 
    
         
            +
                    p_tag.children.find { |child| child.text? }&.text&.strip
         
     | 
| 
       190 
204 
     | 
    
         
             
                  end
         
     | 
| 
       191 
205 
     | 
    
         | 
| 
       192 
206 
     | 
    
         
             
                  def url(node)
         
     | 
| 
       193 
     | 
    
         
            -
                    node.get_attribute(' 
     | 
| 
      
 207 
     | 
    
         
            +
                    href = node.get_attribute('href')
         
     | 
| 
      
 208 
     | 
    
         
            +
                    # Convert relative URLs to absolute if needed
         
     | 
| 
      
 209 
     | 
    
         
            +
                    href&.start_with?('http') ? href : "https://www.picturehouses.com#{href}"
         
     | 
| 
       194 
210 
     | 
    
         
             
                  end
         
     | 
| 
       195 
211 
     | 
    
         
             
                end
         
     | 
| 
       196 
212 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'net/http'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'openssl'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module PicturehouseUk
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @api private
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Internal
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Fetches JSON data from the Picturehouse API
         
     | 
| 
      
 9 
     | 
    
         
            +
                class Api
         
     | 
| 
      
 10 
     | 
    
         
            +
                  API_BASE = 'https://www.picturehouses.com'.freeze
         
     | 
| 
      
 11 
     | 
    
         
            +
                  API_ENDPOINT = '/api/get-movies-ajax'.freeze
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  # Fetch movie data for a cinema
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param cinema_id [String] the cinema ID
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @param date [String] the date in YYYY-MM-DD format, or 'show_all_dates'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # @return [Hash] parsed JSON response
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def get_movies(cinema_id, date = 'show_all_dates')
         
     | 
| 
      
 18 
     | 
    
         
            +
                    uri = URI("#{API_BASE}#{API_ENDPOINT}")
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    http = Net::HTTP.new(uri.host, uri.port)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    http.use_ssl = true
         
     | 
| 
      
 22 
     | 
    
         
            +
                    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    request = Net::HTTP::Post.new(uri.path)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    request.set_form_data(
         
     | 
| 
      
 26 
     | 
    
         
            +
                      'start_date' => date,
         
     | 
| 
      
 27 
     | 
    
         
            +
                      'cinema_id' => cinema_id,
         
     | 
| 
      
 28 
     | 
    
         
            +
                      'filters' => []
         
     | 
| 
      
 29 
     | 
    
         
            +
                    )
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    response = http.request(request)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    return {} unless response.is_a?(Net::HTTPSuccess)
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    JSON.parse(response.body)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 37 
     | 
    
         
            +
                    warn "Failed to fetch movies for cinema #{cinema_id}: #{e.message}"
         
     | 
| 
      
 38 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -15,7 +15,7 @@ module PicturehouseUk 
     | 
|
| 
       15 
15 
     | 
    
         
             
                    # @note Uses the address naming from http://microformats.org/wiki/adr
         
     | 
| 
       16 
16 
     | 
    
         
             
                    def address
         
     | 
| 
       17 
17 
     | 
    
         
             
                      {
         
     | 
| 
       18 
     | 
    
         
            -
                        street_address:    
     | 
| 
      
 18 
     | 
    
         
            +
                        street_address:   address_lines[0],
         
     | 
| 
       19 
19 
     | 
    
         
             
                        extended_address: extended_address,
         
     | 
| 
       20 
20 
     | 
    
         
             
                        locality:         town,
         
     | 
| 
       21 
21 
     | 
    
         
             
                        region:           region,
         
     | 
| 
         @@ -27,23 +27,38 @@ module PicturehouseUk 
     | 
|
| 
       27 
27 
     | 
    
         
             
                    private
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
                    def array
         
     | 
| 
       30 
     | 
    
         
            -
                      @array ||=  
     | 
| 
      
 30 
     | 
    
         
            +
                      @array ||= begin
         
     | 
| 
      
 31 
     | 
    
         
            +
                        # Split on <br> tags first, then strip all HTML tags from each part
         
     | 
| 
      
 32 
     | 
    
         
            +
                        @html.split(/<br\s*\/?>/).map do |part|
         
     | 
| 
      
 33 
     | 
    
         
            +
                          # Strip all HTML tags and clean up whitespace
         
     | 
| 
      
 34 
     | 
    
         
            +
                          Nokogiri::HTML::DocumentFragment.parse(part).text.strip
         
     | 
| 
      
 35 
     | 
    
         
            +
                        end.reject(&:empty?)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      end
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    # Skip the first element (cinema name) and return address lines
         
     | 
| 
      
 40 
     | 
    
         
            +
                    def address_lines
         
     | 
| 
      
 41 
     | 
    
         
            +
                      @address_lines ||= array[1..-1] || []
         
     | 
| 
       31 
42 
     | 
    
         
             
                    end
         
     | 
| 
       32 
43 
     | 
    
         | 
| 
       33 
44 
     | 
    
         
             
                    def extended_address
         
     | 
| 
       34 
     | 
    
         
            -
                       
     | 
| 
      
 45 
     | 
    
         
            +
                      address_lines.length > 4 ? address_lines[1] : nil
         
     | 
| 
       35 
46 
     | 
    
         
             
                    end
         
     | 
| 
       36 
47 
     | 
    
         | 
| 
       37 
48 
     | 
    
         
             
                    def postal_code
         
     | 
| 
       38 
     | 
    
         
            -
                       
     | 
| 
      
 49 
     | 
    
         
            +
                      address_lines[-1]
         
     | 
| 
       39 
50 
     | 
    
         
             
                    end
         
     | 
| 
       40 
51 
     | 
    
         | 
| 
       41 
52 
     | 
    
         
             
                    def region
         
     | 
| 
       42 
     | 
    
         
            -
                       
     | 
| 
      
 53 
     | 
    
         
            +
                      # If there are 4+ address lines (street, town, region, postcode),
         
     | 
| 
      
 54 
     | 
    
         
            +
                      # the region is at -2, otherwise nil
         
     | 
| 
      
 55 
     | 
    
         
            +
                      address_lines.length >= 4 ? address_lines[-2] : nil
         
     | 
| 
       43 
56 
     | 
    
         
             
                    end
         
     | 
| 
       44 
57 
     | 
    
         | 
| 
       45 
58 
     | 
    
         
             
                    def town
         
     | 
| 
       46 
     | 
    
         
            -
                       
     | 
| 
      
 59 
     | 
    
         
            +
                      # Town is always the second-to-last item (before postcode)
         
     | 
| 
      
 60 
     | 
    
         
            +
                      # unless there's a region, then it's third-to-last
         
     | 
| 
      
 61 
     | 
    
         
            +
                      @town ||= address_lines.length >= 4 ? address_lines[-3] : address_lines[-2]
         
     | 
| 
       47 
62 
     | 
    
         
             
                    end
         
     | 
| 
       48 
63 
     | 
    
         
             
                  end
         
     | 
| 
       49 
64 
     | 
    
         
             
                end
         
     | 
| 
         @@ -3,170 +3,122 @@ module PicturehouseUk 
     | 
|
| 
       3 
3 
     | 
    
         
             
              module Internal
         
     | 
| 
       4 
4 
     | 
    
         
             
                # @api private
         
     | 
| 
       5 
5 
     | 
    
         
             
                module Parser
         
     | 
| 
       6 
     | 
    
         
            -
                  # Parses  
     | 
| 
      
 6 
     | 
    
         
            +
                  # Parses JSON API response into an array of screening hashes
         
     | 
| 
       7 
7 
     | 
    
         
             
                  class Screenings
         
     | 
| 
       8 
     | 
    
         
            -
                    # css for a day of films & screenings
         
     | 
| 
       9 
     | 
    
         
            -
                    LISTINGS = '.listings li:not(.dark)'.freeze
         
     | 
| 
       10 
     | 
    
         
            -
                    DATE = '.nav-collapse.collapse'.freeze
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
8 
     | 
    
         
             
                    def initialize(cinema_id)
         
     | 
| 
       13 
9 
     | 
    
         
             
                      @cinema_id = cinema_id
         
     | 
| 
       14 
10 
     | 
    
         
             
                    end
         
     | 
| 
       15 
11 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                    #  
     | 
| 
      
 12 
     | 
    
         
            +
                    # Parse the JSON response into an array of screening attributes
         
     | 
| 
       17 
13 
     | 
    
         
             
                    # @return [Array<Hash>]
         
     | 
| 
       18 
14 
     | 
    
         
             
                    def to_a
         
     | 
| 
       19 
     | 
    
         
            -
                       
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                       
     | 
| 
      
 15 
     | 
    
         
            +
                      return [] unless json_data['response'] == 'success'
         
     | 
| 
      
 16 
     | 
    
         
            +
                      return [] unless json_data['movies']
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                      json_data['movies'].flat_map do |movie|
         
     | 
| 
      
 19 
     | 
    
         
            +
                        parse_movie(movie)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      end.compact
         
     | 
| 
       23 
21 
     | 
    
         
             
                    end
         
     | 
| 
       24 
22 
     | 
    
         | 
| 
       25 
23 
     | 
    
         
             
                    private
         
     | 
| 
       26 
24 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
                    def  
     | 
| 
       28 
     | 
    
         
            -
                      if  
     | 
| 
       29 
     | 
    
         
            -
                        Date.now
         
     | 
| 
       30 
     | 
    
         
            -
                      else
         
     | 
| 
       31 
     | 
    
         
            -
                        html.match(/listings-further-ahead-(\d{4})(\d{2})(\d{2})/) do |m|
         
     | 
| 
       32 
     | 
    
         
            -
                          Date.new(m[1].to_i, m[2].to_i, m[3].to_i)
         
     | 
| 
       33 
     | 
    
         
            -
                        end
         
     | 
| 
       34 
     | 
    
         
            -
                      end
         
     | 
| 
       35 
     | 
    
         
            -
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    def parse_movie(movie)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      return [] if movie['show_times'].nil? || movie['show_times'].empty?
         
     | 
| 
       36 
27 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
      
 28 
     | 
    
         
            +
                      movie['show_times'].map do |timing|
         
     | 
| 
      
 29 
     | 
    
         
            +
                        parse_timing(movie, timing)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      end.compact
         
     | 
| 
       39 
31 
     | 
    
         
             
                    end
         
     | 
| 
       40 
32 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                    def  
     | 
| 
       42 
     | 
    
         
            -
                       
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                end
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                # @api private
         
     | 
| 
       48 
     | 
    
         
            -
                # collection of timings for a specific film
         
     | 
| 
       49 
     | 
    
         
            -
                class FilmWithShowtimes
         
     | 
| 
       50 
     | 
    
         
            -
                  # film name css
         
     | 
| 
       51 
     | 
    
         
            -
                  NAME = '.top-mg-sm a'.freeze
         
     | 
| 
       52 
     | 
    
         
            -
                  # variants css
         
     | 
| 
       53 
     | 
    
         
            -
                  VARIANTS = '.film-times .col-xs-10'.freeze
         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                  def initialize(node, date)
         
     | 
| 
       56 
     | 
    
         
            -
                    @node = node
         
     | 
| 
       57 
     | 
    
         
            -
                    @date = date
         
     | 
| 
       58 
     | 
    
         
            -
                  end
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                  # The film name
         
     | 
| 
       61 
     | 
    
         
            -
                  # @return [String]
         
     | 
| 
       62 
     | 
    
         
            -
                  def name
         
     | 
| 
       63 
     | 
    
         
            -
                    TitleSanitizer.new(raw_name).sanitized
         
     | 
| 
       64 
     | 
    
         
            -
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                    def parse_timing(movie, timing)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      # Skip sold out or unavailable screenings
         
     | 
| 
      
 35 
     | 
    
         
            +
                      return nil if timing['SoldoutStatus'] == 2
         
     | 
| 
      
 36 
     | 
    
         
            +
                      return nil unless timing['date_f'] && timing['time']
         
     | 
| 
       65 
37 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
                    Array(@node.css(VARIANTS)).flat_map do |variant|
         
     | 
| 
       70 
     | 
    
         
            -
                      Variant.new(variant, @date).to_a.map do |hash|
         
     | 
| 
       71 
     | 
    
         
            -
                        {
         
     | 
| 
       72 
     | 
    
         
            -
                          film_name: name,
         
     | 
| 
       73 
     | 
    
         
            -
                          dimension: dimension
         
     | 
| 
       74 
     | 
    
         
            -
                        }.merge(hash)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      # Skip advance booking restrictions if not advertised
         
     | 
| 
      
 39 
     | 
    
         
            +
                      if movie['ABgtToday'] == true && movie['AdvertiseAdvanceBookingDate'] == false
         
     | 
| 
      
 40 
     | 
    
         
            +
                        return nil
         
     | 
| 
       75 
41 
     | 
    
         
             
                      end
         
     | 
| 
       76 
     | 
    
         
            -
                    end
         
     | 
| 
       77 
     | 
    
         
            -
                  end
         
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                  private
         
     | 
| 
       80 
42 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
                # @api private
         
     | 
| 
       91 
     | 
    
         
            -
                # variants can have multiple screenings
         
     | 
| 
       92 
     | 
    
         
            -
                class Variant
         
     | 
| 
       93 
     | 
    
         
            -
                  SHOWTIMES  = '.btn'.freeze
         
     | 
| 
       94 
     | 
    
         
            -
                  VARIANT    = '.film-type-desc'.freeze
         
     | 
| 
       95 
     | 
    
         
            -
                  TRANSLATOR = {
         
     | 
| 
       96 
     | 
    
         
            -
                    'Big Scream'    => 'baby',
         
     | 
| 
       97 
     | 
    
         
            -
                    'IMAX'          => 'imax',
         
     | 
| 
       98 
     | 
    
         
            -
                    "Kids' Club"    => 'kids',
         
     | 
| 
       99 
     | 
    
         
            -
                    'NT Live'       => 'arts',
         
     | 
| 
       100 
     | 
    
         
            -
                    'Screen Arts'   => 'arts',
         
     | 
| 
       101 
     | 
    
         
            -
                    'Silver Screen' => 'senior'
         
     | 
| 
       102 
     | 
    
         
            -
                  }.freeze
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                  def initialize(node, date)
         
     | 
| 
       105 
     | 
    
         
            -
                    @node = node
         
     | 
| 
       106 
     | 
    
         
            -
                    @date = date
         
     | 
| 
       107 
     | 
    
         
            -
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                      {
         
     | 
| 
      
 44 
     | 
    
         
            +
                        film_name: sanitize_title(movie['Title']),
         
     | 
| 
      
 45 
     | 
    
         
            +
                        dimension: determine_dimension(movie['Title'], timing),
         
     | 
| 
      
 46 
     | 
    
         
            +
                        variant: extract_variants(timing),
         
     | 
| 
      
 47 
     | 
    
         
            +
                        booking_url: booking_url(timing),
         
     | 
| 
      
 48 
     | 
    
         
            +
                        starting_at: parse_datetime(timing['date_f'], timing['time'])
         
     | 
| 
      
 49 
     | 
    
         
            +
                      }
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
       108 
51 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
                  def to_a
         
     | 
| 
       112 
     | 
    
         
            -
                    @node.css(SHOWTIMES).map do |node|
         
     | 
| 
       113 
     | 
    
         
            -
                      { variant: variant }.merge(Showtime.new(@node, @date).to_hash)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    def sanitize_title(title)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      TitleSanitizer.new(title).sanitized
         
     | 
| 
       114 
54 
     | 
    
         
             
                    end
         
     | 
| 
       115 
     | 
    
         
            -
                  end
         
     | 
| 
       116 
55 
     | 
    
         | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
      
 56 
     | 
    
         
            +
                    def determine_dimension(title, timing)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      # Check title for 3D indicator
         
     | 
| 
      
 58 
     | 
    
         
            +
                      return '3d' if title =~ /3d/i
         
     | 
| 
       118 
59 
     | 
    
         | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                      # Check timing attributes for 3D
         
     | 
| 
      
 61 
     | 
    
         
            +
                      if timing['SessionAttributesNames']
         
     | 
| 
      
 62 
     | 
    
         
            +
                        return '3d' if timing['SessionAttributesNames'].any? { |attr| attr =~ /3d/i }
         
     | 
| 
      
 63 
     | 
    
         
            +
                      end
         
     | 
| 
       124 
64 
     | 
    
         | 
| 
       125 
     | 
    
         
            -
             
     | 
| 
       126 
     | 
    
         
            -
                     
     | 
| 
       127 
     | 
    
         
            -
                  end
         
     | 
| 
       128 
     | 
    
         
            -
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
                      '2d'
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
       129 
67 
     | 
    
         | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
       131 
     | 
    
         
            -
             
     | 
| 
       132 
     | 
    
         
            -
                class Showtime
         
     | 
| 
       133 
     | 
    
         
            -
                  def initialize(node, date)
         
     | 
| 
       134 
     | 
    
         
            -
                    @node = node
         
     | 
| 
       135 
     | 
    
         
            -
                    @date = date
         
     | 
| 
       136 
     | 
    
         
            -
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                    def extract_variants(timing)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      return [] unless timing['SessionAttributesNames']
         
     | 
| 
       137 
70 
     | 
    
         | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
                    {
         
     | 
| 
       140 
     | 
    
         
            -
                      booking_url: booking_url,
         
     | 
| 
       141 
     | 
    
         
            -
                      starting_at: starting_at
         
     | 
| 
       142 
     | 
    
         
            -
                    }
         
     | 
| 
       143 
     | 
    
         
            -
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                      variants = []
         
     | 
| 
       144 
72 
     | 
    
         | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
                      timing['SessionAttributesNames'].each do |attribute|
         
     | 
| 
      
 74 
     | 
    
         
            +
                        # Map known attributes to variant types
         
     | 
| 
      
 75 
     | 
    
         
            +
                        variants << 'baby' if attribute =~ /big scream/i
         
     | 
| 
      
 76 
     | 
    
         
            +
                        variants << 'imax' if attribute =~ /imax/i
         
     | 
| 
      
 77 
     | 
    
         
            +
                        variants << 'kids' if attribute =~ /kids|toddler/i
         
     | 
| 
      
 78 
     | 
    
         
            +
                        variants << 'arts' if attribute =~ /nt live|screen arts|rbo|roh|met opera/i
         
     | 
| 
      
 79 
     | 
    
         
            +
                        variants << 'senior' if attribute =~ /silver screen/i
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
       146 
81 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
       148 
     | 
    
         
            -
                     
     | 
| 
       149 
     | 
    
         
            -
                    "https://picturehouses.com#{href}"
         
     | 
| 
       150 
     | 
    
         
            -
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                      variants.uniq.sort
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
       151 
84 
     | 
    
         | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                    def booking_url(timing)
         
     | 
| 
      
 86 
     | 
    
         
            +
                      return nil unless timing['SessionId']
         
     | 
| 
       155 
87 
     | 
    
         | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
      
 88 
     | 
    
         
            +
                      # Vista booking URL format
         
     | 
| 
      
 89 
     | 
    
         
            +
                      "https://web.picturehouses.com/order/showtimes/#{@cinema_id}-#{timing['SessionId']}/seats"
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
       159 
91 
     | 
    
         | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
      
 92 
     | 
    
         
            +
                    def parse_datetime(date_str, time_str)
         
     | 
| 
      
 93 
     | 
    
         
            +
                      # date_str format: "2024-10-30"
         
     | 
| 
      
 94 
     | 
    
         
            +
                      # time_str format: "19:30"
         
     | 
| 
      
 95 
     | 
    
         
            +
                      return nil unless date_str && time_str
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                      date_parts = date_str.split('-').map(&:to_i)
         
     | 
| 
      
 98 
     | 
    
         
            +
                      time_parts = time_str.split(':').map(&:to_i)
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                      # Create Time object in local timezone, then convert to UTC
         
     | 
| 
      
 101 
     | 
    
         
            +
                      Time.new(
         
     | 
| 
      
 102 
     | 
    
         
            +
                        date_parts[0], # year
         
     | 
| 
      
 103 
     | 
    
         
            +
                        date_parts[1], # month
         
     | 
| 
      
 104 
     | 
    
         
            +
                        date_parts[2], # day
         
     | 
| 
      
 105 
     | 
    
         
            +
                        time_parts[0], # hour
         
     | 
| 
      
 106 
     | 
    
         
            +
                        time_parts[1], # minute
         
     | 
| 
      
 107 
     | 
    
         
            +
                        0,             # second
         
     | 
| 
      
 108 
     | 
    
         
            +
                        '+00:00'       # UTC timezone
         
     | 
| 
      
 109 
     | 
    
         
            +
                      )
         
     | 
| 
      
 110 
     | 
    
         
            +
                    rescue StandardError => e
         
     | 
| 
      
 111 
     | 
    
         
            +
                      warn "Failed to parse datetime from #{date_str} #{time_str}: #{e.message}"
         
     | 
| 
      
 112 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 113 
     | 
    
         
            +
                    end
         
     | 
| 
       163 
114 
     | 
    
         | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
      
 115 
     | 
    
         
            +
                    def json_data
         
     | 
| 
      
 116 
     | 
    
         
            +
                      @json_data ||= api_client.get_movies(@cinema_id)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    end
         
     | 
| 
       167 
118 
     | 
    
         | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
             
     | 
| 
      
 119 
     | 
    
         
            +
                    def api_client
         
     | 
| 
      
 120 
     | 
    
         
            +
                      @api_client ||= Api.new
         
     | 
| 
      
 121 
     | 
    
         
            +
                    end
         
     | 
| 
       170 
122 
     | 
    
         
             
                  end
         
     | 
| 
       171 
123 
     | 
    
         
             
                end
         
     | 
| 
       172 
124 
     | 
    
         
             
              end
         
     | 
| 
         @@ -12,18 +12,10 @@ module PicturehouseUk 
     | 
|
| 
       12 
12 
     | 
    
         
             
                    get("cinema/#{id}")
         
     | 
| 
       13 
13 
     | 
    
         
             
                  end
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
                  # get the cinema screenings page for passed id
         
     | 
| 
       16 
     | 
    
         
            -
                  # @return [String]
         
     | 
| 
       17 
     | 
    
         
            -
                  def whats_on(id)
         
     | 
| 
       18 
     | 
    
         
            -
                    get("cinema/#{id}/Whats_On")
         
     | 
| 
       19 
     | 
    
         
            -
                  rescue OpenURI::HTTPError
         
     | 
| 
       20 
     | 
    
         
            -
                    ''
         
     | 
| 
       21 
     | 
    
         
            -
                  end
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
15 
     | 
    
         
             
                  # get the cinema contact information page for passed id
         
     | 
| 
       24 
16 
     | 
    
         
             
                  # @return [String]
         
     | 
| 
       25 
     | 
    
         
            -
                  def  
     | 
| 
       26 
     | 
    
         
            -
                    get("cinema 
     | 
| 
      
 17 
     | 
    
         
            +
                  def information(id)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    get("cinema/#{id}/information")
         
     | 
| 
       27 
19 
     | 
    
         
             
                  rescue OpenURI::HTTPError
         
     | 
| 
       28 
20 
     | 
    
         
             
                    ''
         
     | 
| 
       29 
21 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -34,12 +26,18 @@ module PicturehouseUk 
     | 
|
| 
       34 
26 
     | 
    
         
             
                    get(nil)
         
     | 
| 
       35 
27 
     | 
    
         
             
                  end
         
     | 
| 
       36 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                  # get the cinemas listing page
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @return [String]
         
     | 
| 
      
 31 
     | 
    
         
            +
                  def cinemas
         
     | 
| 
      
 32 
     | 
    
         
            +
                    get("cinema")
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
       37 
35 
     | 
    
         
             
                  private
         
     | 
| 
       38 
36 
     | 
    
         | 
| 
       39 
37 
     | 
    
         
             
                  def get(path)
         
     | 
| 
       40 
38 
     | 
    
         
             
                    # SSL verification doesn't work on picturehouses.com
         
     | 
| 
       41 
     | 
    
         
            -
                    open("https://www.picturehouses.com/#{path}",
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 39 
     | 
    
         
            +
                    URI.open("https://www.picturehouses.com/#{path}",
         
     | 
| 
      
 40 
     | 
    
         
            +
                             ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE).read
         
     | 
| 
       43 
41 
     | 
    
         
             
                  end
         
     | 
| 
       44 
42 
     | 
    
         
             
                end
         
     | 
| 
       45 
43 
     | 
    
         
             
              end
         
     | 
    
        data/lib/picturehouse_uk.rb
    CHANGED
    
    | 
         @@ -3,6 +3,7 @@ require 'nokogiri' 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require_relative './picturehouse_uk/version'
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
      
 6 
     | 
    
         
            +
            require_relative './picturehouse_uk/internal/api'
         
     | 
| 
       6 
7 
     | 
    
         
             
            require_relative './picturehouse_uk/internal/parser/address'
         
     | 
| 
       7 
8 
     | 
    
         
             
            require_relative './picturehouse_uk/internal/parser/screenings'
         
     | 
| 
       8 
9 
     | 
    
         
             
            require_relative './picturehouse_uk/internal/title_sanitizer'
         
     | 
    
        data/picturehouse_uk.gemspec
    CHANGED
    
    | 
         @@ -12,15 +12,14 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       12 
12 
     | 
    
         
             
              spec.homepage      = ''
         
     | 
| 
       13 
13 
     | 
    
         
             
              spec.licenses      = %w(AGPL MIT)
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
              spec.files         = `git ls-files`.split( 
     | 
| 
      
 15 
     | 
    
         
            +
              spec.files         = `git ls-files`.split("\n")
         
     | 
| 
       16 
16 
     | 
    
         
             
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         
     | 
| 
       17 
17 
     | 
    
         
             
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         
     | 
| 
       18 
18 
     | 
    
         
             
              spec.require_paths = ['lib']
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
              spec.add_development_dependency 'bundler', '~> 1.3'
         
     | 
| 
       21 
     | 
    
         
            -
              spec.add_development_dependency 'codeclimate-test-reporter'
         
     | 
| 
       22 
20 
     | 
    
         
             
              spec.add_development_dependency 'minitest-reporters'
         
     | 
| 
       23 
21 
     | 
    
         
             
              spec.add_development_dependency 'rake'
         
     | 
| 
      
 22 
     | 
    
         
            +
              spec.add_development_dependency 'simplecov'
         
     | 
| 
       24 
23 
     | 
    
         
             
              spec.add_development_dependency 'webmock'
         
     | 
| 
       25 
24 
     | 
    
         | 
| 
       26 
25 
     | 
    
         
             
              spec.add_runtime_dependency 'cinebase', '~> 3.0'
         
     |