refactor to /api/, add ratelimit, and add beeping

This commit is contained in:
nelle 2024-09-17 20:40:41 -06:00
parent 42b8858b46
commit 42085caf4c
8 changed files with 319 additions and 104 deletions

View file

@ -30,6 +30,7 @@ dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-server-rate-limit:$ktorVersion")
implementation("io.github.oshai:kotlin-logging-jvm:5.1.4")
implementation("org.slf4j:slf4j-simple:2.0.16")

View file

@ -12,14 +12,21 @@ import io.ktor.server.application.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.ratelimit.*
import kotlinx.coroutines.runBlocking
import observer.nelle.nelleObserverBackend.plugins.configureRouting
import kotlin.time.Duration.Companion.minutes
val logger = KotlinLogging.logger("nelle.observer API")
fun main(args: Array<String>): Unit = EngineMain.main(args)
fun Application.module() {
install(RateLimit) {
register {
rateLimiter(limit = 10, refillPeriod = 2.minutes)
}
}
install(ContentNegotiation)
install(CORS) {
allowMethod(HttpMethod.Options)

View file

@ -1,14 +1,14 @@
@file:Suppress("ktlint:standard:no-wildcard-imports")
package observer.nelle.nelleObserverBackend.helpers
package observer.nelle.nelleObserverBackend
import io.ktor.client.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlin.streams.asSequence
// Other Shit
// Get balloons
val balloon = listOf(" \uD83D\uDCAC ", " \uD83D\uDCAD ")
// choose a random balloon out of list
val randomBalloon = balloon.asSequence().shuffled().find { true }
// get randomized meow
fun getMeow(): String {
// source of characters to randomize
val mainSrc = "meowrp"
@ -23,13 +23,8 @@ fun getMeow(): String {
.map(mainSrc::get)
.joinToString("")
// Get balloons (only called if the neocat is called)
val balloon = listOf(" \uD83D\uDCAC ", " \uD83D\uDCAD ")
// choose a random balloon out of list
val randomBalloon = balloon.asSequence().shuffled().find { true }
// get neocat
val neoCat =
val catEmotes =
listOf(
"",
"",
@ -122,7 +117,7 @@ fun getMeow(): String {
":nkobounce_purple:",
)
// choose a random cat emote out of list
val randomCat = neoCat.asSequence().shuffled().find { true }
val randomCat = catEmotes.asSequence().shuffled().find { true }
// if the neocat returns empty, don't use a balloon, else return cat and random balloon
return if (randomCat == "") {
@ -132,32 +127,86 @@ fun getMeow(): String {
}
}
// // Outgoing API Calls
// get randomized beep
fun getBeep(): String {
val bSrc = "b"
val eSrc = "e"
val pSrc = "p"
// Make a post with mastodon API
// TODO: Replace this with a MastodonAPI Library (that i have not finished making)
suspend fun makePost(
client: HttpClient,
postContent: String,
instance: String,
) {
// make a post!
val post: HttpResponse =
client.submitForm(
url =
buildString {
append("https://")
append(instance)
append("/api/v1/statuses")
},
formParameters =
parameters {
append("status", postContent)
append("visibility", "unlisted")
},
val bLength = (1..8).random()
val eLength = (1..16).random()
val pLength = (1..8).random()
val bGen =
java.util
.Random()
.ints(bLength.toLong(), 0, bSrc.length)
.asSequence()
.map(bSrc::get)
.joinToString("")
val eGen =
java.util
.Random()
.ints(eLength.toLong(), 0, eSrc.length)
.asSequence()
.map(eSrc::get)
.joinToString("")
val pGen =
java.util
.Random()
.ints(pLength.toLong(), 0, pSrc.length)
.asSequence()
.map(pSrc::get)
.joinToString("")
val botEmotes =
listOf(
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
":neubot:",
":neubot_pout:",
":neubot_smol:",
":neubot_frown:",
":neubot_glare:",
":neubot_greet:",
":neubot_heart:",
"neubot_laugh:",
":neubot_pout2:",
":neubot_smile:",
":neubot_think:",
":neubot_laugh2:",
":neubot_exclaim:",
":neubot_flushed:",
":neubot_offline:",
":neubot_sideeye:",
":neubot_smoller:",
":neubot_no_mouth:",
":neubot_low_battery:",
":neubot_upside_down:",
":neubot_full_battery:",
":neubot_half_battery:",
":neubot_low_battery_charging:",
":neubot_full_battery_charging:",
":neubot_half_battery_charging:",
)
}
// Send a ntfy message
suspend fun ntfyMsg() {
val randomBot = botEmotes.asSequence().shuffled().find { true }
return if (randomBot == "") {
"$bGen$eGen$pGen"
} else {
"$randomBot$randomBalloon$bGen$eGen$pGen"
}
}

View file

@ -0,0 +1,36 @@
@file:Suppress("ktlint:standard:no-wildcard-imports")
package observer.nelle.nelleObserverBackend.plugins
import io.ktor.client.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
// Make a post with mastodon API
// TODO: Replace this with a MastodonAPI Library (that i have not finished making)
suspend fun makePost(
client: HttpClient,
postContent: String,
instance: String,
) {
// make a post!
val post: HttpResponse =
client.submitForm(
url =
buildString {
append("https://")
append(instance)
append("/api/v1/statuses")
},
formParameters =
parameters {
append("status", postContent)
append("visibility", "unlisted")
},
)
}
// Send a ntfy message
suspend fun ntfyMsg() {
}

View file

@ -5,18 +5,19 @@ package observer.nelle.nelleObserverBackend.plugins
import io.ktor.client.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import observer.nelle.nelleObserverBackend.Config
import observer.nelle.nelleObserverBackend.getMeow
import observer.nelle.nelleObserverBackend.logger
import observer.nelle.nelleObserverBackend.makePost
import observer.nelle.nelleObserverBackend.*
import observer.nelle.nelleObserverBackend.helpers.getBeep
import observer.nelle.nelleObserverBackend.helpers.getMeow
import java.util.*
import kotlin.concurrent.timerTask
import kotlin.time.Duration.Companion.minutes
var meowTimedOut = false
var beepTimedOut = false
var timeoutTime = 33.minutes
fun meowTimer() {
@ -31,47 +32,130 @@ fun meowTimer() {
)
}
fun beepTimer() {
beepTimedOut = true
Timer("SettingUp", true).schedule(
timerTask {
beepTimedOut = false
logger.debug { "timeout reset" }
},
// 33 minutes in milliseconds
timeoutTime.inWholeMilliseconds,
)
}
fun Application.configureRouting(client: HttpClient) {
routing {
// get meow timeout
get("/meowTimeout") {
if (meowTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("Timed Out")
logger.debug { "timed out" }
}
if (!meowTimedOut) {
call.response.status(HttpStatusCode(100, "Not Timed Out"))
call.respondText("Not Timed Out")
logger.debug { "not timed out" }
}
}
rateLimit {
route("/api") {
// meow
route("/meow") {
// get meow timeout
get {
if (meowTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("Timed Out")
logger.debug { "timed out" }
} else {
call.response.status(HttpStatusCode(100, "Not Timed Out"))
call.respondText("Not Timed Out")
logger.debug { "not timed out" }
}
}
// meow button
post {
val meow = getMeow()
if (call.receiveText() == Config().superSecret) {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, meow, Config().instanceDomain)
call.respondText("meowed with bypass")
logger.info { "'$meow' with bypass" }
} else {
if (meowTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("still sleeping...")
logger.info { "failed meow" }
} else {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, meow, Config().instanceDomain)
meowTimer()
call.respondText("'$meow' sent!")
logger.info { "meowed: '$meow'" }
}
}
}
}
// meow button
@Suppress("ktlint:standard:comment-wrapping")
post("/meow") {
if (call.receiveText() == Config().superSecret) {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, getMeow(), Config().instanceDomain)
call.respondText("meowed with bypass")
logger.info { "meowed with bypass" }
} else {
if (meowTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("still Sleeping...")
logger.info { "failed meow" }
} else {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, getMeow(), Config().instanceDomain)
meowTimer()
call.respondText("meow sent!")
logger.info { "meowed" }
// beep
route("/beep") {
val beep = getBeep()
// get meow timeout
get {
if (beepTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("Timed Out")
logger.debug { "timed out" }
} else {
call.response.status(HttpStatusCode(100, "Not Timed Out"))
call.respondText("Not Timed Out")
logger.debug { "not timed out" }
}
}
// meow button
post {
if (call.receiveText() == Config().superSecret) {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, beep, Config().instanceDomain)
call.respondText("beeped with bypass")
logger.info { "'$beep' with bypass" }
} else {
if (beepTimedOut) {
call.response.status(HttpStatusCode(423, "Timed Out"))
call.respondText("still sleeping...")
logger.info { "failed beeped" }
} else {
call.response.status(HttpStatusCode(201, "Meow Posted"))
makePost(client, beep, Config().instanceDomain)
beepTimer()
call.respondText("'$beep' sent!")
logger.info { "beeped: '$beep'" }
}
}
}
}
// TODO
route("/ntfy") {
get {
}
post {
val formParameters = call.receiveParameters()
}
}
}
}
post("/ntfy") {
val formParameters = call.receiveParameters()
// deprecate
route("/meow") {
get {
call.response.status(HttpStatusCode(301, "endpoint moved to /api/meow"))
call.respondText("301: endpoint moved to /api/meow")
}
post {
call.response.status(HttpStatusCode(301, "endpoint moved to /api/meow"))
call.respondText("301: endpoint moved to /api/meow")
}
}
// deprecate
route("/meowTimeout") {
get {
call.response.status(HttpStatusCode(301, "endpoint moved to /api/meow"))
call.respondText("301: endpoint moved to /api/meow")
}
post {
call.response.status(HttpStatusCode(301, "endpoint moved to /api/meow"))
call.respondText("301: endpoint moved to /api/meow")
}
}
}
}

View file

@ -13,15 +13,36 @@ function sendMeow(endpoint) {
}
async function getMeowTimeout(endpoint) {
const response = await fetch(endpoint)
if (response.status === 423) {
meowButton.disabled=true;
meowButton.innerHTML = "<span>sleeping...</span>";
console.warn("TIMED OUT")
const response = await fetch(endpoint)
if (response.status === 423) {
meowButton.disabled=true;
meowButton.innerHTML = "<span>sleeping...</span>";
console.warn("TIMED OUT")
}
if (response.status === 100) {
meowButton.disabled=false;
meowButton.innerHTML = "<span>meow</span>";
console.warn("NOT TIMED OUT")
}
}
if (response.status === 100) {
meowButton.disabled=false;
meowButton.innerHTML = "<span>meow</span>";
console.warn("NOT TIMED OUT")
}
}
function sendBeep(endpoint) {
const request = new XMLHttpRequest();
request.open("POST", endpoint);
request.send("superSecret=null");
console.warn(request.response.text);
}
async function getBeepTimeout(endpoint) {
const response = await fetch(endpoint)
if (response.status === 423) {
beepButton.disabled=true;
beepButton.innerHTML = "<span>sleeping...</span>";
console.warn("TIMED OUT")
}
if (response.status === 100) {
beepButton.disabled=false;
beepButton.innerHTML = "<span>meow</span>";
console.warn("NOT TIMED OUT")
}
}

View file

@ -1,5 +1,5 @@
const meowEndpoint = "https://nelle.observer/meow";
const timeOutEndpoint = "https://nelle.observer/meowTimeout";
const meowEndpoint = "https://nelle.observer/api/meow";
const beepEndpoint = "https://nelle.observer/api/beep";
// loads all the functions to be loaded on load, pretty simple, it loads shit on load.
function onLoad() {
@ -8,7 +8,8 @@ function onLoad() {
redirect();
checkBoxes();
getPlaceholder();
getMeowTimeout(timeOutEndpoint);
getMeowTimeout(meowEndpoint);
getBeepTimeout(beepEndpoint);
}
// if javascript is enabled, this script will load, enabling all site elements that use javascript, by default these are all hidden.
@ -59,13 +60,18 @@ function redirect() {
// meow
const meowButton = document.getElementById("meow-button");
const justMeowed = document.getElementById("justMeowed");
let timeout = 1;
const beepButton = document.getElementById("beep-button");
// on send button click
async function meowClick() {
meowButton.disabled=true;
meowButton.innerHTML = "<span>sleeping for 30 minutes...</span>";
meowButton.innerHTML = "<span>sleeping...</span>";
sendMeow(meowEndpoint);
}
// on send button click
async function beepClick() {
beepButton.disabled=true;
beepButton.innerHTML = "<span>sleeping...</span>";
sendMeow(beepEndpoint);
}

View file

@ -1,11 +1,22 @@
<div class="funny-meow">
<br>
<small>press this button to make me meow on the fediverse, <br>you can POST to <span class="glitch">https://nelle.observer/meow</span>. there is a timeout of 33 minutes globally.</small>
<button
class="custom-btn btn-1"
style="width: 45%; margin-top: 4%; margin-bottom: 4%;"
onclick="meowClick()"
id="meow-button">
<span>meow</span>
</button>
<small>press the buttons bellow to make me meow/beep on the fediverse,
there is a global timeout of 33 minutes, and a separate timer for each button. if it says its sleeping, come back and try again later!
<br>you can POST to <span class="glitch">https://nelle.observer/api/meow</span> or <span class="glitch">https://nelle.observer/api/beep</span>
respectively to make me beep, and GET at the same endpoints to check the status of the timer.</small>
<br>
<button
class="custom-btn btn-1"
style="width: 25%; margin-top: 4%; margin-bottom: 4%;"
onclick="meowClick()"
id="meow-button">
<span>meow</span>
</button>
<button
class="custom-btn btn-1"
style="width: 25%; margin-top: 4%; margin-bottom: 4%;"
onclick="beepClick()"
id="beep-button">
<span>beep</span>
</button>
</div>