module TheatresSearch
Find upcoming cinema session times for a town via Event Cinemas pages and DDG fallback. HTTP is injected via fetch lambdas — no live network in tests.
Constants
- CINEMA_PATTERN
- DDG_HTML_URL
- DEFAULT_LIMIT
- REJECT_PATTERN
Public Instance Methods
Source
# File lib/theatres_search.rb, line 50 def build_ddg_query(town) quoted = town.to_s.strip %("#{quoted}" cinema OR "session times" OR "movie times" OR Hoyts OR "Event Cinemas" OR "Reading Cinemas" Australia) end
Source
# File lib/theatres_search.rb, line 55 def build_search_url(town) "#{DDG_HTML_URL}?q=#{CGI.escape(build_ddg_query(town))}" end
Source
# File lib/theatres_search.rb, line 59 def clean_url(href) return "" if href.nil? || href.to_s.strip.empty? href = href.to_s.strip href = "https:#{href}" if href.start_with?("//") if href.include?("uddg=") decoded = URI.decode_www_form(URI.parse(href).query.to_s).to_h["uddg"] return decoded if decoded && !decoded.empty? end href rescue StandardError href.to_s end
Source
# File lib/theatres_search.rb, line 105 def fetch_catalog_venues(parsed, fetch:) TheatresCatalog.venues_for(parsed).filter_map do |entry| html = fetch.call(entry[:url]) parsed_venue = parse_catalog_html(html, entry) next if parsed_venue[:movies].empty? parsed_venue end end
Source
# File lib/theatres_search.rb, line 133 def fetch_ddg_venues(parsed, fetch:) query_town = parsed[:search_town] || parsed[:town] url = build_search_url(query_town) html = fetch.call(url) links = parse_ddg_results(html, town: parsed[:town]) links.map do |link| { cinema: link[:title], chain: nil, movies: [{ title: link[:snippet].to_s.strip.empty? ? "See listings" : link[:snippet], rating: nil, times: [] }], url: link[:url], title: link[:title] } end end
Source
# File lib/theatres_search.rb, line 150 def format_ddg_report(parsed, links) town = parsed[:town].to_s IrcReply.card( tag: "THEATRES", title: TheatresReport.banner_title(town), body_lines: links.each_with_index.map do |link, index| label = link[:title] || link[:cinema] "#{index + 1}. #{label} (#{link[:url]})" end, footer_parts: [ town.split.map(&:capitalize).join(" "), "#{links.length} links", "web search" ], heading_style: TheatresReport::STYLE_BANNER ) end
Source
# File lib/theatres_search.rb, line 75 def mentions_town?(text, town) town_words = town.to_s.downcase.split(/\s+/) body = text.to_s.downcase town_words.all? { |word| body.include?(word) } end
Source
# File lib/theatres_search.rb, line 115 def parse_catalog_html(html, entry) case entry[:parser].to_s when "jacro" TheatresJacroCinema.parse_sessions( html, cinema: entry[:cinema], chain: entry[:chain] ) else TheatresEventCinemas.parse_sessions( html, cinema: entry[:cinema], chain: entry[:chain] ) end end
Source
# File lib/theatres_search.rb, line 81 def parse_ddg_results(html, town:, limit: DEFAULT_LIMIT) doc = Nokogiri::HTML(html.to_s) results = [] doc.css("div.result").each do |node| link = node.at_css("a.result__a") snippet_node = node.at_css("a.result__snippet") next unless link title = link.text.to_s.strip snippet = snippet_node&.text.to_s.strip || "" url = clean_url(link["href"]) next if title.empty? || url.empty? next unless title.match?(CINEMA_PATTERN) || snippet.match?(CINEMA_PATTERN) || url.match?(/cinemas?|hoyts|session/i) next if title.match?(REJECT_PATTERN) || snippet.match?(REJECT_PATTERN) next unless mentions_town?(title, town) || mentions_town?(snippet, town) || url.match?(/#{Regexp.escape(town.to_s.split.first)}/i) results << { title: title, url: url, snippet: snippet } break if results.length >= limit end results end
Source
# File lib/theatres_search.rb, line 46 def parse_town(text) TownLookup.resolve(text) end
Source
# File lib/theatres_search.rb, line 168 def search(town, fetch:, report: TheatresReport.method(:build_report)) parsed = parse_town(town) return usage_message if parsed[:error] venues = fetch_catalog_venues(parsed, fetch: fetch) return report.call(venues, parsed) unless venues.empty? links = fetch_ddg_venues(parsed, fetch: fetch) return report.call([], parsed) if links.empty? format_ddg_report(parsed, links) end
Source
# File lib/theatres_search.rb, line 42 def usage_message TownLookup::USAGE_MESSAGE end