Compare commits

..

No commits in common. "master" and "v2.0.0-beta" have entirely different histories.

25 changed files with 125 additions and 168 deletions

View File

@ -1,5 +0,0 @@
# Scala Steward: Reformat with sbt-java-formatter 0.8.0
57ce691a00babb03e0cae03a26fe56d63fc609af
# Scala Steward: Reformat with scalafmt 3.6.1
f2b19baca828a4d88b46bc009aef6d7115e63924

View File

@ -1,3 +0,0 @@
# If true, Scala Steward will sign off all commits (e.g. `git --signoff`).
# Default: false
signoffCommits = true

View File

@ -16,7 +16,6 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 1.11
- uses: sbt/setup-sbt@v1
- name: Run tests
run: sbt test assembly
- name: Run linter

View File

@ -54,10 +54,6 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@ -1,3 +1,3 @@
version="3.8.3"
version="3.5.0"
maxColumn = 120
runner.dialect = scala3

View File

@ -1,10 +1,9 @@
FROM eclipse-temurin:17-jre-jammy AS base-builder
ARG SBT_VERSION=1.7.1
FROM adoptopenjdk/openjdk16:alpine AS base-builder
ARG SBT_VERSION=1.6.2
RUN apk add --no-cache bash
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
ENV PATH=$PATH:${JAVA_HOME}/bin
RUN \
apt update && \
apt install -y wget && \
wget -O sbt-$SBT_VERSION.tgz https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz && \
tar -xzvf sbt-$SBT_VERSION.tgz && \
rm sbt-$SBT_VERSION.tgz
@ -23,15 +22,15 @@ FROM sbt-builder as builder
COPY src/ src/
RUN sbt assembly
FROM eclipse-temurin:17-jre-jammy AS base-core
FROM adoptopenjdk/openjdk16:alpine-jre AS base-core
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
RUN apt update && apt install -y fonts-dejavu
RUN apk add --update ttf-dejavu
ENV PATH=$PATH:${JAVA_HOME}/bin
FROM base-core
WORKDIR /lc-core
COPY --from=builder /build/target/scala-3.6.2/LibreCaptcha.jar .
COPY --from=builder /build/target/scala-3.1.1/LibreCaptcha.jar .
RUN mkdir data/
EXPOSE 8888

View File

@ -2,12 +2,12 @@
LibreCaptcha is a framework that allows developers to create their own [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA)s.
The framework defines the API for a CAPTCHA generator and takes care of mundane details
such as:
* An HTTP interface for serving CAPTCHAs
* Background workers to pre-compute CAPTCHAs and to store them in a database
* Managing secrets for the CAPTCHAs (tokens, expected answers, etc)
* Safe re-impressions of CAPTCHA images (by creating unique tokens for every impression)
* Garbage collection of stale CAPTCHAs
* Sandboxed plugin architecture (TBD)
* An HTTP interface for serving CAPTCHAs
* Background workers to pre-compute CAPTCHAs and to store them in a database
* Managing secrets for the CAPTCHAs (tokens, expected answers, etc)
* Safe re-impressions of CAPTCHA images (by creating unique tokens for every impression)
* Garbage collection of stale CAPTCHAs
* Sandboxed plugin architecture (TBD)
Some sample CAPTCHA generators are included in the distribution (see below). We will continue adding more samples to the list. For quick
deployments the samples themselves might be sufficient. Projects with more resources might want create their own CAPTCHAs
@ -47,25 +47,18 @@ docker-compose up
Using `docker`:
```
docker run -p=8888:8888 -v ./lcdata:/lc-core/data librecaptcha/lc-core:2.0
docker run -v lcdata:/lc-core/data librecaptcha/lc-core:1.1.0-stable
```
A default `config.json` is automatically created in the mounted volume.
The above commands should work with `podman` as well, if docker.io registry is pre-configured. Otherwise,
you can manually specify the repository like so:
```
podman run -p=8888:8888 -v ./lcdata:/lc-core/data docker.io/librecaptcha/lc-core:2.0
```
## Quick test
Open [localhost:8888/demo/index.html](http://localhost:8888/demo/index.html) in browser.
Alternatively, on the command line, try:
```
> $ curl -d '{"media":"image/png","level":"easy","input_type":"text","size":"350x100"}' localhost:8888/v2/captcha
> $ curl -d '{"media":"image/png","level":"easy","input_type":"text"}' localhost:8888/v1/captcha
{"id":"3bf928ce-a1e7-4616-b34f-8252d777855d"}
> $ curl "localhost:8888/v1/media?id=3bf928ce-a1e7-4616-b34f-8252d777855d" -o sample.png
@ -131,48 +124,48 @@ If a sufficient number of users agree on their answer to the unknown word, it is
The service can be accessed using a simple HTTP API.
### - `/v1/captcha`: `POST`
- Parameters:
- Parameters:
- `level`: `String` -
The difficulty level of a captcha
- easy
- medium
- hard
- easy
- medium
- hard
- `input_type`: `String` -
The type of input option for a captcha
- text
- (More to come)
- text
- (More to come)
- `media`: `String` -
The type of media of a captcha
- image/png
- image/gif
- (More to come)
- image/png
- image/gif
- (More to come)
- `size`: String -
The dimensions of a captcha. It needs to be a string in the format `"widthxheight"` in pixels, and will be matched
with the `allowedSizes` config setting. Example: `size: "450x200"` which requests an image of width 450 and height
200 pixels.
- Returns:
- Returns:
- `id`: `String` - The uuid of the captcha generated
### - `/v1/media`: `GET`
- Parameters:
- Parameters:
- `id`: `String` - The uuid of the captcha
- Returns:
- Returns:
- `image`: `Array[Byte]` - The requested media as bytes
### - `/v1/answer`: `POST`
- Parameter:
- Parameter:
- `id`: `String` - The uuid of the captcha that needs to be solved
- `answer`: `String` - The answer to the captcha that needs to be validated
- Returns:
- Returns:
- `result`: `String` - The result after validation/checking of the answer
- True - If the answer is correct
- False - If the answer is incorrect
- Expired - If the time limit to solve the captcha exceeds
- True - If the answer is correct
- False - If the answer is incorrect
- Expired - If the time limit to solve the captcha exceeds
## Example usage
@ -180,9 +173,9 @@ The service can be accessed using a simple HTTP API.
In javascript:
```js
const resp = await fetch("/v2/captcha", {
method: 'POST',
body: JSON.stringify({level: "easy", media: "image/png", "input_type" : "text", size: "350x100"})
const resp = await fetch("/v1/captcha", {
method: 'POST',
body: JSON.stringify({level: "easy", media: "image/png", "input_type" : "text"})
})
const respJson = await resp.json();
@ -190,19 +183,19 @@ const respJson = await resp.json();
let captchaId = null;
if (resp.ok) {
// The CAPTCHA can be displayed using the data in respJson.
console.log(respJson);
// Store the id somewhere so that it can be used later for answer verification
captchaId = respJson.id;
// The CAPTCHA can be displayed using the data in respJson.
console.log(respJson);
// Store the id somewhere so that it can be used later for answer verification
captchaId = respJson.id;
} else {
console.err(respJson);
console.err(respJson);
}
// When user submits an answer it can be sent to the server for verification thusly:
const resp = await fetch("/v2/answer", {
method: 'POST',
body: JSON.stringify({id: captchaId, answer: "user input"})
const resp = await fetch("/v1/answer", {
method: 'POST',
body: JSON.stringify({id: captchaId, answer: "user input"})
});
const respJson = await resp.json();
console.log(respJson.result);

View File

@ -1,12 +1,12 @@
FROM eclipse-temurin:17-jre-jammy AS base-core
FROM adoptopenjdk/openjdk16:alpine-jre AS base-core
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
RUN apt update && apt install -y fonts-dejavu
RUN apk add --update ttf-dejavu
ENV PATH=$PATH:${JAVA_HOME}/bin
FROM base-core
RUN mkdir /lc-core
COPY target/scala-3.6.2/LibreCaptcha.jar /lc-core
COPY target/scala-3.1.1/LibreCaptcha.jar /lc-core
WORKDIR /lc-core
RUN mkdir data/

View File

@ -2,8 +2,8 @@ lazy val root = (project in file(".")).settings(
inThisBuild(
List(
organization := "com.example",
scalaVersion := "3.6.2",
version := "0.2.1-snapshot",
scalaVersion := "3.1.1",
version := "0.1.0-SNAPSHOT",
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision
@ -12,9 +12,9 @@ lazy val root = (project in file(".")).settings(
)
),
name := "LibreCaptcha",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.3.0",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.3.0",
libraryDependencies += "org.json4s" %% "json4s-jackson" % "4.0.7"
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.31",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.31",
libraryDependencies += "org.json4s" %% "json4s-jackson" % "4.0.4"
)
Compile / unmanagedResourceDirectories += { baseDirectory.value / "lib" }

View File

@ -1 +1 @@
sbt.version=1.10.6
sbt.version=1.6.2

View File

@ -1,4 +1,4 @@
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.13.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.8.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.7.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0")

View File

@ -5,12 +5,12 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.util.List;
import java.util.Map;
import java.util.List;
import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.HelperFunctions;
import lc.misc.PngImageWriter;
import lc.misc.HelperFunctions;
public class FontFunCaptcha implements ChallengeProvider {
@ -58,8 +58,7 @@ public class FontFunCaptcha implements ChallengeProvider {
return null;
}
private byte[] fontFun(
final int width, final int height, String captchaText, String level, String path) {
private byte[] fontFun(final int width, final int height, String captchaText, String level, String path) {
String[] colors = {"#f68787", "#f8a978", "#f1eb9a", "#a4f6a5"};
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = img.createGraphics();
@ -88,8 +87,7 @@ public class FontFunCaptcha implements ChallengeProvider {
final int width = size2D[0];
final int height = size2D[1];
String path = "./lib/fonts/";
return new Challenge(
fontFun(width, height, secret, "medium", path), "image/png", secret.toLowerCase());
return new Challenge(fontFun(width, height, secret, "medium", path), "image/png", secret.toLowerCase());
}
public boolean checkAnswer(String secret, String answer) {

View File

@ -1,26 +1,26 @@
package lc.captchas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.List;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.io.ByteArrayOutputStream;
import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.GifSequenceWriter;
import lc.misc.HelperFunctions;
import lc.misc.GifSequenceWriter;
public class PoppingCharactersCaptcha implements ChallengeProvider {
private int[] computeOffsets(
final Font font, final int width, final int height, final String text) {
private int[] computeOffsets(final Font font, final int width, final int height, final String text) {
final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final var graphics2D = img.createGraphics();
final var frc = graphics2D.getFontRenderContext();
@ -32,20 +32,17 @@ public class PoppingCharactersCaptcha implements ChallengeProvider {
advances[i] = currX;
currX += font.getStringBounds(String.valueOf(c), frc).getWidth();
currX += spacing;
}
;
};
advances[text.length()] = currX;
graphics2D.dispose();
return advances;
}
private BufferedImage makeImage(
final Font font, final int width, final int height, final Consumer<Graphics2D> f) {
private BufferedImage makeImage(final Font font, final int width, final int height, final Consumer<Graphics2D> f) {
final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final var graphics2D = img.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics2D.setFont(font);
f.accept(graphics2D);
graphics2D.dispose();
@ -67,38 +64,24 @@ public class PoppingCharactersCaptcha implements ChallengeProvider {
final var expectedWidth = advances[advances.length - 1];
final var scale = width / (float) expectedWidth;
final var prevColor = Color.getHSBColor(0f, 0f, 0.1f);
IntStream.range(0, text.length())
.forEach(
i -> {
final var color =
Color.getHSBColor(HelperFunctions.randomNumber(0, 100) / 100.0f, 0.6f, 1.0f);
final var nextImage =
makeImage(
font,
width,
height,
(g) -> {
g.scale(scale, 1);
if (i > 0) {
final var prevI = (i - 1) % text.length();
g.setColor(prevColor);
g.drawString(
String.valueOf(text.charAt(prevI)),
advances[prevI] + jitter(),
fontHeight * 1.1f + jitter());
}
g.setColor(color);
g.drawString(
String.valueOf(text.charAt(i)),
advances[i] + jitter(),
fontHeight * 1.1f + jitter());
});
try {
writer.writeToSequence(nextImage);
} catch (final IOException e) {
e.printStackTrace();
}
});
IntStream.range(0, text.length()).forEach(i -> {
final var color = Color.getHSBColor(HelperFunctions.randomNumber(0, 100)/100.0f, 0.6f, 1.0f);
final var nextImage = makeImage(font, width, height, (g) -> {
g.scale(scale, 1);
if (i > 0) {
final var prevI = (i - 1) % text.length();
g.setColor(prevColor);
g.drawString(String.valueOf(text.charAt(prevI)), advances[prevI] + jitter(), fontHeight*1.1f + jitter());
}
g.setColor(color);
g.drawString(String.valueOf(text.charAt(i)), advances[i] + jitter(), fontHeight*1.1f + jitter());
});
try {
writer.writeToSequence(nextImage);
} catch (final IOException e) {
e.printStackTrace();
}
});
writer.close();
output.close();
return byteArrayOutputStream.toByteArray();

View File

@ -1,18 +1,20 @@
package lc.captchas;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;
import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
import java.util.List;
import lc.misc.HelperFunctions;
import lc.misc.PngImageWriter;
import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
public class ShadowTextCaptcha implements ChallengeProvider {
@ -54,24 +56,20 @@ public class ShadowTextCaptcha implements ChallengeProvider {
graphics2D.setPaint(Color.BLACK);
graphics2D.setFont(font);
final var stringWidth = graphics2D.getFontMetrics().stringWidth(text);
final var padding = (stringWidth > width) ? 0 : (width - stringWidth) / 2;
final var scaleX = (stringWidth > width) ? width / ((double) stringWidth) : 1d;
final var padding = (stringWidth > width) ? 0 : (width - stringWidth)/2;
final var scaleX = (stringWidth > width) ? width/((double) stringWidth) : 1d;
graphics2D.scale(scaleX, 1d);
graphics2D.drawString(text, padding, fontHeight * 1.1f);
graphics2D.drawString(text, padding, fontHeight*1.1f);
graphics2D.dispose();
final int kernelSize = (int) Math.ceil((Math.min(width, height) / 50.0));
ConvolveOp op =
new ConvolveOp(
new Kernel(kernelSize, kernelSize, makeKernel(kernelSize)),
ConvolveOp.EDGE_NO_OP,
null);
ConvolveOp op = new ConvolveOp(new Kernel(kernelSize, kernelSize, makeKernel(kernelSize)), ConvolveOp.EDGE_NO_OP, null);
BufferedImage img2 = op.filter(img, null);
Graphics2D g2d = img2.createGraphics();
HelperFunctions.setRenderingHints(g2d);
g2d.setPaint(Color.WHITE);
g2d.scale(scaleX, 1d);
g2d.setFont(font);
g2d.drawString(text, padding - kernelSize, fontHeight * 1.1f);
g2d.drawString(text, padding-kernelSize, fontHeight*1.1f);
g2d.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {

View File

@ -1,7 +1,7 @@
package lc.captchas.interfaces;
import java.util.List;
import java.util.Map;
import java.util.List;
public interface ChallengeProvider {
public String getId();

View File

@ -3,12 +3,12 @@
package lc.misc;
import java.awt.image.*;
import java.io.*;
import java.util.Iterator;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import java.awt.image.*;
import java.io.*;
import java.util.Iterator;
public class GifSequenceWriter {
protected ImageWriter gifWriter;

View File

@ -1,9 +1,9 @@
package lc.misc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
@ -13,6 +13,7 @@ import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
public class PngImageWriter {
@ -25,8 +26,7 @@ public class PngImageWriter {
iw.hasNext(); ) {
ImageWriter writer = iw.next();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier =
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
continue;

View File

@ -3,21 +3,21 @@
package org.limium.picoserve;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedList;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.LinkedList;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
public final class Server {
private final HttpServer server;

View File

@ -46,7 +46,7 @@ class BackgroundTask(config: Config, captchaManager: CaptchaManager) {
(captcha.allowedLevels).flatMap { level =>
(captcha.allowedMedia).flatMap { media =>
(captcha.allowedInputType).flatMap { inputType =>
(captcha.allowedSizes).map { size =>
(captcha.allowedSizes).map {size =>
Parameters(level, media, inputType, size)
}
}

View File

@ -43,16 +43,16 @@ class FilterChallenge extends ChallengeProvider {
val height = size2D(1)
val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val g = canvas.createGraphics()
val fontHeight = (height * 0.6).toInt
val fontHeight = (height*0.6).toInt
g.setColor(Color.WHITE)
g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
g.setColor(Color.BLACK)
val font = new Font("Serif", Font.BOLD, fontHeight)
g.setFont(font)
val stringWidth = g.getFontMetrics().stringWidth(secret)
val scaleX = if (stringWidth > width) width / (stringWidth.toDouble) else 1d
val scaleX = if (stringWidth > width) width/(stringWidth.toDouble) else 1d
val margin = if (stringWidth > width) 0 else (width - stringWidth)
val xOffset = (margin * r.nextDouble).toInt
val xOffset = (margin*r.nextDouble).toInt
g.scale(scaleX, 1d)
g.drawString(secret, xOffset, fontHeight)
g.dispose()

View File

@ -128,13 +128,13 @@ class RainDropsCP extends ChallengeProvider {
val textX = if (textWidth > width) 0 else ((width - textWidth) / 2)
// this will be overlapped by the following text to show the top outline because of the offset
val yOffset = (fontHeight * 0.01).ceil.toInt
val yOffset = (fontHeight*0.01).ceil.toInt
g.setColor(textHighlightColor)
g.drawString(secret, textX, (fontHeight * 1.1).toInt - yOffset)
g.drawString(secret, textX, (fontHeight*1.1).toInt - yOffset)
// paint the text
g.setColor(textColor)
g.drawString(secret, textX, (fontHeight * 1.1).toInt)
g.drawString(secret, textX, (fontHeight*1.1).toInt)
g.dispose()
writer.writeToSequence(canvas)

View File

@ -36,7 +36,7 @@ case class ConfigField(
lazy val captchaExpiryTimeLimitInt: Option[Int] = mapInt(captchaExpiryTimeLimit)
lazy val threadDelayInt: Option[Int] = mapInt(threadDelay)
lazy val maxAttemptsRatioFloat: Option[Float] = mapFloat(maxAttemptsRatio)
lazy val playgroundEnabledBool: Option[Boolean] = playgroundEnabled.map(_ || false)
lazy val playgroundEnabledBool: Option[Boolean] = playgroundEnabled.map(_ || true)
private def mapInt(x: Option[Integer]): Option[Int] = {
x.map(_ + 0)

View File

@ -82,7 +82,6 @@ class Server(
new StringResponse(200, str)
}
)
println("Playground enabled on /demo/index.html")
}
val server: picoserve.Server = serverBuilder.build()

View File

@ -4,7 +4,7 @@ python3 -m venv testEnv
source ./testEnv/bin/activate
pip install locust
mkdir -p data/
java -jar target/scala-3.6.2/LibreCaptcha.jar &
java -jar target/scala-3.1.1/LibreCaptcha.jar &
JAVA_PID=$!
sleep 4
@ -22,7 +22,7 @@ echo Run functional test
cp data/config.json data/config.json.bak
cp tests/debug-config.json data/config.json
java -jar target/scala-3.6.2/LibreCaptcha.jar &
java -jar target/scala-3.1.1/LibreCaptcha.jar &
JAVA_PID=$!
sleep 4