class Snakes
Constants
- ANIMAL
- ARTICLE_LIMIT
- CATCHERS_LIMIT
- COMMAND
- DDG_HTML_URL
- EMOJI
- FEEDS
- LOOKUP_MORE_OFFSET
- LOOKUP_PARAGRAPH_LIMIT
- REJECT_CATCHER_PATTERN
- REJECT_CATCHER_URL_PATTERN
- SNAKE_CATCHER_PATTERN
Public Class Methods
Source
# File plugins/snakes.rb, line 311 def self.area_snake_catchers(region, fetch:, limit: CATCHERS_LIMIT) area = region.to_s.strip return [] if area.empty? seen = {} results = [] snake_catcher_search_queries(area).each do |query| break if results.length >= limit html = fetch.call(ddg_search_url(query)) extract_catchers_from_ddg_html(html, region: area).each do |catcher| key = normalize_catcher_name(catcher[:name]) next if key.empty? || seen[key] seen[key] = true results << catcher break if results.length >= limit end end results rescue StandardError [] end
Source
# File plugins/snakes.rb, line 155 def self.article_detail_paragraphs(url, item:, fetch:, limit: LOOKUP_PARAGRAPH_LIMIT, offset: 0) paragraphs = fetch_article_paragraphs(url, fetch: fetch, limit: limit, offset: offset) if paragraphs.empty? && offset.zero? paragraphs = fallback_article_paragraphs(item, limit: limit) end paragraphs end
Source
# File plugins/snakes.rb, line 195 def self.batchexecute_post(uri, body) AnimalNews.batchexecute_post(uri, body) end
Source
# File plugins/snakes.rb, line 138 def self.build_location_feed_url(location) AnimalNews.build_location_feed_url(ANIMAL, location) end
Source
# File plugins/snakes.rb, line 397 def self.build_lookup_report(index, item, fetch:, paragraphs:) primary = format_article_detail( index, title: item[:title], paragraphs: paragraphs, source: item[:source] ) region = item[:region].to_s.strip catchers = region.empty? ? [] : area_snake_catchers(region, fetch: fetch) format_lookup_report(primary, region: region, catchers: catchers) end
Source
# File plugins/snakes.rb, line 282 def self.catcher_candidate(title, snippet, href) title = title.to_s.strip href = href.to_s return nil if title.empty? return nil if reject_catcher_url?(href) name = extract_catcher_name_from_title(title, href) return nil if name.nil? || name.empty? details = clean_catcher_details(snippet) return nil if details.empty? return nil unless valid_catcher_name?(name) || details.match?(SNAKE_CATCHER_PATTERN) name = clean_catcher_title(name) return nil unless valid_catcher_name?(name) { name: name, details: details } end
Source
# File plugins/snakes.rb, line 271 def self.clean_catcher_details(text) text.to_s .gsub(%r{https?://\S+}, "") .gsub(/\s+/, " ") .strip end
Source
# File plugins/snakes.rb, line 234 def self.clean_catcher_title(title) text = title.to_s.strip return "" if text.empty? if text.match?(/\A(?:home|menu|about us)\s*\|\s*/i) text = text.split(/\s*\|\s*/, 2).last.to_s else text = text.split(/\s*\|\s*/).first.to_s end text = text.sub(/\s+in\s+.+$/i, "").strip text.sub(/\s+[-·]\s+Google\s+Maps\z/i, "").strip text.sub(/\s+-\s+.+$/, "").strip end
Source
# File plugins/snakes.rb, line 86 def self.clean_google_title(title) AnimalNews.clean_google_title(title) end
Source
# File plugins/snakes.rb, line 82 def self.clean_paragraph(html) AnimalNews.clean_paragraph(html) end
Source
# File plugins/snakes.rb, line 110 def self.clear_recent_articles! AnimalNews.clear_recent_articles! end
Source
# File plugins/snakes.rb, line 409 def self.collect_snake_news(fetch:) AnimalNews.collect_news(ANIMAL, fetch: fetch) end
Source
# File plugins/snakes.rb, line 413 def self.collect_snake_news_for_location(location, fetch:) AnimalNews.collect_news_for_location(ANIMAL, location, fetch: fetch) end
Source
# File plugins/snakes.rb, line 25 def self.command_pattern AnimalNews.dedicated_command_pattern_for(COMMAND) end
Source
# File plugins/snakes.rb, line 216 def self.ddg_search_url(query) "#{DDG_HTML_URL}?q=#{CGI.escape(query)}" end
Source
# File plugins/snakes.rb, line 179 def self.decode_google_news_batch_response(body) AnimalNews.decode_google_news_batch_response(body) end
Source
# File plugins/snakes.rb, line 183 def self.decode_google_news_url(source_url, fetch:, post:) AnimalNews.decode_google_news_url(source_url, fetch: fetch, post: post) end
Source
# File plugins/snakes.rb, line 142 def self.extract_article_paragraphs(html, limit: LOOKUP_PARAGRAPH_LIMIT, offset: 0) needed = offset + limit paragraphs = AnimalNews.extract_article_paragraphs(html, limit: needed) paragraphs.drop(offset).first(limit) end
Source
# File plugins/snakes.rb, line 249 def self.extract_catcher_name_from_title(title, href) title = title.to_s.strip href = href.to_s if href.include?("facebook.com") if (match = title.match(/-\s*(.+?)\s*\|\s*Facebook\z/i)) return clean_catcher_title(match[1]) end if (match = title.match(/\A(?:home\s*\|\s*)?(.+?)\s*\|\s*Facebook\z/i)) return clean_catcher_title(match[1]) end end if href.include?("google.com/maps") || title.match?(/Google\s+Maps/i) if (match = title.match(/\A(.+?)\s*[-·|]\s*Google\s+Maps\z/i)) return clean_catcher_title(match[1]) end end clean_catcher_title(title) end
Source
# File plugins/snakes.rb, line 301 def self.extract_catchers_from_ddg_html(html, region:) doc = Nokogiri::HTML(html.to_s) doc.css(".result").filter_map do |result| title = result.at_css(".result__a")&.text&.strip href = Duck.clean_url(result.at_css(".result__a")&.[]("href")) snippet = result.at_css(".result__snippet")&.text&.strip catcher_candidate(title, snippet, href) end end
Source
# File plugins/snakes.rb, line 175 def self.extract_google_news_decode_params(html) AnimalNews.extract_google_news_decode_params(html) end
Source
# File plugins/snakes.rb, line 191 def self.fallback_article_paragraphs(item, limit: 2) AnimalNews.fallback_article_paragraphs(item, limit: limit) end
Source
# File plugins/snakes.rb, line 203 def self.feed_for_region(region) AnimalNews.feed_for_region(ANIMAL, region) end
Source
# File plugins/snakes.rb, line 148 def self.fetch_article_paragraphs(url, fetch:, limit: LOOKUP_PARAGRAPH_LIMIT, offset: 0) html = fetch.call(url) extract_article_paragraphs(html, limit: limit, offset: offset) rescue StandardError [] end
Source
# File plugins/snakes.rb, line 102 def self.format_article(index, title:, paragraph:, source:) AnimalNews.format_article(index, title: title, paragraph: paragraph, source: source) end
Source
# File plugins/snakes.rb, line 199 def self.format_article_detail(index, title:, paragraphs:, source:) AnimalNews.format_article_detail(index, title: title, paragraphs: paragraphs, source: source) end
Source
# File plugins/snakes.rb, line 337 def self.format_catcher(index, name:, details:) "#{index}. #{name} — #{details}" end
Source
# File plugins/snakes.rb, line 134 def self.format_location_name(location) AnimalNews.format_location_name(location) end
Source
# File plugins/snakes.rb, line 341 def self.format_lookup_report(primary, region:, catchers:) return primary if catchers.empty? region_line = IrcFormat.divider("Snake catchers in #{region}") catcher_lines = catchers.each_with_index.map do |catcher, offset| format_catcher(offset + 1, name: catcher[:name], details: catcher[:details]) end [primary, region_line, *catcher_lines].join("\n") end
Source
# File plugins/snakes.rb, line 106 def self.format_report(items, center: nil) AnimalNews.format_report(ANIMAL, items, center: center, emoji: EMOJI) end
Source
# File plugins/snakes.rb, line 417 def self.gather_snake_news(fetch:) AnimalNews.gather_news(ANIMAL, fetch: fetch, emoji: EMOJI) end
Source
# File plugins/snakes.rb, line 421 def self.gather_snake_news_for_location(location, fetch:) AnimalNews.gather_news_for_location(ANIMAL, location, fetch: fetch, emoji: EMOJI) end
Source
# File plugins/snakes.rb, line 171 def self.google_news_article_url?(url) AnimalNews.google_news_article_url?(url) end
Source
# File plugins/snakes.rb, line 163 def self.lookup_paragraph_options(more: false) if more { limit: LOOKUP_PARAGRAPH_LIMIT, offset: LOOKUP_MORE_OFFSET } else { limit: LOOKUP_PARAGRAPH_LIMIT, offset: 0 } end end
Source
# File plugins/snakes.rb, line 352 def self.lookup_snake_article(index, articles:, fetch:, post: method(:batchexecute_post), more: false) return "Run !snakes first" if articles.empty? return "Pick a story from 1 to 5" unless valid_snake_index?(index) item = articles[index - 1] return "Pick a story from 1 to 5" unless item url = item[:url].to_s.strip return "No article link available" if url.empty? lookup_snake_article_with_item(index, item, articles: articles, fetch: fetch, post: post, more: more) end
Source
# File plugins/snakes.rb, line 365 def self.lookup_snake_article_with_item(index, item, articles:, fetch:, post:, more: false) url = item[:url].to_s.strip resolved_url = resolve_article_url(url, fetch: fetch, post: post) paragraph_options = lookup_paragraph_options(more: more) paragraphs = article_detail_paragraphs(resolved_url, item: item, fetch: fetch, **paragraph_options) if paragraphs.empty? return "No more paragraphs available" if more return "Could not fetch article" end if more return format_article_detail( index, title: item[:title], paragraphs: paragraphs, source: item[:source] ) end build_lookup_report(index, item, fetch: fetch, paragraphs: paragraphs) rescue StandardError if more return "No more paragraphs available" end paragraphs = fallback_article_paragraphs(item, limit: LOOKUP_PARAGRAPH_LIMIT) return "Could not fetch article" if paragraphs.empty? build_lookup_report(index, item, fetch: fetch, paragraphs: paragraphs) end
Source
# File plugins/snakes.rb, line 278 def self.normalize_catcher_name(name) name.to_s.downcase.gsub(/[^a-z0-9]+/, " ").squeeze(" ").strip end
Source
# File plugins/snakes.rb, line 90 def self.normalize_title(title) AnimalNews.normalize_title(title) end
Source
# File plugins/snakes.rb, line 130 def self.parse_location(text) AnimalNews.parse_location(text) end
Source
# File plugins/snakes.rb, line 94 def self.parse_rss_items(xml, source: "Google News") AnimalNews.parse_rss_items(xml, source: source) end
Source
# File plugins/snakes.rb, line 126 def self.parse_snake_index(text) AnimalNews.parse_article_index(text) end
Source
# File plugins/snakes.rb, line 118 def self.recent_articles_snapshot(channel) AnimalNews.recent_articles_snapshot(channel, ANIMAL) end
Source
# File plugins/snakes.rb, line 220 def self.reject_catcher_url?(href) href.to_s.match?(REJECT_CATCHER_URL_PATTERN) end
Source
# File plugins/snakes.rb, line 187 def self.resolve_article_url(url, fetch:, post:) AnimalNews.resolve_article_url(url, fetch: fetch, post: post) end
Source
# File plugins/snakes.rb, line 207 def self.snake_catcher_search_queries(region) area = region.to_s.strip [ "snake catcher #{area}", "snake removal #{area} queensland", "#{area} snake catcher phone" ] end
Source
# File plugins/snakes.rb, line 37 def self.snake_location_pattern AnimalNews.dedicated_location_pattern_for(COMMAND) end
Source
# File plugins/snakes.rb, line 33 def self.snake_lookup_more_pattern /#{COMMAND} ([1-5]) more$/i end
Source
# File plugins/snakes.rb, line 29 def self.snake_lookup_pattern AnimalNews.dedicated_lookup_pattern_for(COMMAND) end
Source
# File plugins/snakes.rb, line 114 def self.store_recent_articles(channel, items) AnimalNews.store_recent_articles(channel, ANIMAL, items) end
Source
# File plugins/snakes.rb, line 98 def self.unique_items(items, limit: ARTICLE_LIMIT) AnimalNews.unique_items(items, limit: limit) end
Source
# File plugins/snakes.rb, line 224 def self.valid_catcher_name?(name) text = name.to_s.strip return false if text.empty? || text.length > 80 return false if text == "No results found" return false if text.match?(REJECT_CATCHER_PATTERN) return false unless text.match?(SNAKE_CATCHER_PATTERN) true end
Source
# File plugins/snakes.rb, line 122 def self.valid_snake_index?(index) AnimalNews.valid_article_index?(index) end
Public Instance Methods
Source
# File plugins/snakes.rb, line 437 def batchexecute_post(uri, body) self.class.batchexecute_post(uri, body) end
Source
# File plugins/snakes.rb, line 445 def channel_key(message) target = message.channel || message.user target.respond_to?(:name) ? target.name : target.to_s end
Source
# File plugins/snakes.rb, line 450 def execute(m) fetch = method(:fetch_feed) items = self.class.collect_snake_news(fetch: fetch) self.class.store_recent_articles(channel_key(m), items) text = items.empty? ? AnimalNews.no_news_message(ANIMAL) : self.class.format_report(items) themed_flood_safe_reply(m, text) rescue StandardError themed_flood_safe_reply(m, AnimalNews.fetch_error_message(ANIMAL)) end
Source
# File plugins/snakes.rb, line 460 def execute_location(m, location_text) location = self.class.parse_location(location_text) unless location themed_flood_safe_reply(m, "Pick a story from 1 to 5") return end fetch = method(:fetch_feed) items = self.class.collect_snake_news_for_location(location, fetch: fetch) self.class.store_recent_articles(channel_key(m), items) area = self.class.format_location_name(location) text = items.empty? ? AnimalNews.no_news_message(ANIMAL) : self.class.format_report(items, center: area) themed_flood_safe_reply(m, text) rescue StandardError themed_flood_safe_reply(m, AnimalNews.fetch_error_message(ANIMAL)) end
Source
# File plugins/snakes.rb, line 441 def fetch_feed(url) URI.parse(url).open("User-Agent" => AnimalNews::USER_AGENT).read end
Source
# File plugins/snakes.rb, line 425 def gather_snake_news(fetch: method(:fetch_feed)) self.class.gather_snake_news(fetch: fetch) end
Source
# File plugins/snakes.rb, line 429 def gather_snake_news_for_location(location, fetch: method(:fetch_feed)) self.class.gather_snake_news_for_location(location, fetch: fetch) end
Source
# File plugins/snakes.rb, line 477 def lookup_snake(m, index_text) index = self.class.parse_snake_index(index_text) unless index themed_flood_safe_reply(m, "Pick a story from 1 to 5") return end articles = self.class.recent_articles_snapshot(channel_key(m)) fetch = method(:fetch_feed) post = method(:batchexecute_post) themed_flood_safe_reply(m, lookup_snake_article(index, articles: articles, fetch: fetch, post: post)) end
Source
# File plugins/snakes.rb, line 433 def lookup_snake_article(index, articles:, fetch: method(:fetch_feed), post: method(:batchexecute_post), more: false) self.class.lookup_snake_article(index, articles: articles, fetch: fetch, post: post, more: more) end
Source
# File plugins/snakes.rb, line 490 def lookup_snake_more(m, index_text) index = self.class.parse_snake_index(index_text) unless index themed_flood_safe_reply(m, "Pick a story from 1 to 5") return end articles = self.class.recent_articles_snapshot(channel_key(m)) fetch = method(:fetch_feed) post = method(:batchexecute_post) themed_flood_safe_reply(m, lookup_snake_article(index, articles: articles, fetch: fetch, post: post, more: true)) end