# frozen_string_literal: true require "tzinfo" require "boppers/uptime/version" require "boppers/uptime/relative_time" module Boppers class Uptime FORMAT = "%Y-%m-%dT%H:%M:%S%:z" attr_reader :name, :url, :interval, :status, :contain, :min_failures, :timezone, :format, :failures, :failed_at def initialize( name:, url:, interval: 30, status: 200, contain: nil, min_failures: 2, timezone: Time.now.getlocal.zone, format: FORMAT ) @name = name @url = url @interval = interval @status = [status].flatten.map(&:to_i) @contain = contain @min_failures = min_failures @timezone = find_timezone(timezone) || find_timezone("Etc/UTC") @format = format @failures = [] end def call succeed = begin response = HttpClient.get(url, {}, {}, timeout: 1) valid_response?(response) rescue SocketError, Timeout::Error, Net::OpenTimeout # rubocop:disable Lint/ShadowedException false end return succeed! if succeed # Check failed, so track the failure and check # if we need to send any notification (only sends a notification # when threadshold is reached for the first time). failures << now reached_threshold = failures.size == min_failures went_offline! if reached_threshold end def succeed! offline_at = failures.first online_at = now failure_threshold_exceeded = failures.size >= min_failures failures.clear back_online!(offline_at, online_at) if failure_threshold_exceeded end def valid_response?(response) validations = [] validations << status.include?(response.code) validations << response.body.include?(contain) if contain.is_a?(String) validations << response.body.match?(contain) if contain.is_a?(Regexp) validations.all? end def back_online!(offline_at, online_at) duration = RelativeTime.call(offline_at, online_at) title = "#{name} is up" message = "#{name} is back online at #{format_time(online_at)}, " \ "after #{duration} of downtime.\n" \ "You can check it at #{url}." Boppers.notify( :uptime, title: title, message: message, options: {color: :green} ) end def went_offline! failed_at = failures.first title = "#{name} is down" message = [ "#{name} is offline since #{format_time(failed_at)}.", "You can check it at #{url}." ].join("\n") Boppers.notify(:uptime, title: title, message: message, options: {color: :red}) end def now period = timezone.current_period utc_offset = period.offset.utc_total_offset Time.now.getlocal(utc_offset) end def format_time(time) time.strftime(format) end def find_timezone(name) TZInfo::Timezone.get(name) rescue TZInfo::InvalidTimezoneIdentifier TZInfo::Timezone.all.find do |zone| zone.current_period.abbreviation.to_s == name end end end end