main #8
8 changed files with 319 additions and 104 deletions
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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() {
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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>
|
Loading…
Reference in a new issue