From 42085caf4caf9fe3f9871c71b3d950d7fe21786f Mon Sep 17 00:00:00 2001 From: limepotato Date: Tue, 17 Sep 2024 20:40:41 -0600 Subject: [PATCH] refactor to /api/, add ratelimit, and add beeping --- backend/build.gradle.kts | 1 + .../nelle/nelleObserverBackend/Application.kt | 7 + .../{Client.kt => helpers/PostGen.kt} | 129 +++++++++----- .../nelleObserverBackend/plugins/Client.kt | 36 ++++ .../nelleObserverBackend/plugins/Routing.kt | 160 +++++++++++++----- frontend/public/scripts/api.js | 43 +++-- frontend/public/scripts/main.js | 20 ++- .../components/main-page/widgets/meow.astro | 27 ++- 8 files changed, 319 insertions(+), 104 deletions(-) rename backend/src/main/kotlin/observer/nelle/nelleObserverBackend/{Client.kt => helpers/PostGen.kt} (59%) create mode 100644 backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Client.kt diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 044e140..b9f66af 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -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") diff --git a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Application.kt b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Application.kt index b68e9af..3b033ed 100644 --- a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Application.kt +++ b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Application.kt @@ -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): Unit = EngineMain.main(args) fun Application.module() { + install(RateLimit) { + register { + rateLimiter(limit = 10, refillPeriod = 2.minutes) + } + } install(ContentNegotiation) install(CORS) { allowMethod(HttpMethod.Options) diff --git a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Client.kt b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/helpers/PostGen.kt similarity index 59% rename from backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Client.kt rename to backend/src/main/kotlin/observer/nelle/nelleObserverBackend/helpers/PostGen.kt index 52d536c..80b8bd8 100644 --- a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/Client.kt +++ b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/helpers/PostGen.kt @@ -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" + } } diff --git a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Client.kt b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Client.kt new file mode 100644 index 0000000..3777c07 --- /dev/null +++ b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Client.kt @@ -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() { +} diff --git a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Routing.kt b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Routing.kt index 7ea8d6f..026e672 100644 --- a/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Routing.kt +++ b/backend/src/main/kotlin/observer/nelle/nelleObserverBackend/plugins/Routing.kt @@ -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") + } } } } diff --git a/frontend/public/scripts/api.js b/frontend/public/scripts/api.js index 9c508f0..76431a7 100644 --- a/frontend/public/scripts/api.js +++ b/frontend/public/scripts/api.js @@ -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 = "sleeping..."; - console.warn("TIMED OUT") + const response = await fetch(endpoint) + if (response.status === 423) { + meowButton.disabled=true; + meowButton.innerHTML = "sleeping..."; + console.warn("TIMED OUT") + } + if (response.status === 100) { + meowButton.disabled=false; + meowButton.innerHTML = "meow"; + console.warn("NOT TIMED OUT") + } } -if (response.status === 100) { - meowButton.disabled=false; - meowButton.innerHTML = "meow"; - 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 = "sleeping..."; + console.warn("TIMED OUT") + } + if (response.status === 100) { + beepButton.disabled=false; + beepButton.innerHTML = "meow"; + console.warn("NOT TIMED OUT") + } +} \ No newline at end of file diff --git a/frontend/public/scripts/main.js b/frontend/public/scripts/main.js index 228265b..daa4439 100644 --- a/frontend/public/scripts/main.js +++ b/frontend/public/scripts/main.js @@ -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 = "sleeping for 30 minutes..."; + meowButton.innerHTML = "sleeping..."; sendMeow(meowEndpoint); } + +// on send button click +async function beepClick() { + beepButton.disabled=true; + beepButton.innerHTML = "sleeping..."; + sendMeow(beepEndpoint); +} \ No newline at end of file diff --git a/frontend/src/components/main-page/widgets/meow.astro b/frontend/src/components/main-page/widgets/meow.astro index 1f3a5dd..d76e455 100644 --- a/frontend/src/components/main-page/widgets/meow.astro +++ b/frontend/src/components/main-page/widgets/meow.astro @@ -1,11 +1,22 @@

- press this button to make me meow on the fediverse,
you can POST to https://nelle.observer/meow. there is a timeout of 33 minutes globally.
- + 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! +
you can POST to https://nelle.observer/api/meow or https://nelle.observer/api/beep + respectively to make me beep, and GET at the same endpoints to check the status of the timer.
+
+ +
\ No newline at end of file