initial
This commit is contained in:
commit
871d8975ca
13 changed files with 653 additions and 0 deletions
10
Containerfile
Normal file
10
Containerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM ruby:3.4
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY app.rb .
|
||||||
|
|
||||||
|
RUN bundle init
|
||||||
|
RUN bundle add sinatra rack-accept rackup puma
|
||||||
|
|
||||||
|
EXPOSE 4567
|
||||||
|
CMD ["ruby", "app.rb", "-o", "::"]
|
||||||
8
Gemfile
Normal file
8
Gemfile
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
# gem "rails"
|
||||||
|
|
||||||
|
gem "sinatra", "~> 4.1"
|
||||||
|
gem "rack-accept", "~> 0.4.5"
|
||||||
37
Gemfile.lock
Normal file
37
Gemfile.lock
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
base64 (0.2.0)
|
||||||
|
logger (1.7.0)
|
||||||
|
mustermann (3.0.3)
|
||||||
|
ruby2_keywords (~> 0.0.1)
|
||||||
|
rack (3.1.14)
|
||||||
|
rack-accept (0.4.5)
|
||||||
|
rack (>= 0.4)
|
||||||
|
rack-protection (4.1.1)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
|
logger (>= 1.6.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-session (2.1.1)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
|
rack (>= 3.0.0)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
sinatra (4.1.1)
|
||||||
|
logger (>= 1.6.0)
|
||||||
|
mustermann (~> 3.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-protection (= 4.1.1)
|
||||||
|
rack-session (>= 2.0.0, < 3)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
tilt (2.6.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
x86_64-linux
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
rack-accept (~> 0.4.5)
|
||||||
|
sinatra (~> 4.1)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.6.8
|
||||||
77
README.md
Normal file
77
README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
# No-as-a-Service
|
||||||
|
|
||||||
|
This is a simple Ruby web service that responds with "No" in various languages. It's designed to accept both HTML and JSON requests and includes rate limiting, structured logging, and support for deployment using **Podman** and **systemd**.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Returns "No" in multiple languages and styles.
|
||||||
|
- Supports both HTML and JSON response formats.
|
||||||
|
- Rate limiting to avoid abuse (30 requests per minute per IP).
|
||||||
|
- Logs structured output, forwarding logs to **journald** for integration with **systemd**.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- **app.rb**: The main Ruby application file with Sinatra web service.
|
||||||
|
- **Containerfile**: Instructions for building the container image using Podman.
|
||||||
|
- **no-as-a-service.service**: systemd service configuration for managing the application.
|
||||||
|
|
||||||
|
## Installation & Deployment
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- **Podman**: To build and run the container.
|
||||||
|
- **systemd**: For managing the service.
|
||||||
|
- **Journald**: For logging.
|
||||||
|
|
||||||
|
### Step 1: Build the Container Image
|
||||||
|
|
||||||
|
Clone the repository or download the files, and navigate to the folder containing the `Containerfile` and `app.rb`.
|
||||||
|
|
||||||
|
Run the following command to build the container image with Podman:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman build -t noaas .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create the systemd Service
|
||||||
|
|
||||||
|
Copy the **`no-as-a-service.service`** file to your systemd service directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp no-as-a-service.service /etc/systemd/system/
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and start the service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reexec
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now no-as-a-service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Accessing the Service
|
||||||
|
|
||||||
|
Once the service is running, you can access it at:
|
||||||
|
|
||||||
|
- **HTML Response**: Open a browser and visit `http://localhost:8080/`.
|
||||||
|
- **JSON Response**: Send a GET request to `http://localhost:8080/` with an `Accept: application/json` header, or append `?format=json` to the URL.
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
Logs are forwarded to **Journald**. You can view logs using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u no-as-a-service -t noaas -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
Each IP is limited to 30 requests per minute. If this limit is exceeded, the response will be a `429 Too Many Requests` status with a JSON error message.
|
||||||
|
|
||||||
|
### Customizing Language Responses
|
||||||
|
|
||||||
|
You can customize the "No" responses in various languages by modifying the `NO_RESPONSES` hash in `app.rb`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
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
|
||||||
7
extra/no-as-a-service/.gitignore
vendored
Normal file
7
extra/no-as-a-service/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
*.log
|
||||||
|
*.gem
|
||||||
|
.bundle
|
||||||
|
vendor/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
|
||||||
19
extra/no-as-a-service/Containerfile
Normal file
19
extra/no-as-a-service/Containerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM ruby:3.2
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependencies
|
||||||
|
COPY Gemfile .
|
||||||
|
|
||||||
|
# Install Sinatra and other gems
|
||||||
|
RUN bundle install
|
||||||
|
|
||||||
|
# Copy application
|
||||||
|
COPY app.rb .
|
||||||
|
|
||||||
|
# Expose Sinatra port
|
||||||
|
EXPOSE 4567
|
||||||
|
|
||||||
|
# Run the app
|
||||||
|
CMD ["ruby", "app.rb"]
|
||||||
8
extra/no-as-a-service/Dockerfile
Normal file
8
extra/no-as-a-service/Dockerfile
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM ruby:3.2
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY Gemfile .
|
||||||
|
RUN bundle install
|
||||||
|
COPY app.rb .
|
||||||
|
EXPOSE 4567
|
||||||
|
CMD ["ruby", "app.rb"]
|
||||||
|
|
||||||
3
extra/no-as-a-service/Gemfile
Normal file
3
extra/no-as-a-service/Gemfile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
gem 'sinatra'
|
||||||
|
|
||||||
12
extra/no-as-a-service/License
Normal file
12
extra/no-as-a-service/License
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📄 `LICENSE`
|
||||||
|
|
||||||
|
You can use the [MIT License](https://opensource.org/license/mit/) if you want to keep it simple:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy...
|
||||||
|
|
||||||
18
extra/no-as-a-service/README.md
Normal file
18
extra/no-as-a-service/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Polite No Service
|
||||||
|
|
||||||
|
A simple Ruby + Sinatra microservice that returns polite (or impolite) ways to say "No" in various languages.
|
||||||
|
|
||||||
|
## 🌐 Example
|
||||||
|
|
||||||
|
JSON:
|
||||||
|
http://localhost:4567/?language=Japanese&tone=Polite
|
||||||
|
|
||||||
|
HTML:
|
||||||
|
http://localhost:4567/?style=Blunt&format=html
|
||||||
|
|
||||||
|
## 🚀 Run with Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t polite-no-service .
|
||||||
|
docker run -p 4567:4567 polite-no-service
|
||||||
|
|
||||||
302
extra/no-as-a-service/app.rb
Normal file
302
extra/no-as-a-service/app.rb
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
require 'sinatra'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
set :port, 4567
|
||||||
|
|
||||||
|
PHRASES = [
|
||||||
|
# English Phrases
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Formal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "I'm afraid I must decline, but thank you for asking."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Informal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "Nah, but I appreciate it!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Blunt",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "Nope. Not interested."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Sarcastic",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "Oh sure, let me cancel everything for *that*."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Apologetic",
|
||||||
|
tone: "Empathetic",
|
||||||
|
phrase: "I'm so sorry, but I really can't manage this right now."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Professional",
|
||||||
|
tone: "Neutral",
|
||||||
|
phrase: "After careful consideration, I must respectfully decline."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Witty",
|
||||||
|
tone: "Playful",
|
||||||
|
phrase: "I'd love to, but my schedule is already having a midlife crisis."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Overexplained",
|
||||||
|
tone: "Anxious",
|
||||||
|
phrase: "I've thought about this for hours, analyzed every possible angle, and I just can't see a way to make this work."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Dramatic",
|
||||||
|
tone: "Emotional",
|
||||||
|
phrase: "The universe is conspiring to prevent me from saying yes!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "English",
|
||||||
|
style: "Cryptic",
|
||||||
|
tone: "Mysterious",
|
||||||
|
phrase: "Some things are not meant to be, and this is one of them."
|
||||||
|
},
|
||||||
|
|
||||||
|
# French Phrases
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Formal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "Je suis désolé, mais je dois refuser poliment."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Informal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "Non merci, pas aujourd'hui."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Blunt",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "Non. C'est tout."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Sarcastic",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "Ah oui, bien sûr... Et pendant qu'on y est!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Poetic",
|
||||||
|
tone: "Refined",
|
||||||
|
phrase: "Hélas, mon cœur doit décliner votre proposition."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Diplomatic",
|
||||||
|
tone: "Neutral",
|
||||||
|
phrase: "Je comprends votre proposition, mais je ne peux pas accepter."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Dramatic",
|
||||||
|
tone: "Emotional",
|
||||||
|
phrase: "Mon Dieu! C'est absolument impossible!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Philosophical",
|
||||||
|
tone: "Reflective",
|
||||||
|
phrase: "Les circonstances de la vie me poussent à refuser."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Witty",
|
||||||
|
tone: "Playful",
|
||||||
|
phrase: "Mon agenda et moi sommes en pleine négociation, et la réponse est non."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "French",
|
||||||
|
style: "Overexplained",
|
||||||
|
tone: "Anxious",
|
||||||
|
phrase: "J'ai passé des heures à réfléchir, et franchement, c'est un non catégorique."
|
||||||
|
},
|
||||||
|
|
||||||
|
# Japanese Phrases
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Formal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "申し訳ありませんが、お断りさせていただきます。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Informal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "ごめん、ちょっと無理かも。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Cold",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "無理です。以上。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Humble",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "誠に恐縮ですが、お断りさせていただきます。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Indirect",
|
||||||
|
tone: "Diplomatic",
|
||||||
|
phrase: "少し難しい状況でして..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Apologetic",
|
||||||
|
tone: "Empathetic",
|
||||||
|
phrase: "本当に申し訳ありません。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Poetic",
|
||||||
|
tone: "Refined",
|
||||||
|
phrase: "心の中で、静かに拒絶の花が咲きます。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Dramatic",
|
||||||
|
tone: "Emotional",
|
||||||
|
phrase: "天と地が、この申し出を許しません!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Cryptic",
|
||||||
|
tone: "Mysterious",
|
||||||
|
phrase: "運命は別の道を示しています。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Japanese",
|
||||||
|
style: "Overexplained",
|
||||||
|
tone: "Anxious",
|
||||||
|
phrase: "深く考えた末、どうしても受け入れられない理由が山ほどあります。"
|
||||||
|
},
|
||||||
|
|
||||||
|
# Sign Language Conceptual Variations
|
||||||
|
{
|
||||||
|
language: "Sign Language",
|
||||||
|
style: "Formal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "Respectful decline with a gentle head shake and apologetic facial expression."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Sign Language",
|
||||||
|
style: "Blunt",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "Sharp, abrupt hand wave signaling absolute rejection."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Sign Language",
|
||||||
|
style: "Dramatic",
|
||||||
|
tone: "Emotional",
|
||||||
|
phrase: "Exaggerated full-body 'No' with wide eyes and dramatic hand gesture."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Sign Language",
|
||||||
|
style: "Playful",
|
||||||
|
tone: "Humorous",
|
||||||
|
phrase: "Cheeky wink followed by a playful 'No' sign."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Sign Language",
|
||||||
|
style: "Subtle",
|
||||||
|
tone: "Diplomatic",
|
||||||
|
phrase: "Soft, almost imperceptible head tilt with a nuanced hand movement."
|
||||||
|
},
|
||||||
|
|
||||||
|
# Emoji Communication
|
||||||
|
{
|
||||||
|
language: "Emoji",
|
||||||
|
style: "Formal",
|
||||||
|
tone: "Polite",
|
||||||
|
phrase: "🙇♀️🚫 (Bowing apologetically with a stop sign)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Emoji",
|
||||||
|
style: "Casual",
|
||||||
|
tone: "Impolite",
|
||||||
|
phrase: "🙅♂️😤 (Crossed arms with frustrated face)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Emoji",
|
||||||
|
style: "Dramatic",
|
||||||
|
tone: "Emotional",
|
||||||
|
phrase: "😱🚫🌋 (Shocked face, stop sign, volcano)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Emoji",
|
||||||
|
style: "Witty",
|
||||||
|
tone: "Playful",
|
||||||
|
phrase: "🤷♀️🤦♂️😂 (Shrug, facepalm, laughing)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "Emoji",
|
||||||
|
style: "Cryptic",
|
||||||
|
tone: "Mysterious",
|
||||||
|
phrase: "🕳️🔮❌ (Hole, crystal ball, cancel)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Method to find phrases by language or style
|
||||||
|
def find_phrases(language: nil, style: nil)
|
||||||
|
PHRASES.select do |phrase|
|
||||||
|
(language.nil? || phrase[:language] == language) &&
|
||||||
|
(style.nil? || phrase[:style] == style)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Method to generate a random decline phrase
|
||||||
|
def random_decline
|
||||||
|
PHRASES.sample
|
||||||
|
end
|
||||||
|
|
||||||
|
# Method to find most dramatic phrases
|
||||||
|
def dramatic_phrases
|
||||||
|
find_phrases(style: "Dramatic")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Method to get unique languages
|
||||||
|
def languages
|
||||||
|
PHRASES.map { |phrase| phrase[:language] }.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/' do
|
||||||
|
content_type :json
|
||||||
|
language = params['language']
|
||||||
|
style = params['style']
|
||||||
|
tone = params['tone']
|
||||||
|
format = params['format'] || 'json'
|
||||||
|
|
||||||
|
filtered = PHRASES.select do |phrase|
|
||||||
|
(language.nil? || phrase[:language].casecmp(language) == 0) &&
|
||||||
|
(style.nil? || phrase[:style].casecmp(style) == 0) &&
|
||||||
|
(tone.nil? || phrase[:tone].casecmp(tone) == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
case format
|
||||||
|
when 'html'
|
||||||
|
content_type :html
|
||||||
|
"<ul>" + filtered.map { |p| "<li><strong>#{p[:language]}</strong> (#{p[:style]}, #{p[:tone]}): #{p[:phrase]}</li>" }.join + "</ul>"
|
||||||
|
else
|
||||||
|
JSON.pretty_generate(filtered)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
13
no-as-a-service.service
Normal file
13
no-as-a-service.service
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=No as a Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/podman run --rm --name noaas -p 8080:4567 noaas
|
||||||
|
Restart=always
|
||||||
|
SyslogIdentifier=noaas
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Loading…
Add table
Add a link
Reference in a new issue