2018-08-04 20:30:44 +00:00
|
|
|
# See http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
|
|
|
|
def ci_lower_bound(pos, n)
|
|
|
|
if n == 0
|
|
|
|
return 0.0
|
|
|
|
end
|
|
|
|
|
|
|
|
# z value here represents a confidence level of 0.95
|
|
|
|
z = 1.96
|
|
|
|
phat = 1.0*pos/n
|
|
|
|
|
|
|
|
return (phat + z*z/(2*n) - z * Math.sqrt((phat*(1 - phat) + z*z/(4*n))/n))/(1 + z*z/n)
|
|
|
|
end
|
|
|
|
|
|
|
|
def elapsed_text(elapsed)
|
|
|
|
millis = elapsed.total_milliseconds
|
|
|
|
return "#{millis.round(2)}ms" if millis >= 1
|
|
|
|
|
|
|
|
"#{(millis * 1000).round(2)}µs"
|
|
|
|
end
|
|
|
|
|
2018-11-17 23:33:30 +00:00
|
|
|
def make_client(url, proxies = {} of String => Array({ip: String, port: Int32}), region = nil)
|
2018-08-04 20:30:44 +00:00
|
|
|
context = OpenSSL::SSL::Context::Client.new
|
|
|
|
context.add_options(
|
|
|
|
OpenSSL::SSL::Options::ALL |
|
|
|
|
OpenSSL::SSL::Options::NO_SSL_V2 |
|
|
|
|
OpenSSL::SSL::Options::NO_SSL_V3
|
|
|
|
)
|
2018-11-17 23:33:30 +00:00
|
|
|
client = HTTPClient.new(url, context)
|
2018-08-04 20:30:44 +00:00
|
|
|
client.read_timeout = 10.seconds
|
|
|
|
client.connect_timeout = 10.seconds
|
2018-11-17 23:33:30 +00:00
|
|
|
|
|
|
|
if region
|
2018-11-20 17:18:48 +00:00
|
|
|
proxies[region]?.try &.sample(40).each do |proxy|
|
2018-11-17 23:33:30 +00:00
|
|
|
begin
|
|
|
|
proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
|
|
|
|
client.set_proxy(proxy)
|
|
|
|
break
|
|
|
|
rescue ex
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-04 20:30:44 +00:00
|
|
|
return client
|
|
|
|
end
|
|
|
|
|
|
|
|
def decode_length_seconds(string)
|
|
|
|
length_seconds = string.split(":").map { |a| a.to_i }
|
|
|
|
length_seconds = [0] * (3 - length_seconds.size) + length_seconds
|
|
|
|
length_seconds = Time::Span.new(length_seconds[0], length_seconds[1], length_seconds[2])
|
|
|
|
length_seconds = length_seconds.total_seconds.to_i
|
|
|
|
|
|
|
|
return length_seconds
|
|
|
|
end
|
|
|
|
|
2018-10-21 01:37:55 +00:00
|
|
|
def recode_length_seconds(time)
|
|
|
|
if time <= 0
|
|
|
|
return ""
|
|
|
|
else
|
|
|
|
time = time.seconds
|
|
|
|
text = "#{time.minutes.to_s.rjust(2, '0')}:#{time.seconds.to_s.rjust(2, '0')}"
|
|
|
|
|
|
|
|
if time.hours > 0
|
|
|
|
text = "#{time.hours.to_s.rjust(2, '0')}:#{text}"
|
|
|
|
end
|
|
|
|
|
|
|
|
text = text.lchop('0')
|
|
|
|
|
|
|
|
return text
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-04 20:30:44 +00:00
|
|
|
def decode_time(string)
|
|
|
|
time = string.try &.to_f?
|
|
|
|
|
|
|
|
if !time
|
|
|
|
hours = /(?<hours>\d+)h/.match(string).try &.["hours"].try &.to_f
|
|
|
|
hours ||= 0
|
|
|
|
|
|
|
|
minutes = /(?<minutes>\d+)m(?!s)/.match(string).try &.["minutes"].try &.to_f
|
|
|
|
minutes ||= 0
|
|
|
|
|
|
|
|
seconds = /(?<seconds>\d+)s/.match(string).try &.["seconds"].try &.to_f
|
|
|
|
seconds ||= 0
|
|
|
|
|
|
|
|
millis = /(?<millis>\d+)ms/.match(string).try &.["millis"].try &.to_f
|
|
|
|
millis ||= 0
|
|
|
|
|
|
|
|
time = hours * 3600 + minutes * 60 + seconds + millis / 1000
|
|
|
|
end
|
|
|
|
|
|
|
|
return time
|
|
|
|
end
|
|
|
|
|
|
|
|
def decode_date(string : String)
|
2018-08-05 23:35:52 +00:00
|
|
|
# String matches 'YYYY'
|
2018-08-15 15:22:36 +00:00
|
|
|
if string.match(/^\d{4}/)
|
2018-08-05 23:35:52 +00:00
|
|
|
return Time.new(string.to_i, 1, 1)
|
|
|
|
end
|
|
|
|
|
2018-08-15 15:22:36 +00:00
|
|
|
# Try to parse as format Jul 10, 2000
|
|
|
|
begin
|
|
|
|
return Time.parse(string, "%b %-d, %Y", Time::Location.local)
|
|
|
|
rescue ex
|
|
|
|
end
|
|
|
|
|
|
|
|
case string
|
|
|
|
when "today"
|
|
|
|
return Time.now
|
|
|
|
when "yesterday"
|
|
|
|
return Time.now - 1.day
|
|
|
|
end
|
|
|
|
|
2018-08-08 15:20:07 +00:00
|
|
|
# String matches format "20 hours ago", "4 months ago"...
|
2018-08-04 20:30:44 +00:00
|
|
|
date = string.split(" ")[-3, 3]
|
|
|
|
delta = date[0].to_i
|
|
|
|
|
|
|
|
case date[1]
|
2018-08-07 13:10:24 +00:00
|
|
|
when .includes? "second"
|
|
|
|
delta = delta.seconds
|
2018-08-04 20:30:44 +00:00
|
|
|
when .includes? "minute"
|
|
|
|
delta = delta.minutes
|
|
|
|
when .includes? "hour"
|
|
|
|
delta = delta.hours
|
|
|
|
when .includes? "day"
|
|
|
|
delta = delta.days
|
|
|
|
when .includes? "week"
|
|
|
|
delta = delta.weeks
|
|
|
|
when .includes? "month"
|
|
|
|
delta = delta.months
|
|
|
|
when .includes? "year"
|
|
|
|
delta = delta.years
|
|
|
|
else
|
|
|
|
raise "Could not parse #{string}"
|
|
|
|
end
|
|
|
|
|
|
|
|
return Time.now - delta
|
|
|
|
end
|
|
|
|
|
2019-02-20 14:49:39 +00:00
|
|
|
def recode_date(time : Time, locale)
|
2018-08-04 20:30:44 +00:00
|
|
|
span = Time.now - time
|
|
|
|
|
|
|
|
if span.total_days > 365.0
|
2019-02-20 14:49:39 +00:00
|
|
|
span = translate(locale, "`x` years", (span.total_days.to_i / 365).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
elsif span.total_days > 30.0
|
2019-02-20 14:49:39 +00:00
|
|
|
span = translate(locale, "`x` months", (span.total_days.to_i / 30).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
elsif span.total_days > 7.0
|
2019-02-20 14:49:39 +00:00
|
|
|
span = translate(locale, "`x` weeks", (span.total_days.to_i / 7).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
elsif span.total_hours > 24.0
|
2019-02-20 15:37:33 +00:00
|
|
|
span = translate(locale, "`x` days", (span.total_days.to_i).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
elsif span.total_minutes > 60.0
|
2019-02-20 15:37:33 +00:00
|
|
|
span = translate(locale, "`x` hours", (span.total_hours.to_i).to_s)
|
2018-08-07 13:10:24 +00:00
|
|
|
elsif span.total_seconds > 60.0
|
2019-02-20 15:37:33 +00:00
|
|
|
span = translate(locale, "`x` minutes", (span.total_minutes.to_i).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
else
|
2019-02-20 15:37:33 +00:00
|
|
|
span = translate(locale, "`x` seconds", (span.total_seconds.to_i).to_s)
|
2018-08-04 20:30:44 +00:00
|
|
|
end
|
|
|
|
|
2019-02-20 14:49:39 +00:00
|
|
|
return span
|
2018-08-04 20:30:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def number_with_separator(number)
|
|
|
|
number.to_s.reverse.gsub(/(\d{3})(?=\d)/, "\\1,").reverse
|
|
|
|
end
|
|
|
|
|
2018-10-19 16:14:26 +00:00
|
|
|
def number_to_short_text(number)
|
|
|
|
seperated = number_with_separator(number).gsub(",", ".").split("")
|
|
|
|
text = seperated.first(2).join
|
|
|
|
|
|
|
|
if seperated[2]? && seperated[2] != "."
|
|
|
|
text += seperated[2]
|
|
|
|
end
|
|
|
|
|
|
|
|
text = text.rchop(".0")
|
|
|
|
|
|
|
|
if number / 1000000 != 0
|
|
|
|
text += "M"
|
|
|
|
elsif number / 1000 != 0
|
|
|
|
text += "K"
|
|
|
|
end
|
|
|
|
|
|
|
|
text
|
|
|
|
end
|
|
|
|
|
2018-08-04 20:30:44 +00:00
|
|
|
def arg_array(array, start = 1)
|
|
|
|
if array.size == 0
|
|
|
|
args = "NULL"
|
|
|
|
else
|
|
|
|
args = [] of String
|
|
|
|
(start..array.size + start - 1).each { |i| args << "($#{i})" }
|
|
|
|
args = args.join(",")
|
|
|
|
end
|
|
|
|
|
|
|
|
return args
|
|
|
|
end
|
2018-08-05 04:07:38 +00:00
|
|
|
|
2019-03-05 18:56:59 +00:00
|
|
|
def make_host_url(config, kemal_config)
|
|
|
|
ssl = config.https_only || kemal_config.ssl
|
|
|
|
|
2018-08-05 04:07:38 +00:00
|
|
|
if ssl
|
|
|
|
scheme = "https://"
|
|
|
|
else
|
|
|
|
scheme = "http://"
|
|
|
|
end
|
|
|
|
|
2019-03-05 18:56:59 +00:00
|
|
|
if kemal_config.port != 80 && kemal_config.port != 443
|
|
|
|
port = ":#{kemal_config.port}"
|
2019-03-03 17:55:14 +00:00
|
|
|
else
|
2019-03-05 18:56:59 +00:00
|
|
|
port = ""
|
|
|
|
end
|
|
|
|
|
|
|
|
if !config.domain
|
2019-03-03 17:55:14 +00:00
|
|
|
return ""
|
|
|
|
end
|
2019-03-05 18:56:59 +00:00
|
|
|
|
|
|
|
host = config.domain.not_nil!.lchop(".")
|
|
|
|
|
|
|
|
return "#{scheme}#{host}#{port}"
|
2018-08-05 04:07:38 +00:00
|
|
|
end
|
2018-08-09 01:26:02 +00:00
|
|
|
|
|
|
|
def get_referer(env, fallback = "/")
|
2018-08-17 15:19:20 +00:00
|
|
|
referer = env.params.query["referer"]?
|
|
|
|
referer ||= env.request.headers["referer"]?
|
2018-08-09 01:26:02 +00:00
|
|
|
referer ||= fallback
|
|
|
|
|
2018-08-17 15:19:20 +00:00
|
|
|
referer = URI.parse(referer)
|
|
|
|
|
2018-09-06 02:10:32 +00:00
|
|
|
# "Unroll" nested referrers
|
2018-08-17 15:19:20 +00:00
|
|
|
loop do
|
|
|
|
if referer.query
|
|
|
|
params = HTTP::Params.parse(referer.query.not_nil!)
|
|
|
|
if params["referer"]?
|
|
|
|
referer = URI.parse(URI.unescape(params["referer"]))
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
referer = referer.full_path
|
2018-09-06 02:07:19 +00:00
|
|
|
referer = "/" + referer.lstrip("\/\\")
|
2018-08-09 01:26:02 +00:00
|
|
|
|
|
|
|
if referer == env.request.path
|
|
|
|
referer = fallback
|
|
|
|
end
|
|
|
|
|
|
|
|
return referer
|
|
|
|
end
|
2018-09-04 13:52:30 +00:00
|
|
|
|
|
|
|
def read_var_int(bytes)
|
2019-02-09 16:15:14 +00:00
|
|
|
num_read = 0
|
2018-09-04 13:52:30 +00:00
|
|
|
result = 0
|
|
|
|
|
2019-02-09 16:15:14 +00:00
|
|
|
read = bytes[num_read]
|
2018-09-04 13:52:30 +00:00
|
|
|
|
|
|
|
if bytes.size == 1
|
|
|
|
result = bytes[0].to_i32
|
|
|
|
else
|
|
|
|
while ((read & 0b10000000) != 0)
|
2019-02-09 16:15:14 +00:00
|
|
|
read = bytes[num_read].to_u64
|
2018-09-04 13:52:30 +00:00
|
|
|
value = (read & 0b01111111)
|
2019-02-09 16:15:14 +00:00
|
|
|
result |= (value << (7 * num_read))
|
2018-09-04 13:52:30 +00:00
|
|
|
|
2019-02-09 16:15:14 +00:00
|
|
|
num_read += 1
|
|
|
|
if num_read > 5
|
2018-09-04 13:52:30 +00:00
|
|
|
raise "VarInt is too big"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
|
|
|
def write_var_int(value : Int)
|
|
|
|
bytes = [] of UInt8
|
|
|
|
value = value.to_u32
|
|
|
|
|
|
|
|
if value == 0
|
|
|
|
bytes = [0_u8]
|
|
|
|
else
|
|
|
|
while value != 0
|
|
|
|
temp = (value & 0b01111111).to_u8
|
|
|
|
value = value >> 7
|
|
|
|
|
|
|
|
if value != 0
|
|
|
|
temp |= 0b10000000
|
|
|
|
end
|
|
|
|
|
|
|
|
bytes << temp
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-04 21:17:10 +00:00
|
|
|
return Slice.new(bytes.to_unsafe, bytes.size)
|
2018-09-04 13:52:30 +00:00
|
|
|
end
|
2018-10-09 13:40:29 +00:00
|
|
|
|
|
|
|
def sha256(text)
|
|
|
|
digest = OpenSSL::Digest.new("SHA256")
|
|
|
|
digest << text
|
|
|
|
return digest.hexdigest
|
|
|
|
end
|