module EhSearch
EhSearch — fetch open CTFtime events with prizes, sorted by value. Public: events_url, parse_events, open?, list_open, dispatch_command, lookup, lookup_list Depends: EhPrize, EhReport, RabbotHttp Tests: test/lib/eh_search_test.rb
Constants
- API_BASE
- LIST_LIMIT
- MAX_EVENTS
- USER_AGENT
- WINDOW_FUTURE_DAYS
- WINDOW_PAST_DAYS
Public Instance Methods
Source
# File lib/eh_search.rb, line 143 def default_fetch lambda do |url| RabbotHttp.fetch(url, user_agent: USER_AGENT) end end
Source
# File lib/eh_search.rb, line 110 def dispatch_command(args, fetch: nil, now: Time.now.utc) text = args.to_s.strip return list_open(fetch: fetch, now: now) if text.empty? || text.casecmp?("list") if text.match?(/\A\d+\z/) index = text.to_i return { error: :usage } if index < 1 list = list_open(fetch: fetch, now: now) return list if list[:error] item = list[:items][index - 1] return { error: :not_found, index: index, listed: list[:listed] } unless item return { item: item, index: index, listed: list[:listed], total_open: list[:total_open] } end { error: :usage } end
Source
# File lib/eh_search.rb, line 30 def events_url(now: Time.now.utc) start = (now - (WINDOW_PAST_DAYS * 86_400)).to_i finish = (now + (WINDOW_FUTURE_DAYS * 86_400)).to_i "#{API_BASE}?limit=#{MAX_EVENTS}&start=#{start}&finish=#{finish}" end
Source
# File lib/eh_search.rb, line 82 def host_from_url(url) host = URI.parse(url.to_s).host.to_s host.sub(/\Awww\./, "") rescue URI::InvalidURIError "" end
Source
# File lib/eh_search.rb, line 93 def list_open(fetch:, now: Time.now.utc) url = events_url(now: now) payload = (fetch || default_fetch).call(url) events = parse_events(payload) open_events = events.select { |event| open?(event, now: now) && event[:prize_usd].positive? } open_events.sort_by! { |event| [-event[:prize_usd], event[:finish_at].to_i] } listed = open_events.first(LIST_LIMIT) { items: listed, total_open: open_events.length, listed: listed.length } rescue StandardError { error: :fetch } end
Source
# File lib/eh_search.rb, line 135 def lookup(args = nil, fetch: nil, now: Time.now.utc) EhReport.format(dispatch_command(args, fetch: fetch, now: now)) end
Source
# File lib/eh_search.rb, line 139 def lookup_list(fetch: nil, now: Time.now.utc) lookup(nil, fetch: fetch, now: now) end
Source
# File lib/eh_search.rb, line 89 def open?(event, now: Time.now.utc) event[:finish_at] > now end
Source
# File lib/eh_search.rb, line 43 def parse_event(entry) title = entry["title"].to_s.strip return nil if title.empty? finish_at = parse_time(entry["finish"]) return nil unless finish_at prizes = entry["prizes"].to_s description = entry["description"].to_s prize_usd = EhPrize.max_usd(prizes, description) prize_label = EhPrize.best_label(prizes, description) || EhPrize.format_label(prize_usd, nil) url = entry["url"].to_s.strip url = entry["ctftime_url"].to_s.strip if url.empty? { id: entry["id"], title: title, url: url, ctftime_url: entry["ctftime_url"].to_s.strip, host: host_from_url(url), finish_at: finish_at, start_at: parse_time(entry["start"]), prize_usd: prize_usd, prize_label: prize_label, prizes_text: prizes.strip, description: description.strip, format: entry["format"].to_s.strip } end
Source
# File lib/eh_search.rb, line 36 def parse_events(payload) data = payload.is_a?(String) ? JSON.parse(payload) : payload Array(data).filter_map { |entry| parse_event(entry) } rescue StandardError [] end
Source
# File lib/eh_search.rb, line 74 def parse_time(value) return nil if value.to_s.strip.empty? Time.parse(value).utc rescue ArgumentError nil end
Source
# File lib/eh_search.rb, line 26 def usage_message "Usage: !eh | !eh list | !eh <#> — open ethical hacking CTF challenges sorted by prize" end