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-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$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("io.github.oshai:kotlin-logging-jvm:5.1.4")
implementation("org.slf4j:slf4j-simple:2.0.16") 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.netty.*
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.ratelimit.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import observer.nelle.nelleObserverBackend.plugins.configureRouting import observer.nelle.nelleObserverBackend.plugins.configureRouting
import kotlin.time.Duration.Companion.minutes
val logger = KotlinLogging.logger("nelle.observer API") val logger = KotlinLogging.logger("nelle.observer API")
fun main(args: Array<String>): Unit = EngineMain.main(args) fun main(args: Array<String>): Unit = EngineMain.main(args)
fun Application.module() { fun Application.module() {
install(RateLimit) {
register {
rateLimiter(limit = 10, refillPeriod = 2.minutes)
}
}
install(ContentNegotiation) install(ContentNegotiation)
install(CORS) { install(CORS) {
allowMethod(HttpMethod.Options) 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 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 { fun getMeow(): String {
// source of characters to randomize // source of characters to randomize
val mainSrc = "meowrp" val mainSrc = "meowrp"
@ -23,13 +23,8 @@ fun getMeow(): String {
.map(mainSrc::get) .map(mainSrc::get)
.joinToString("") .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 // get neocat
val neoCat = val catEmotes =
listOf( listOf(
"", "",
"", "",
@ -122,7 +117,7 @@ fun getMeow(): String {
":nkobounce_purple:", ":nkobounce_purple:",
) )
// choose a random cat emote out of list // 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 // if the neocat returns empty, don't use a balloon, else return cat and random balloon
return if (randomCat == "") { 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 val bLength = (1..8).random()
// TODO: Replace this with a MastodonAPI Library (that i have not finished making) val eLength = (1..16).random()
suspend fun makePost( val pLength = (1..8).random()
client: HttpClient,
postContent: String, val bGen =
instance: String, java.util
) { .Random()
// make a post! .ints(bLength.toLong(), 0, bSrc.length)
val post: HttpResponse = .asSequence()
client.submitForm( .map(bSrc::get)
url = .joinToString("")
buildString { val eGen =
append("https://") java.util
append(instance) .Random()
append("/api/v1/statuses") .ints(eLength.toLong(), 0, eSrc.length)
}, .asSequence()
formParameters = .map(eSrc::get)
parameters { .joinToString("")
append("status", postContent) val pGen =
append("visibility", "unlisted") 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 val randomBot = botEmotes.asSequence().shuffled().find { true }
suspend fun ntfyMsg() {
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.client.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import observer.nelle.nelleObserverBackend.Config import observer.nelle.nelleObserverBackend.*
import observer.nelle.nelleObserverBackend.getMeow import observer.nelle.nelleObserverBackend.helpers.getBeep
import observer.nelle.nelleObserverBackend.logger import observer.nelle.nelleObserverBackend.helpers.getMeow
import observer.nelle.nelleObserverBackend.makePost
import java.util.* import java.util.*
import kotlin.concurrent.timerTask import kotlin.concurrent.timerTask
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
var meowTimedOut = false var meowTimedOut = false
var beepTimedOut = false
var timeoutTime = 33.minutes var timeoutTime = 33.minutes
fun meowTimer() { 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) { fun Application.configureRouting(client: HttpClient) {
routing { routing {
// get meow timeout rateLimit {
get("/meowTimeout") { route("/api") {
if (meowTimedOut) { // meow
call.response.status(HttpStatusCode(423, "Timed Out")) route("/meow") {
call.respondText("Timed Out") // get meow timeout
logger.debug { "timed out" } get {
} if (meowTimedOut) {
if (!meowTimedOut) { call.response.status(HttpStatusCode(423, "Timed Out"))
call.response.status(HttpStatusCode(100, "Not Timed Out")) call.respondText("Timed Out")
call.respondText("Not Timed Out") logger.debug { "timed out" }
logger.debug { "not 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 // beep
@Suppress("ktlint:standard:comment-wrapping") route("/beep") {
post("/meow") { val beep = getBeep()
if (call.receiveText() == Config().superSecret) { // get meow timeout
call.response.status(HttpStatusCode(201, "Meow Posted")) get {
makePost(client, getMeow(), Config().instanceDomain) if (beepTimedOut) {
call.respondText("meowed with bypass") call.response.status(HttpStatusCode(423, "Timed Out"))
logger.info { "meowed with bypass" } call.respondText("Timed Out")
} else { logger.debug { "timed out" }
if (meowTimedOut) { } else {
call.response.status(HttpStatusCode(423, "Timed Out")) call.response.status(HttpStatusCode(100, "Not Timed Out"))
call.respondText("still Sleeping...") call.respondText("Not Timed Out")
logger.info { "failed meow" } logger.debug { "not timed out" }
} else { }
call.response.status(HttpStatusCode(201, "Meow Posted")) }
makePost(client, getMeow(), Config().instanceDomain) // meow button
meowTimer() post {
call.respondText("meow sent!") if (call.receiveText() == Config().superSecret) {
logger.info { "meowed" } 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") { // deprecate
val formParameters = call.receiveParameters() 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) { async function getMeowTimeout(endpoint) {
const response = await fetch(endpoint) const response = await fetch(endpoint)
if (response.status === 423) { if (response.status === 423) {
meowButton.disabled=true; meowButton.disabled=true;
meowButton.innerHTML = "<span>sleeping...</span>"; meowButton.innerHTML = "<span>sleeping...</span>";
console.warn("TIMED OUT") 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; function sendBeep(endpoint) {
meowButton.innerHTML = "<span>meow</span>"; const request = new XMLHttpRequest();
console.warn("NOT TIMED OUT") 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 meowEndpoint = "https://nelle.observer/api/meow";
const timeOutEndpoint = "https://nelle.observer/meowTimeout"; const beepEndpoint = "https://nelle.observer/api/beep";
// loads all the functions to be loaded on load, pretty simple, it loads shit on load. // loads all the functions to be loaded on load, pretty simple, it loads shit on load.
function onLoad() { function onLoad() {
@ -8,7 +8,8 @@ function onLoad() {
redirect(); redirect();
checkBoxes(); checkBoxes();
getPlaceholder(); 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. // 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 // meow
const meowButton = document.getElementById("meow-button"); const meowButton = document.getElementById("meow-button");
const justMeowed = document.getElementById("justMeowed"); const beepButton = document.getElementById("beep-button");
let timeout = 1;
// on send button click // on send button click
async function meowClick() { async function meowClick() {
meowButton.disabled=true; meowButton.disabled=true;
meowButton.innerHTML = "<span>sleeping for 30 minutes...</span>"; meowButton.innerHTML = "<span>sleeping...</span>";
sendMeow(meowEndpoint); 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"> <div class="funny-meow">
<br> <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> <small>press the buttons bellow to make me meow/beep on the fediverse,
<button 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!
class="custom-btn btn-1" <br>you can POST to <span class="glitch">https://nelle.observer/api/meow</span> or <span class="glitch">https://nelle.observer/api/beep</span>
style="width: 45%; margin-top: 4%; margin-bottom: 4%;" respectively to make me beep, and GET at the same endpoints to check the status of the timer.</small>
onclick="meowClick()" <br>
id="meow-button"> <button
<span>meow</span> class="custom-btn btn-1"
</button> 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> </div>