Compare commits
406 Commits
fe00e7d50b
...
renovate/s
Author | SHA1 | Date | |
---|---|---|---|
|
6d66bdf6c1 | ||
d9b07c3e1b | |||
80df977047 | |||
526d68763f | |||
72a2a4bb3a | |||
855b040e8f | |||
|
31f5ca464d | ||
e0c92ca95a | |||
57dc61f745 | |||
b06cbdddda | |||
5292da63b5 | |||
39407ac8b0 | |||
2e319488a9 | |||
490b04ba61 | |||
531c18626a | |||
1570ade4c0 | |||
4675708a8b | |||
295081431b | |||
01743d2499 | |||
7e7ac42b8e | |||
08acdaef03 | |||
ad215b7b72 | |||
9d594a8c37 | |||
856e445182 | |||
ae94af5980 | |||
00a9dc0ce8 | |||
fce1fa24b1 | |||
a2615ac96a | |||
1962110dbd | |||
758a3bf0aa | |||
7abd00a692 | |||
e4c92cc0e0 | |||
|
f9614bacf7 | ||
|
debc8a43bf | ||
6d3418e16c | |||
bb9a3044a7 | |||
d7076c6fa6 | |||
f79d42a45e | |||
58045b19e1 | |||
0028f27c20 | |||
83aee00a72 | |||
fed80a0aa7 | |||
|
f70503f80b | ||
|
a615239386 | ||
0cf97926ea | |||
acb91cc829 | |||
790beafce4 | |||
211bfd0d2b | |||
bb416a8734 | |||
2be04ec1d6 | |||
bbeeed8f84 | |||
4e2e0f328c | |||
16b295a784 | |||
dbd6e825f1 | |||
a43c40d88f | |||
2783396446 | |||
494266d2ea | |||
4069932f0d | |||
1a07fbedb6 | |||
0655c0b651 | |||
2494d9ab45 | |||
8b332ab400 | |||
477ac04d91 | |||
104dc11e35 | |||
4fbc977034 | |||
409b273edc | |||
f943ca54fb | |||
a683b7225e | |||
6c8d486052 | |||
1474598a1a | |||
0ba927b7d1 | |||
e6a12c3675 | |||
f9bd49c4bb | |||
43d1ea9ddd | |||
77e5daa375 | |||
f08ff7602f | |||
d929d948ab | |||
10669f1b4a | |||
|
ffa65b8ee4 | ||
cf5ee56b64 | |||
6b38e9f301 | |||
cd7c178250 | |||
|
72bf0cdcd0 | ||
|
fecd1bfade | ||
d35443db33 | |||
40526cd0f8 | |||
|
2f94355398 | ||
51dcc75a86 | |||
d9c09a1aae | |||
0a530413c4 | |||
9dc5ad029b | |||
ab7f6162fb | |||
743f789a7f | |||
7b1745822d | |||
e21db5c876 | |||
8e43044877 | |||
|
a4fad0af0a | ||
88705f363b | |||
db88533eed | |||
68c932c39b | |||
0f9bea22f5 | |||
10ab624d63 | |||
53f66cd7e4 | |||
a22c6c16be | |||
4b55674a98 | |||
|
b359200a18 | ||
|
9ec0c95e6d | ||
8456829c30 | |||
cc67bfd054 | |||
2261bde6cb | |||
52ff61c554 | |||
253c889b90 | |||
549e46c978 | |||
023395ea69 | |||
90aea4a9cc | |||
8f297aef26 | |||
7b602a0f21 | |||
fb14eef179 | |||
81886fa097 | |||
f3a57dc8d9 | |||
1392d82480 | |||
02695ce7a2 | |||
5c32804114 | |||
1a4929d8b5 | |||
c689434ec4 | |||
345e1532a4 | |||
4d575fdf2e | |||
867b9b263a | |||
491295f5bd | |||
2eb9286dcf | |||
2ef6f6e4e2 | |||
eda3045cf4 | |||
8b31904095 | |||
58a97708fd | |||
3c912a4591 | |||
f021eb4809 | |||
5d2136ce5a | |||
a33c1681bd | |||
e50e6553c8 | |||
097a5d74fe | |||
fb0b82edd3 | |||
060e991741 | |||
8642740f90 | |||
17094d7cec | |||
fe34001790 | |||
8ea4c20137 | |||
21354da41e | |||
183c61cfdc | |||
538bfc4d75 | |||
132f45eec8 | |||
577851c69b | |||
986f3b8f02 | |||
b8cecee57e | |||
c469b2d5cf | |||
280df436d2 | |||
6963085295 | |||
349747d555 | |||
0413e0f448 | |||
6f36c8bc4e | |||
335ed10a53 | |||
3ea7e8c70e | |||
e1e02ae0f4 | |||
8f6f5094bc | |||
75fabe648e | |||
ee6a20054c | |||
e8000a1fbc | |||
e7bdfe1f65 | |||
0f77b9aa68 | |||
9769149689 | |||
a31dcc7618 | |||
42f9e7090f | |||
f65cc4e804 | |||
0a4f00866d | |||
47d2982666 | |||
14122b0267 | |||
53c19d8e48 | |||
0dac73bff4 | |||
62d89def61 | |||
4cd88be2ad | |||
4372832196 | |||
ec332842e7 | |||
0ccfd5ea22 | |||
0d77a2e219 | |||
e1acb73805 | |||
e9f30885b0 | |||
534e67b6f0 | |||
5e67ce1f34 | |||
a665d5d0ba | |||
74392b6288 | |||
7afd38bcc2 | |||
9e60a7d8c5 | |||
b1d2e895ee | |||
c286ae4a21 | |||
b26718df8b | |||
bc769651c3 | |||
f308456a4a | |||
8bda711215 | |||
9f2dc1d4ed | |||
1a96d6a522 | |||
791f88e11b | |||
e4176a4dbe | |||
e4d05f5104 | |||
02b1eea17b | |||
e92622c023 | |||
a8cf72e981 | |||
ab05ad8a3e | |||
9a78a6988b | |||
98adb3d22d | |||
d46fd443d4 | |||
4af63c65a0 | |||
8cdd990938 | |||
408ad7d7b7 | |||
c6f118d629 | |||
1244b6d142 | |||
4bccec1f5d | |||
2f1a16f3f2 | |||
c11383107c | |||
f5c593a95f | |||
b3db5c5081 | |||
c758314d97 | |||
0d9c5cbfc6 | |||
2edbacd55c | |||
3194acfc83 | |||
|
0d5e185ffd | ||
6fa05990c6 | |||
bf1c1af0e5 | |||
e3e8df6953 | |||
507ae5c413 | |||
2aed687713 | |||
f7dea28738 | |||
b08cb6efb6 | |||
f1cd72e8b3 | |||
ab56798ae9 | |||
5e086c17b8 | |||
817493bfd4 | |||
|
28e28c8ade | ||
c4d915095c | |||
a57e95806e | |||
618d523ca2 | |||
bacdc9319f | |||
cb0280f817 | |||
fd16430307 | |||
979018b508 | |||
6e21847c3b | |||
e104bc8728 | |||
e38b9ff539 | |||
038766b8b1 | |||
|
9332bbde13 | ||
727a8f801f | |||
4da56be833 | |||
|
4bfeb88989 | ||
8bcc5aa352 | |||
46a0869b85 | |||
cb75a3a4c8 | |||
bf3a09afd4 | |||
4b5c8d1d89 | |||
d1d78ef71b | |||
7c9ba6d151 | |||
a20b8008f4 | |||
4494bd74ec | |||
56563802be | |||
367c974cb3 | |||
b52f9d1d38 | |||
a1dfa6b6fa | |||
9905673b1e | |||
9dbe9632c0 | |||
69ce0fc469 | |||
c9f15011af | |||
cb6e5dc794 | |||
0610a6acfa | |||
6790083006 | |||
b560341068 | |||
6a9ec1fe9f | |||
2f419c1754 | |||
fa2a2dd8d8 | |||
eadcebab4d | |||
4dfc081305 | |||
7dd5adfffb | |||
|
2de5aa5581 | ||
132b3e0315 | |||
6d728dcb10 | |||
87d6e0ab3b | |||
eff8af532a | |||
05131b614d | |||
b6d59b9598 | |||
|
ca343f22c6 | ||
ab495cd471 | |||
ae4ece4ee5 | |||
54c116b85c | |||
d5187aff6c | |||
f4f51b8912 | |||
51b8d763c1 | |||
45f38396c6 | |||
b89fd74515 | |||
490ba046ef | |||
03df9aabdf | |||
9b87a84215 | |||
21f31c0f1d | |||
0216662767 | |||
c7d0f31852 | |||
198d8cf1c1 | |||
c707b0d3ac | |||
a503e814f9 | |||
bfd6a93229 | |||
3431e7fe03 | |||
0972e6d102 | |||
83b2a96490 | |||
6dbbc50610 | |||
0fb320c076 | |||
3beccf01c8 | |||
c6259e3c8d | |||
3588b5f93c | |||
a90299fae7 | |||
7fe1b51973 | |||
abc35e4b21 | |||
86f71ad0c1 | |||
10b12a60da | |||
8195239251 | |||
6dca925ee4 | |||
31806a6d58 | |||
8f2cd92e62 | |||
c367fc7474 | |||
b5251cbf59 | |||
7465ba6175 | |||
750c4cbc63 | |||
3d48b9fd26 | |||
ec393c0f64 | |||
7958fb3c21 | |||
061b7df8b1 | |||
18c298376a | |||
d29004f673 | |||
0072dc0362 | |||
158b3ec69e | |||
12cf444578 | |||
01a55e63b4 | |||
90e170b577 | |||
75316d5f6b | |||
9cf2f10ee9 | |||
77bff8067e | |||
9be372e3e0 | |||
053bfedfa1 | |||
fc382fad27 | |||
bb81be1e28 | |||
3b9cf6f818 | |||
cacf1dc4cc | |||
a51528616d | |||
9f1ce626df | |||
1277644bcb | |||
ffe005d995 | |||
3365d01c8e | |||
2214636c47 | |||
74a3305ddd | |||
3171346d4b | |||
fbbb4e1483 | |||
834ae9df44 | |||
3d409dc661 | |||
cad22ad30a | |||
9139972955 | |||
6322af94bb | |||
d1365ae261 | |||
3e42ed414a | |||
d834badb78 | |||
baac1391ee | |||
a218dc7ec3 | |||
1eb4a9ec0e | |||
bf9fa6582f | |||
b07d8c0888 | |||
aa3813cb34 | |||
cf345c3410 | |||
76201edd92 | |||
1c1ea04cf0 | |||
49a6e9b7b0 | |||
4c614525b0 | |||
6c98d1e862 | |||
b087666afb | |||
67d6df8916 | |||
dfe99c1acf | |||
4b3e478329 | |||
42cfab11f2 | |||
92a609ece5 | |||
6a561a8000 | |||
39556c2a8d | |||
96cbbc1632 | |||
f4df437fc7 | |||
57bfc1b9da | |||
07ab78fd08 | |||
f4b8d5708f | |||
6c4ee55b95 | |||
d1b33d3d6e | |||
549bbc864e | |||
d49c9b20e4 | |||
7ad3c0f1f3 | |||
e3b88ec32e | |||
4c0346ab3e | |||
13d0a344b9 | |||
7ebeb39cc3 | |||
acae5dbcd6 | |||
2d58908626 | |||
50b5c8ba4b | |||
6e096554d8 | |||
71cbf6ef1b | |||
ae4079b6d8 | |||
7e98b68db0 | |||
e8b365b6f0 | |||
d45b26fe09 | |||
13c0a76e97 |
@ -3,20 +3,18 @@ name: Deploy API
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths: ["API/**"]
|
||||
paths: [".gitea/workflows/deploy-api.yml", "API/**", "!API/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["ubuntu-latest"]
|
||||
git-version: ["2.44.0"]
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: ${{ matrix.arch }}
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./API
|
||||
working-directory: "./API"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
@ -46,5 +44,5 @@ jobs:
|
||||
- name: Deploy to Dokku
|
||||
uses: dokku/github-action@master
|
||||
with:
|
||||
git_remote_url: "ssh://dokku@10.10.3.28:22/restfulmc"
|
||||
git_remote_url: "ssh://dokku@10.10.3.28:22/restfulmc-api"
|
||||
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
38
.gitea/workflows/deploy-bot.yml
Normal file
38
.gitea/workflows/deploy-bot.yml
Normal file
@ -0,0 +1,38 @@
|
||||
name: Deploy Discord Bot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths: [".gitea/workflows/deploy-bot.yml", "DiscordBot/**", "!DiscordBot/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./DiscordBot"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Java and Maven
|
||||
- name: Set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@v1.12.0
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: "zulu"
|
||||
maven-version: ${{ matrix.maven-version }}
|
||||
|
||||
# Deploy to Dokku
|
||||
- name: Deploy to Dokku
|
||||
uses: dokku/github-action@master
|
||||
with:
|
||||
git_remote_url: "ssh://dokku@10.10.3.28:22/restfulmc-bot"
|
||||
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
32
.gitea/workflows/deploy-frontend.yml
Normal file
32
.gitea/workflows/deploy-frontend.yml
Normal file
@ -0,0 +1,32 @@
|
||||
name: Deploy Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths: [".gitea/workflows/deploy-frontend.yml", "Frontend/**", "!Frontend/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./Frontend"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Deploy to Dokku
|
||||
- name: Deploy to Dokku
|
||||
uses: dokku/github-action@master
|
||||
with:
|
||||
git_remote_url: "ssh://dokku@10.10.3.28:22/restfulmc-web"
|
||||
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
53
.gitea/workflows/publish-java-sdk.yml
Normal file
53
.gitea/workflows/publish-java-sdk.yml
Normal file
@ -0,0 +1,53 @@
|
||||
name: Publish Java SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
[".gitea/workflows/publish-java-sdk.yml", "Java-SDK/**", "!Java-SDK/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./Java-SDK"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Java and Maven
|
||||
- name: Set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@v1.12.0
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: "zulu"
|
||||
maven-version: ${{ matrix.maven-version }}
|
||||
|
||||
# Configure Maven settings
|
||||
- name: Maven Settings
|
||||
uses: s4u/maven-settings-action@v3.0.0
|
||||
with:
|
||||
servers: |-
|
||||
[
|
||||
{
|
||||
"id": "rainnny-repo-public",
|
||||
"username": "${{ secrets.PRIVATE_MAVEN_USER }}",
|
||||
"password": "${{ secrets.PRIVATE_MAVEN_PASS }}"
|
||||
}
|
||||
]
|
||||
|
||||
# Build the project
|
||||
- name: Maven Build
|
||||
run: mvn clean package -e -T6C
|
||||
|
||||
# Publish to Maven
|
||||
- name: Publish to Maven
|
||||
run: mvn deploy -Pgen-javadocs -B -Dstyle.color=always --update-snapshots -e -T6C
|
38
.gitea/workflows/publish-js-sdk.yml
Normal file
38
.gitea/workflows/publish-js-sdk.yml
Normal file
@ -0,0 +1,38 @@
|
||||
name: Publish JS SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
[".gitea/workflows/publish-js-sdk.yml", "JS-SDK/**", "!JS-SDK/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./JS-SDK"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Bun
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
# Install Dependencies
|
||||
- name: Install Dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
# Run Tests
|
||||
- name: Run Tests
|
||||
run: bun test
|
||||
|
||||
# Publish to NPM
|
||||
- name: Publish to NPM
|
||||
run: npm publish
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.PUBLISH_NPM_TOKEN }}
|
17
API/README.md
Normal file
17
API/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
[![Deploy Workflow](https://git.rainnny.club/Rainnny/RESTfulMC/actions/workflows/deploy-api.yml/badge.svg)](./actions?workflow=deploy-api.yml)
|
||||
|
||||
# RESTfulMC Springboot API
|
||||
The springboot API backend for RESTfulMC.
|
||||
|
||||
## Swagger API
|
||||
View the [Swagger API Docs](https://api.restfulmc.cc/swagger-ui.html) for easy testing!
|
||||
|
||||
![Swagger Logo](https://cdn.rainnny.club/UoeVQF7wYGg4.png)
|
||||
|
||||
---
|
||||
|
||||
## YourKit
|
||||
YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications.
|
||||
YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler), and [YourKit YouMonitor](https://www.yourkit.com/youmonitor).
|
||||
|
||||
![Yourkit Logo](https://www.yourkit.com/images/yklogo.png)
|
26
API/pom.xml
26
API/pom.xml
@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<version>3.2.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- Project Details -->
|
||||
<groupId>me.braydon</groupId>
|
||||
<groupId>cc.restfulmc</groupId>
|
||||
<artifactId>RESTfulMC</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<description>A simple, yet useful RESTful API for Minecraft utilizing Springboot.</description>
|
||||
@ -107,6 +107,12 @@
|
||||
<version>1.20-R0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-archiver</artifactId>
|
||||
<version>4.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- DNS Lookup -->
|
||||
<dependency>
|
||||
@ -116,6 +122,14 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- GeoIP -->
|
||||
<dependency>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
<artifactId>geoip2</artifactId>
|
||||
<version>4.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SwaggerUI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
@ -124,6 +138,14 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Sentry -->
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
|
||||
<version>7.8.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc;
|
||||
package cc.restfulmc.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
@ -97,7 +97,7 @@ public final class ColorUtils {
|
||||
nextIsColor = false;
|
||||
continue;
|
||||
}
|
||||
builder.append(character); // Append the char...
|
||||
builder.append(character == ' ' ? " " : character); // Append the char...
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import cc.restfulmc.api.model.dns.impl.ARecord;
|
||||
import cc.restfulmc.api.model.dns.impl.SRVRecord;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import me.braydon.mc.model.dns.impl.ARecord;
|
||||
import me.braydon.mc.model.dns.impl.SRVRecord;
|
||||
import org.xbill.DNS.Lookup;
|
||||
import org.xbill.DNS.Record;
|
||||
import org.xbill.DNS.Type;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.UtilityClass;
|
||||
@ -35,8 +35,9 @@ public final class EnvironmentUtils {
|
||||
* Is the app running in a production environment?
|
||||
*/
|
||||
@Getter private static final boolean production;
|
||||
static { // Are we running on production?
|
||||
String env = System.getenv("APP_ENV");
|
||||
production = env != null && (env.equals("production"));
|
||||
static {
|
||||
// Are we running on production?
|
||||
String appEnv = System.getenv("APP_ENV");
|
||||
production = appEnv != null && (appEnv.equals("production"));
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.NonNull;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
@ -36,7 +36,7 @@ import java.util.regex.Pattern;
|
||||
@UtilityClass
|
||||
public final class MiscUtils {
|
||||
private static final Pattern USERNAME_REGEX = Pattern.compile("^[a-zA-Z0-9_]{2,16}$");
|
||||
private static final List<String> WHITELISTED_NAMES = Arrays.asList("8", "g");
|
||||
private static final List<String> WHITELISTED_USERNAMES = Arrays.asList("8", "g");
|
||||
|
||||
/**
|
||||
* Check if the given username is a valid.
|
||||
@ -45,9 +45,6 @@ public final class MiscUtils {
|
||||
* @return whether the username is valid
|
||||
*/
|
||||
public static boolean isUsernameValid(@NonNull String username) {
|
||||
if (WHITELISTED_NAMES.contains(username.toLowerCase())) { // Name is whitelisted
|
||||
return true;
|
||||
}
|
||||
return USERNAME_REGEX.matcher(username).matches();
|
||||
return WHITELISTED_USERNAMES.contains(username.toLowerCase()) || USERNAME_REGEX.matcher(username).matches();
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@ -39,14 +39,20 @@ import java.net.UnknownHostException;
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public enum MojangServer {
|
||||
SESSION("https://sessionserver.mojang.com"),
|
||||
API("https://api.mojang.com"),
|
||||
TEXTURES("https://textures.minecraft.net"),
|
||||
ASSETS("https://assets.mojang.com"),
|
||||
LIBRARIES("https://libraries.minecraft.net");
|
||||
SESSION("Session Server", "https://sessionserver.mojang.com"),
|
||||
API("Mojang API", "https://api.mojang.com"),
|
||||
TEXTURES("Textures Server", "https://textures.minecraft.net"),
|
||||
ASSETS("Assets Server", "https://assets.mojang.com"),
|
||||
LIBRARIES("Libraries Server", "https://libraries.minecraft.net"),
|
||||
SERVICES("Minecraft Services", "https://api.minecraftservices.com");
|
||||
|
||||
private static final int STATUS_TIMEOUT = 7000;
|
||||
|
||||
/**
|
||||
* The name of this server.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The endpoint of this service.
|
||||
*/
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common;
|
||||
package cc.restfulmc.api.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.common.packet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public abstract class JavaQueryPacket extends UDPPacket {
|
||||
protected static byte[] MAGIC = { (byte) 0xFE, (byte) 0xFD };
|
||||
|
||||
protected final byte[] padArrayEnd(byte[] array, int amount) {
|
||||
byte[] result = new byte[array.length + amount];
|
||||
System.arraycopy(array, 0, result, 0, array.length);
|
||||
for (int i = array.length; i < result.length; i++) {
|
||||
result[i] = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected final byte[] intToBytes(int input) {
|
||||
return new byte[] {
|
||||
(byte) (input >>> 24 & 0xFF),
|
||||
(byte) (input >>> 16 & 0xFF),
|
||||
(byte) (input >>> 8 & 0xFF),
|
||||
(byte) (input & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
protected final byte[] trim(byte[] arr) {
|
||||
int begin = 0, end = arr.length;
|
||||
for (int i = 0; i < arr.length; i++) { // find the first non-null byte{
|
||||
if (arr[i] != 0) {
|
||||
begin = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = arr.length - 1; i >= 0; i--) { //find the last non-null byte
|
||||
if (arr[i] != 0) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return subarray(arr, begin, end);
|
||||
}
|
||||
|
||||
protected final byte[] subarray(byte[] in, int a, int b) {
|
||||
if (b - a > in.length) {
|
||||
return in;
|
||||
}
|
||||
byte[] out = new byte[(b - a) + 1];
|
||||
if (b + 1 - a >= 0) {
|
||||
System.arraycopy(in, a, out, 0, b + 1 - a);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
protected final byte[][] split(byte[] input) {
|
||||
ArrayList<byte[]> temp = new ArrayList<>();
|
||||
int index_cache = 0;
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
if (input[i] == 0x00) {
|
||||
byte[] b = subarray(input, index_cache, i - 1);
|
||||
temp.add(b);
|
||||
index_cache = i + 1;//note, this is the index *after* the null byte
|
||||
}
|
||||
}
|
||||
//get the remaining part
|
||||
if (index_cache != 0) { //prevent duplication if there are no null bytes
|
||||
byte[] b = subarray(input, index_cache, input.length - 1);
|
||||
temp.add(b);
|
||||
}
|
||||
byte[][] output = new byte[temp.size()][input.length];
|
||||
for (int i = 0; i < temp.size(); i++) {
|
||||
output[i] = temp.get(i);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet;
|
||||
package cc.restfulmc.api.common.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -36,7 +36,7 @@ import java.io.IOException;
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Protocol">Protocol Docs</a>
|
||||
*/
|
||||
public abstract class MinecraftJavaPacket {
|
||||
public abstract class TCPPacket {
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet;
|
||||
package cc.restfulmc.api.common.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -35,12 +35,12 @@ import java.net.DatagramSocket;
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol">Protocol Docs</a>
|
||||
*/
|
||||
public interface MinecraftBedrockPacket {
|
||||
public abstract class UDPPacket {
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
void process(@NonNull DatagramSocket socket) throws IOException;
|
||||
public abstract void process(@NonNull DatagramSocket socket) throws IOException;
|
||||
}
|
@ -21,10 +21,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet.impl.bedrock;
|
||||
package cc.restfulmc.api.common.packet.impl.bedrock;
|
||||
|
||||
import cc.restfulmc.api.common.packet.UDPPacket;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.packet.MinecraftBedrockPacket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
@ -40,7 +40,7 @@ import java.nio.ByteOrder;
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Ping">Protocol Docs</a>
|
||||
*/
|
||||
public final class BedrockPacketUnconnectedPing implements MinecraftBedrockPacket {
|
||||
public final class BedrockUnconnectedPingPacket extends UDPPacket {
|
||||
private static final byte ID = 0x01; // The ID of the packet
|
||||
private static final byte[] MAGIC = { 0, -1, -1, 0, -2, -2, -2, -2, -3, -3, -3, -3, 18, 52, 86, 120 };
|
||||
|
@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet.impl.bedrock;
|
||||
package cc.restfulmc.api.common.packet.impl.bedrock;
|
||||
|
||||
import cc.restfulmc.api.common.packet.UDPPacket;
|
||||
import cc.restfulmc.api.model.server.BedrockMinecraftServer;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.packet.MinecraftBedrockPacket;
|
||||
import me.braydon.mc.model.server.BedrockMinecraftServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
@ -37,13 +37,13 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* This packet is sent by the server to the client in
|
||||
* response to the {@link BedrockPacketUnconnectedPing}.
|
||||
* response to the {@link BedrockUnconnectedPingPacket}.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Pong">Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class BedrockPacketUnconnectedPong implements MinecraftBedrockPacket {
|
||||
public final class BedrockUnconnectedPongPacket extends UDPPacket {
|
||||
private static final byte ID = 0x1C; // The ID of the packet
|
||||
|
||||
/**
|
@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet.impl.java;
|
||||
package cc.restfulmc.api.common.packet.impl.java.tcp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.TCPPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import me.braydon.mc.common.packet.MinecraftJavaPacket;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
@ -41,7 +41,7 @@ import java.io.IOException;
|
||||
* @see <a href="https://wiki.vg/Protocol#Handshake">Protocol Docs</a>
|
||||
*/
|
||||
@AllArgsConstructor @ToString
|
||||
public final class JavaPacketHandshakingInSetProtocol extends MinecraftJavaPacket {
|
||||
public final class JavaHandshakingInSetProtocolPacket extends TCPPacket {
|
||||
private static final byte ID = 0x00; // The ID of the packet
|
||||
private static final int STATUS_HANDSHAKE = 1; // The status handshake ID
|
||||
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.packet.impl.java;
|
||||
package cc.restfulmc.api.common.packet.impl.java.tcp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.TCPPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.packet.MinecraftJavaPacket;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@ -40,7 +40,7 @@ import java.io.IOException;
|
||||
* @see <a href="https://wiki.vg/Protocol#Status_Request">Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class JavaPacketStatusInStart extends MinecraftJavaPacket {
|
||||
public final class JavaStatusInStartPacket extends TCPPacket {
|
||||
private static final byte ID = 0x00; // The ID of the packet
|
||||
|
||||
/**
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.common.packet.impl.java.udp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.JavaQueryPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
/**
|
||||
* This packet is sent by the client to the server to request the
|
||||
* full stats of the server. The server will respond with a payload
|
||||
* containing the server's stats.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Query#Request_3">Query Protocol Docs</a>
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public final class JavaQueryFullStatRequestPacket extends JavaQueryPacket {
|
||||
private static final int ID = 0; // The ID of the packet
|
||||
|
||||
/**
|
||||
* The response from the {@link JavaQueryHandshakeRequestPacket}.
|
||||
*/
|
||||
private final byte[] handshakeResponse;
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DatagramSocket socket) throws IOException {
|
||||
try (
|
||||
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(1460);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(arrayOutputStream)
|
||||
) {
|
||||
dataOutputStream.write(MAGIC);
|
||||
dataOutputStream.write(ID); // Packet ID
|
||||
dataOutputStream.writeInt(1); // Session ID
|
||||
dataOutputStream.write(padArrayEnd(handshakeResponse, 4)); // The handshake response payload
|
||||
|
||||
// Send the packet
|
||||
byte[] bytes = arrayOutputStream.toByteArray();
|
||||
socket.send(new DatagramPacket(bytes, bytes.length));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.common.packet.impl.java.udp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.JavaQueryPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This packet is sent by the server to the client in
|
||||
* response to the {@link JavaQueryFullStatRequestPacket}.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Query#Response_3">Query Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class JavaQueryFullStatResponsePacket extends JavaQueryPacket {
|
||||
/**
|
||||
* The response from the server, null if none.
|
||||
*/
|
||||
private Map<String, String> response;
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DatagramSocket socket) throws IOException {
|
||||
// Handle receiving of the packet
|
||||
byte[] receiveData = new byte[1024];
|
||||
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
|
||||
socket.receive(receivePacket);
|
||||
|
||||
// Construct a response from the received packet.
|
||||
Map<String, String> response = new HashMap<>();
|
||||
|
||||
String previousEntry = null;
|
||||
for (byte[] bytes : split(trim(receivePacket.getData()))) {
|
||||
String entry = new String(bytes); // The entry
|
||||
|
||||
if (previousEntry != null) {
|
||||
response.put(previousEntry, entry);
|
||||
previousEntry = null;
|
||||
continue;
|
||||
}
|
||||
previousEntry = entry;
|
||||
}
|
||||
this.response = response;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.common.packet.impl.java.udp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.JavaQueryPacket;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
/**
|
||||
* This packet is sent by the client to the
|
||||
* server to request a handshake. This will
|
||||
* then allow us to send further packets such
|
||||
* as {@link JavaQueryFullStatRequestPacket}.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Query#Request">Query Protocol Docs</a>
|
||||
*/
|
||||
public final class JavaQueryHandshakeRequestPacket extends JavaQueryPacket {
|
||||
private static final int ID = 9; // The ID of the packet
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DatagramSocket socket) throws IOException {
|
||||
try (
|
||||
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(1460);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(arrayOutputStream)
|
||||
) {
|
||||
dataOutputStream.write(MAGIC);
|
||||
dataOutputStream.write(ID); // Packet ID
|
||||
dataOutputStream.writeInt(1); // Session ID
|
||||
dataOutputStream.write(new byte[] {}); // No payload data
|
||||
|
||||
// Send the packet
|
||||
byte[] bytes = arrayOutputStream.toByteArray();
|
||||
bytes = padArrayEnd(bytes, 11 - bytes.length);
|
||||
socket.send(new DatagramPacket(bytes, bytes.length));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.common.packet.impl.java.udp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.JavaQueryPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
/**
|
||||
* This packet is sent by the server to the client in
|
||||
* response to the {@link JavaQueryHandshakeRequestPacket}.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Query#Response">Query Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class JavaQueryHandshakeResponsePacket extends JavaQueryPacket {
|
||||
/**
|
||||
* The response from the server.
|
||||
*/
|
||||
private byte[] response;
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DatagramSocket socket) throws IOException {
|
||||
// Handle receiving of the packet
|
||||
byte[] receiveData = new byte[1024];
|
||||
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
|
||||
socket.receive(receivePacket);
|
||||
|
||||
// Set the response to the integer value of the received data
|
||||
response = intToBytes(Integer.parseInt(new String(receivePacket.getData()).trim()));
|
||||
}
|
||||
}
|
@ -21,10 +21,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.renderer;
|
||||
package cc.restfulmc.api.common.renderer;
|
||||
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.renderer;
|
||||
package cc.restfulmc.api.common.renderer;
|
||||
|
||||
import cc.restfulmc.api.common.ImageUtils;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.braydon.mc.common.ImageUtils;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.renderer.impl;
|
||||
package cc.restfulmc.api.common.renderer.impl;
|
||||
|
||||
import cc.restfulmc.api.common.ImageUtils;
|
||||
import cc.restfulmc.api.common.renderer.SkinRenderer;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.ImageUtils;
|
||||
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.renderer.impl;
|
||||
package cc.restfulmc.api.common.renderer.impl;
|
||||
|
||||
import cc.restfulmc.api.common.renderer.IsometricSkinRenderer;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.renderer.IsometricSkinRenderer;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.renderer.impl;
|
||||
package cc.restfulmc.api.common.renderer.impl;
|
||||
|
||||
import cc.restfulmc.api.common.renderer.SkinRenderer;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.web;
|
||||
package cc.restfulmc.api.common.web;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.common.web;
|
||||
package cc.restfulmc.api.common.web;
|
||||
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.braydon.mc.config.AppConfig;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.config;
|
||||
package cc.restfulmc.api.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.config;
|
||||
package cc.restfulmc.api.config;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
@ -21,14 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.controller;
|
||||
package cc.restfulmc.api.controller;
|
||||
|
||||
import cc.restfulmc.api.common.MojangServer;
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.service.MojangService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.common.MojangServer;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.service.MojangService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -37,7 +37,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -68,11 +70,17 @@ public final class MojangController {
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, String>> getStatus() throws BadRequestException {
|
||||
Map<String, String> response = new HashMap<>();
|
||||
public ResponseEntity<Map<String, List<Map<String, Object>>>> getStatus() throws BadRequestException {
|
||||
List<Map<String, Object>> servers = new ArrayList<>();
|
||||
for (Map.Entry<MojangServer, MojangServer.Status> entry : mojangService.getMojangServerStatuses().entrySet()) {
|
||||
response.put(entry.getKey().getEndpoint(), entry.getValue().name());
|
||||
MojangServer server = entry.getKey();
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("name", server.getName());
|
||||
serverStatus.put("endpoint", server.getEndpoint());
|
||||
serverStatus.put("status", entry.getValue().name());
|
||||
servers.add(serverStatus);
|
||||
}
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(Map.of("servers", servers));
|
||||
}
|
||||
}
|
@ -21,18 +21,18 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.controller;
|
||||
package cc.restfulmc.api.controller;
|
||||
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.exception.impl.MojangRateLimitException;
|
||||
import cc.restfulmc.api.exception.impl.ResourceNotFoundException;
|
||||
import cc.restfulmc.api.model.Player;
|
||||
import cc.restfulmc.api.model.cache.CachedPlayer;
|
||||
import cc.restfulmc.api.service.MojangService;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.MojangRateLimitException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import me.braydon.mc.service.MojangService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
@ -21,17 +21,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.controller;
|
||||
package cc.restfulmc.api.controller;
|
||||
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.exception.impl.ResourceNotFoundException;
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.cache.CachedMinecraftServer;
|
||||
import cc.restfulmc.api.service.MojangService;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import me.braydon.mc.model.cache.CachedMinecraftServer;
|
||||
import me.braydon.mc.service.MojangService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
@ -21,10 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.exception;
|
||||
package cc.restfulmc.api.exception;
|
||||
|
||||
import cc.restfulmc.api.model.response.ErrorResponse;
|
||||
import io.sentry.Sentry;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.model.response.ErrorResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
@ -66,6 +67,7 @@ public final class ExceptionControllerAdvice {
|
||||
}
|
||||
if (status == null) { // Fallback to 500
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
Sentry.captureException(ex); // Capture with Sentry
|
||||
}
|
||||
return new ResponseEntity<>(new ErrorResponse(status, message), status);
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.exception.impl;
|
||||
package cc.restfulmc.api.exception.impl;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
import org.springframework.http.HttpStatus;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.exception.impl;
|
||||
package cc.restfulmc.api.exception.impl;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.exception.impl;
|
||||
package cc.restfulmc.api.exception.impl;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
import org.springframework.http.HttpStatus;
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.log;
|
||||
package cc.restfulmc.api.log;
|
||||
|
||||
import cc.restfulmc.api.common.IPUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.braydon.mc.common.IPUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model;
|
||||
package cc.restfulmc.api.model;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.*;
|
@ -21,15 +21,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model;
|
||||
package cc.restfulmc.api.model;
|
||||
|
||||
import cc.restfulmc.api.common.ColorUtils;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import cc.restfulmc.api.model.token.JavaServerStatusToken;
|
||||
import cc.restfulmc.api.service.pinger.MinecraftServerPinger;
|
||||
import cc.restfulmc.api.service.pinger.impl.BedrockMinecraftServerPinger;
|
||||
import cc.restfulmc.api.service.pinger.impl.JavaMinecraftServerPinger;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import com.maxmind.geoip2.record.City;
|
||||
import com.maxmind.geoip2.record.Continent;
|
||||
import com.maxmind.geoip2.record.Country;
|
||||
import com.maxmind.geoip2.record.Location;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.common.ColorUtils;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
import me.braydon.mc.model.token.JavaServerStatusToken;
|
||||
import me.braydon.mc.service.pinger.MinecraftServerPinger;
|
||||
import me.braydon.mc.service.pinger.impl.BedrockMinecraftServerPinger;
|
||||
import me.braydon.mc.service.pinger.impl.JavaMinecraftServerPinger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -41,7 +46,7 @@ import java.util.UUID;
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
@AllArgsConstructor @Setter @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public class MinecraftServer {
|
||||
/**
|
||||
* The hostname of this server.
|
||||
@ -59,9 +64,14 @@ public class MinecraftServer {
|
||||
@EqualsAndHashCode.Include private final int port;
|
||||
|
||||
/**
|
||||
* The DNS records resolved for this server.
|
||||
* The DNS records resolved for this server, null if none.
|
||||
*/
|
||||
@NonNull private final DNSRecord[] records;
|
||||
private final DNSRecord[] records;
|
||||
|
||||
/**
|
||||
* The Geo location of this server, null if unknown.
|
||||
*/
|
||||
private GeoLocation geo;
|
||||
|
||||
/**
|
||||
* The player counts of this server.
|
||||
@ -73,6 +83,74 @@ public class MinecraftServer {
|
||||
*/
|
||||
@NonNull private final MOTD motd;
|
||||
|
||||
/**
|
||||
* The Geo location of a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class GeoLocation {
|
||||
/**
|
||||
* The continent of this server.
|
||||
*/
|
||||
@NonNull private final LocationData continent;
|
||||
|
||||
/**
|
||||
* The country of this server.
|
||||
*/
|
||||
@NonNull private final LocationData country;
|
||||
|
||||
/**
|
||||
* The city of this server, null if unknown.
|
||||
*/
|
||||
private final String city;
|
||||
|
||||
/**
|
||||
* The latitude of this server.
|
||||
*/
|
||||
private final double latitude;
|
||||
|
||||
/**
|
||||
* The longitude of this server.
|
||||
*/
|
||||
private final double longitude;
|
||||
|
||||
/**
|
||||
* Create new geo location data
|
||||
* from the given city response.
|
||||
*
|
||||
* @param geo the geo city response
|
||||
* @return the geo location
|
||||
*/
|
||||
@NonNull
|
||||
public static GeoLocation create(@NonNull CityResponse geo) {
|
||||
Continent continent = geo.getContinent();
|
||||
Country country = geo.getCountry();
|
||||
City city = geo.getCity();
|
||||
Location location = geo.getLocation();
|
||||
return new GeoLocation(
|
||||
new LocationData(continent.getCode(), continent.getName()),
|
||||
new LocationData(country.getIsoCode(), country.getName()),
|
||||
city == null ? null : city.getName(),
|
||||
location.getLatitude(), location.getLongitude()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a location.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class LocationData {
|
||||
/**
|
||||
* The location code.
|
||||
*/
|
||||
@NonNull private final String code;
|
||||
|
||||
/**
|
||||
* The location name.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Player count data for a server.
|
||||
*/
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model;
|
||||
package cc.restfulmc.api.model;
|
||||
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import cc.restfulmc.api.model.token.MojangProfileToken;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
import me.braydon.mc.model.token.MojangProfileToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model;
|
||||
package cc.restfulmc.api.model;
|
||||
|
||||
/**
|
||||
* Profile actions that can
|
@ -21,12 +21,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.cache;
|
||||
package cc.restfulmc.api.model.cache;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
@ -21,18 +21,18 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.cache;
|
||||
package cc.restfulmc.api.model.cache;
|
||||
|
||||
import cc.restfulmc.api.model.Cape;
|
||||
import cc.restfulmc.api.model.Player;
|
||||
import cc.restfulmc.api.model.ProfileAction;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import cc.restfulmc.api.model.token.MojangProfileToken;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import me.braydon.mc.model.Cape;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.ProfileAction;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
import me.braydon.mc.model.token.MojangProfileToken;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.cache;
|
||||
package cc.restfulmc.api.model.cache;
|
||||
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.Id;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.cache;
|
||||
package cc.restfulmc.api.model.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.dns;
|
||||
package cc.restfulmc.api.model.dns;
|
||||
|
||||
import lombok.*;
|
||||
|
@ -21,10 +21,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.dns.impl;
|
||||
package cc.restfulmc.api.model.dns.impl;
|
||||
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.dns.impl;
|
||||
package cc.restfulmc.api.model.dns.impl;
|
||||
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.response;
|
||||
package cc.restfulmc.api.model.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.server;
|
||||
package cc.restfulmc.api.model.server;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
|
||||
/**
|
||||
* A Bedrock edition {@link MinecraftServer}.
|
||||
@ -54,10 +54,10 @@ public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
*/
|
||||
@NonNull private final GameMode gamemode;
|
||||
|
||||
private BedrockMinecraftServer(@NonNull String id, @NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records,
|
||||
@NonNull Edition edition, @NonNull Version version, @NonNull Players players, @NonNull MOTD motd,
|
||||
@NonNull GameMode gamemode) {
|
||||
super(hostname, ip, port, records, players, motd);
|
||||
private BedrockMinecraftServer(@NonNull String id, @NonNull String hostname, String ip, int port, GeoLocation geo,
|
||||
DNSRecord[] records, @NonNull Edition edition, @NonNull Version version,
|
||||
@NonNull Players players, @NonNull MOTD motd, @NonNull GameMode gamemode) {
|
||||
super(hostname, ip, port, records, geo, players, motd);
|
||||
this.id = id;
|
||||
this.edition = edition;
|
||||
this.version = version;
|
||||
@ -70,20 +70,19 @@ public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
* @param hostname the hostname of the server
|
||||
* @param ip the IP address of the server
|
||||
* @param port the port of the server
|
||||
* @param records the DNS records of the server
|
||||
* @param records the DNS records of the server, if any
|
||||
* @param token the status token
|
||||
* @return the Bedrock Minecraft server
|
||||
*/
|
||||
@NonNull
|
||||
public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port,
|
||||
@NonNull DNSRecord[] records, @NonNull String token) {
|
||||
public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, @NonNull String token) {
|
||||
String[] split = token.split(";"); // Split the token
|
||||
Edition edition = Edition.valueOf(split[0]);
|
||||
Version version = new Version(Integer.parseInt(split[2]), split[3]);
|
||||
Players players = new Players(Integer.parseInt(split[4]), Integer.parseInt(split[5]), null);
|
||||
MOTD motd = MOTD.create(split[1] + "\n" + split[7]);
|
||||
GameMode gameMode = new GameMode(split[8], split.length > 9 ? Integer.parseInt(split[9]) : -1);
|
||||
return new BedrockMinecraftServer(split[6], hostname, ip, port, records, edition, version, players, motd, gameMode);
|
||||
return new BedrockMinecraftServer(split[6], hostname, ip, port, null, records, edition, version, players, motd, gameMode);
|
||||
}
|
||||
|
||||
/**
|
@ -21,19 +21,24 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.server;
|
||||
package cc.restfulmc.api.model.server;
|
||||
|
||||
import cc.restfulmc.api.common.JavaMinecraftVersion;
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import cc.restfulmc.api.model.token.JavaServerChallengeStatusToken;
|
||||
import cc.restfulmc.api.model.token.JavaServerStatusToken;
|
||||
import cc.restfulmc.api.service.MojangService;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.common.JavaMinecraftVersion;
|
||||
import me.braydon.mc.config.AppConfig;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
import me.braydon.mc.model.token.JavaServerStatusToken;
|
||||
import me.braydon.mc.service.MojangService;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Java edition {@link MinecraftServer}.
|
||||
*
|
||||
@ -51,6 +56,17 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
*/
|
||||
private final Favicon favicon;
|
||||
|
||||
/**
|
||||
* The software of this server, present if query is on.
|
||||
*/
|
||||
private final String software;
|
||||
|
||||
/**
|
||||
* The plugins on this server, present if
|
||||
* query is on and plugins are present.
|
||||
*/
|
||||
private final Plugin[] plugins;
|
||||
|
||||
/**
|
||||
* The Forge mod information for this server, null if none.
|
||||
* <p>
|
||||
@ -67,6 +83,16 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
*/
|
||||
private final ForgeData forgeData;
|
||||
|
||||
/**
|
||||
* The main world of this server, present if query is on.
|
||||
*/
|
||||
private final String world;
|
||||
|
||||
/**
|
||||
* Does this server support querying?
|
||||
*/
|
||||
private final boolean queryEnabled;
|
||||
|
||||
/**
|
||||
* Does this server preview chat?
|
||||
*
|
||||
@ -97,14 +123,20 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
*/
|
||||
private boolean mojangBanned;
|
||||
|
||||
private JavaMinecraftServer(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records, @NonNull Version version,
|
||||
@NonNull Players players, @NonNull MOTD motd, Favicon favicon, ModInfo modInfo, ForgeData forgeData,
|
||||
boolean previewsChat, boolean enforcesSecureChat, boolean preventsChatReports, boolean mojangBanned) {
|
||||
super(hostname, ip, port, records, players, motd);
|
||||
private JavaMinecraftServer(@NonNull String hostname, String ip, int port, DNSRecord[] records, GeoLocation geo,
|
||||
@NonNull Version version, @NonNull Players players, @NonNull MOTD motd, Favicon favicon,
|
||||
String software, Plugin[] plugins, ModInfo modInfo, ForgeData forgeData, String world,
|
||||
boolean queryEnabled, boolean previewsChat, boolean enforcesSecureChat, boolean preventsChatReports,
|
||||
boolean mojangBanned) {
|
||||
super(hostname, ip, port, records, geo, players, motd);
|
||||
this.version = version;
|
||||
this.favicon = favicon;
|
||||
this.software = software;
|
||||
this.plugins = plugins;
|
||||
this.modInfo = modInfo;
|
||||
this.forgeData = forgeData;
|
||||
this.world = world;
|
||||
this.queryEnabled = queryEnabled;
|
||||
this.previewsChat = previewsChat;
|
||||
this.enforcesSecureChat = enforcesSecureChat;
|
||||
this.preventsChatReports = preventsChatReports;
|
||||
@ -117,20 +149,35 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
* @param hostname the hostname of the server
|
||||
* @param ip the IP address of the server
|
||||
* @param port the port of the server
|
||||
* @param records the DNS records of the server
|
||||
* @param token the status token
|
||||
* @param records the DNS records of the server, if any
|
||||
* @param statusToken the status token
|
||||
* @param challengeStatusToken the challenge status token, null if none
|
||||
* @return the Java Minecraft server
|
||||
*/
|
||||
@NonNull
|
||||
public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port,
|
||||
@NonNull DNSRecord[] records, @NonNull JavaServerStatusToken token) {
|
||||
String motdString = token.getDescription() instanceof String ? (String) token.getDescription() : null;
|
||||
public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records,
|
||||
@NonNull JavaServerStatusToken statusToken, JavaServerChallengeStatusToken challengeStatusToken) {
|
||||
String motdString = statusToken.getDescription() instanceof String ? (String) statusToken.getDescription() : null;
|
||||
if (motdString == null) { // Not a string motd, convert from Json
|
||||
motdString = new TextComponent(ComponentSerializer.parse(AppConfig.GSON.toJson(token.getDescription()))).toLegacyText();
|
||||
motdString = new TextComponent(ComponentSerializer.parse(AppConfig.GSON.toJson(statusToken.getDescription()))).toLegacyText();
|
||||
}
|
||||
return new JavaMinecraftServer(hostname, ip, port, records, token.getVersion().detailedCopy(), Players.create(token.getPlayers()),
|
||||
MOTD.create(motdString), Favicon.create(token.getFavicon(), hostname), token.getModInfo(), token.getForgeData(),
|
||||
token.isPreviewsChat(), token.isEnforcesSecureChat(), token.isPreventsChatReports(), false
|
||||
String software = challengeStatusToken == null ? null : challengeStatusToken.getSoftware(); // The server software
|
||||
|
||||
// Get the plugins from the challenge token
|
||||
Plugin[] plugins = null;
|
||||
if (challengeStatusToken != null) {
|
||||
List<Plugin> list = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : challengeStatusToken.getPlugins().entrySet()) {
|
||||
list.add(new Plugin(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
plugins = list.toArray(new Plugin[0]);
|
||||
}
|
||||
String world = challengeStatusToken == null ? null : challengeStatusToken.getMap(); // The main server world
|
||||
|
||||
return new JavaMinecraftServer(hostname, ip, port, records, null, statusToken.getVersion().detailedCopy(), Players.create(statusToken.getPlayers()),
|
||||
MOTD.create(motdString), Favicon.create(statusToken.getFavicon(), hostname), software, plugins, statusToken.getModInfo(),
|
||||
statusToken.getForgeData(), world, challengeStatusToken != null, statusToken.isPreviewsChat(),
|
||||
statusToken.isEnforcesSecureChat(), statusToken.isPreventsChatReports(), false
|
||||
);
|
||||
}
|
||||
|
||||
@ -180,6 +227,9 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
}
|
||||
}
|
||||
JavaMinecraftVersion minecraftVersion = JavaMinecraftVersion.byProtocol(protocol);
|
||||
if (minecraftVersion == JavaMinecraftVersion.UNKNOWN) {
|
||||
minecraftVersion = null;
|
||||
}
|
||||
return new Version(name, platform, protocol, supportedVersions, minecraftVersion == null ? null : minecraftVersion.getName());
|
||||
}
|
||||
}
|
||||
@ -217,6 +267,22 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A plugin for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Plugin {
|
||||
/**
|
||||
* The name of this plugin.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The version of this plugin.
|
||||
*/
|
||||
@NonNull private final String version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forge mod information for a server.
|
||||
* <p>
|
@ -21,13 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.skin;
|
||||
package cc.restfulmc.api.model.skin;
|
||||
|
||||
import cc.restfulmc.api.common.renderer.SkinRenderer;
|
||||
import cc.restfulmc.api.common.renderer.impl.BodySkinPartRenderer;
|
||||
import cc.restfulmc.api.common.renderer.impl.IsometricHeadSkinPartRenderer;
|
||||
import cc.restfulmc.api.common.renderer.impl.VanillaSkinPartRenderer;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.common.renderer.SkinRenderer;
|
||||
import me.braydon.mc.common.renderer.impl.BodySkinPartRenderer;
|
||||
import me.braydon.mc.common.renderer.impl.IsometricHeadSkinPartRenderer;
|
||||
import me.braydon.mc.common.renderer.impl.VanillaSkinPartRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@ -21,15 +21,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.skin;
|
||||
package cc.restfulmc.api.model.skin;
|
||||
|
||||
import cc.restfulmc.api.common.ImageUtils;
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import cc.restfulmc.api.model.Player;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.common.ImageUtils;
|
||||
import me.braydon.mc.config.AppConfig;
|
||||
import me.braydon.mc.model.Player;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.model.token;
|
||||
|
||||
import cc.restfulmc.api.model.server.JavaMinecraftServer;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A token representing the response from
|
||||
* sending a challenge request via UDP to
|
||||
* a {@link JavaMinecraftServer} using the
|
||||
* query.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString
|
||||
public final class JavaServerChallengeStatusToken {
|
||||
/**
|
||||
* The map (world) of this server.
|
||||
*/
|
||||
@NonNull private final String map;
|
||||
|
||||
/**
|
||||
* The software of this server.
|
||||
*/
|
||||
@NonNull private final String software;
|
||||
|
||||
/**
|
||||
* The plugins of this server.
|
||||
*/
|
||||
private final Map<String, String> plugins;
|
||||
|
||||
/**
|
||||
* Create a new challenge token
|
||||
* from the given raw data.
|
||||
*
|
||||
* @param rawData the raw data
|
||||
* @return the challenge token
|
||||
*/
|
||||
@NonNull
|
||||
public static JavaServerChallengeStatusToken create(@NonNull Map<String, String> rawData) {
|
||||
String[] splitPlugins = rawData.get("plugins").split(": ");
|
||||
String software = splitPlugins[0]; // The server software
|
||||
|
||||
Map<String, String> plugins = new HashMap<>();
|
||||
for (String plugin : splitPlugins[1].split("; ")) {
|
||||
String[] split = plugin.split(" ");
|
||||
plugins.put(split[0], split[1]);
|
||||
}
|
||||
return new JavaServerChallengeStatusToken(rawData.get("map"), software, plugins);
|
||||
}
|
||||
}
|
@ -21,14 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.token;
|
||||
package cc.restfulmc.api.model.token;
|
||||
|
||||
import cc.restfulmc.api.model.server.JavaMinecraftServer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import me.braydon.mc.model.server.JavaMinecraftServer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -21,15 +21,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.token;
|
||||
package cc.restfulmc.api.model.token;
|
||||
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import cc.restfulmc.api.model.Cape;
|
||||
import cc.restfulmc.api.model.ProfileAction;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.*;
|
||||
import me.braydon.mc.config.AppConfig;
|
||||
import me.braydon.mc.model.Cape;
|
||||
import me.braydon.mc.model.ProfileAction;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
|
||||
import java.util.Base64;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.model.token;
|
||||
package cc.restfulmc.api.model.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
@ -21,9 +21,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.repository;
|
||||
package cc.restfulmc.api.repository;
|
||||
|
||||
import me.braydon.mc.model.cache.CachedMinecraftServer;
|
||||
import cc.restfulmc.api.model.cache.CachedMinecraftServer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
@ -21,9 +21,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.repository;
|
||||
package cc.restfulmc.api.repository;
|
||||
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import cc.restfulmc.api.model.cache.CachedPlayer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
@ -21,9 +21,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.repository;
|
||||
package cc.restfulmc.api.repository;
|
||||
|
||||
import me.braydon.mc.model.cache.CachedPlayerName;
|
||||
import cc.restfulmc.api.model.cache.CachedPlayerName;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
@ -21,10 +21,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.repository;
|
||||
package cc.restfulmc.api.repository;
|
||||
|
||||
import me.braydon.mc.model.cache.CachedSkinPartTexture;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.cache.CachedSkinPartTexture;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
220
API/src/main/java/cc/restfulmc/api/service/MaxMindService.java
Normal file
220
API/src/main/java/cc/restfulmc/api/service/MaxMindService.java
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.service;
|
||||
|
||||
import com.maxmind.db.CHMCache;
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.AddressNotFoundException;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.*;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Service @Log4j2(topic = "MaxMind")
|
||||
public final class MaxMindService {
|
||||
/**
|
||||
* The directory to store databases.
|
||||
*/
|
||||
private static final File DATABASES_DIRECTORY = new File("databases");
|
||||
|
||||
/**
|
||||
* The endpoint to download database files from.
|
||||
*/
|
||||
private static final String DATABASE_DOWNLOAD_ENDPOINT = "https://download.maxmind.com/app/geoip_download?edition_id=%s&license_key=%s&suffix=tar.gz";
|
||||
|
||||
@Value("${maxmind.license}")
|
||||
private String license;
|
||||
|
||||
/**
|
||||
* The currently loaded databases.
|
||||
*/
|
||||
private final Map<Database, DatabaseReader> databases = new HashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void onInitialize() {
|
||||
// Load the databases
|
||||
if (!license.equals("CHANGE_ME")) {
|
||||
loadDatabases();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the databases.
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void loadDatabases() {
|
||||
log.info("Loading databases...");
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if (!DATABASES_DIRECTORY.exists()) {
|
||||
DATABASES_DIRECTORY.mkdirs();
|
||||
}
|
||||
|
||||
// Download missing databases
|
||||
for (Database database : Database.values()) {
|
||||
File databaseFile = new File(DATABASES_DIRECTORY, database.getEdition() + ".mmdb");
|
||||
if (!databaseFile.exists()) { // Doesn't exist, download it
|
||||
downloadDatabase(database, databaseFile);
|
||||
}
|
||||
// Load the database and store it
|
||||
databases.put(database, new DatabaseReader.Builder(databaseFile)
|
||||
.withCache(new CHMCache()) // Enable caching
|
||||
.build()
|
||||
);
|
||||
log.info("Loaded database {}", database.getEdition());
|
||||
}
|
||||
log.info("Loaded {} database(s)", databases.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a city by the given address.
|
||||
*
|
||||
* @param address the address
|
||||
* @return the city response, null if none
|
||||
*/
|
||||
@SneakyThrows
|
||||
public CityResponse lookupCity(@NonNull InetAddress address) {
|
||||
DatabaseReader database = getDatabase(Database.CITY);
|
||||
try {
|
||||
return database == null ? null : database.city(address);
|
||||
} catch (AddressNotFoundException ignored) {
|
||||
// Safely ignore this and return null instead
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the required files
|
||||
* for the given database.
|
||||
*
|
||||
* @param database the database to download
|
||||
* @param databaseFile the file for the database
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void downloadDatabase(@NonNull Database database, @NonNull File databaseFile) {
|
||||
File downloadedFile = new File(DATABASES_DIRECTORY, database.getEdition() + ".tar.gz"); // The downloaded file
|
||||
|
||||
// Download the database if required
|
||||
if (!downloadedFile.exists()) {
|
||||
log.info("Downloading database {}...", database.getEdition());
|
||||
long before = System.currentTimeMillis();
|
||||
try (
|
||||
BufferedInputStream inputStream = new BufferedInputStream(new URL(DATABASE_DOWNLOAD_ENDPOINT.formatted(database.getEdition(), license)).openStream());
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(downloadedFile)
|
||||
) {
|
||||
byte[] dataBuffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(dataBuffer, 0, 1024)) != -1) {
|
||||
fileOutputStream.write(dataBuffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
log.info("Downloaded database {} in {}ms", database.getEdition(), System.currentTimeMillis() - before);
|
||||
}
|
||||
|
||||
// Extract the database once downloaded
|
||||
log.info("Extracting database {}...", database.getEdition());
|
||||
TarGZipUnArchiver archiver = new TarGZipUnArchiver();
|
||||
archiver.setSourceFile(downloadedFile);
|
||||
archiver.setDestDirectory(DATABASES_DIRECTORY);
|
||||
archiver.extract();
|
||||
log.info("Extracted database {}", database.getEdition());
|
||||
|
||||
// Locate the database file in the extracted directory
|
||||
File[] files = DATABASES_DIRECTORY.listFiles();
|
||||
assert files != null; // Ensure files is present
|
||||
dirLoop: for (File directory : files) {
|
||||
if (!directory.isDirectory() || !directory.getName().startsWith(database.getEdition())) {
|
||||
continue;
|
||||
}
|
||||
File[] downloadedFiles = directory.listFiles();
|
||||
assert downloadedFiles != null; // Ensures downloaded files is present
|
||||
|
||||
// Find the file for the database, move it to the
|
||||
// correct directory, and delete the downloaded contents
|
||||
for (File file : downloadedFiles) {
|
||||
if (file.isFile() && file.getName().equals(databaseFile.getName())) {
|
||||
Files.move(file.toPath(), databaseFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// Delete the downloaded contents
|
||||
FileUtils.deleteDirectory(directory);
|
||||
FileUtils.deleteQuietly(downloadedFile);
|
||||
|
||||
break dirLoop; // We're done here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reader for the given database.
|
||||
*
|
||||
* @param database the database to get
|
||||
* @return the database reader, null if none
|
||||
*/
|
||||
public DatabaseReader getDatabase(@NonNull Database database) {
|
||||
return databases.get(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup when the app is destroyed.
|
||||
*/
|
||||
@PreDestroy @SneakyThrows
|
||||
public void cleanup() {
|
||||
for (DatabaseReader database : databases.values()) {
|
||||
database.close();
|
||||
}
|
||||
databases.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* A database for MaxMind.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public enum Database {
|
||||
CITY("GeoLite2-City");
|
||||
|
||||
/**
|
||||
* The edition of this database.
|
||||
*/
|
||||
@NonNull private final String edition;
|
||||
}
|
||||
}
|
@ -21,42 +21,44 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.service;
|
||||
package cc.restfulmc.api.service;
|
||||
|
||||
import cc.restfulmc.api.common.*;
|
||||
import cc.restfulmc.api.common.web.JsonWebException;
|
||||
import cc.restfulmc.api.common.web.JsonWebRequest;
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.exception.impl.MojangRateLimitException;
|
||||
import cc.restfulmc.api.exception.impl.ResourceNotFoundException;
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.Player;
|
||||
import cc.restfulmc.api.model.ProfileAction;
|
||||
import cc.restfulmc.api.model.cache.CachedMinecraftServer;
|
||||
import cc.restfulmc.api.model.cache.CachedPlayer;
|
||||
import cc.restfulmc.api.model.cache.CachedPlayerName;
|
||||
import cc.restfulmc.api.model.cache.CachedSkinPartTexture;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import cc.restfulmc.api.model.dns.impl.ARecord;
|
||||
import cc.restfulmc.api.model.dns.impl.SRVRecord;
|
||||
import cc.restfulmc.api.model.server.JavaMinecraftServer;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import cc.restfulmc.api.model.token.MojangProfileToken;
|
||||
import cc.restfulmc.api.model.token.MojangUsernameToUUIDToken;
|
||||
import cc.restfulmc.api.repository.MinecraftServerCacheRepository;
|
||||
import cc.restfulmc.api.repository.PlayerCacheRepository;
|
||||
import cc.restfulmc.api.repository.PlayerNameCacheRepository;
|
||||
import cc.restfulmc.api.repository.SkinPartTextureCacheRepository;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.common.*;
|
||||
import me.braydon.mc.common.web.JsonWebException;
|
||||
import me.braydon.mc.common.web.JsonWebRequest;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.MojangRateLimitException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.ProfileAction;
|
||||
import me.braydon.mc.model.cache.CachedMinecraftServer;
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import me.braydon.mc.model.cache.CachedPlayerName;
|
||||
import me.braydon.mc.model.cache.CachedSkinPartTexture;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
import me.braydon.mc.model.dns.impl.ARecord;
|
||||
import me.braydon.mc.model.dns.impl.SRVRecord;
|
||||
import me.braydon.mc.model.server.JavaMinecraftServer;
|
||||
import me.braydon.mc.model.skin.ISkinPart;
|
||||
import me.braydon.mc.model.skin.Skin;
|
||||
import me.braydon.mc.model.token.MojangProfileToken;
|
||||
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
|
||||
import me.braydon.mc.repository.MinecraftServerCacheRepository;
|
||||
import me.braydon.mc.repository.PlayerCacheRepository;
|
||||
import me.braydon.mc.repository.PlayerNameCacheRepository;
|
||||
import me.braydon.mc.repository.SkinPartTextureCacheRepository;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@ -64,6 +66,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -90,6 +93,11 @@ public final class MojangService {
|
||||
private static final Splitter DOT_SPLITTER = Splitter.on('.');
|
||||
private static final Joiner DOT_JOINER = Joiner.on('.');
|
||||
|
||||
/**
|
||||
* The MaxMind service to use for Geo lookups.
|
||||
*/
|
||||
@NonNull private final MaxMindService maxMindService;
|
||||
|
||||
/**
|
||||
* The cache repository for {@link Player}'s by their username.
|
||||
*/
|
||||
@ -134,8 +142,9 @@ public final class MojangService {
|
||||
private final ExpiringSet<String> blockedServersCache = new ExpiringSet<>(ExpirationPolicy.CREATED, 10L, TimeUnit.MINUTES);
|
||||
|
||||
@Autowired
|
||||
public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache,
|
||||
public MojangService(@NonNull MaxMindService maxMindService, @NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache,
|
||||
@NonNull SkinPartTextureCacheRepository skinPartTextureCache, @NonNull MinecraftServerCacheRepository minecraftServerCache) {
|
||||
this.maxMindService = maxMindService;
|
||||
this.playerNameCache = playerNameCache;
|
||||
this.playerCache = playerCache;
|
||||
this.skinPartTextureCache = skinPartTextureCache;
|
||||
@ -154,13 +163,13 @@ public final class MojangService {
|
||||
}, 0L, 60L * 3L * 1000L);
|
||||
|
||||
// Schedule a task to fetch blocked
|
||||
// servers from Mojang every 15 minutes.
|
||||
// servers from Mojang every hour.
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchBlockedServers();
|
||||
}
|
||||
}, 0L, 60L * 15L * 1000L);
|
||||
}, 0L, 60L * 60L * 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -347,10 +356,13 @@ public final class MojangService {
|
||||
} catch (BadRequestException | ResourceNotFoundException ignored) {
|
||||
// Safely ignore these, we will use the default server icon
|
||||
}
|
||||
if (icon == null) { // Use the default server icon
|
||||
icon = DEFAULT_SERVER_ICON;
|
||||
try {
|
||||
assert icon != null;
|
||||
return Base64.getDecoder().decode(icon); // Return the decoded favicon
|
||||
} catch (Exception ex) { // Use the default server icon
|
||||
log.error("Failed getting server favicon for %s:".formatted(hostname), ex);
|
||||
return Base64.getDecoder().decode(DEFAULT_SERVER_ICON);
|
||||
}
|
||||
return Base64.getDecoder().decode(icon); // Return the decoded favicon
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,9 +441,10 @@ public final class MojangService {
|
||||
throw new BadRequestException("Invalid port defined");
|
||||
}
|
||||
}
|
||||
String cacheKey = "%s-%s".formatted(platform.name(), lookupHostname.replace(":", "-"));
|
||||
|
||||
// Check the cache for the server
|
||||
Optional<CachedMinecraftServer> cached = minecraftServerCache.findById("%s-%s".formatted(platform.name(), lookupHostname));
|
||||
Optional<CachedMinecraftServer> cached = minecraftServerCache.findById(cacheKey);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found server in cache: {}", hostname);
|
||||
return cached.get();
|
||||
@ -453,13 +466,28 @@ public final class MojangService {
|
||||
log.info("Resolved hostname: {} -> {}", hostname, ip);
|
||||
}
|
||||
|
||||
// Attempt to perform a Geo lookup on the server
|
||||
CityResponse geo = null; // The server's Geo location
|
||||
try {
|
||||
log.info("Looking up Geo location data for {}...", ip);
|
||||
|
||||
InetAddress address = InetAddress.getByName(ip == null ? hostname : ip);
|
||||
geo = maxMindService.lookupCity(address);
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed looking up Geo location data for %s:".formatted(ip), ex);
|
||||
}
|
||||
|
||||
// Build our server model, cache it, and then return it
|
||||
MinecraftServer response = platform.getPinger().ping(hostname, ip, port, records.toArray(new DNSRecord[0])); // Ping the server and await a response
|
||||
if (response == null) { // No response from ping
|
||||
throw new ResourceNotFoundException("Server didn't respond to ping");
|
||||
}
|
||||
if (geo != null) { // Update Geo location data in the server if present
|
||||
response.setGeo(MinecraftServer.GeoLocation.create(geo));
|
||||
}
|
||||
|
||||
CachedMinecraftServer minecraftServer = new CachedMinecraftServer(
|
||||
platform.name() + "-" + lookupHostname, response, System.currentTimeMillis()
|
||||
cacheKey, response, System.currentTimeMillis()
|
||||
);
|
||||
|
||||
// Get the blocked status of the Java server
|
||||
@ -565,4 +593,14 @@ public final class MojangService {
|
||||
}
|
||||
return blocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup when the app is destroyed.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
mojangServerStatuses.clear();
|
||||
bannedServerHashes.clear();
|
||||
blockedServersCache.clear();
|
||||
}
|
||||
}
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.service.pinger;
|
||||
package cc.restfulmc.api.service.pinger;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.model.MinecraftServer;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
|
||||
/**
|
||||
* A {@link MinecraftServerPinger} is
|
@ -21,17 +21,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.service.pinger.impl;
|
||||
package cc.restfulmc.api.service.pinger.impl;
|
||||
|
||||
import cc.restfulmc.api.common.packet.impl.bedrock.BedrockUnconnectedPingPacket;
|
||||
import cc.restfulmc.api.common.packet.impl.bedrock.BedrockUnconnectedPongPacket;
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.exception.impl.ResourceNotFoundException;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import cc.restfulmc.api.model.server.BedrockMinecraftServer;
|
||||
import cc.restfulmc.api.service.pinger.MinecraftServerPinger;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPing;
|
||||
import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPong;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
import me.braydon.mc.model.server.BedrockMinecraftServer;
|
||||
import me.braydon.mc.service.pinger.MinecraftServerPinger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
@ -60,7 +60,7 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger
|
||||
*/
|
||||
@Override
|
||||
public BedrockMinecraftServer ping(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records) {
|
||||
log.info("Pinging {}:{}...", hostname, port);
|
||||
log.info("Opening UDP connection to {}:{}...", hostname, port);
|
||||
long before = System.currentTimeMillis(); // Timestamp before pinging
|
||||
|
||||
// Open a socket connection to the server
|
||||
@ -69,13 +69,13 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger
|
||||
socket.connect(new InetSocketAddress(hostname, port));
|
||||
|
||||
long ping = System.currentTimeMillis() - before; // Calculate the ping
|
||||
log.info("Pinged {}:{} in {}ms", hostname, port, ping);
|
||||
log.info("UDP Connection to {}:{} opened. Ping: {}ms", hostname, port, ping);
|
||||
|
||||
// Send the unconnected ping packet
|
||||
new BedrockPacketUnconnectedPing().process(socket);
|
||||
new BedrockUnconnectedPingPacket().process(socket);
|
||||
|
||||
// Handle the received unconnected pong packet
|
||||
BedrockPacketUnconnectedPong unconnectedPong = new BedrockPacketUnconnectedPong();
|
||||
BedrockUnconnectedPongPacket unconnectedPong = new BedrockUnconnectedPongPacket();
|
||||
unconnectedPong.process(socket);
|
||||
String response = unconnectedPong.getResponse();
|
||||
if (response == null) { // No pong response
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package cc.restfulmc.api.service.pinger.impl;
|
||||
|
||||
import cc.restfulmc.api.common.JavaMinecraftVersion;
|
||||
import cc.restfulmc.api.common.packet.impl.java.tcp.JavaHandshakingInSetProtocolPacket;
|
||||
import cc.restfulmc.api.common.packet.impl.java.tcp.JavaStatusInStartPacket;
|
||||
import cc.restfulmc.api.common.packet.impl.java.udp.JavaQueryFullStatRequestPacket;
|
||||
import cc.restfulmc.api.common.packet.impl.java.udp.JavaQueryFullStatResponsePacket;
|
||||
import cc.restfulmc.api.common.packet.impl.java.udp.JavaQueryHandshakeRequestPacket;
|
||||
import cc.restfulmc.api.common.packet.impl.java.udp.JavaQueryHandshakeResponsePacket;
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import cc.restfulmc.api.exception.impl.BadRequestException;
|
||||
import cc.restfulmc.api.exception.impl.ResourceNotFoundException;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import cc.restfulmc.api.model.server.JavaMinecraftServer;
|
||||
import cc.restfulmc.api.model.token.JavaServerChallengeStatusToken;
|
||||
import cc.restfulmc.api.model.token.JavaServerStatusToken;
|
||||
import cc.restfulmc.api.service.pinger.MinecraftServerPinger;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* The {@link MinecraftServerPinger} for pinging
|
||||
* {@link JavaMinecraftServer}'s over TCP/UDP.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Log4j2(topic = "Java MC Server Pinger")
|
||||
public final class JavaMinecraftServerPinger implements MinecraftServerPinger<JavaMinecraftServer> {
|
||||
private static final int TIMEOUT = 3000; // The timeout for the socket
|
||||
|
||||
/**
|
||||
* Ping the server with the given hostname and port.
|
||||
*
|
||||
* @param hostname the hostname of the server
|
||||
* @param ip the ip of the server, null if unresolved
|
||||
* @param port the port of the server
|
||||
* @param records the DNS records of the server
|
||||
* @return the server that was pinged
|
||||
*/
|
||||
@Override
|
||||
public JavaMinecraftServer ping(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records) {
|
||||
log.info("Pinging {}:{}...", hostname, port);
|
||||
|
||||
try {
|
||||
// Ping the server and retrieve both the status token, and the challenge status token
|
||||
JavaServerStatusToken statusToken = retrieveStatusToken(hostname, port);
|
||||
JavaServerChallengeStatusToken challengeStatusToken = null;
|
||||
try {
|
||||
challengeStatusToken = retrieveChallengeStatusToken(hostname, port);
|
||||
} catch (Exception ex) {
|
||||
// An exception will be raised if querying
|
||||
// is disabled on the server. If the exception
|
||||
// is not caused by querying being disabled, we
|
||||
// want to log the error.
|
||||
if (!(ex instanceof IOException)) {
|
||||
log.error("Failed retrieving challenge status token for %s:%s:".formatted(hostname, port), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the server
|
||||
return JavaMinecraftServer.create(hostname, ip, port, records, statusToken, challengeStatusToken);
|
||||
} catch (IOException ex) {
|
||||
if (ex instanceof UnknownHostException) {
|
||||
throw new BadRequestException("Unknown hostname: %s".formatted(hostname));
|
||||
} else if (ex instanceof ConnectException || ex instanceof SocketTimeoutException) {
|
||||
throw new ResourceNotFoundException(ex);
|
||||
}
|
||||
log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping a server and retrieve its response.
|
||||
*
|
||||
* @param hostname the hostname to ping
|
||||
* @param port the port to ping
|
||||
* @return the status token
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @throws ResourceNotFoundException if the server didn't respond
|
||||
*/
|
||||
@NonNull
|
||||
private JavaServerStatusToken retrieveStatusToken(@NonNull String hostname, int port) throws IOException, ResourceNotFoundException {
|
||||
log.info("Opening TCP connection to {}:{}...", hostname, port);
|
||||
long before = System.currentTimeMillis(); // Timestamp before pinging
|
||||
|
||||
// Open a socket connection to the server
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.setTcpNoDelay(true);
|
||||
socket.connect(new InetSocketAddress(hostname, port), TIMEOUT);
|
||||
|
||||
long ping = System.currentTimeMillis() - before; // Calculate the ping
|
||||
log.info("TCP Connection to {}:{} opened. Ping: {}ms", hostname, port, ping);
|
||||
|
||||
// Begin packet transaction
|
||||
try (DataInputStream inputStream = new DataInputStream(socket.getInputStream());
|
||||
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
|
||||
// Begin handshaking with the server
|
||||
new JavaHandshakingInSetProtocolPacket(hostname, port, JavaMinecraftVersion.getMinimumVersion().getProtocol())
|
||||
.process(inputStream, outputStream);
|
||||
|
||||
// Send the status request to the server, and await back the response
|
||||
JavaStatusInStartPacket packetStatusInStart = new JavaStatusInStartPacket();
|
||||
packetStatusInStart.process(inputStream, outputStream);
|
||||
String response = packetStatusInStart.getResponse();
|
||||
if (response == null) { // No response
|
||||
throw new ResourceNotFoundException("Server didn't respond to status request");
|
||||
}
|
||||
return AppConfig.GSON.fromJson(response, JavaServerStatusToken.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping a server and retrieve its challenge status token.
|
||||
*
|
||||
* @param hostname the hostname to ping
|
||||
* @param port the port to ping
|
||||
* @return the challenge token
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@NonNull
|
||||
private JavaServerChallengeStatusToken retrieveChallengeStatusToken(@NonNull String hostname, int port) throws IOException {
|
||||
log.info("Opening UDP connection to {}:{}...", hostname, port);
|
||||
long before = System.currentTimeMillis(); // Timestamp before pinging
|
||||
|
||||
// Open a socket connection to the server
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
socket.setSoTimeout(500);
|
||||
socket.connect(new InetSocketAddress(hostname, port));
|
||||
|
||||
long ping = System.currentTimeMillis() - before; // Calculate the ping
|
||||
log.info("UDP Connection to {}:{} opened. Ping: {}ms", hostname, port, ping);
|
||||
|
||||
// Begin handshaking with the server
|
||||
new JavaQueryHandshakeRequestPacket().process(socket);
|
||||
JavaQueryHandshakeResponsePacket handshakeResponse = new JavaQueryHandshakeResponsePacket();
|
||||
handshakeResponse.process(socket);
|
||||
|
||||
// Send the full stats request to the server, and await back the response
|
||||
new JavaQueryFullStatRequestPacket(handshakeResponse.getResponse()).process(socket);
|
||||
JavaQueryFullStatResponsePacket fullStatResponse = new JavaQueryFullStatResponsePacket();
|
||||
fullStatResponse.process(socket);
|
||||
|
||||
// Return the challenge token
|
||||
return JavaServerChallengeStatusToken.create(fullStatResponse.getResponse());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Braydon (Rainnny).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.service.pinger.impl;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.common.JavaMinecraftVersion;
|
||||
import me.braydon.mc.common.packet.impl.java.JavaPacketHandshakingInSetProtocol;
|
||||
import me.braydon.mc.common.packet.impl.java.JavaPacketStatusInStart;
|
||||
import me.braydon.mc.config.AppConfig;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.dns.DNSRecord;
|
||||
import me.braydon.mc.model.server.JavaMinecraftServer;
|
||||
import me.braydon.mc.model.token.JavaServerStatusToken;
|
||||
import me.braydon.mc.service.pinger.MinecraftServerPinger;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* The {@link MinecraftServerPinger} for pinging
|
||||
* {@link JavaMinecraftServer}'s over TCP.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Log4j2(topic = "Java MC Server Pinger")
|
||||
public final class JavaMinecraftServerPinger implements MinecraftServerPinger<JavaMinecraftServer> {
|
||||
private static final int TIMEOUT = 3000; // The timeout for the socket
|
||||
|
||||
/**
|
||||
* Ping the server with the given hostname and port.
|
||||
*
|
||||
* @param hostname the hostname of the server
|
||||
* @param ip the ip of the server, null if unresolved
|
||||
* @param port the port of the server
|
||||
* @param records the DNS records of the server
|
||||
* @return the server that was pinged
|
||||
*/
|
||||
@Override
|
||||
public JavaMinecraftServer ping(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records) {
|
||||
log.info("Pinging {}:{}...", hostname, port);
|
||||
long before = System.currentTimeMillis(); // Timestamp before pinging
|
||||
|
||||
// Open a socket connection to the server
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.setTcpNoDelay(true);
|
||||
socket.connect(new InetSocketAddress(hostname, port), TIMEOUT);
|
||||
|
||||
long ping = System.currentTimeMillis() - before; // Calculate the ping
|
||||
log.info("Pinged {}:{} in {}ms", hostname, port, ping);
|
||||
|
||||
// Open data streams to begin packet transaction
|
||||
try (DataInputStream inputStream = new DataInputStream(socket.getInputStream());
|
||||
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
|
||||
// Begin handshaking with the server
|
||||
new JavaPacketHandshakingInSetProtocol(hostname, port, JavaMinecraftVersion.getMinimumVersion().getProtocol())
|
||||
.process(inputStream, outputStream);
|
||||
|
||||
// Send the status request to the server, and await back the response
|
||||
JavaPacketStatusInStart packetStatusInStart = new JavaPacketStatusInStart();
|
||||
packetStatusInStart.process(inputStream, outputStream);
|
||||
String response = packetStatusInStart.getResponse();
|
||||
if (response == null) { // No response
|
||||
throw new ResourceNotFoundException("Server didn't respond to status request");
|
||||
}
|
||||
JavaServerStatusToken token = AppConfig.GSON.fromJson(response, JavaServerStatusToken.class);
|
||||
return JavaMinecraftServer.create(hostname, ip, port, records, token); // Return the server
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (ex instanceof UnknownHostException) {
|
||||
throw new BadRequestException("Unknown hostname: %s".formatted(hostname));
|
||||
} else if (ex instanceof ConnectException || ex instanceof SocketTimeoutException) {
|
||||
throw new ResourceNotFoundException(ex);
|
||||
}
|
||||
log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -11,6 +11,16 @@ logging:
|
||||
file:
|
||||
path: "./logs"
|
||||
|
||||
# Sentry Configuration
|
||||
sentry:
|
||||
dsn: https://87487c1562d043f79c09e77e4bc359b8@sentry.rainnny.club/2
|
||||
tracesSampleRate: 1.0
|
||||
|
||||
# MaxMind Configuration
|
||||
# Used for IP Geo location
|
||||
maxmind:
|
||||
license: "CHANGE_ME"
|
||||
|
||||
# Spring Configuration
|
||||
spring:
|
||||
data:
|
||||
|
@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="A simple, yet useful RESTful API for Minecraft utilizing Springboot.">
|
||||
<meta name="theme-color" content="#3A34EB">
|
||||
<meta name="theme-color" content="#3C8627">
|
||||
<title>RESTfulMC</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>RESTfulMC</h1>
|
||||
<h1>RESTfulMC API</h1>
|
||||
<p>A simple, yet useful RESTful API for Minecraft utilizing Springboot.</p>
|
||||
<p>View the source <a href="https://git.rainnny.club/Rainnny/RESTfulMC">here</a>!</p>
|
||||
</body>
|
||||
|
@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.test.config;
|
||||
package cc.restfulmc.api.test.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.test.controller;
|
||||
package cc.restfulmc.api.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.MojangController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.controller.MojangController;
|
||||
import me.braydon.mc.test.config.TestRedisConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.test.controller;
|
||||
package cc.restfulmc.api.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.PlayerController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.controller.PlayerController;
|
||||
import me.braydon.mc.test.config.TestRedisConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
@ -21,11 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package me.braydon.mc.test.controller;
|
||||
package cc.restfulmc.api.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.ServerController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.controller.ServerController;
|
||||
import me.braydon.mc.test.config.TestRedisConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
4
CONTRIBUTING.md
Normal file
4
CONTRIBUTING.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Contributing
|
||||
Made changes or improvements to RESTfulMC? Consider opening a [pull request](https://git.rainnny.club/Rainnny/RESTfulMC/pulls) to merge your changes into the project. Remember to stick with project conventions!
|
||||
|
||||
This project follows [Semantic Versioning](https://semver.org).
|
29
DemoPlugin/.gitignore
vendored
Normal file
29
DemoPlugin/.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
*.class
|
||||
*.log
|
||||
*.ctxt
|
||||
.mtj.tmp/
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
.idea
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
build/
|
||||
jars/
|
||||
target/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
git.properties
|
||||
pom.xml.versionsBackup
|
5
DemoPlugin/README.md
Normal file
5
DemoPlugin/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# RESTfulMC Demo Plugin
|
||||
This plugin provides an example of what can be returned from the API.
|
||||
|
||||
## Demo Server
|
||||
Ping `demo.restfulmc.cc` on Java or Bedrock, or better yet, view it [here](https://restfulmc.cc/server/java/demo.restfulmc.cc)
|
85
DemoPlugin/pom.xml
Normal file
85
DemoPlugin/pom.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!--Project Details-->
|
||||
<groupId>me.braydon</groupId>
|
||||
<artifactId>DemoPlugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<!-- Properties -->
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<!-- Used for compiling the source code with the proper Java version -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
|
||||
<!-- Enable incremental builds, this is reversed due to -->
|
||||
<!-- a bug as seen in https://issues.apache.org/jira/browse/MCOMPILER-209 -->
|
||||
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Handles shading of dependencies in the final output jar -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<!-- Filter the resources dir for placeholders -->
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<!-- Repositories -->
|
||||
<repositories>
|
||||
<!-- Used by Velocity -->
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<dependencies>
|
||||
<!-- Server Jars -->
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
34
DemoPlugin/src/main/java/cc/restfulmc/demo/DemoPlugin.java
Normal file
34
DemoPlugin/src/main/java/cc/restfulmc/demo/DemoPlugin.java
Normal file
@ -0,0 +1,34 @@
|
||||
package cc.restfulmc.demo;
|
||||
|
||||
import cc.restfulmc.demo.listener.ServerPingListener;
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Plugin(id = "demoplugin", name = "DemoPlugin", version = "1.0.0")
|
||||
public final class DemoPlugin {
|
||||
private final ProxyServer server;
|
||||
|
||||
@Inject
|
||||
public DemoPlugin(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInitialize(ProxyInitializeEvent event) {
|
||||
server.getEventManager().register(this, new ServerPingListener());
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.FIRST)
|
||||
public void onLogin(PreLoginEvent event) {
|
||||
event.setResult(PreLoginEvent.PreLoginComponentResult.denied(Component.text("§cYou can't join a demo server :(")));
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package cc.restfulmc.demo.listener;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.api.util.ModInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class ServerPingListener {
|
||||
private static final String[] MESSAGES = new String[] {
|
||||
"wow omg so cool!",
|
||||
"Hello World!",
|
||||
"Rainnny was here",
|
||||
"Star on GitHub!",
|
||||
"restfulmc.cc",
|
||||
"discord.restfulmc.cc"
|
||||
};
|
||||
private static final String[] PLAYERS = new String[] {
|
||||
"Rainnny", "Notch", "jeb_", "hypixel", "Dinnerbone", "C418", "g", "hey"
|
||||
};
|
||||
|
||||
@Subscribe
|
||||
public void onProxyPing(ProxyPingEvent event) {
|
||||
ServerPing ping = event.getPing(); // Get the ping response
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
|
||||
// Update the version
|
||||
ServerPing.Version version = new ServerPing.Version(ping.getVersion().getProtocol(), "RESTfulMC Demo");
|
||||
|
||||
// Update the player count
|
||||
List<ServerPing.SamplePlayer> playerSamples = new ArrayList<>();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
playerSamples.add(new ServerPing.SamplePlayer(PLAYERS[random.nextInt(PLAYERS.length)], UUID.randomUUID()));
|
||||
}
|
||||
ServerPing.Players players = new ServerPing.Players(random.nextInt(300, 25000), 30000, playerSamples);
|
||||
|
||||
TextComponent motd = Component.text(String.join("\n",
|
||||
"§f §2§lRESTfulMC §7Demo Server",
|
||||
"§7 " + MESSAGES[random.nextInt(MESSAGES.length)]
|
||||
));
|
||||
|
||||
// Update the mod info
|
||||
ModInfo modInfo = new ModInfo(ModInfo.DEFAULT.getType(), Arrays.asList(
|
||||
new ModInfo.Mod("bob", "1.0"),
|
||||
new ModInfo.Mod("ross", "1.0")
|
||||
));
|
||||
|
||||
// Set the ping response
|
||||
event.setPing(new ServerPing(version, players, motd, ping.getFavicon().orElse(null), modInfo));
|
||||
}
|
||||
}
|
28
DiscordBot/.gitignore
vendored
Normal file
28
DiscordBot/.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
*.class
|
||||
*.log
|
||||
*.ctxt
|
||||
.mtj.tmp/
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
.idea
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
build/
|
||||
target/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
git.properties
|
||||
pom.xml.versionsBackup
|
13
DiscordBot/Dockerfile
Normal file
13
DiscordBot/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM maven:3.8.5-openjdk-17-slim
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /home/container
|
||||
|
||||
# Copy project files
|
||||
COPY . .
|
||||
|
||||
# Build the app
|
||||
RUN mvn clean package -q -T4C
|
||||
|
||||
# Start the app
|
||||
CMD ["java", "-jar", "target/DiscordBot.jar"]
|
2
DiscordBot/README.md
Normal file
2
DiscordBot/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# RESTfulMC Discord Bot
|
||||
This Discord bot allows you to interact with the API directly from Discord. Want to try it out? Join the [RESTfulMC Discord](https://discord.restfulmc.cc)
|
122
DiscordBot/pom.xml
Normal file
122
DiscordBot/pom.xml
Normal file
@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!--Project Details-->
|
||||
<groupId>cc.restfulmc</groupId>
|
||||
<artifactId>DiscordBot</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<!-- Properties -->
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j.version>2.1.0-alpha1</slf4j.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<!-- Used for compiling the source code with the proper Java version -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
|
||||
<!-- Enable incremental builds, this is reversed due to -->
|
||||
<!-- a bug as seen in https://issues.apache.org/jira/browse/MCOMPILER-209 -->
|
||||
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Handles shading of dependencies in the final output jar -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Specify the apps main class -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>cc.restfulmc.bot.DiscordBot</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- Repositories -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>rainnny-repo-public</id>
|
||||
<url>https://maven.rainnny.club/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.32</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Libraries -->
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>5.0.0-beta.23</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>club.minnced</groupId>
|
||||
<artifactId>opus-java</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cc.restfulmc</groupId>
|
||||
<artifactId>Java-SDK</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
62
DiscordBot/src/main/java/cc/restfulmc/bot/DiscordBot.java
Normal file
62
DiscordBot/src/main/java/cc/restfulmc/bot/DiscordBot.java
Normal file
@ -0,0 +1,62 @@
|
||||
package cc.restfulmc.bot;
|
||||
|
||||
import cc.restfulmc.bot.command.CommandManager;
|
||||
import cc.restfulmc.sdk.client.ClientConfig;
|
||||
import cc.restfulmc.sdk.client.RESTfulMCClient;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.SelfUser;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Slf4j(topic = "RESTfulMC Bot") @Getter
|
||||
public final class DiscordBot {
|
||||
/**
|
||||
* The JDA bot instance.
|
||||
*/
|
||||
private JDA jda;
|
||||
|
||||
@SneakyThrows
|
||||
public DiscordBot() {
|
||||
String token = System.getenv("BOT_TOKEN");
|
||||
if (token == null) { // Missing BOT_TOKEN
|
||||
throw new NullPointerException("Missing BOT_TOKEN environment variable");
|
||||
}
|
||||
jda = JDABuilder.createLight(token)
|
||||
.setActivity(Activity.watching("Minecraft servers"))
|
||||
.build();
|
||||
jda.awaitReady(); // Wait for JDA to become ready
|
||||
|
||||
// Setup the API SDK
|
||||
RESTfulMCClient apiClient = new RESTfulMCClient(ClientConfig.defaultConfig());
|
||||
|
||||
// Commands
|
||||
new CommandManager(this, apiClient);
|
||||
|
||||
SelfUser self = jda.getSelfUser();
|
||||
log.info("Logged in as bot {} ({})", self.getAsTag(), self.getId());
|
||||
|
||||
// Add a cleanup hook to cleanup the bot when the JVM shuts down
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::cleanup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the bot.
|
||||
*/
|
||||
public void cleanup() {
|
||||
log.info("Cleaning up...");
|
||||
jda.shutdown();
|
||||
jda = null;
|
||||
log.info("Goodbye!");
|
||||
}
|
||||
|
||||
public static void main(@NonNull String[] args) {
|
||||
new DiscordBot();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package cc.restfulmc.bot.command;
|
||||
|
||||
import cc.restfulmc.bot.DiscordBot;
|
||||
import cc.restfulmc.bot.command.impl.PlayerCommand;
|
||||
import cc.restfulmc.bot.command.impl.ServerCommand;
|
||||
import cc.restfulmc.sdk.client.RESTfulMCClient;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class CommandManager extends ListenerAdapter {
|
||||
/**
|
||||
* The registered slash commands.
|
||||
*/
|
||||
private final List<SlashCommand> commands = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public CommandManager(@NonNull DiscordBot bot, @NonNull RESTfulMCClient apiClient) {
|
||||
// Register commands
|
||||
registerCommand(new PlayerCommand(apiClient));
|
||||
registerCommand(new ServerCommand(apiClient));
|
||||
|
||||
// Update the commands on Discord
|
||||
CommandListUpdateAction updateCommands = bot.getJda().updateCommands();
|
||||
for (SlashCommand command : commands) {
|
||||
updateCommands.addCommands(Commands.slash(command.getName(), command.getDescription()).addOptions(command.getOptions()));
|
||||
}
|
||||
updateCommands.queue();
|
||||
|
||||
// Handle registered events
|
||||
bot.getJda().addEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(@NonNull SlashCommandInteractionEvent event) {
|
||||
for (SlashCommand command : commands) {
|
||||
if (command.getName().equals(event.getName())) {
|
||||
event.deferReply().queue(); // Inform Discord we received the command
|
||||
command.onExecute(event.getUser(), event.getMember(), event); // Invoke the command
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a slash command.
|
||||
*
|
||||
* @param command the command to register
|
||||
*/
|
||||
public void registerCommand(@NonNull SlashCommand command) {
|
||||
commands.add(command);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package cc.restfulmc.bot.command;
|
||||
|
||||
import cc.restfulmc.sdk.exception.RESTfulMCAPIException;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
/**
|
||||
* A wrapper for slash commands.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Getter
|
||||
public abstract class SlashCommand {
|
||||
/**
|
||||
* The name of this command.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The description of this command.
|
||||
*/
|
||||
@NonNull private final String description;
|
||||
|
||||
/**
|
||||
* Optional options for this command.
|
||||
*/
|
||||
private final OptionData[] options;
|
||||
|
||||
public SlashCommand(@NonNull String name, @NonNull String description, OptionData... options) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when this command is executed.
|
||||
*
|
||||
* @param user the executing user
|
||||
* @param member the executing member, null if in dms
|
||||
* @param event the event that triggered this command
|
||||
*/
|
||||
public abstract void onExecute(@NonNull User user, Member member, @NonNull SlashCommandInteractionEvent event);
|
||||
|
||||
/**
|
||||
* Reply to an interaction with an API error.
|
||||
*
|
||||
* @param event the event to reply to
|
||||
* @param apiError the api error to reply with
|
||||
*/
|
||||
protected final void replyWithApiError(@NonNull SlashCommandInteractionEvent event, @NonNull RESTfulMCAPIException apiError) {
|
||||
replyWithGenericError(event, apiError.getCode() + " | API Error", apiError.getLocalizedMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to an interaction with a generic error.
|
||||
*
|
||||
* @param event the event to reply to
|
||||
* @param title the title of the error
|
||||
* @param description the description of the error
|
||||
*/
|
||||
protected final void replyWithGenericError(@NonNull SlashCommandInteractionEvent event, @NonNull String title, @NonNull String description) {
|
||||
event.getHook().sendMessageEmbeds(new EmbedBuilder()
|
||||
.setColor(0xAA0000)
|
||||
.setTitle(title)
|
||||
.setDescription(description)
|
||||
.build()).queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package cc.restfulmc.bot.command.impl;
|
||||
|
||||
import cc.restfulmc.bot.command.SlashCommand;
|
||||
import cc.restfulmc.sdk.client.RESTfulMCClient;
|
||||
import cc.restfulmc.sdk.exception.RESTfulMCAPIException;
|
||||
import cc.restfulmc.sdk.response.Player;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Slf4j(topic = "Server Lookup Command")
|
||||
public final class PlayerCommand extends SlashCommand {
|
||||
/**
|
||||
* The API client to use for lookups.
|
||||
*/
|
||||
@NonNull private final RESTfulMCClient apiClient;
|
||||
|
||||
public PlayerCommand(@NonNull RESTfulMCClient apiClient) {
|
||||
super("player", "Lookup a player by their username or UUID",
|
||||
new OptionData(OptionType.STRING, "query", "The player username or UUID").setRequired(true)
|
||||
);
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when this command is executed.
|
||||
*
|
||||
* @param user the executing user
|
||||
* @param member the executing member, null if in dms
|
||||
* @param event the event that triggered this command
|
||||
*/
|
||||
@Override
|
||||
public void onExecute(@NonNull User user, Member member, @NonNull SlashCommandInteractionEvent event) {
|
||||
OptionMapping query = event.getOption("query"); // Get the query
|
||||
assert query != null;
|
||||
String queryValue = query.getAsString(); // Get the query value
|
||||
|
||||
// Lookup the requested player by the given query
|
||||
apiClient.async().getPlayer(queryValue).whenComplete((player, ex) -> {
|
||||
// Failed to lookup the player, handle the error
|
||||
if (ex != null) {
|
||||
if (ex.getCause() instanceof RESTfulMCAPIException apiError) {
|
||||
replyWithApiError(event, apiError);
|
||||
} else { // Only print real errors
|
||||
log.error("Failed fetching player:", ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Respond with the player
|
||||
long cached = player.getCached(); // The timestamp the player was cached
|
||||
event.getHook().sendMessageEmbeds(new EmbedBuilder()
|
||||
.setColor(0x55FF55)
|
||||
.setTitle("<:steve:1232815662599114753> Player Response", "https://api.restfulmc.cc/player/" + queryValue)
|
||||
.addField("Unique ID", player.getUniqueId().toString(), true)
|
||||
.addField("Username", player.getUsername(), true)
|
||||
.addField("Legacy", player.isLegacy() ? "Yes" : "No", true)
|
||||
.addField("Cached", cached == -1L ? "No" : "Yes, <t:" + (cached / 1000L) + ":R>", true)
|
||||
.setThumbnail(player.getSkin().getParts().get(Player.SkinPart.HEAD))
|
||||
.setFooter("Requested by " + user.getName() + " | " + user.getId(), user.getEffectiveAvatarUrl())
|
||||
.build()).queue();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package cc.restfulmc.bot.command.impl;
|
||||
|
||||
import cc.restfulmc.bot.command.SlashCommand;
|
||||
import cc.restfulmc.bot.common.StringUtils;
|
||||
import cc.restfulmc.sdk.client.RESTfulMCClient;
|
||||
import cc.restfulmc.sdk.exception.RESTfulMCAPIException;
|
||||
import cc.restfulmc.sdk.response.server.BedrockMinecraftServer;
|
||||
import cc.restfulmc.sdk.response.server.JavaMinecraftServer;
|
||||
import cc.restfulmc.sdk.response.server.MinecraftServer;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Slf4j(topic = "Server Lookup Command")
|
||||
public final class ServerCommand extends SlashCommand {
|
||||
private static final Map<MinecraftServer.Platform, String> MAPPED_PLATFORM_EMOJIS = new HashMap<>() {{
|
||||
put(MinecraftServer.Platform.JAVA, "<:grass_block:1232798337300828181>");
|
||||
put(MinecraftServer.Platform.BEDROCK, "<:bedrock_block:1232798365427830928>");
|
||||
}};
|
||||
private static final DecimalFormat PLAYERS_FORMAT = new DecimalFormat("#,##0");
|
||||
|
||||
/**
|
||||
* The API client to use for lookups.
|
||||
*/
|
||||
@NonNull private final RESTfulMCClient apiClient;
|
||||
|
||||
public ServerCommand(@NonNull RESTfulMCClient apiClient) {
|
||||
super("server", "Lookup a server by its platform and hostname",
|
||||
new OptionData(OptionType.STRING, "platform", "The platform: JAVA | BEDROCK").setRequired(true),
|
||||
new OptionData(OptionType.STRING, "hostname", "The hostname of the server").setRequired(true)
|
||||
);
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when this command is executed.
|
||||
*
|
||||
* @param user the executing user
|
||||
* @param member the executing member, null if in dms
|
||||
* @param event the event that triggered this command
|
||||
*/
|
||||
@Override
|
||||
public void onExecute(@NonNull User user, Member member, @NonNull SlashCommandInteractionEvent event) {
|
||||
OptionMapping platformOption = event.getOption("platform"); // The platform to query
|
||||
assert platformOption != null;
|
||||
String platformValue = platformOption.getAsString();
|
||||
MinecraftServer.Platform platform; // The platform to lookup
|
||||
try {
|
||||
platform = MinecraftServer.Platform.valueOf(platformValue.toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
replyWithGenericError(event, "Invalid Platform", "You must specify either **Java** or **Bedrock** as the platform.");
|
||||
return;
|
||||
}
|
||||
|
||||
OptionMapping hostname = event.getOption("hostname"); // Get the hostname to query
|
||||
assert hostname != null;
|
||||
String hostnameValue = hostname.getAsString(); // Get the hostname value
|
||||
|
||||
// Lookup the requested server by the given platform and hostname
|
||||
apiClient.async().getMinecraftServer(platform, hostnameValue).whenComplete((server, ex) -> {
|
||||
// Failed to lookup the server, handle the error
|
||||
if (ex != null) {
|
||||
if (ex.getCause() instanceof RESTfulMCAPIException apiError) {
|
||||
replyWithApiError(event, apiError);
|
||||
} else { // Only print real errors
|
||||
log.error("Failed fetching Minecraft Server:", ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Respond with the server
|
||||
String ip = server.getIp(); // The resolved IP of the server
|
||||
MinecraftServer.GeoLocation geo = server.getGeo(); // The geo location of the server
|
||||
MinecraftServer.Players players = server.getPlayers(); // The players on the server
|
||||
long cached = server.getCached(); // The timestamp the server was cached
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setColor(0x55FF55)
|
||||
.setTitle(
|
||||
MAPPED_PLATFORM_EMOJIS.get(platform) + " " + StringUtils.capitalize(platform.name()) + " Server Response: " + server.getHostname(),
|
||||
"https://api.restfulmc.cc/server/" + platformValue + "/" + hostnameValue
|
||||
).addField("IP", (ip == null ? server.getHostname() : ip) + ":" + server.getPort(), true);
|
||||
|
||||
// Show Geo location if resolved
|
||||
if (geo != null) {
|
||||
MinecraftServer.GeoLocation.LocationData country = geo.getCountry(); // The server's country
|
||||
embed.addField("Located", ":flag_" + country.getCode().toLowerCase() + ": " + country.getName(), true);
|
||||
}
|
||||
|
||||
// Append platform specific details to the embed
|
||||
if (server instanceof JavaMinecraftServer javaServer) {
|
||||
appendJavaEmbed(embed, javaServer);
|
||||
} else {
|
||||
appendBedrockEmbed(embed, (BedrockMinecraftServer) server);
|
||||
}
|
||||
|
||||
event.getHook().sendMessageEmbeds(embed
|
||||
.addField("Players", PLAYERS_FORMAT.format(players.getOnline()) + "/" + PLAYERS_FORMAT.format(players.getMax()), true)
|
||||
.addField("Cached", cached == -1L ? "No" : "Yes, <t:" + (cached / 1000L) + ":R>", true)
|
||||
.setThumbnail(server instanceof JavaMinecraftServer javaServer ? javaServer.getFavicon().getUrl() : "https://api.restfulmc.cc/server/icon/invalid")
|
||||
.setFooter("Requested by " + user.getName() + " | " + user.getId(), user.getEffectiveAvatarUrl())
|
||||
.build()).queue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Java specific details to an embed.
|
||||
*
|
||||
* @param embed the embed to append to
|
||||
* @param javaServer the java server
|
||||
*/
|
||||
private void appendJavaEmbed(@NonNull EmbedBuilder embed, @NonNull JavaMinecraftServer javaServer) {
|
||||
JavaMinecraftServer.Version version = javaServer.getVersion(); // The version of the server
|
||||
String protocolName = version.getProtocolName(); // The name of the server's version protocol
|
||||
String platform = version.getPlatform(); // The server's platform
|
||||
String world = javaServer.getWorld(); // The server's main world
|
||||
JavaMinecraftServer.Plugin[] plugins = javaServer.getPlugins(); // The server's plugins
|
||||
|
||||
embed.addField("Version", version.getProtocol() + (protocolName == null ? "" : " (" + protocolName + ")"), true);
|
||||
if (platform != null) {
|
||||
embed.addField("Platform", platform, true);
|
||||
}
|
||||
if (world != null) {
|
||||
embed.addField("World", world, true);
|
||||
}
|
||||
if (plugins != null) {
|
||||
embed.addField("Plugins", String.valueOf(plugins.length), true);
|
||||
}
|
||||
|
||||
embed.addField("Query", javaServer.isQueryEnabled() ? "Enabled" : "Disabled", true);
|
||||
embed.addField("Mojang Banned", javaServer.isMojangBanned() ? "Yes" : "No", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Bedrock specific details to an embed.
|
||||
*
|
||||
* @param embed the embed to append to
|
||||
* @param bedrockServer the bedrock server
|
||||
*/
|
||||
private void appendBedrockEmbed(@NonNull EmbedBuilder embed, @NonNull BedrockMinecraftServer bedrockServer) {
|
||||
BedrockMinecraftServer.Version version = bedrockServer.getVersion(); // The version of the server
|
||||
BedrockMinecraftServer.GameMode gamemode = bedrockServer.getGamemode(); // The game mode of the server
|
||||
|
||||
embed.addField("Version", version.getProtocol() + " (" + version.getName() + ")", true);
|
||||
embed.addField("Edition", bedrockServer.getEdition().name(), true);
|
||||
embed.addField("GameMode", gamemode.getName() + " (" + gamemode.getNumericId() + ")", true);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cc.restfulmc.bot.common;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class StringUtils {
|
||||
/**
|
||||
* Capitalize the first character in the given string.
|
||||
*
|
||||
* @param input the input to capitalize
|
||||
* @return the capitalized string
|
||||
*/
|
||||
@NonNull
|
||||
public static String capitalize(@NonNull String input) {
|
||||
return Character.toUpperCase(input.charAt(0)) + input.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
26
Frontend/.dockerignore
Normal file
26
Frontend/.dockerignore
Normal file
@ -0,0 +1,26 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
**/.gitea
|
||||
**/.next
|
||||
**/.env
|
||||
LICENSE
|
||||
README.md
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user