initial
This commit is contained in:
commit
871d8975ca
13 changed files with 653 additions and 0 deletions
139
app.rb
Normal file
139
app.rb
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
|
||||
require 'sinatra'
|
||||
require 'json'
|
||||
require 'rack/accept'
|
||||
require 'time'
|
||||
|
||||
use Rack::Accept
|
||||
|
||||
NO_RESPONSES = {
|
||||
"en" => [
|
||||
"No.", "Nope.", "Nah.", "Nuh-uh.", "Negative.", "Nay.",
|
||||
"Hell no.", "Not in a million years.", "Get lost.",
|
||||
"Not happening, buddy.", "Why don't you try asking someone else?",
|
||||
"Are you kidding me?", "Absolutely not, go away.",
|
||||
"I’m terribly sorry, but no.", "I regret to inform you that I cannot comply.",
|
||||
"Unfortunately, that will not be possible at this time.",
|
||||
"I must respectfully decline your request.", "I wish I could, but sadly I cannot.",
|
||||
"It would be my pleasure to help, but I must regretfully decline.",
|
||||
"With the greatest respect, I must say no.",
|
||||
"I apologize, but I must kindly refuse your request."
|
||||
],
|
||||
"de" => ["Nein.", "Keineswegs."],
|
||||
"fr" => ["Non.", "Jamais."],
|
||||
"ja" => ["いいえ", "ダメです。"],
|
||||
"ru" => ["Нет", "Никак нет"],
|
||||
"pt" => ["Não.", "De jeito nenhum."],
|
||||
"es" => ["No.", "Para nada."],
|
||||
"pl" => ["Nie.", "W żadnym wypadku."],
|
||||
"fi" => ["Ei.", "Ei todellakaan."],
|
||||
"tr" => ["Hayır.", "Bu değil."],
|
||||
"he" => ["לא"],
|
||||
"hi" => ["नहीं", "बिलकुल नहीं"],
|
||||
"no" => ["Nei.", "Absolutt ikke.", "Ikke tale om."],
|
||||
"sv" => ["Nej.", "Inte alls."],
|
||||
"da" => ["Nej.", "Ikke en chance."],
|
||||
"it" => ["No.", "Assolutamente no."],
|
||||
"nl" => ["Nee.", "Absoluut niet."],
|
||||
"cs" => ["Ne.", "Rozhodně ne."],
|
||||
"ko" => ["아니요.", "절대 안 돼요."],
|
||||
"zh" => ["不。", "绝对不行。"]
|
||||
}
|
||||
|
||||
RATE_LIMIT = {
|
||||
window: 60, # seconds
|
||||
limit: 60 # requests per IP per window
|
||||
}
|
||||
|
||||
$requests = {}
|
||||
|
||||
helpers do
|
||||
def client_ip
|
||||
request.env['HTTP_X_FORWARDED_FOR'] || request.ip
|
||||
end
|
||||
|
||||
def log_to_journald(message)
|
||||
IO.popen(["/usr/bin/systemd-cat", "--identifier=noaas"], "w") { |io| io.puts message }
|
||||
rescue
|
||||
puts message # fallback to stdout
|
||||
end
|
||||
|
||||
def log_structured(lang:, format:, status:, duration_ms:)
|
||||
log_entry = {
|
||||
timestamp: Time.now.utc.iso8601,
|
||||
ip: client_ip,
|
||||
method: request.request_method,
|
||||
path: request.path,
|
||||
user_agent: request.user_agent,
|
||||
language: lang,
|
||||
format: format,
|
||||
status: status,
|
||||
duration_ms: duration_ms
|
||||
}
|
||||
log_to_journald(JSON.generate(log_entry))
|
||||
end
|
||||
|
||||
def rate_limited?
|
||||
ip = client_ip
|
||||
now = Time.now.to_i
|
||||
$requests[ip] ||= []
|
||||
$requests[ip].reject! { |timestamp| timestamp < now - RATE_LIMIT[:window] }
|
||||
if $requests[ip].size >= RATE_LIMIT[:limit]
|
||||
true
|
||||
else
|
||||
$requests[ip] << now
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def preferred_language
|
||||
params["lang"] || request.env["HTTP_ACCEPT_LANGUAGE"].to_s.scan(/[a-z]{2}/).find do |lang|
|
||||
NO_RESPONSES.key?(lang)
|
||||
end || "en"
|
||||
end
|
||||
|
||||
def no_message(lang)
|
||||
NO_RESPONSES[lang].sample
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
@start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
|
||||
after do
|
||||
duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time) * 1000).round(2)
|
||||
lang = preferred_language
|
||||
format = request.preferred_type&.to_s || request.accept.first.to_s
|
||||
log_structured(lang: lang, format: format, status: response.status, duration_ms: duration)
|
||||
end
|
||||
|
||||
get '/' do
|
||||
if rate_limited?
|
||||
status 429
|
||||
content_type :json
|
||||
return { error: "Too Many Requests" }.to_json
|
||||
end
|
||||
|
||||
lang = preferred_language
|
||||
message = no_message(lang)
|
||||
|
||||
if request.preferred_type.to_s == 'application/json' || request.accept.include?('application/json')
|
||||
content_type :json
|
||||
status 200
|
||||
{ message: message, language: lang }.to_json
|
||||
else
|
||||
content_type :html
|
||||
status 200
|
||||
"<!DOCTYPE html>
|
||||
<html lang='#{lang}'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<title>No as a Service</title>
|
||||
</head>
|
||||
<body style='font-family: sans-serif; text-align: center; margin-top: 20%; font-size: 2em;'>
|
||||
<p>#{message}</p>
|
||||
</body>
|
||||
</html>"
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue