Compare commits

...

72 Commits

Author SHA1 Message Date
Taah 2e40ddc6bc begin oauth2 setup 2024-03-05 19:41:08 -08:00
Telesphoreo ff9cf12acc Add schematic validation to the HTTPD 2024-01-25 15:59:27 -06:00
Telesphoreo 94528860f2
Update CommandsEndpoint.java 2023-12-13 21:28:54 -06:00
Telesphoreo c83f8f1d8e
Merge pull request #12 from plexusorg/commands-route
Add commands route
2023-12-13 21:25:41 -06:00
Focusvity 31cd561b92
Add commands route 2023-12-10 15:35:30 +11:00
Telesphoreo f0582e03e6 Update dependencies 2023-11-26 00:03:16 -06:00
Telesphoreo 4e593e1b5e Update dependencies 2023-10-29 22:59:33 -05:00
Telesphoreo 026b29a98c make module name consistent 2023-09-05 18:14:43 -05:00
Telesphoreo 266a8d6384 Remove admin link 2023-09-05 17:49:50 -05:00
Telesphoreo 21ee54cf98
Update for latest version of Plex 2023-08-28 19:02:46 -05:00
Telesphoreo 492c6bd8eb
Release 1.3 2023-07-22 22:51:42 -05:00
Telesphoreo a7c76e6089 Update Gradle and dependencies 2023-04-29 15:10:24 -05:00
Telesphoreo 7a996004f0 HTTPD now supports dark mode 2023-04-03 15:39:22 -05:00
Telesphoreo 46ff09bf58 Update compatibility with Plex and Gradle 2023-03-08 13:21:31 -06:00
Telesphoreo 4426c4b4b7 Update dependencies and Gradle 2023-03-02 23:09:54 -06:00
Telesphoreo fc8811d453
Update Gradle 2022-11-27 22:18:55 -06:00
Telesphoreo 459f62e0d3
Update dependencies 2022-11-27 22:15:00 -06:00
Telesphoreo 4918cf55f3 Update Gradle + remove API 2022-08-02 20:24:27 -05:00
Telesphoreo e9395e193a
B R U H 2022-08-02 00:09:46 -05:00
Telesphoreo a257fb2f9c Update to 1.19 2022-06-13 23:00:38 -05:00
Telesphoreo fc9924754a Update for 1.2 2022-06-05 22:13:20 -05:00
Telesphoreo 867e3ff79b Closes #8 2022-05-30 20:55:28 -05:00
Telesphoreo 8bef589b7a
Merge pull request #7 from plexusorg/content-type
Set Content-Type header to application/json for JSON endpoints (closes #6)
2022-05-30 02:44:29 -05:00
Allink 5a0ae8746c
Set Content-Type header to application/json for JSON endpoints (closes #6) 2022-05-30 03:55:03 +01:00
Telesphoreo 41a2475e3d Update version 2022-05-28 22:04:07 -05:00
Telesphoreo 0bcbc7e79c Update repo 2022-05-21 21:05:38 -05:00
Telesphoreo 95043a33db
Create CONTRIBUTING.md 2022-05-17 14:58:30 -05:00
Telesphoreo 93619dd05e
Update Bootstrap 2022-05-16 23:21:50 -05:00
Taah 244d027e5d fix this condition 2022-05-10 22:34:39 -07:00
Telesphoreo ceea975729
Add GitHub workflow 2022-05-08 14:00:41 -05:00
Telesphoreo 32f17a23f3
Update dependencies 2022-05-08 13:59:50 -05:00
Telesphoreo 1148ff5da9
Update README.md 2022-05-05 22:33:52 -05:00
Telesphoreo a0ae4a9720
i hate this 2022-04-24 17:24:38 -05:00
Telesphoreo 82238fa3ed
switch to server/api 2022-04-24 16:21:36 -05:00
Telesphoreo 6e152417b7 This needs to be changed 2022-04-21 20:24:43 -05:00
Telesphoreo 22bb27af24 Make it look nicer if you decide to view the source for some reason
Yes I know this is useless
2022-04-21 19:28:12 -05:00
Telesphoreo 2b24f0ee4c Absolutely based 2022-04-21 18:24:15 -05:00
Telesphoreo 7769f34f74 Back to snapshot 2022-04-20 22:01:54 -05:00
Telesphoreo 8337e0dfca Release v1.0.2 2022-04-20 22:00:31 -05:00
Taah 41459ad404 update to use zoned date time 2022-04-19 14:45:35 -07:00
Telesphoreo 7760eb6ff2 Make the pages more consistent 2022-04-18 17:02:08 -05:00
Telesphoreo cbc7907aea improvements 2022-04-18 01:36:45 -05:00
ayunami2000 1f91e9c684 mess with design 2022-04-18 00:30:00 -04:00
ayunami2000 f8bd688fa5 uhhhh
uhhhhhh 😳
2022-04-17 23:09:53 -04:00
ayunami2000 2117cf7041 NOTHING
HAPPENED
2022-04-17 23:06:23 -04:00
ayunami2000 16ee5378ad add schematic uploading + use template 2022-04-17 23:05:20 -04:00
Telesphoreo 391e146d60 this looks bad on mobile 2022-04-17 20:29:32 -05:00
Telesphoreo 4bca726338 Add frontend for uploading stuff 2022-04-17 20:16:13 -05:00
Telesphoreo 820eb57a0a format 2022-04-17 20:02:39 -05:00
ayunami2000 139374ba55 Merge branch 'master' of https://github.com/plexusorg/Module-HTTPD 2022-04-17 21:00:11 -04:00
ayunami2000 822bc5b483 getting real 2022-04-17 21:00:02 -04:00
Telesphoreo 3dde5f7007 why 2022-04-17 19:57:57 -05:00
ayunami2000 6e79310ef8 improve sanitization 2022-04-17 20:23:23 -04:00
Telesphoreo 1802d91fad Update SchematicDownloadEndpoint.java 2022-04-17 19:21:12 -05:00
Telesphoreo 3fa54cec72 change url back 2022-04-17 19:10:12 -05:00
Telesphoreo e1dd36a0fb Merge branch 'master' of https://github.com/plexusorg/Module-HTTPD 2022-04-17 18:54:43 -05:00
Telesphoreo 33a72983d2 add size 2022-04-17 18:54:38 -05:00
ayunami2000 b980ea837b improve cache file size check 2022-04-17 19:54:10 -04:00
ayunami2000 c7658647fc fix list 2022-04-17 19:44:42 -04:00
ayunami2000 5823b0c790 Merge branch 'master' of https://github.com/plexusorg/Module-HTTPD 2022-04-17 19:39:32 -04:00
ayunami2000 7163042f1b limit cache file size lol 2022-04-17 19:39:24 -04:00
Telesphoreo 0b3c6ddbec Add dropdown to navbar instead 2022-04-17 18:37:01 -05:00
Telesphoreo 1ec17ce7e7 these files got swapped somehow 2022-04-17 18:30:18 -05:00
Telesphoreo 1a139034b5 merge 2022-04-17 18:26:01 -05:00
ayunami2000 b6b5a7f227 add caching (untested) 2022-04-17 19:21:44 -04:00
Telesphoreo 01d2cf44ea add list page 2022-04-17 17:37:00 -05:00
Telesphoreo ef5ff6b540 Start work on schematics 2022-04-17 17:27:30 -05:00
Telesphoreo 2ce0a1e698 Actually make the HTTPD look nice 2022-04-17 16:39:08 -05:00
Telesphoreo e6e8439a72 ok aynami 2022-04-15 14:03:26 -05:00
Telesphoreo 7550db5fb0 work in progress 2022-04-15 13:55:40 -05:00
Telesphoreo b4ab3b609a i watched cs50 lecture 8 2022-04-15 13:09:19 -05:00
Telesphoreo c4ad009a34 Use config.yml + you can now hit enter to search 2022-04-13 23:49:12 -05:00
46 changed files with 1304 additions and 260 deletions

18
.github/workflows/gradle.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Gradle
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Build with Gradle
run: chmod a+x gradlew && ./gradlew build --no-daemon

View File

@ -0,0 +1,49 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Plexus Code Style" version="1">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="20" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
</JetCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="LAMBDA_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPACE_AFTER_TYPE_CAST" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="0" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Plexus Code Style" />
</state>
</component>

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
For those who are wanting to contribute, we fully encourage doing so. There are a few rules we require following when contributing however.
## Steps
1. Make an issue and get feedback. It's important to know if your idea will be accepted before writing any code.
- If it is a feature request, describe the feature and be extremely specific.
- If it is a bug report, ensure you include how to reproduce the bug and the expected outcome
- If it is an enhancement, describe your proposed changes. Ensure you are extremely specific.
2. Fork this project
3. Create a new branch that describes the new feature, enhancement, or bug fix. For example, this is good: `feature/add-xyz`. This is bad: `fix-this-lol`.
4. Write the code that addresses your change.
- Keep in mind that it **must** be formatted correctly. If you are using IntelliJ, there is a `codeStyle.xml` file that tells IntelliJ how to format your code. Check this link for information on how to use the file: https://www.jetbrains.com/help/idea/configuring-code-style.html#import-export-schemes
- If you are not using IntelliJ, that is fine. We use the Plexus Code Style (which is almost the same as Allman) so please format your code accordingly.
6. Push your changes to your new branch and make a PR based off of that branch.
## Requirements for a PR
- The issue must be marked as approved
- It must only address each specific issue. Don't make one PR for multiple issues.
- Your PR must compile and work. If it does not compile or work, your PR will most likely be rejected.
## Code requirements
- Most importantly, your code must be efficient. Your pull request may be rejected if your code is deemed inefficient or sloppy.
- Do not repeat yourself. Create functions as needed if you're using large blocks of code over and over again.
- Do not use an excessive amount of commits when making your PR. It makes the master branch look messy.
- Your code must be consistent with Plex's codebase. If a function already exists, use it.

View File

@ -1,2 +1,2 @@
# Module-HTTPD
# Module-HTTPD [![Build Status](https://ci.plex.us.org/job/Module-HTTPD/badge/icon)](https://ci.plex.us.org/job/Module-HTTPD/)
The HTTPD module for Plex

View File

@ -5,91 +5,75 @@ plugins {
}
group = "dev.plex"
version = "1.0.1"
version = "1.4-SNAPSHOT"
description = "Module-HTTPD"
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri("https://papermc.io/repo/repository/maven-public/")
url = uri("https://repo.papermc.io/repository/maven-public/")
}
maven {
url = uri("https://nexus.telesphoreo.me/repository/plex/")
}
maven {
url = uri("https://nexus.telesphoreo.me/repository/totalfreedom/")
}
maven {
url = uri("https://jitpack.io")
content {
includeGroup("com.github.MilkBowl")
}
}
}
dependencies {
implementation("org.projectlombok:lombok:1.18.22")
annotationProcessor("org.projectlombok:lombok:1.18.22")
implementation("io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT")
implementation("dev.plex:Plex:1.0")
implementation("org.json:json:20220320")
implementation("org.projectlombok:lombok:1.18.30")
annotationProcessor("org.projectlombok:lombok:1.18.30")
implementation("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
implementation("dev.plex:server:1.4-SNAPSHOT")
implementation("org.json:json:20231013")
implementation("org.reflections:reflections:0.10.2")
implementation("org.eclipse.jetty:jetty-server:11.0.8")
implementation("org.eclipse.jetty:jetty-servlet:11.0.8")
implementation("org.eclipse.jetty:jetty-proxy:11.0.8")
implementation("com.github.MilkBowl:VaultAPI:1.7")
implementation("org.eclipse.jetty:jetty-server:11.0.19")
implementation("org.eclipse.jetty:jetty-servlet:11.0.19")
implementation("org.eclipse.jetty:jetty-proxy:11.0.19")
implementation("com.github.MilkBowl:VaultAPI:1.7.1") {
exclude("org.bukkit", "bukkit")
}
implementation(platform("com.intellectualsites.bom:bom-newest:1.40")) // Ref: https://github.com/IntellectualSites/bom
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
implementation("commons-io:commons-io:2.15.1")
}
tasks.getByName<Jar>("jar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveBaseName.set("Module-HTTPD")
archiveVersion.set("")
from("src/main/resources") {
exclude("dev/**")
}
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
tasks {
compileJava {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
// See https://openjdk.java.net/jeps/247 for more information.
options.release.set(17)
options.encoding = Charsets.UTF_8.name()
}
javadoc {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
filteringCharset = Charsets.UTF_8.name()
}
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}
tasks.getByName<Jar>("jar") {
// duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveBaseName.set("Plex-HTTPD")
archiveVersion.set("")
// from("src/main/resources") {
// include("**/**")
// }
// from("src/main/java") {
// include("**/**")
// }
}
sourceSets {
main {
resources {
srcDirs("src/main/java", "src/main/resources")
include("**/**")
exclude("**/**.java")
}
}
}
tasks.getByName<Copy>("processResources") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
}

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

31
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1,27 +1,25 @@
package dev.plex;
import dev.plex.authentication.AuthenticationManager;
import dev.plex.cache.FileCache;
import dev.plex.config.ModuleConfig;
import dev.plex.module.PlexModule;
import dev.plex.request.impl.AdminsEndpoint;
import dev.plex.request.impl.IndefBansEndpoint;
import dev.plex.request.impl.ListEndpoint;
import dev.plex.request.impl.PunishmentsEndpoint;
import dev.plex.request.AbstractServlet;
import dev.plex.request.SchematicUploadServlet;
import dev.plex.request.impl.*;
import dev.plex.util.PlexLog;
import java.io.File;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.servlet.MultipartConfigElement;
import lombok.Getter;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
public class HTTPDModule extends PlexModule
{
@ -34,10 +32,17 @@ public class HTTPDModule extends PlexModule
public static ModuleConfig moduleConfig;
public static final FileCache fileCache = new FileCache();
public static final String template = AbstractServlet.readFileReal(HTTPDModule.class.getResourceAsStream("/httpd/template.html"));
private AuthenticationManager authenticationManager;
@Override
public void load()
{
moduleConfig = new ModuleConfig(this, "settings.yml");
// Move it from /httpd/config.yml to /plugins/Plex/modules/Plex-HTTPD/config.yml
moduleConfig = new ModuleConfig(this, "httpd/config.yml", "config.yml");
}
@Override
@ -45,10 +50,22 @@ public class HTTPDModule extends PlexModule
{
moduleConfig.load();
PlexLog.debug("HTTPD Module Port: {0}", moduleConfig.getInt("server.port"));
if (!setupPermissions() && getPlex().getSystem().equalsIgnoreCase("permissions") && !Bukkit.getPluginManager().isPluginEnabled("Vault"))
if ((!Bukkit.getPluginManager().isPluginEnabled("Vault") || !setupPermissions()))
{
throw new RuntimeException("Plex-HTTPD requires the 'Vault' plugin as well as a Permissions plugin that hooks into 'Vault'. We recommend LuckPerms!");
}
this.authenticationManager = new AuthenticationManager();
if (this.authenticationManager.provider() != null)
{
PlexLog.debug(this.authenticationManager.provider().generateLogin());
}
else
{
PlexLog.debug("Provider was not found for Authentication so disabled");
}
serverThread = new Thread(() ->
{
Server server = new Server();
@ -62,10 +79,22 @@ public class HTTPDModule extends PlexModule
connector.setHost(moduleConfig.getString("server.bind-address"));
connector.setPort(moduleConfig.getInt("server.port"));
new AdminsEndpoint();
new IndefBansEndpoint();
new IndexEndpoint();
new ListEndpoint();
new PunishmentsEndpoint();
new CommandsEndpoint();
new SchematicDownloadEndpoint();
new SchematicUploadEndpoint();
ServletHolder uploadHolder = HTTPDModule.context.addServlet(SchematicUploadServlet.class, "/api/schematics/uploading");
File uploadLoc = new File(System.getProperty("java.io.tmpdir"), "schematic-temp-dir");
if (!uploadLoc.exists())
{
uploadLoc.mkdirs();
}
uploadHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(uploadLoc.getAbsolutePath(), 1024 * 1024 * 5, 1024 * 1024 * 25, 1024 * 1024));
server.setConnectors(new Connector[]{connector});
server.setHandler(context);
@ -106,4 +135,34 @@ public class HTTPDModule extends PlexModule
permissions = rsp.getProvider();
return permissions != null;
}
public static File getWorldeditFolder()
{
if (Bukkit.getPluginManager().isPluginEnabled("WorldEdit"))
{
return new File(Bukkit.getPluginManager().getPlugin("WorldEdit").getDataFolder() + "/schematics/");
}
else if (Bukkit.getPluginManager().isPluginEnabled("FastAsyncWorldEdit"))
{
return new File(Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit").getDataFolder() + "/schematics/");
}
else
{
return null;
}
}
private static boolean isFileSystemCaseSensitive = !new File("a").equals(new File("A"));
public static boolean fileNameEquals(String filename1, String filename2)
{
if (isFileSystemCaseSensitive)
{
return filename1.equals(filename2);
}
else
{
return filename1.equalsIgnoreCase(filename2);
}
}
}

View File

@ -0,0 +1,24 @@
package dev.plex.authentication;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.List;
/**
* @author Taah
* @since 6:37 PM [03-05-2024]
*/
@Data
@Accessors(fluent = true)
public class AuthenticatedUser
{
private final String ip;
private final ZonedDateTime lastAuthenticated;
private final LinkedList<String> roles = Lists.newLinkedList();
private final UserType userType = UserType.UNKNOWN;
}

View File

@ -0,0 +1,57 @@
package dev.plex.authentication;
import dev.plex.HTTPDModule;
import dev.plex.authentication.impl.DiscordOAuth2Provider;
import dev.plex.util.PlexLog;
import org.apache.commons.lang3.NotImplementedException;
/**
* @author Taah
* @since 7:08 PM [03-05-2024]
*/
public class AuthenticationManager
{
private final OAuth2Provider provider;
public AuthenticationManager()
{
final boolean enabled = HTTPDModule.moduleConfig.getBoolean("authentication.enabled", false);
if (!enabled)
{
provider = null;
return;
}
PlexLog.debug("[HTTPD] Auth is enabled");
final String providerName = HTTPDModule.moduleConfig.getString("authentication.provider.name", "");
if (providerName.isEmpty())
{
PlexLog.error("OAuth2 Authentication is enabled but no provider was given!");
provider = null;
return;
}
PlexLog.debug("[HTTPD] Provider name is {0}", providerName);
switch (providerName.toLowerCase())
{
case "discord" -> {
provider = new DiscordOAuth2Provider();
}
case "xenforo" -> {
throw new NotImplementedException("XenForo OAuth2 is not implemented yet!");
}
default -> {
provider = null;
}
}
PlexLog.log("Using {0} provider for authentication", providerName);
}
public OAuth2Provider provider()
{
return this.provider;
}
}

View File

@ -0,0 +1,21 @@
package dev.plex.authentication;
import org.eclipse.jetty.server.Response;
import java.util.HashMap;
/**
* @author Taah
* @since 6:36 PM [03-05-2024]
*/
public interface OAuth2Provider
{
HashMap<String, AuthenticatedUser> sessions();
AuthenticatedUser login(Response response, UserType type);
String[] roles(AuthenticatedUser user);
String generateLogin();
}

View File

@ -0,0 +1,10 @@
package dev.plex.authentication;
/**
* @author Taah
* @since 6:37 PM [03-05-2024]
*/
public enum UserType
{
DISCORD, UNKNOWN
}

View File

@ -0,0 +1,81 @@
package dev.plex.authentication.impl;
import com.google.common.collect.Maps;
import dev.plex.HTTPDModule;
import dev.plex.authentication.AuthenticatedUser;
import dev.plex.authentication.OAuth2Provider;
import dev.plex.authentication.UserType;
import dev.plex.util.PlexLog;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.server.Response;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* @author Taah
* @since 6:41 PM [03-05-2024]
*/
public class DiscordOAuth2Provider implements OAuth2Provider
{
private final HashMap<String, AuthenticatedUser> sessions = Maps.newHashMap();
private final String token;
private final String clientId;
private final String redirectUri;
public DiscordOAuth2Provider()
{
token = System.getenv("BOT_TOKEN").isEmpty() ? HTTPDModule.moduleConfig.getString("authentication.provider.discord.token", System.getProperty("BOT_TOKEN", "")) : System.getenv("BOT_TOKEN");
clientId = HTTPDModule.moduleConfig.getString("authentication.provider.discord.clientId", "");
redirectUri = URLEncoder.encode(HTTPDModule.moduleConfig.getString("authentication.provider.redirectUri", ""), StandardCharsets.UTF_8);
PlexLog.debug("[HTTPD] Client ID: {0}, Redirect URL: {1}", clientId, redirectUri);
if (redirectUri.isEmpty())
{
PlexLog.error("Provided authentication redirect url was empty for HTTPD!");
return;
}
if (token.isEmpty())
{
PlexLog.error("Provided discord authentication token was empty for HTTPD!");
return;
}
if (clientId.isEmpty())
{
PlexLog.error("Provided discord client ID was empty for HTTPD!");
}
}
@Override
public HashMap<String, AuthenticatedUser> sessions()
{
return sessions;
}
@Override
public AuthenticatedUser login(Response response, UserType type)
{
return null;
}
@Override
public String[] roles(AuthenticatedUser user)
{
return new String[0];
}
@Override
public String generateLogin()
{
return String.format("https://discord.com/oauth2/authorize?client_id=%s&scope=%s&redirect_uri=%s",
clientId,
"identify%20guilds%20guilds.members.read",
redirectUri);
}
}

View File

@ -0,0 +1,19 @@
package dev.plex.cache;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
public class CacheItem
{
public String path;
public byte[] file;
public long timestamp;
public CacheItem(File f) throws IOException
{
this.path = f.getPath();
this.file = Files.readAllBytes(f.toPath());
this.timestamp = System.currentTimeMillis();
}
}

View File

@ -0,0 +1,37 @@
package dev.plex.cache;
import com.google.common.collect.EvictingQueue;
import dev.plex.HTTPDModule;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Queue;
public class FileCache
{
private final Queue<CacheItem> cache = EvictingQueue.create(15);
public byte[] getFile(File file) throws IOException
{
return getFile(file.getPath());
}
public byte[] getFile(String path) throws IOException
{
CacheItem theItem = cache.stream().filter(cacheItem -> HTTPDModule.fileNameEquals(cacheItem.path, path)).findFirst().orElse(null);
if (theItem == null)
{
theItem = new CacheItem(new File(path));
if (theItem.file.length < 1048576) cache.add(theItem);
}
if (System.currentTimeMillis() - theItem.timestamp > 3 * 60 * 1000) // 3 minutes
{
cache.remove(theItem);
theItem = new CacheItem(new File(path));
if (theItem.file.length < 1048576) cache.add(theItem);
}
return theItem.file;
}
}

View File

@ -4,7 +4,6 @@ import dev.plex.HTTPDModule;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
public class Log
{
public static void log(String message, Object... strings)

View File

@ -3,15 +3,20 @@ package dev.plex.request;
import com.google.common.collect.Lists;
import dev.plex.HTTPDModule;
import dev.plex.logging.Log;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.List;
import java.util.Objects;
import lombok.Data;
import org.eclipse.jetty.servlet.ServletHolder;
@ -43,7 +48,7 @@ public class AbstractServlet extends HttpServlet
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String ipAddress = req.getRemoteAddr();
if (ipAddress == null)
if (ipAddress.equals("127.0.0.1"))
{
ipAddress = req.getHeader("X-FORWARDED-FOR");
}
@ -69,8 +74,11 @@ public class AbstractServlet extends HttpServlet
resp.setStatus(HttpServletResponse.SC_OK);
try
{
Object object = mapping.method.invoke(this, req);
resp.getWriter().println(object.toString());
Object object = mapping.method.invoke(this, req, resp);
if (object != null)
{
resp.getWriter().println(object.toString());
}
}
catch (IOException | IllegalAccessException | InvocationTargetException e)
{
@ -79,6 +87,62 @@ public class AbstractServlet extends HttpServlet
});
}
public static String readFile(InputStream filename)
{
String base = HTTPDModule.template;
String page = readFileReal(filename);
String[] info = page.split("\n", 3);
base = base.replace("${TITLE}", info[0]);
base = base.replace("${ACTIVE_" + info[1] + "}", "active\" aria-current=\"page");
base = base.replace("${ACTIVE_HOME}", "");
base = base.replace("${ACTIVE_ADMINS}", "");
base = base.replace("${ACTIVE_INDEFBANS}", "");
base = base.replace("${ACTIVE_LIST}", "");
base = base.replace("${ACTIVE_COMMANDS}", "");
base = base.replace("${ACTIVE_PUNISHMENTS}", "");
base = base.replace("${ACTIVE_SCHEMATICS}", "");
base = base.replace("${CONTENT}", info[2]);
return base;
}
public static String readFileReal(InputStream filename)
{
StringBuilder contentBuilder = new StringBuilder();
try
{
BufferedReader in = new BufferedReader(new InputStreamReader(Objects.requireNonNull(filename)));
String str;
while ((str = in.readLine()) != null)
{
contentBuilder.append(str).append("\n");
}
in.close();
}
catch (IOException ignored)
{
}
return contentBuilder.toString();
}
// Code from https://programming.guide/java/formatting-byte-size-to-human-readable-format.html
public static String formattedSize(long bytes)
{
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024)
{
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10)
{
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
@Data
public static class Mapping
{

View File

@ -0,0 +1,120 @@
package dev.plex.request;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import dev.plex.HTTPDModule;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.util.PlexLog;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.regex.Pattern;
public class SchematicUploadServlet extends HttpServlet
{
private static final Pattern schemNameMatcher = Pattern.compile("^[a-z0-9'!,_ -]{1,30}\\.schem(atic)?$", Pattern.CASE_INSENSITIVE);
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getRemoteAddr() == null)
{
response.getWriter().println(schematicUploadBadHTML("Your IP address could not be detected. Please ensure you are using IPv4."));
return;
}
PlexPlayer plexPlayer = DataUtils.getPlayerByIP(request.getRemoteAddr());
if (plexPlayer == null)
{
response.getWriter().println(schematicUploadBadHTML("Couldn't load your IP Address: " + request.getRemoteAddr() + ". Have you joined the server before?"));
return;
}
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(plexPlayer.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{
response.getWriter().println(schematicUploadBadHTML("You do not have permission to upload schematics."));
return;
}
File worldeditFolder = HTTPDModule.getWorldeditFolder();
if (worldeditFolder == null)
{
response.getWriter().println(schematicUploadBadHTML("Worldedit is not installed!"));
return;
}
File[] schematics = worldeditFolder.listFiles();
Part uploadPart;
try
{
uploadPart = request.getPart("file");
}
catch (IllegalStateException e)
{
response.getWriter().println(schematicUploadBadHTML("That schematic is too large!"));
return;
}
String filename = uploadPart.getSubmittedFileName().replaceAll("[^a-zA-Z0-9'!,_ .-]", "_");
if (!schemNameMatcher.matcher(filename).matches())
{
response.getWriter().println(schematicUploadBadHTML("That is not a valid schematic filename!"));
return;
}
boolean alreadyExists = schematics != null && Arrays.stream(schematics).anyMatch(file -> HTTPDModule.fileNameEquals(file.getName(), filename));
if (alreadyExists)
{
response.getWriter().println(schematicUploadBadHTML("A schematic with the name <b>" + filename + "</b> already exists!"));
return;
}
InputStream inputStream = uploadPart.getInputStream();
File schematicFile = new File(worldeditFolder, filename);
FileUtils.copyInputStreamToFile(inputStream, schematicFile);
ClipboardFormat schematicFormat = ClipboardFormats.findByFile(schematicFile);
if (schematicFormat == null)
{
PlexLog.log("IP Address: " + request.getRemoteAddr() + " FAILED to upload schematic with filename: " + filename);
response.getWriter().println(schematicUploadBadHTML("Schematic is not a valid format."));
FileUtils.deleteQuietly(schematicFile);
return;
}
try
{
schematicFormat.getReader(new FileInputStream(schematicFile));
}
catch (IOException e)
{
PlexLog.log("IP Address: " + request.getRemoteAddr() + " FAILED to upload schematic with filename: " + filename);
response.getWriter().println(schematicUploadBadHTML("Schematic is not a valid format."));
FileUtils.deleteQuietly(schematicFile);
return;
}
// Files.copy(inputStream, schematic.toPath(), StandardCopyOption.REPLACE_EXISTING);
inputStream.close();
response.getWriter().println(schematicUploadGoodHTML("Successfully uploaded <b>" + filename + "</b>."));
PlexLog.log("IP Address: " + request.getRemoteAddr() + " uploaded schematic with filename: " + filename);
}
private String schematicUploadBadHTML(String message)
{
String file = AbstractServlet.readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload_bad.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
private String schematicUploadGoodHTML(String message)
{
String file = AbstractServlet.readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload_good.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
}

View File

@ -1,59 +0,0 @@
package dev.plex.request.impl;
import com.google.common.collect.Lists;
import com.google.gson.GsonBuilder;
import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import dev.plex.util.adapter.LocalDateTimeSerializer;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
public class AdminsEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "/api/admins/")
public String getAdmins(HttpServletRequest request)
{
String ipAddress = request.getRemoteAddr();
if (ipAddress == null)
{
return "An IP address could not be detected. Please ensure you are connecting using IPv4.";
}
final PlexPlayer player = DataUtils.getPlayerByIP(ipAddress);
if (player == null)
{
// This likely means they've never joined the server before. That's okay. We can just not return IPs.
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
if (Plex.get().getSystem().equalsIgnoreCase("ranks"))
{
PlexLog.debug("Plex-HTTPD using ranks check");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
// Don't return IPs either if the person is not an Admin or above.
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.admins.access"))
{
// If the person doesn't have permission, don't return IPs
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
}
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers());
}
}

View File

@ -0,0 +1,27 @@
package dev.plex.request.impl;
import dev.plex.command.PlexCommand;
import dev.plex.command.annotation.CommandPermissions;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginIdentifiableCommand;
import java.util.*;
public class AuthenticationEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "/oauth2")
public String login(HttpServletRequest request, HttpServletResponse response)
{
// TODO: Nuh uh
return "";
}
}

View File

@ -0,0 +1,111 @@
package dev.plex.request.impl;
import dev.plex.command.PlexCommand;
import dev.plex.command.annotation.CommandPermissions;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginIdentifiableCommand;
public class CommandsEndpoint extends AbstractServlet
{
private final StringBuilder list = new StringBuilder();
private boolean loadedCommands = false;
@GetMapping(endpoint = "/api/commands/")
public String getCommands(HttpServletRequest request, HttpServletResponse response)
{
if (!loadedCommands)
{
final SortedMap<String, List<Command>> commandMap = new TreeMap<>();
final CommandMap map = Bukkit.getCommandMap();
for (Command command : map.getKnownCommands().values())
{
String plugin = "Bukkit";
if (command instanceof PluginIdentifiableCommand)
{
plugin = ((PluginIdentifiableCommand) command).getPlugin().getName();
}
List<Command> pluginCommands = commandMap.computeIfAbsent(plugin, k -> new ArrayList<>());
if (!pluginCommands.contains(command))
{
pluginCommands.add(command);
}
}
for (String key : commandMap.keySet())
{
commandMap.get(key).sort(Comparator.comparing(Command::getName));
StringBuilder rows = new StringBuilder();
for (Command command : commandMap.get(key))
{
String permission = command.getPermission();
if (command instanceof PlexCommand plexCmd)
{
CommandPermissions perms = plexCmd.getClass().getAnnotation(CommandPermissions.class);
if (perms != null)
{
permission = (perms.permission().isBlank() ? "N/A" : perms.permission());
}
}
rows.append(createRow(command.getName(), command.getAliases(), command.getDescription(), command.getUsage(), permission));
}
list.append(createTable(key, rows.toString())).append("\n");
}
loadedCommands = true;
}
return commandsHTML(list.toString());
}
private String commandsHTML(String commandsList)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/commands.html"));
file = file.replace("${commands}", commandsList);
return file;
}
private String createTable(String pluginName, String commandRows)
{
return "<details id=\"" + pluginName + "\"><summary>" + pluginName + "</summary>\n"
+ "<table id=\"" + pluginName + "Table\" class=\"table table-striped table-bordered\">\n"
+ " <thead>\n <tr>\n <th scope=\"col\">Name (Aliases)</th>\n "
+ "<th scope=\"col\">Description</th>\n "
+ "<th scope=\"col\">Usage</th>\n "
+ "<th scope=\"col\">Permission</th>\n </tr>\n</thead>\n"
+ "<tbody>\n " + commandRows + "\n</tbody>\n</table>\n</details>";
}
private String createRow(String name, List<String> aliases, String description, String usage, String permission)
{
return " <tr>\n <th scope=\"row\">" + name
+ (aliases.isEmpty() || aliases.toString().equals("[]") ? "" : " (" + String.join(", ", aliases) + ")") + "</th>\n"
+ " <th scope=\"row\">" + description + "</th>\n"
+ " <th scope=\"row\"><code>" + cleanUsage(usage) + "</code></th>\n"
+ " <th scope=\"row\">" + (permission != null ? permission.replaceAll(";", "<br>") : "N/A") + "</th>\n </tr>";
}
private String cleanUsage(String usage)
{
usage = usage.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
if (usage.isBlank())
{
usage = "Not Provided";
}
return usage.startsWith("/") || usage.equals("Not Provided") ? usage : "/" + usage;
}
}

View File

@ -5,47 +5,46 @@ import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest;
import java.util.UUID;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
public class IndefBansEndpoint extends AbstractServlet
{
private static final String TITLE = "Indefinite Bans - Plex HTTPD";
@GetMapping(endpoint = "/api/indefbans/")
public String getBans(HttpServletRequest request)
public String getBans(HttpServletRequest request, HttpServletResponse response)
{
String ipAddress = request.getRemoteAddr();
if (ipAddress == null)
{
return "An IP address could not be detected. Please ensure you are connecting using IPv4.";
return indefbansHTML("An IP address could not be detected. Please ensure you are connecting using IPv4.");
}
final PlexPlayer player = DataUtils.getPlayerByIP(ipAddress);
if (player == null)
{
return "Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?";
return indefbansHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?");
}
if (Plex.get().getSystem().equalsIgnoreCase("ranks"))
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.indefbans.access"))
{
PlexLog.debug("Plex-HTTPD using ranks check");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
return "Not a high enough rank to view this page.";
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.indefbans.access"))
{
return "Not enough permissions to view this page.";
}
return indefbansHTML("Not enough permissions to view this page.");
}
response.setHeader("content-type", "application/json");
return new GsonBuilder().setPrettyPrinting().create().toJson(Plex.get().getPunishmentManager().getIndefiniteBans().stream().toList());
}
private String indefbansHTML(String message)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/indefbans.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
}

View File

@ -0,0 +1,34 @@
package dev.plex.request.impl;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
public class IndexEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "//")
public String getIndex(HttpServletRequest request, HttpServletResponse response)
{
return indexHTML();
}
@GetMapping(endpoint = "/api/")
public String getAPI(HttpServletRequest request, HttpServletResponse response)
{
return indexHTML();
}
private String indexHTML()
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/index.html"));
String isAre = Bukkit.getOnlinePlayers().size() == 1 ? " is " : " are ";
String pluralOnline = Bukkit.getOnlinePlayers().size() == 1 ? " player " : " players ";
String pluralMax = Bukkit.getMaxPlayers() == 1 ? " player " : " players ";
file = file.replace("${is_are}", isAre);
file = file.replace("${server_online_players}", Bukkit.getOnlinePlayers().size() + pluralOnline);
file = file.replace("${server_total_players}", Bukkit.getMaxPlayers() + pluralMax);
return file;
}
}

View File

@ -3,19 +3,22 @@ package dev.plex.request.impl;
import com.google.gson.GsonBuilder;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.request.MappingHeaders;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class ListEndpoint extends AbstractServlet
{
List<String> players = new ArrayList<>();
@GetMapping(endpoint = "/api/list/")
public String getOnlinePlayers(HttpServletRequest request)
@MappingHeaders(headers = "content-type;application/json")
public String getOnlinePlayers(HttpServletRequest request, HttpServletResponse response)
{
List<String> players = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers())
{
players.add(player.getName());

View File

@ -2,69 +2,31 @@ package dev.plex.request.impl;
import com.google.gson.GsonBuilder;
import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import dev.plex.util.adapter.LocalDateTimeSerializer;
import dev.plex.util.adapter.ZonedDateTimeAdapter;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import jakarta.servlet.http.HttpServletResponse;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import java.time.LocalDateTime;
import java.util.UUID;
public class PunishmentsEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "/api/punishments/")
public String getPunishments(HttpServletRequest request)
public String getPunishments(HttpServletRequest request, HttpServletResponse response)
{
String ipAddress = request.getRemoteAddr();
if (ipAddress == null)
{
return "An IP address could not be detected. Please ensure you are connecting using IPv4.";
return punishmentsHTML("An IP address could not be detected. Please ensure you are connecting using IPv4.");
}
if (request.getPathInfo() == null)
if (request.getPathInfo() == null || request.getPathInfo().equals("/"))
{
/*StringBuilder contentBuilder = new StringBuilder();
PlexLog.log(this.getClass().getClassLoader().getResource("punishments.html").getPath());
try
{
BufferedReader in = new BufferedReader(new FileReader(this.getClass().getClassLoader().getResource("punishments.html").getFile().replace("!", "")));
String str;
while ((str = in.readLine()) != null)
{
contentBuilder.append(str);
}
in.close();
}
catch (IOException ignored)
{
}
return contentBuilder.toString();*/
return """
<!DOCTYPE html>
<body>
<div style="text-align: center;">
<h2 style="font-family:Helvetica">Enter the UUID or username of the player you want to check</h2>
<input id="test" type="text" autofocus>
<button type="button" onclick="redirect()">Submit</button>
<script>
function redirect() {
var url = document.getElementById('test').value
window.location = "punishments/" + url
}
</script>
</div>
</body>
</html>""";
return readFile(this.getClass().getResourceAsStream("/httpd/punishments.html"));
}
UUID pathUUID;
String pathPlexPlayer;
@ -83,36 +45,39 @@ public class PunishmentsEndpoint extends AbstractServlet
final PlexPlayer player = DataUtils.getPlayerByIP(ipAddress);
if (punishedPlayer == null)
{
return "This player has never joined the server before.";
return punishmentsHTML("This player has never joined the server before.");
}
if (punishedPlayer.getPunishments().isEmpty())
{
return "This player has been a good boy. They have no punishments!";
return punishmentsGoodHTML("This player has been a good boy. They have no punishments!");
}
if (player == null)
{
// If the player is null, give it to them without the IPs
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
if (Plex.get().getSystem().equalsIgnoreCase("ranks"))
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.punishments.access"))
{
PlexLog.debug("Plex-HTTPD using ranks check");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
// Don't return IPs either if the person is not an Admin or above.
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
// If the person doesn't have permission, don't return IPs
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.punishments.access"))
{
// If the person doesn't have permission, don't return IPs
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
}
return new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().toList());
response.setHeader("content-type", "application/json");
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().toList());
}
private String punishmentsHTML(String message)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/punishments_error.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
private String punishmentsGoodHTML(String message)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/punishments_good.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
}

View File

@ -0,0 +1,112 @@
package dev.plex.request.impl;
import dev.plex.HTTPDModule;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SchematicDownloadEndpoint extends AbstractServlet
{
List<File> files = new ArrayList<>();
@GetMapping(endpoint = "/api/schematics/download/")
public String downloadSchematic(HttpServletRequest request, HttpServletResponse response)
{
if (request.getPathInfo() == null || request.getPathInfo().equals("/"))
{
return schematicHTML();
}
else
{
OutputStream outputStream;
try
{
outputStream = response.getOutputStream();
}
catch (IOException e)
{
return null;
}
schematicServe(request.getPathInfo().replace("/", ""), outputStream);
return null;
}
}
private void schematicServe(String requestedSchematic, OutputStream outputStream)
{
File worldeditFolder = HTTPDModule.getWorldeditFolder();
if (worldeditFolder == null)
{
return;
}
File[] schems = worldeditFolder.listFiles();
if (schems != null)
{
File schemFile = Arrays.stream(schems).filter(file -> file.getName().equals(requestedSchematic)).findFirst().orElse(null);
if (schemFile != null)
{
try
{
byte[] schemData = HTTPDModule.fileCache.getFile(schemFile);
if (schemData != null)
{
outputStream.write(schemData);
}
}
catch (IOException ignored)
{
}
}
}
}
private String schematicHTML()
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/schematic_download.html"));
File worldeditFolder = HTTPDModule.getWorldeditFolder();
if (worldeditFolder == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
for (File worldeditFile : listFilesForFolder(worldeditFolder))
{
String fixedPath = worldeditFile.getPath().replace("plugins/FastAsyncWorldEdit/schematics/", "");
fixedPath = fixedPath.replace("plugins/WorldEdit/schematics/", "");
String sanitizedName = fixedPath.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
sb.append(" <tr>\n" +
" <th scope=\"row\">\n <a href=\"" + fixedPath + "\" download>" + sanitizedName + "</a>\n </th>\n" +
" <td>\n " + formattedSize(worldeditFile.length()) + "\n </td>\n" +
" </tr>\n");
}
file = file.replace("${schematics}", sb.toString());
files.clear();
return file;
}
public List<File> listFilesForFolder(final File folder)
{
for (File fileEntry : folder.listFiles())
{
if (fileEntry.isDirectory())
{
PlexLog.debug("Found directory");
listFilesForFolder(fileEntry);
}
else
{
files.add(fileEntry);
}
}
return files;
}
}

View File

@ -0,0 +1,44 @@
package dev.plex.request.impl;
import dev.plex.HTTPDModule;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
public class SchematicUploadEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "/api/schematics/upload/")
public String uploadSchematic(HttpServletRequest request, HttpServletResponse response)
{
String ipAddress = request.getRemoteAddr();
if (ipAddress == null)
{
return schematicsHTML("An IP address could not be detected. Please ensure you are connecting using IPv4.");
}
final PlexPlayer player = DataUtils.getPlayerByIP(ipAddress);
if (player == null)
{
return schematicsHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?");
}
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{
return schematicsHTML("You do not have permission to upload schematics.");
}
return readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload.html"));
}
private String schematicsHTML(String message)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload_bad.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/Telesphoreo/bootstrap-color-switcher@master/script.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@200;400&display=swap" rel="stylesheet">
<title>${TITLE} - Plex HTTPD</title>
</head>
<body style="font-family: 'IBM Plex Sans', sans-serif;">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/" style="font-weight:200;">Plex HTTPD</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link ${ACTIVE_HOME}" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_LIST}" href="/api/list/">List</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_PUNISHMENTS}" href="/api/punishments/">Punishments</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle ${ACTIVE_SCHEMATICS}" id="navbarDropdownMenuLink" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
Schematics
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li><a class="dropdown-item" href="/api/schematics/download/">Download</a></li>
<li><a class="dropdown-item" href="/api/schematics/upload/">Upload</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div style="text-align: center;" class="col-auto m-0 row justify-content-center p-4">
</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
Commands
COMMANDS
<style>
summary {
font-size: 24px;
padding: 16px;
}
</style>
<h2>Commands List</h2>
<h5>A list of commands is below.</h5>
<br><br>
${commands}

View File

@ -0,0 +1,15 @@
server:
bind-address: 0.0.0.0
port: 27192
logging: false
authentication:
enabled: false
# Providers: discord
provider:
name: discord
redirectUri: ""
discord: # Fill if using discord provider
clientId: ""
token: "" # Can also use environment variable or system property BOT_TOKEN

View File

@ -0,0 +1,4 @@
Indefinite Bans
INDEFBANS
<h2>Indefinite Bans</h2>
<h5 class="alert alert-danger mb-3 w-auto p-3" role="alert"><b>Error:</b> ${MESSAGE}</h5>

View File

@ -0,0 +1,5 @@
Home
HOME
<h2>Welcome to the Plex HTTPD!</h2>
<h4>Use the sidebar to navigate the available pages.</h4>
<h4><br>There ${is_are} currently ${server_online_players} online out of ${server_total_players} total.</h4>

View File

@ -0,0 +1,23 @@
Punishments
PUNISHMENTS
<h2>Punishment Search</h2>
<label for="uuid"><h5>Enter the UUID or username of the player you want to lookup</h5></label>
<div class="input-group mb-3 w-75 p-3">
<input id="uuid" type="text" autocomplete="off" autofocus class="form-control">
<button class="btn btn-outline-primary" type="submit"
onclick="redirect();">Submit
</button>
</div>
<script>
function redirect() {
const url = document.getElementById('uuid').value;
window.location = "/api/punishments/" + url
}
</script>
<script>
document.getElementById('uuid').addEventListener('keypress', function (event) {
if (event.keyCode === 13) {
redirect();
}
});
</script>

View File

@ -0,0 +1,4 @@
Punishments
PUNISHMENTS
<h2>Punishment Search</h2>
<h5 class="alert alert-danger mb-3 w-auto p-3" role="alert"><b>Error:</b> ${MESSAGE}</h5>

View File

@ -0,0 +1,4 @@
Punishments
PUNISHMENTS
<h2>Punishment Search</h2>
<h5 class="alert alert-success mb-3 w-auto p-3" role="alert">${MESSAGE}</h5>

View File

@ -0,0 +1,35 @@
Schematics
SCHEMATICS
<h2>Schematic Download</h2>
<label for="schemList"><h5>A list of schematics is below. You can click on the schematic name to download it.</h5>
</label>
<div class="input-group mb-3 w-75 p-3">
<input type="text" autocomplete="off" autofocus class="form-control" oninput="filterTable(this.value)"
placeholder="Search for a schematic...">
</div>
<table id="schemList" class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Size</th>
</tr>
</thead>
<tbody>
${schematics}
</tbody>
</table>
<script>
let schemList = document.getElementById("schemList").getElementsByTagName("tbody")[0].getElementsByTagName("tr");
function filterTable(query) {
for (let i = 0; i < schemList.length; i++) {
let th = schemList[i].getElementsByTagName("th")[0];
let schemName = th.textContent || th.innerText;
if (schemName.toLowerCase().includes(query.toLowerCase())) {
schemList[i].style.display = "";
} else {
schemList[i].style.display = "none";
}
}
}
</script>

View File

@ -0,0 +1,13 @@
Schematics
SCHEMATICS
<h2>Schematic Upload</h2>
<div class="cos-xs-8 col-lg-5">
<label for="formFile" class="form-label"><h5>Please select a schematic file to upload.</h5></label>
<form class="input-group justify-content-center" enctype="multipart/form-data" method="post"
action="/api/schematics/uploading">
<div class="input-group">
<input type="file" class="form-control" id="formFile" name="file" aria-describedby="formFile" aria-label="Upload">
<button class="btn btn-outline-primary" type="submit">Upload</button>
</div>
</form>
</div>

View File

@ -0,0 +1,4 @@
Schematics
SCHEMATICS
<h2>Schematic Upload</h2>
<h5 class="alert alert-danger mb-3 w-auto p-3" role="alert"><b>Error:</b> ${MESSAGE}</h5>

View File

@ -0,0 +1,4 @@
Schematics
SCHEMATICS
<h2>Schematic Upload</h2>
<h5 class="alert alert-success mb-3 w-auto p-3" role="alert">${MESSAGE}</h5>

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/Telesphoreo/bootstrap-color-switcher@master/script.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@200;400&display=swap" rel="stylesheet">
<title>${TITLE} - Plex HTTPD</title>
</head>
<body style="font-family: 'IBM Plex Sans', sans-serif;">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/" style="font-weight:200;">Plex HTTPD</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link ${ACTIVE_HOME}" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_LIST}" href="/api/list/">List</a>
</li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_PUNISHMENTS}" href="/api/punishments/">Punishments</a>
</li>
<li>
<a class="nav-link ${ACTIVE_COMMANDS}" href="/api/commands/">Commands</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle ${ACTIVE_SCHEMATICS}" id="navbarDropdownMenuLink" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
Schematics
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li><a class="dropdown-item" href="/api/schematics/download/">Download</a></li>
<li><a class="dropdown-item" href="/api/schematics/upload/">Upload</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div style="text-align: center;" class="col-auto m-0 row justify-content-center p-4">
${CONTENT}
</div>
</body>
</html>

View File

@ -1,4 +1,4 @@
name: Plex-HTTPD
version: 1.0.1
name: Module-HTTPD
version: 1.4-SNAPSHOT
description: HTTPD server for Plex
main: dev.plex.HTTPDModule

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<body>
<div style="text-align: center;">
<h2 style="font-family:Helvetica">Enter the UUID or username of the player you want to check</h2>
<input id="test" type="text" autofocus>
<button type="button" onclick="redirect()">Submit</button>
<script>
function redirect() {
var url = document.getElementById('test').value
window.location = "punishments/" + url
}
</script>
</div>
</body>
</html>

View File

@ -1,4 +0,0 @@
server:
bind-address: 0.0.0.0
port: 27192
logging: false