Compare commits
510 Commits
49501676fa
...
renovate/n
Author | SHA1 | Date | |
---|---|---|---|
|
20041f14ee | ||
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 | |||
fe00e7d50b | |||
18293cc579 | |||
499663aa64 | |||
abccde5896 | |||
1968f835fd | |||
9f0d85cf5b | |||
c0ce86faf6 | |||
eabea2546b | |||
9751a21c9f | |||
4777d7d5eb | |||
4721b46ce9 | |||
27c83a55ba | |||
7ab210edb7 | |||
348f7fa1eb | |||
3f1290fd07 | |||
d6a21fd5a3 | |||
9c4a39641d | |||
b800badcc3 | |||
6c6b9349f2 | |||
6d112ac658 | |||
f01c208a9b | |||
5099ad9dfe | |||
a03bac8f1d | |||
655ee50a21 | |||
63121afe32 | |||
6993fe6681 | |||
d8962cee98 | |||
0890a151eb | |||
0de598b205 | |||
4f24b8eee5 | |||
dbeeb77fc8 | |||
43f2ba041d | |||
5f37b6b768 | |||
16e23e82f9 | |||
2f7b9f6b10 | |||
16a42d1af3 | |||
160fed45e8 | |||
27beec9dc7 | |||
0cb6d3f028 | |||
3a098433e0 | |||
2df4a7978c | |||
20adfaeaad | |||
501d315c96 | |||
592bca234b | |||
d2a54ac828 | |||
d11f87e36e | |||
cd147684e5 | |||
33d1f9669a | |||
81029a57a1 | |||
c9426d11af | |||
af9df6571c | |||
69c0cd3179 | |||
9725237885 | |||
a4a3486341 | |||
|
f1e6dbf08f | ||
b6ebe767ee | |||
ca794e288c | |||
657c7196dd | |||
59f6c51717 | |||
ca0624995d | |||
16e15a3dd3 | |||
0831b509e1 | |||
931e6c2de1 | |||
66e84b9c08 | |||
1d5aa930f0 | |||
9e99db3aa3 | |||
546a187f52 | |||
ad51d4d357 | |||
82162b5f63 | |||
f2601f6971 | |||
decb38e6dd | |||
46d0e80454 | |||
ec69120a26 | |||
32c93b5e58 | |||
936a1c47eb | |||
69393472c1 | |||
725cd00163 | |||
04a0fc8f0e | |||
dea3227a05 | |||
9250a6514a | |||
6b1465e29f | |||
7f19304b9e | |||
e9ce888d06 | |||
8cca0c2b51 | |||
e5342b5445 | |||
91f207448e | |||
1ad27fc812 | |||
ef36799979 | |||
fb7bd3299f | |||
77ba09d810 | |||
de0cd5ef8c | |||
712208aad8 | |||
4dd16da26a | |||
d5547c95b7 | |||
bf9555674d | |||
38fa1fe4cb | |||
ecb8b31df1 | |||
5b80d41992 | |||
e9e6999adc | |||
9ab0b15869 | |||
5a044df870 | |||
c62917f0c7 | |||
8fc503617a | |||
|
fb4ea67a77 |
48
.gitea/workflows/deploy-api.yml
Normal file
48
.gitea/workflows/deploy-api.yml
Normal file
@ -0,0 +1,48 @@
|
||||
name: Deploy API
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths: [".gitea/workflows/deploy-api.yml", "API/**", "!API/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./API"
|
||||
|
||||
# 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 }}
|
||||
|
||||
# Run JUnit Tests
|
||||
- name: Run Tests
|
||||
run: mvn --batch-mode test -q
|
||||
|
||||
# Re-checkout to reset the FS before deploying to Dokku
|
||||
- name: Checkout - Reset FS
|
||||
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-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 }}
|
@ -1,21 +1,20 @@
|
||||
name: Deploy App
|
||||
name: Deploy Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- docker-compose.yml
|
||||
- postman_collection.json
|
||||
paths: [".gitea/workflows/deploy-frontend.yml", "Frontend/**", "!Frontend/README.md"]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["ubuntu-latest"]
|
||||
git-version: ["2.44.0"]
|
||||
runs-on: ${{ matrix.arch }}
|
||||
java-version: ["17"]
|
||||
maven-version: ["3.8.5"]
|
||||
runs-on: "ubuntu-latest"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "./Frontend"
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
@ -29,5 +28,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-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 }}
|
4
.gitignore → API/.gitignore
vendored
4
.gitignore → API/.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
### Primary template
|
||||
*.class
|
||||
*.log
|
||||
*.ctxt
|
||||
@ -19,6 +18,7 @@ cmake-build-*/
|
||||
out/
|
||||
build/
|
||||
work/
|
||||
target/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
com_crashlytics_export_strings.xml
|
||||
@ -26,4 +26,4 @@ crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
git.properties
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.versionsBackup
|
@ -7,7 +7,7 @@ WORKDIR /home/container
|
||||
COPY . .
|
||||
|
||||
# Build the app
|
||||
RUN mvn clean package
|
||||
RUN mvn clean package -q -DskipTests -T4C
|
||||
|
||||
# Exposting on port 80 so we can
|
||||
# access via a reverse proxy for Dokku
|
||||
@ -15,5 +15,8 @@ ENV HOSTNAME "0.0.0.0"
|
||||
EXPOSE 80
|
||||
ENV PORT 80
|
||||
|
||||
# We're running in production
|
||||
ENV APP_ENV "production"
|
||||
|
||||
# Start the app
|
||||
CMD ["java", "-jar", "target/RESTfulMC.jar"]
|
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)
|
@ -6,14 +6,15 @@
|
||||
<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-dev</version>
|
||||
<version>1.0.0</version>
|
||||
<description>A simple, yet useful RESTful API for Minecraft utilizing Springboot.</description>
|
||||
|
||||
<!-- Properties -->
|
||||
<properties>
|
||||
@ -31,6 +32,19 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-info</id>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalProperties>
|
||||
<description>${project.description}</description>
|
||||
</additionalProperties>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@ -50,14 +64,6 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
<!-- Exclude the default Jackson dependency -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis for caching -->
|
||||
@ -95,6 +101,18 @@
|
||||
<version>0.5.11</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<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>
|
||||
@ -104,10 +122,27 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- GeoIP -->
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>1.20-R0.2</version>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
<artifactId>geoip2</artifactId>
|
||||
<version>4.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SwaggerUI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<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>
|
||||
|
59
API/src/main/java/cc/restfulmc/api/RESTfulMC.java
Normal file
59
API/src/main/java/cc/restfulmc/api/RESTfulMC.java
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Slf4j(topic = "RESTfulMC")
|
||||
public class RESTfulMC {
|
||||
@SneakyThrows
|
||||
public static void main(@NonNull String[] args) {
|
||||
// Handle loading of our configuration file
|
||||
File config = new File("application.yml");
|
||||
if (!config.exists()) { // Saving the default config if it doesn't exist locally
|
||||
Files.copy(Objects.requireNonNull(RESTfulMC.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
log.info("Saved the default configuration to '{}', please re-launch the application", // Log the default config being saved
|
||||
config.getAbsolutePath()
|
||||
);
|
||||
return;
|
||||
}
|
||||
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
|
||||
|
||||
// Start the app
|
||||
SpringApplication.run(RESTfulMC.class, args);
|
||||
}
|
||||
}
|
104
API/src/main/java/cc/restfulmc/api/common/ColorUtils.java
Normal file
104
API/src/main/java/cc/restfulmc/api/common/ColorUtils.java
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class ColorUtils {
|
||||
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)§[0-9A-FK-OR]");
|
||||
private static final Map<Character, String> COLOR_MAP = new HashMap<>();
|
||||
static {
|
||||
// Map each color to its corresponding hex code
|
||||
COLOR_MAP.put('0', "#000000"); // Black
|
||||
COLOR_MAP.put('1', "#0000AA"); // Dark Blue
|
||||
COLOR_MAP.put('2', "#00AA00"); // Dark Green
|
||||
COLOR_MAP.put('3', "#00AAAA"); // Dark Aqua
|
||||
COLOR_MAP.put('4', "#AA0000"); // Dark Red
|
||||
COLOR_MAP.put('5', "#AA00AA"); // Dark Purple
|
||||
COLOR_MAP.put('6', "#FFAA00"); // Gold
|
||||
COLOR_MAP.put('7', "#AAAAAA"); // Gray
|
||||
COLOR_MAP.put('8', "#555555"); // Dark Gray
|
||||
COLOR_MAP.put('9', "#5555FF"); // Blue
|
||||
COLOR_MAP.put('a', "#55FF55"); // Green
|
||||
COLOR_MAP.put('b', "#55FFFF"); // Aqua
|
||||
COLOR_MAP.put('c', "#FF5555"); // Red
|
||||
COLOR_MAP.put('d', "#FF55FF"); // Light Purple
|
||||
COLOR_MAP.put('e', "#FFFF55"); // Yellow
|
||||
COLOR_MAP.put('f', "#FFFFFF"); // White
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the color codes
|
||||
* from the given input.
|
||||
*
|
||||
* @param input the input to strip
|
||||
* @return the stripped input
|
||||
*/
|
||||
@NonNull
|
||||
public static String stripColor(@NonNull String input) {
|
||||
return STRIP_COLOR_PATTERN.matcher(input).replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given input into HTML format.
|
||||
* <p>
|
||||
* This will replace each color code with
|
||||
* a span tag with the respective color in
|
||||
* hex format.
|
||||
* </p>
|
||||
*
|
||||
* @param input the input to convert
|
||||
* @return the converted input
|
||||
*/
|
||||
@NonNull
|
||||
public static String toHTML(@NonNull String input) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean nextIsColor = false; // Is the next char a color code?
|
||||
|
||||
for (char character : input.toCharArray()) {
|
||||
// Found color symbol, next color is the color
|
||||
if (character == '§') {
|
||||
nextIsColor = true;
|
||||
continue;
|
||||
}
|
||||
if (nextIsColor) { // Map the current color to its hex code
|
||||
String color = COLOR_MAP.getOrDefault(Character.toLowerCase(character), "");
|
||||
builder.append("<span style=\"color:").append(color).append("\">");
|
||||
nextIsColor = false;
|
||||
continue;
|
||||
}
|
||||
builder.append(character == ' ' ? " " : character); // Append the char...
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
81
API/src/main/java/cc/restfulmc/api/common/DNSUtils.java
Normal file
81
API/src/main/java/cc/restfulmc/api/common/DNSUtils.java
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 org.xbill.DNS.Lookup;
|
||||
import org.xbill.DNS.Record;
|
||||
import org.xbill.DNS.Type;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class DNSUtils {
|
||||
private static final String SRV_QUERY_PREFIX = "_minecraft._tcp.%s";
|
||||
|
||||
/**
|
||||
* Get the resolved address and port of the
|
||||
* given hostname by resolving the SRV records.
|
||||
*
|
||||
* @param hostname the hostname to resolve
|
||||
* @return the resolved address and port, null if none
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static SRVRecord resolveSRV(@NonNull String hostname) {
|
||||
Record[] records = new Lookup(SRV_QUERY_PREFIX.formatted(hostname), Type.SRV).run(); // Resolve SRV records
|
||||
if (records == null) { // No records exist
|
||||
return null;
|
||||
}
|
||||
SRVRecord result = null;
|
||||
for (Record record : records) {
|
||||
result = new SRVRecord((org.xbill.DNS.SRVRecord) record);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolved address of the given
|
||||
* hostname by resolving the A records.
|
||||
*
|
||||
* @param hostname the hostname to resolve
|
||||
* @return the resolved address, null if none
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static ARecord resolveA(@NonNull String hostname) {
|
||||
Record[] records = new Lookup(hostname, Type.A).run(); // Resolve A records
|
||||
if (records == null) { // No records exist
|
||||
return null;
|
||||
}
|
||||
ARecord result = null;
|
||||
for (Record record : records) {
|
||||
result = new ARecord((org.xbill.DNS.ARecord) record);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
49
API/src/main/java/cc/restfulmc/api/common/EnumUtils.java
Normal file
49
API/src/main/java/cc/restfulmc/api/common/EnumUtils.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class EnumUtils {
|
||||
/**
|
||||
* Get the enum constant of the specified enum type with the specified name.
|
||||
*
|
||||
* @param enumType the enum type
|
||||
* @param name the name of the constant to return
|
||||
* @param <T> the type of the enum
|
||||
* @return the enum constant of the specified enum type with the specified name
|
||||
*/
|
||||
public <T extends Enum<T>> T getEnumConstant(@NonNull Class<T> enumType, @NonNull String name) {
|
||||
try {
|
||||
return Enum.valueOf(enumType, name);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
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 appEnv = System.getenv("APP_ENV");
|
||||
production = appEnv != null && (appEnv.equals("production"));
|
||||
}
|
||||
}
|
157
API/src/main/java/cc/restfulmc/api/common/ExpiringSet.java
Normal file
157
API/src/main/java/cc/restfulmc/api/common/ExpiringSet.java
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
||||
import net.jodah.expiringmap.ExpiringMap;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A simple set that expires elements after a certain
|
||||
* amount of time, utilizing the {@link ExpiringMap} library.
|
||||
*
|
||||
* @param <T> The type of element to store within this set
|
||||
* @author Braydon
|
||||
*/
|
||||
@ThreadSafe
|
||||
public final class ExpiringSet<T> implements Iterable<T> {
|
||||
/**
|
||||
* The internal cache for this set.
|
||||
*/
|
||||
@NonNull private final ExpiringMap<T, Long> cache;
|
||||
|
||||
/**
|
||||
* The lifetime (in millis) of the elements in this set.
|
||||
*/
|
||||
private final long lifetime;
|
||||
|
||||
public ExpiringSet(@NonNull ExpirationPolicy expirationPolicy, long duration, @NonNull TimeUnit timeUnit) {
|
||||
this(expirationPolicy, duration, timeUnit, ignored -> {});
|
||||
}
|
||||
|
||||
public ExpiringSet(@NonNull ExpirationPolicy expirationPolicy, long duration, @NonNull TimeUnit timeUnit, @NonNull Consumer<T> onExpire) {
|
||||
//noinspection unchecked
|
||||
this.cache = ExpiringMap.builder()
|
||||
.expirationPolicy(expirationPolicy)
|
||||
.expiration(duration, timeUnit)
|
||||
.expirationListener((key, ignored) -> onExpire.accept((T) key))
|
||||
.build();
|
||||
this.lifetime = timeUnit.toMillis(duration); // Get the lifetime in millis
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to this set.
|
||||
*
|
||||
* @param element the element
|
||||
* @return whether the element was added
|
||||
*/
|
||||
public boolean add(@NonNull T element) {
|
||||
boolean contains = contains(element); // Does this set already contain the element?
|
||||
this.cache.put(element, System.currentTimeMillis() + this.lifetime);
|
||||
return !contains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry time of an element in this set.
|
||||
*
|
||||
* @param element the element
|
||||
* @return the entry time, -1 if not contained
|
||||
*/
|
||||
public long getEntryTime(@NonNull T element) {
|
||||
return contains(element) ? this.cache.get(element) - this.lifetime : -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is
|
||||
* contained within this set.
|
||||
*
|
||||
* @param element the element
|
||||
* @return whether the element is contained
|
||||
*/
|
||||
public boolean contains(@NonNull T element) {
|
||||
Long timeout = this.cache.get(element); // Get the timeout for the element
|
||||
return timeout != null && (timeout > System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this set is empty.
|
||||
*
|
||||
* @return whether this set is empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.cache.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this set.
|
||||
*
|
||||
* @return the size
|
||||
*/
|
||||
public int size() {
|
||||
return this.cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from this set.
|
||||
*
|
||||
* @param element the element
|
||||
* @return whether the element was removed
|
||||
*/
|
||||
public boolean remove(@NonNull T element) {
|
||||
return this.cache.remove(element) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this set.
|
||||
*/
|
||||
public void clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elements in this set.
|
||||
*
|
||||
* @return the elements
|
||||
*/
|
||||
@NonNull
|
||||
public Set<T> getElements() {
|
||||
return this.cache.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over elements of type {@code T}.
|
||||
*
|
||||
* @return an Iterator.
|
||||
*/
|
||||
@Override @NonNull
|
||||
public Iterator<T> iterator() {
|
||||
return this.cache.keySet().iterator();
|
||||
}
|
||||
}
|
67
API/src/main/java/cc/restfulmc/api/common/IPUtils.java
Normal file
67
API/src/main/java/cc/restfulmc/api/common/IPUtils.java
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class IPUtils {
|
||||
private static final String[] IP_HEADERS = new String[] {
|
||||
"CF-Connecting-IP",
|
||||
"X-Forwarded-For"
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the real IP from the given request.
|
||||
*
|
||||
* @param request the request
|
||||
* @return the real IP
|
||||
*/
|
||||
@NonNull
|
||||
public static String getRealIp(@NonNull HttpServletRequest request) {
|
||||
String ip = request.getRemoteAddr();
|
||||
for (String headerName : IP_HEADERS) {
|
||||
String header = request.getHeader(headerName);
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
if (!header.contains(",")) { // Handle single IP
|
||||
ip = header;
|
||||
break;
|
||||
}
|
||||
// Handle multiple IPs
|
||||
String[] ips = header.split(",");
|
||||
for (String ipHeader : ips) {
|
||||
ip = ipHeader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
86
API/src/main/java/cc/restfulmc/api/common/ImageUtils.java
Normal file
86
API/src/main/java/cc/restfulmc/api/common/ImageUtils.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class ImageUtils {
|
||||
/**
|
||||
* Scale the given image to the provided size.
|
||||
*
|
||||
* @param image the image to scale
|
||||
* @param size the size to scale the image to
|
||||
* @return the scaled image
|
||||
*/
|
||||
@NonNull
|
||||
public static BufferedImage resize(@NonNull BufferedImage image, double size) {
|
||||
BufferedImage scaled = new BufferedImage((int) (image.getWidth() * size), (int) (image.getHeight() * size), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = scaled.createGraphics();
|
||||
graphics.drawImage(image, AffineTransform.getScaleInstance(size, size), null);
|
||||
graphics.dispose();
|
||||
return scaled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the given image.
|
||||
*
|
||||
* @param image the image to flip
|
||||
* @return the flipped image
|
||||
*/
|
||||
@NonNull
|
||||
public static BufferedImage flip(@NonNull BufferedImage image) {
|
||||
BufferedImage flipped = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = flipped.createGraphics();
|
||||
graphics.drawImage(image, image.getWidth(), 0, 0, image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
|
||||
graphics.dispose();
|
||||
return flipped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the byte array from the given image.
|
||||
*
|
||||
* @param image the image to extract from
|
||||
* @return the byte array of the image
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static byte[] toByteArray(@NonNull BufferedImage image) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(image, "png", outputStream);
|
||||
outputStream.flush();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* A list of versions for the
|
||||
* Java edition of Minecraft.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Protocol_version_numbers">Protocol Version Numbers</a>
|
||||
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16">Spigot NMS (1.16+)</a>
|
||||
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-10-1-15">Spigot NMS (1.10 - 1.15)</a>
|
||||
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy">Spigot NMS (1.8 - 1.9)</a>
|
||||
*/
|
||||
@RequiredArgsConstructor @Getter @ToString
|
||||
public enum JavaMinecraftVersion {
|
||||
V1_20_3(765, "v1_20_R3"), // 1.20.3 & 1.20.4
|
||||
V1_20_2(764, "v1_20_R2"), // 1.20.2
|
||||
V1_20(763, "v1_20_R1"), // 1.20 & 1.20.1
|
||||
|
||||
V1_19_4(762, "v1_19_R3"), // 1.19.4
|
||||
V1_19_3(761, "v1_19_R2"), // 1.19.3
|
||||
V1_19_1(760, "v1_19_R1"), // 1.19.1 & 1.19.2
|
||||
V1_19(759, "v1_19_R1"), // 1.19
|
||||
|
||||
V1_18_2(758, "v1_18_R2"), // 1.18.2
|
||||
V1_18(757, "v1_18_R1"), // 1.18 & 1.18.1
|
||||
|
||||
V1_17_1(756, "v1_17_R1"), // 1.17.1
|
||||
V1_17(755, "v1_17_R1"), // 1.17
|
||||
|
||||
V1_16_4(754, "v1_16_R3"), // 1.16.4 & 1.16.5
|
||||
V1_16_3(753, "v1_16_R2"), // 1.16.3
|
||||
V1_16_2(751, "v1_16_R2"), // 1.16.2
|
||||
V1_16_1(736, "v1_16_R1"), // 1.16.1
|
||||
V1_16(735, "v1_16_R1"), // 1.16
|
||||
|
||||
V1_15_2(578, "v1_15_R1"), // 1.15.2
|
||||
V1_15_1(575, "v1_15_R1"), // 1.15.1
|
||||
V1_15(573, "v1_15_R1"), // 1.15
|
||||
|
||||
V1_14_4(498, "v1_14_R1"), // 1.14.4
|
||||
V1_14_3(490, "v1_14_R1"), // 1.14.3
|
||||
V1_14_2(485, "v1_14_R1"), // 1.14.2
|
||||
V1_14_1(480, "v1_14_R1"), // 1.14.1
|
||||
V1_14(477, "v1_14_R1"), // 1.14
|
||||
|
||||
V1_13_2(404, "v1_13_R2"), // 1.13.2
|
||||
V1_13_1(401, "v1_13_R2"), // 1.13.1
|
||||
V1_13(393, "v1_13_R1"), // 1.13
|
||||
|
||||
V1_12_2(340, "v1_12_R1"), // 1.12.2
|
||||
V1_12_1(338, "v1_12_R1"), // 1.12.1
|
||||
V1_12(335, "v1_12_R1"), // 1.12
|
||||
|
||||
V1_11_1(316, "v1_11_R1"), // 1.11.1 & 1.11.2
|
||||
V1_11(315, "v1_11_R1"), // 1.11
|
||||
|
||||
V1_10(210, "v1_10_R1"), // 1.10.x
|
||||
|
||||
V1_9_3(110, "v1_9_R2"), // 1.9.3 & 1.9.4
|
||||
V1_9_2(109, "v1_9_R1"), // 1.9.2
|
||||
V1_9_1(108, "v1_9_R1"), // 1.9.1
|
||||
V1_9(107, "v1_9_R1"), // 1.9
|
||||
|
||||
V1_8(47, "v1_8_R3"), // 1.8.x
|
||||
|
||||
V1_7_6(5, "v1_7_R4"), // 1.7.6 - 1.7.10
|
||||
|
||||
UNKNOWN(-1, "Unknown");
|
||||
|
||||
// Game Updates
|
||||
public static final JavaMinecraftVersion TRAILS_AND_TALES = JavaMinecraftVersion.V1_20;
|
||||
public static final JavaMinecraftVersion THE_WILD_UPDATE = JavaMinecraftVersion.V1_19;
|
||||
public static final JavaMinecraftVersion CAVES_AND_CLIFFS_PT_2 = JavaMinecraftVersion.V1_18;
|
||||
public static final JavaMinecraftVersion CAVES_AND_CLIFFS_PT_1 = JavaMinecraftVersion.V1_17;
|
||||
public static final JavaMinecraftVersion NETHER_UPDATE = JavaMinecraftVersion.V1_16;
|
||||
public static final JavaMinecraftVersion BUZZY_BEES = JavaMinecraftVersion.V1_15;
|
||||
public static final JavaMinecraftVersion VILLAGE_AND_PILLAGE = JavaMinecraftVersion.V1_14;
|
||||
public static final JavaMinecraftVersion UPDATE_AQUATIC = JavaMinecraftVersion.V1_13;
|
||||
public static final JavaMinecraftVersion WORLD_OF_COLOR_UPDATE = JavaMinecraftVersion.V1_12;
|
||||
public static final JavaMinecraftVersion EXPLORATION_UPDATE = JavaMinecraftVersion.V1_11;
|
||||
public static final JavaMinecraftVersion FROSTBURN_UPDATE = JavaMinecraftVersion.V1_10;
|
||||
public static final JavaMinecraftVersion THE_COMBAT_UPDATE = JavaMinecraftVersion.V1_9;
|
||||
public static final JavaMinecraftVersion BOUNTIFUL_UPDATE = JavaMinecraftVersion.V1_8;
|
||||
|
||||
private static final JavaMinecraftVersion[] VALUES = JavaMinecraftVersion.values();
|
||||
|
||||
/**
|
||||
* The protocol number of this version.
|
||||
*/
|
||||
private final int protocol;
|
||||
|
||||
/**
|
||||
* The server version for this version.
|
||||
*/
|
||||
private final String nmsVersion;
|
||||
|
||||
/**
|
||||
* The cached name of this version.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Get the name of this protocol version.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
// We have a name
|
||||
if (this.name != null) {
|
||||
return this.name;
|
||||
}
|
||||
// Use the server version as the name if unknown
|
||||
if (this == UNKNOWN) {
|
||||
this.name = this.getNmsVersion();
|
||||
} else { // Parse the name
|
||||
this.name = name().substring(1);
|
||||
this.name = this.name.replace("_", ".");
|
||||
}
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum Minecraft version.
|
||||
*
|
||||
* @return the minimum version
|
||||
*/
|
||||
@NonNull
|
||||
public static JavaMinecraftVersion getMinimumVersion() {
|
||||
return VALUES[VALUES.length - 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version from the given protocol.
|
||||
*
|
||||
* @param protocol the protocol to get the version for
|
||||
* @return the version, null if none
|
||||
*/
|
||||
public static JavaMinecraftVersion byProtocol(int protocol) {
|
||||
for (JavaMinecraftVersion version : values()) {
|
||||
if (version != UNKNOWN && version.getProtocol() == protocol) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
50
API/src/main/java/cc/restfulmc/api/common/MiscUtils.java
Normal file
50
API/src/main/java/cc/restfulmc/api/common/MiscUtils.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@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_USERNAMES = Arrays.asList("8", "g");
|
||||
|
||||
/**
|
||||
* Check if the given username is a valid.
|
||||
*
|
||||
* @param username the username to check
|
||||
* @return whether the username is valid
|
||||
*/
|
||||
public static boolean isUsernameValid(@NonNull String username) {
|
||||
return WHITELISTED_USERNAMES.contains(username.toLowerCase()) || USERNAME_REGEX.matcher(username).matches();
|
||||
}
|
||||
}
|
107
API/src/main/java/cc/restfulmc/api/common/MojangServer.java
Normal file
107
API/src/main/java/cc/restfulmc/api/common/MojangServer.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Represents a service provided by Mojang.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public enum MojangServer {
|
||||
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.
|
||||
*/
|
||||
@NonNull private final String endpoint;
|
||||
|
||||
/**
|
||||
* Ping this service and get the status of it.
|
||||
*
|
||||
* @return the service status
|
||||
*/
|
||||
@NonNull
|
||||
public Status getStatus() {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(endpoint.substring(8));
|
||||
long before = System.currentTimeMillis();
|
||||
if (address.isReachable(STATUS_TIMEOUT)) {
|
||||
// The time it took to reach the host is 75% of
|
||||
// the timeout, consider it to be degraded.
|
||||
if ((System.currentTimeMillis() - before) > STATUS_TIMEOUT * 0.75D) {
|
||||
return Status.DEGRADED;
|
||||
}
|
||||
return Status.ONLINE;
|
||||
}
|
||||
} catch (UnknownHostException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ignored) {
|
||||
// We can safely ignore any errors, we're simply checking
|
||||
// if the host is reachable, if it's not, it's offline.
|
||||
}
|
||||
return Status.OFFLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of a service.
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* The service is online and accessible.
|
||||
*/
|
||||
ONLINE,
|
||||
|
||||
/**
|
||||
* The service is online, but is experiencing degraded performance.
|
||||
*/
|
||||
DEGRADED,
|
||||
|
||||
/**
|
||||
* The service is offline and inaccessible.
|
||||
*/
|
||||
OFFLINE
|
||||
}
|
||||
}
|
50
API/src/main/java/cc/restfulmc/api/common/UUIDUtils.java
Normal file
50
API/src/main/java/cc/restfulmc/api/common/UUIDUtils.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@UtilityClass
|
||||
public final class UUIDUtils {
|
||||
/**
|
||||
* Add dashes to an untrimmed uuid.
|
||||
*
|
||||
* @param trimmed the untrimmed uuid
|
||||
* @return the uuid with dashes
|
||||
*/
|
||||
@NonNull
|
||||
public static UUID addDashes(@NonNull String trimmed) {
|
||||
StringBuilder builder = new StringBuilder(trimmed);
|
||||
for (int i = 0, pos = 20; i < 4; i++, pos -= 4) {
|
||||
builder.insert(pos, "-");
|
||||
}
|
||||
return UUID.fromString(builder.toString());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 lombok.NonNull;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Represents a packet in the
|
||||
* Minecraft Java protocol.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Protocol">Protocol Docs</a>
|
||||
*/
|
||||
public abstract class TCPPacket {
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @param outputStream the output stream to write to
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public abstract void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Write a variable integer to the output stream.
|
||||
*
|
||||
* @param outputStream the output stream to write to
|
||||
* @param paramInt the integer to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected final void writeVarInt(DataOutputStream outputStream, int paramInt) throws IOException {
|
||||
while (true) {
|
||||
if ((paramInt & 0xFFFFFF80) == 0) {
|
||||
outputStream.writeByte(paramInt);
|
||||
return;
|
||||
}
|
||||
outputStream.writeByte(paramInt & 0x7F | 0x80);
|
||||
paramInt >>>= 7;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a variable integer from the input stream.
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @return the integer that was read
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected final int readVarInt(@NonNull DataInputStream inputStream) throws IOException {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (true) {
|
||||
int k = inputStream.readByte();
|
||||
i |= (k & 0x7F) << j++ * 7;
|
||||
if (j > 5) {
|
||||
throw new RuntimeException("VarInt too big");
|
||||
}
|
||||
if ((k & 0x80) != 128) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
/**
|
||||
* Represents a packet in the
|
||||
* Minecraft Bedrock protocol.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol">Protocol Docs</a>
|
||||
*/
|
||||
public abstract class UDPPacket {
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param socket the socket to process the packet for
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public abstract void process(@NonNull DatagramSocket socket) throws IOException;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.bedrock;
|
||||
|
||||
import cc.restfulmc.api.common.packet.UDPPacket;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* This packet is sent by the client to the server to
|
||||
* request a pong response from the server. The server
|
||||
* will respond with a string containing the server's status.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Ping">Protocol Docs</a>
|
||||
*/
|
||||
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 };
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// Construct the packet buffer
|
||||
ByteBuffer buffer = ByteBuffer.allocate(33).order(ByteOrder.LITTLE_ENDIAN);;
|
||||
buffer.put(ID); // Packet ID
|
||||
buffer.putLong(System.currentTimeMillis()); // Timestamp
|
||||
buffer.put(MAGIC); // Magic
|
||||
buffer.putLong(0L); // Client GUID
|
||||
|
||||
// Send the packet
|
||||
socket.send(new DatagramPacket(buffer.array(), 0, buffer.limit()));
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.bedrock;
|
||||
|
||||
import cc.restfulmc.api.common.packet.UDPPacket;
|
||||
import cc.restfulmc.api.model.server.BedrockMinecraftServer;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* This packet is sent by the server to the client in
|
||||
* response to the {@link BedrockUnconnectedPingPacket}.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Pong">Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class BedrockUnconnectedPongPacket extends UDPPacket {
|
||||
private static final byte ID = 0x1C; // The ID of the packet
|
||||
|
||||
/**
|
||||
* The response from the server, null if none.
|
||||
*/
|
||||
private 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[2048];
|
||||
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
|
||||
socket.receive(receivePacket);
|
||||
|
||||
// Construct a buffer from the received packet
|
||||
ByteBuffer buffer = ByteBuffer.wrap(receivePacket.getData()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
byte id = buffer.get(); // The received packet id
|
||||
if (id == ID) {
|
||||
String response = new String(buffer.array(), StandardCharsets.UTF_8).trim(); // Extract the response
|
||||
|
||||
// Trim the length of the response (short) from the
|
||||
// start of the string, which begins with the edition name
|
||||
for (BedrockMinecraftServer.Edition edition : BedrockMinecraftServer.Edition.values()) {
|
||||
int startIndex = response.indexOf(edition.name());
|
||||
if (startIndex != -1) {
|
||||
response = response.substring(startIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.tcp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.TCPPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This packet is sent by the client to the server to set
|
||||
* the hostname, port, and protocol version of the client.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Protocol#Handshake">Protocol Docs</a>
|
||||
*/
|
||||
@AllArgsConstructor @ToString
|
||||
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
|
||||
|
||||
/**
|
||||
* The hostname of the server.
|
||||
*/
|
||||
@NonNull private final String hostname;
|
||||
|
||||
/**
|
||||
* The port of the server.
|
||||
*/
|
||||
private final int port;
|
||||
|
||||
/**
|
||||
* The protocol version of the server.
|
||||
*/
|
||||
private final int protocolVersion;
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @param outputStream the output stream to write to
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
|
||||
try (ByteArrayOutputStream handshakeBytes = new ByteArrayOutputStream();
|
||||
DataOutputStream handshake = new DataOutputStream(handshakeBytes)
|
||||
) {
|
||||
handshake.writeByte(ID); // Write the ID of the packet
|
||||
writeVarInt(handshake, protocolVersion); // Write the protocol version
|
||||
writeVarInt(handshake, hostname.length()); // Write the length of the hostname
|
||||
handshake.writeBytes(hostname); // Write the hostname
|
||||
handshake.writeShort(port); // Write the port
|
||||
writeVarInt(handshake, STATUS_HANDSHAKE); // Write the status handshake ID
|
||||
|
||||
// Write the handshake bytes to the output stream
|
||||
writeVarInt(outputStream, handshakeBytes.size());
|
||||
outputStream.write(handshakeBytes.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.tcp;
|
||||
|
||||
import cc.restfulmc.api.common.packet.TCPPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This packet is sent by the client to the server to request the
|
||||
* status of the server. The server will respond with a json object
|
||||
* containing the server's status.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Protocol#Status_Request">Protocol Docs</a>
|
||||
*/
|
||||
@Getter
|
||||
public final class JavaStatusInStartPacket extends TCPPacket {
|
||||
private static final byte ID = 0x00; // The ID of the packet
|
||||
|
||||
/**
|
||||
* The response json from the server, null if none.
|
||||
*/
|
||||
private String response;
|
||||
|
||||
/**
|
||||
* Process this packet.
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @param outputStream the output stream to write to
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
|
||||
// Send the status request
|
||||
outputStream.writeByte(0x01); // Size of packet
|
||||
outputStream.writeByte(ID);
|
||||
|
||||
// Read the status response
|
||||
readVarInt(inputStream); // Size of the response
|
||||
int id = readVarInt(inputStream);
|
||||
if (id == -1) { // The stream was prematurely ended
|
||||
throw new IOException("Server prematurely ended stream.");
|
||||
} else if (id != ID) { // Invalid packet ID
|
||||
throw new IOException("Server returned invalid packet ID.");
|
||||
}
|
||||
|
||||
int length = readVarInt(inputStream); // Length of the response
|
||||
if (length == -1) { // The stream was prematurely ended
|
||||
throw new IOException("Server prematurely ended stream.");
|
||||
} else if (length == 0) {
|
||||
throw new IOException("Server returned unexpected value.");
|
||||
}
|
||||
|
||||
// Get the json response
|
||||
byte[] data = new byte[length];
|
||||
inputStream.readFully(data);
|
||||
response = new String(data);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.renderer;
|
||||
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A isometric renderer for a {@link ISkinPart}.
|
||||
*
|
||||
* @param <T> the type of part to render
|
||||
* @author Braydon
|
||||
*/
|
||||
public abstract class IsometricSkinRenderer<T extends ISkinPart> extends SkinRenderer<T> {
|
||||
/**
|
||||
* Draw a part onto the texture.
|
||||
*
|
||||
* @param graphics the graphics to draw to
|
||||
* @param partImage the part image to draw
|
||||
* @param transform the transform to apply
|
||||
* @param x the x position to draw at
|
||||
* @param y the y position to draw at
|
||||
* @param width the part image width
|
||||
* @param height the part image height
|
||||
*/
|
||||
protected final void drawPart(@NonNull Graphics2D graphics, @NonNull BufferedImage partImage, @NonNull AffineTransform transform,
|
||||
double x, double y, int width, int height) {
|
||||
graphics.setTransform(transform);
|
||||
graphics.drawImage(partImage, (int) x, (int) y, width, height, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.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 javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* A renderer for a {@link ISkinPart}.
|
||||
*
|
||||
* @param <T> the type of part to render
|
||||
* @author Braydon
|
||||
*/
|
||||
public abstract class SkinRenderer<T extends ISkinPart> {
|
||||
/**
|
||||
* Invoke this render to render the
|
||||
* given skin part for the provided skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param part the part to render
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@NonNull public abstract BufferedImage render(@NonNull Skin skin, @NonNull T part, boolean overlays, int size);
|
||||
|
||||
/**
|
||||
* Get the texture of a part of a skin.
|
||||
*
|
||||
* @param skin the skin to get the part texture from
|
||||
* @param part the part of the skin to get
|
||||
* @param size the size to scale the texture to
|
||||
* @return the texture of the skin part
|
||||
*/
|
||||
@SneakyThrows
|
||||
protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) {
|
||||
ISkinPart.Vanilla.Coordinates coordinates = part.getCoordinates(); // The coordinates of the part
|
||||
|
||||
// The skin texture is legacy, use legacy coordinates
|
||||
if (skin.isLegacy() && part.hasLegacyCoordinates()) {
|
||||
coordinates = part.getLegacyCoordinates();
|
||||
}
|
||||
int width = part.getWidth(); // The width of the part
|
||||
if (skin.getModel() == Skin.Model.SLIM && part.isFrontArm()) {
|
||||
width--;
|
||||
}
|
||||
BufferedImage skinImage = ImageIO.read(new ByteArrayInputStream(skin.getSkinImage())); // The skin texture
|
||||
BufferedImage partTexture = getSkinPartTexture(skinImage, coordinates.getX(), coordinates.getY(), width, part.getHeight(), size);
|
||||
if (coordinates instanceof ISkinPart.Vanilla.LegacyCoordinates legacyCoordinates && legacyCoordinates.isFlipped()) {
|
||||
partTexture = ImageUtils.flip(partTexture);
|
||||
}
|
||||
return partTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the texture of a specific part of a skin.
|
||||
*
|
||||
* @param skinImage the skin image to get the part from
|
||||
* @param x the x position of the part
|
||||
* @param y the y position of the part
|
||||
* @param width the width of the part
|
||||
* @param height the height of the part
|
||||
* @param size the size to scale the part to
|
||||
* @return the texture of the skin part
|
||||
*/
|
||||
@SneakyThrows
|
||||
private BufferedImage getSkinPartTexture(@NonNull BufferedImage skinImage, int x, int y, int width, int height, double size) {
|
||||
// Create a new BufferedImage for the part of the skin texture
|
||||
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
// Crop just the part we want based on our x, y, width, and height
|
||||
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
|
||||
|
||||
// Scale the skin part texture
|
||||
if (size > 0D) {
|
||||
headTexture = ImageUtils.resize(headTexture, size);
|
||||
}
|
||||
return headTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an overlay to a texture.
|
||||
*
|
||||
* @param overlayImage the part to overlay
|
||||
*/
|
||||
protected final void applyOverlay(@NonNull BufferedImage overlayImage) {
|
||||
applyOverlay(overlayImage.createGraphics(), overlayImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an overlay to a texture.
|
||||
*
|
||||
* @param graphics the graphics to overlay on
|
||||
* @param overlayImage the part to overlay
|
||||
*/
|
||||
protected final void applyOverlay(@NonNull Graphics2D graphics, @NonNull BufferedImage overlayImage) {
|
||||
try {
|
||||
graphics.drawImage(overlayImage, 0, 0, null);
|
||||
graphics.dispose();
|
||||
} catch (Exception ignored) {
|
||||
// We can safely ignore this, legacy
|
||||
// skins don't have overlays
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.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 java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A basic 2D renderer for a {@link ISkinPart.Custom#BODY_FLAT}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class BodySkinPartRenderer extends SkinRenderer<ISkinPart.Custom> {
|
||||
public static final BodySkinPartRenderer INSTANCE = new BodySkinPartRenderer();
|
||||
|
||||
/**
|
||||
* Invoke this render to render the
|
||||
* given skin part for the provided skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param part the part to render
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@Override @NonNull
|
||||
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Custom part, boolean overlays, int size) {
|
||||
BufferedImage texture = new BufferedImage(16, 32, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||
|
||||
// Get the Vanilla skin parts to draw
|
||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, -1);
|
||||
BufferedImage body = getVanillaSkinPart(skin, ISkinPart.Vanilla.BODY_FRONT, -1);
|
||||
BufferedImage leftArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_ARM_FRONT, -1);
|
||||
BufferedImage rightArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_ARM_FRONT, -1);
|
||||
BufferedImage leftLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_LEG_FRONT, -1);
|
||||
BufferedImage rightLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_LEG_FRONT, -1);
|
||||
|
||||
// Draw the body parts
|
||||
graphics.drawImage(face, 4, 0, null);
|
||||
graphics.drawImage(body, 4, 8, null);
|
||||
graphics.drawImage(leftArm, skin.getModel() == Skin.Model.SLIM ? 1 : 0, 8, null);
|
||||
graphics.drawImage(rightArm, 12, 8, null);
|
||||
graphics.drawImage(leftLeg, 8, 20, null);
|
||||
graphics.drawImage(rightLeg, 4, 20, null);
|
||||
|
||||
graphics.dispose();
|
||||
return ImageUtils.resize(texture, size / 8D);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.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 java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A isometric 3D renderer for a {@link ISkinPart.Custom#HEAD}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class IsometricHeadSkinPartRenderer extends IsometricSkinRenderer<ISkinPart.Custom> {
|
||||
public static final IsometricHeadSkinPartRenderer INSTANCE = new IsometricHeadSkinPartRenderer();
|
||||
|
||||
private static final double SKEW_A = 26D / 45D; // 0.57777777
|
||||
private static final double SKEW_B = SKEW_A * 2D; // 1.15555555
|
||||
|
||||
private static final AffineTransform HEAD_TOP_TRANSFORM = new AffineTransform(1D, -SKEW_A, 1, SKEW_A, 0, 0);
|
||||
private static final AffineTransform FACE_TRANSFORM = new AffineTransform(1D, -SKEW_A, 0D, SKEW_B, 0d, SKEW_A);
|
||||
private static final AffineTransform HEAD_LEFT_TRANSFORM = new AffineTransform(1D, SKEW_A, 0D, SKEW_B, 0D, 0D);
|
||||
|
||||
/**
|
||||
* Invoke this render to render the
|
||||
* given skin part for the provided skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param part the part to render
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@Override @NonNull
|
||||
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Custom part, boolean overlays, int size) {
|
||||
double scale = (size / 8D) / 2.5;
|
||||
double zOffset = scale * 3.5D;
|
||||
double xOffset = scale * 2D;
|
||||
|
||||
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||
|
||||
// Get the Vanilla skin parts to draw
|
||||
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale);
|
||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale);
|
||||
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale);
|
||||
|
||||
// Draw the top head part
|
||||
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
||||
|
||||
// Draw the face part
|
||||
double x = xOffset + 8 * scale;
|
||||
drawPart(graphics, face, FACE_TRANSFORM, x, x + zOffset - 0.5, face.getWidth(), face.getHeight());
|
||||
|
||||
// Draw the left head part
|
||||
drawPart(graphics, headLeft, HEAD_LEFT_TRANSFORM, xOffset + 1, zOffset - 0.5, headLeft.getWidth(), headLeft.getHeight());
|
||||
|
||||
graphics.dispose();
|
||||
return texture;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.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 java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A basic 2D renderer for a {@link ISkinPart.Vanilla}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class VanillaSkinPartRenderer extends SkinRenderer<ISkinPart.Vanilla> {
|
||||
public static final VanillaSkinPartRenderer INSTANCE = new VanillaSkinPartRenderer();
|
||||
|
||||
/**
|
||||
* Invoke this render to render the
|
||||
* given skin part for the provided skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param part the part to render
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@Override @NonNull
|
||||
public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, boolean overlays, int size) {
|
||||
double scale = size / 8D;
|
||||
BufferedImage partImage = getVanillaSkinPart(skin, part, scale); // Get the part image
|
||||
if (!overlays) { // Not rendering overlays
|
||||
return partImage;
|
||||
}
|
||||
// Create a new image, draw our skin part texture, and then apply overlays
|
||||
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||
graphics.drawImage(partImage, 0, 0, null);
|
||||
|
||||
// Draw part overlays
|
||||
ISkinPart.Vanilla[] overlayParts = part.getOverlays();
|
||||
if (overlayParts != null) {
|
||||
for (ISkinPart.Vanilla overlay : overlayParts) {
|
||||
applyOverlay(graphics, getVanillaSkinPart(skin, overlay, scale));
|
||||
}
|
||||
}
|
||||
graphics.dispose();
|
||||
return texture;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* This exception is raised when a
|
||||
* {@link JsonWebRequest} encounters an error.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Getter
|
||||
public class JsonWebException extends RuntimeException {
|
||||
/**
|
||||
* The status code of the response.
|
||||
*/
|
||||
private final int statusCode;
|
||||
|
||||
protected JsonWebException(int statusCode, @NonNull String message) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
protected JsonWebException(int statusCode, @NonNull Throwable cause) {
|
||||
super(cause);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import cc.restfulmc.api.config.AppConfig;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Json web request.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Getter
|
||||
public final class JsonWebRequest {
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
/**
|
||||
* The endpoint to make the request to.
|
||||
*/
|
||||
@NonNull private final String endpoint;
|
||||
|
||||
/**
|
||||
* The method to use for the request.
|
||||
*/
|
||||
@NonNull private final HttpMethod method;
|
||||
|
||||
/**
|
||||
* The headers to send with the request.
|
||||
*/
|
||||
private final Map<String, String> headers = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/**
|
||||
* Make a new web request.
|
||||
*
|
||||
* @param endpoint the endpoint to make the request to
|
||||
* @param method the method of the request to make
|
||||
* @return the web request
|
||||
*/
|
||||
@NonNull
|
||||
public static JsonWebRequest makeRequest(@NonNull String endpoint, @NonNull HttpMethod method) {
|
||||
return new JsonWebRequest(endpoint, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a header for this request.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
* @return the request
|
||||
*/
|
||||
@NonNull
|
||||
public JsonWebRequest header(@NonNull String name, @NonNull String value) {
|
||||
headers.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this request.
|
||||
*
|
||||
* @param responseType the response type
|
||||
* @return the response
|
||||
* @param <T> the type of the response
|
||||
* @throws JsonWebException if an error is encountered while making the request
|
||||
*/
|
||||
public <T> T execute(@NonNull Class<T> responseType) throws JsonWebException {
|
||||
// Build the request
|
||||
HttpRequest.Builder request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(endpoint))
|
||||
.method(method.name(), HttpRequest.BodyPublishers.noBody());
|
||||
// Append headers
|
||||
headers.put("User-Agent", "RESTfulMC");
|
||||
headers.put("Content-Type", "application/json");
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
request.header(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
// Send the request and get the response
|
||||
int status = -1; // The response status code
|
||||
try {
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request.build(), HttpResponse.BodyHandlers.ofString());
|
||||
status = response.statusCode(); // Set the response status
|
||||
if (status != HttpStatus.OK.value()) { // Status code is not OK, raise an exception
|
||||
throw new IOException("Failed to make a %s request to %s: %s".formatted(method.name(), endpoint, status));
|
||||
}
|
||||
// Return with the response as the type
|
||||
return AppConfig.GSON.fromJson(response.body(), responseType);
|
||||
} catch (Exception ex) {
|
||||
if (!(ex instanceof JsonWebException)) {
|
||||
throw new JsonWebException(status, ex);
|
||||
}
|
||||
throw (JsonWebException) ex;
|
||||
}
|
||||
}
|
||||
}
|
112
API/src/main/java/cc/restfulmc/api/config/AppConfig.java
Normal file
112
API/src/main/java/cc/restfulmc/api/config/AppConfig.java
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* The configuration for the app.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Configuration @Getter
|
||||
public class AppConfig {
|
||||
public static AppConfig INSTANCE;
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.setDateFormat("MM-dd-yyyy HH:mm:ss")
|
||||
.create();
|
||||
|
||||
@Value("${server.publicUrl}")
|
||||
private String serverPublicUrl;
|
||||
|
||||
/**
|
||||
* The build properties of the
|
||||
* app, null if the app is not built.
|
||||
*/
|
||||
private final BuildProperties buildProperties;
|
||||
|
||||
@Autowired
|
||||
public AppConfig(BuildProperties buildProperties) {
|
||||
this.buildProperties = buildProperties;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void onInitialize() {
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the OpenAI specification for this app.
|
||||
*
|
||||
* @return the specification
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public OpenAPI defineOpenAPI() {
|
||||
Info info = new Info();
|
||||
info.setTitle("RESTfulMC");
|
||||
info.setVersion(buildProperties == null ? "N/A" : buildProperties.getVersion());
|
||||
info.setDescription(buildProperties == null ? "N/A" : buildProperties.get("description"));
|
||||
info.setContact(new Contact().name("Braydon (Rainnny)").url("https://rainnny.club").email("braydonrainnny@gmail.com"));
|
||||
info.setLicense(new License().name("MIT License").url("https://opensource.org/licenses/MIT"));
|
||||
|
||||
return new OpenAPI()
|
||||
.info(info)
|
||||
.addServersItem(new Server().url(serverPublicUrl).description("The public server URL"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure CORS for the app.
|
||||
*
|
||||
* @return the WebMvc config
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public WebMvcConfigurer configureCors() {
|
||||
return new WebMvcConfigurer() {
|
||||
@Override
|
||||
public void addCorsMappings(@NonNull CorsRegistry registry) {
|
||||
// Allow all origins to access the API
|
||||
registry.addMapping("/**")
|
||||
.allowedOrigins("*") // Allow all origins
|
||||
.allowedMethods("*") // Allow all methods
|
||||
.allowedHeaders("*"); // Allow all headers
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
96
API/src/main/java/cc/restfulmc/api/config/RedisConfig.java
Normal file
96
API/src/main/java/cc/restfulmc/api/config/RedisConfig.java
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Configuration
|
||||
@Log4j2(topic = "Redis")
|
||||
public class RedisConfig {
|
||||
/**
|
||||
* The Redis server host.
|
||||
*/
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* The Redis server port.
|
||||
*/
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* The Redis database index.
|
||||
*/
|
||||
@Value("${spring.data.redis.database}")
|
||||
private int database;
|
||||
|
||||
/**
|
||||
* The optional Redis password.
|
||||
*/
|
||||
@Value("${spring.data.redis.auth}")
|
||||
private String auth;
|
||||
|
||||
/**
|
||||
* Build the config to use for Redis.
|
||||
*
|
||||
* @return the config
|
||||
* @see RedisTemplate for config
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public RedisTemplate<String, Object> redisTemplate() {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(jedisConnectionFactory());
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the connection factory to use
|
||||
* when making connections to Redis.
|
||||
*
|
||||
* @return the built factory
|
||||
* @see JedisConnectionFactory for factory
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public JedisConnectionFactory jedisConnectionFactory() {
|
||||
log.info("Connecting to Redis at {}:{}/{}", host, port, database);
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
|
||||
config.setDatabase(database);
|
||||
if (!auth.trim().isEmpty()) { // Auth with our provided password
|
||||
log.info("Using auth...");
|
||||
config.setPassword(auth);
|
||||
}
|
||||
return new JedisConnectionFactory(config);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The controller for handling
|
||||
* Mojang related requests.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/mojang", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Log4j2(topic = "Mojang Controller")
|
||||
@Tag(name = "Mojang Controller", description = "The controller for handling Mojang related requests.")
|
||||
public final class MojangController {
|
||||
/**
|
||||
* The Mojang service to use.
|
||||
*/
|
||||
@NonNull private final MojangService mojangService;
|
||||
|
||||
@Autowired
|
||||
public MojangController(@NonNull MojangService mojangService) {
|
||||
this.mojangService = mojangService;
|
||||
}
|
||||
|
||||
/**
|
||||
* A GET route to get the status of Mojang servers.
|
||||
*
|
||||
* @return the status response
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
@ResponseBody
|
||||
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()) {
|
||||
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(Map.of("servers", servers));
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* The controller for handling
|
||||
* {@link Player} related requests.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/player", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Log4j2(topic = "Player Controller")
|
||||
@Tag(name = "Player Controller", description = "The controller for handling player related requests.")
|
||||
public final class PlayerController {
|
||||
/**
|
||||
* The Mojang service to use.
|
||||
*/
|
||||
@NonNull private final MojangService mojangService;
|
||||
|
||||
@Autowired
|
||||
public PlayerController(@NonNull MojangService mojangService) {
|
||||
this.mojangService = mojangService;
|
||||
}
|
||||
|
||||
/**
|
||||
* A GET route to get a player by their username or UUID.
|
||||
*
|
||||
* @param query the query to search for the player by
|
||||
* @param signed whether the profile is signed
|
||||
* @return the player response
|
||||
* @throws BadRequestException if the UUID or username is invalid
|
||||
* @throws ResourceNotFoundException if the player is not found
|
||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
||||
*/
|
||||
@GetMapping("/{query}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<CachedPlayer> getPlayer(
|
||||
@Parameter(description = "The player username or UUID to get", example = "Rainnny") @PathVariable @NonNull String query,
|
||||
@Parameter(description = "Whether the profile is signed by Mojang") @RequestParam(required = false) boolean signed
|
||||
) throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
||||
return ResponseEntity.ofNullable(mojangService.getPlayer(query, signed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the part of a skin texture for
|
||||
* a player by their username or UUID.
|
||||
* <p>
|
||||
* If the player being searched is
|
||||
* invalid, the default Steve skin
|
||||
* will be used.
|
||||
* </p>
|
||||
*
|
||||
* @param partName the part of the player's skin texture to get
|
||||
* @param query the query to search for the player by
|
||||
* @param extension the skin part image extension
|
||||
* @param size the size of the skin part image
|
||||
* @return the skin part texture
|
||||
* @throws BadRequestException if the extension is invalid
|
||||
*/
|
||||
@GetMapping(value = "/{partName}/{query}.{extension}", produces = { MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE })
|
||||
@ResponseBody
|
||||
public ResponseEntity<byte[]> getPartTexture(
|
||||
@Parameter(description = "The skin part to get the texture of", example = "head") @PathVariable @NonNull String partName,
|
||||
@Parameter(description = "The player username or UUID to get", example = "Rainnny") @PathVariable @NonNull String query,
|
||||
@Parameter(description = "The image extension", example = "png") @PathVariable @NonNull String extension,
|
||||
@Parameter(description = "Whether to render skin overlays") @RequestParam(required = false, defaultValue = "true") boolean overlays,
|
||||
@Parameter(description = "The size to scale the skin part texture to", example = "256") @RequestParam(required = false) String size
|
||||
) throws BadRequestException {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG)
|
||||
.body(mojangService.getSkinPartTexture(partName, query, extension, overlays, size));
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The controller for handling
|
||||
* {@link MinecraftServer} related requests.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/server", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Log4j2(topic = "Server Controller")
|
||||
@Tag(name = "Server Controller", description = "The controller for handling server related requests.")
|
||||
public final class ServerController {
|
||||
/**
|
||||
* The Mojang service to use.
|
||||
*/
|
||||
@NonNull private final MojangService mojangService;
|
||||
|
||||
@Autowired
|
||||
public ServerController(@NonNull MojangService mojangService) {
|
||||
this.mojangService = mojangService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Minecraft server by its platform and hostname.
|
||||
*
|
||||
* @param platform the platform of the server
|
||||
* @param hostname the hostname of the server
|
||||
* @return the server
|
||||
* @throws BadRequestException if the hostname, platform, or port is invalid
|
||||
* @throws ResourceNotFoundException if the server isn't found
|
||||
*/
|
||||
@GetMapping("/{platform}/{hostname}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<CachedMinecraftServer> getServer(
|
||||
@Parameter(description = "The platform of the server", example = "java") @PathVariable @NonNull String platform,
|
||||
@Parameter(description = "The server hostname to lookup (Append :<port> for port)", example = "hypixel.net") @PathVariable @NonNull String hostname
|
||||
) throws BadRequestException, ResourceNotFoundException {
|
||||
return ResponseEntity.ofNullable(mojangService.getMinecraftServer(platform, hostname));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the server with the
|
||||
* given hostname is blocked by Mojang.
|
||||
*
|
||||
* @param hostname the server hostname to check
|
||||
* @return whether the hostname is blocked
|
||||
*/
|
||||
@GetMapping("/blocked/{hostname}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> isServerBlocked(
|
||||
@Parameter(description = "The hostname of the server", example = "hypixel.net") @PathVariable @NonNull String hostname
|
||||
) {
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"blocked", mojangService.isServerBlocked(hostname)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server icon of a Minecraft
|
||||
* server by its platform and hostname.
|
||||
*
|
||||
* @param hostname the hostname of the server
|
||||
* @return the server icon
|
||||
*/
|
||||
@GetMapping(value = "/icon/{hostname}", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<byte[]> getServerFavicon(
|
||||
@Parameter(description = "The server hostname to lookup (Append :<port> for port)", example = "hypixel.net") @PathVariable @NonNull String hostname
|
||||
) {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=%s.png".formatted(hostname))
|
||||
.body(mojangService.getServerFavicon(hostname));
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
import cc.restfulmc.api.model.response.ErrorResponse;
|
||||
import io.sentry.Sentry;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
/**
|
||||
* Advice for handling raised exceptions.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public final class ExceptionControllerAdvice {
|
||||
/**
|
||||
* Handle a raised exception.
|
||||
*
|
||||
* @param ex the raised exception
|
||||
* @return the error response
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<?> handleException(@NonNull Exception ex) {
|
||||
HttpStatus status = null; // Get the HTTP status
|
||||
if (ex instanceof NoResourceFoundException) { // Not found
|
||||
status = HttpStatus.NOT_FOUND;
|
||||
} else if (ex instanceof UnsupportedOperationException) { // Not implemented
|
||||
status = HttpStatus.NOT_IMPLEMENTED;
|
||||
}
|
||||
if (ex.getClass().isAnnotationPresent(ResponseStatus.class)) { // Get from the @ResponseStatus annotation
|
||||
status = ex.getClass().getAnnotation(ResponseStatus.class).value();
|
||||
}
|
||||
String message = ex.getLocalizedMessage(); // Get the error message
|
||||
if (message == null) { // Fallback
|
||||
message = "An internal error has occurred.";
|
||||
}
|
||||
// Print the stack trace if no response status is present
|
||||
if (status == null) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.exception.impl;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* This exception is raised
|
||||
* when a bad request is made.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@StandardException
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public final class BadRequestException extends RuntimeException { }
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.exception.impl;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* This exception is raised when the
|
||||
* Mojang API rate limit is reached.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
|
||||
public final class MojangRateLimitException extends RuntimeException {
|
||||
public MojangRateLimitException() {
|
||||
super("Mojang requests exhausted, please try again later.");
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.exception.impl;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* This exception is raised
|
||||
* when a resource is not found.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@StandardException
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public final class ResourceNotFoundException extends RuntimeException { }
|
107
API/src/main/java/cc/restfulmc/api/log/TransactionLogger.java
Normal file
107
API/src/main/java/cc/restfulmc/api/log/TransactionLogger.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.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 org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Responsible for logging request and
|
||||
* response transactions to the terminal.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@Slf4j(topic = "Req/Res Transaction")
|
||||
public class TransactionLogger implements ResponseBodyAdvice<Object> {
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NonNull ServerHttpRequest rawRequest,
|
||||
@NonNull ServerHttpResponse rawResponse) {
|
||||
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
|
||||
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
|
||||
|
||||
// Get the request ip ip
|
||||
String ip = IPUtils.getRealIp(request);
|
||||
|
||||
// Getting params
|
||||
Map<String, String> params = new HashMap<>();
|
||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||
params.put(entry.getKey(), Arrays.toString(entry.getValue()));
|
||||
}
|
||||
|
||||
// Getting headers
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
headers.put(headerName, request.getHeader(headerName));
|
||||
}
|
||||
|
||||
// Log the request
|
||||
log.info(String.format("[Req] %s | %s | '%s', params=%s, headers=%s",
|
||||
request.getMethod(),
|
||||
ip,
|
||||
request.getRequestURI(),
|
||||
params,
|
||||
headers
|
||||
));
|
||||
|
||||
// Getting response headers
|
||||
headers = new HashMap<>();
|
||||
for (String headerName : response.getHeaderNames()) {
|
||||
headers.put(headerName, response.getHeader(headerName));
|
||||
}
|
||||
|
||||
// Log the response
|
||||
log.info(String.format("[Res] %s, headers=%s",
|
||||
response.getStatus(),
|
||||
headers
|
||||
));
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return true;
|
||||
}
|
||||
}
|
53
API/src/main/java/cc/restfulmc/api/model/Cape.java
Normal file
53
API/src/main/java/cc/restfulmc/api/model/Cape.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* A cape for a {@link Player}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString
|
||||
public final class Cape {
|
||||
/**
|
||||
* The texture URL of this cape.
|
||||
*/
|
||||
@NonNull private final String url;
|
||||
|
||||
/**
|
||||
* Build a cape from the given Json object.
|
||||
*
|
||||
* @param jsonObject the json object to build from
|
||||
* @return the built cape
|
||||
*/
|
||||
public static Cape fromJsonObject(JsonObject jsonObject) {
|
||||
if (jsonObject == null) { // No object to parse
|
||||
return null;
|
||||
}
|
||||
return new Cape(jsonObject.get("url").getAsString());
|
||||
}
|
||||
}
|
304
API/src/main/java/cc/restfulmc/api/model/MinecraftServer.java
Normal file
304
API/src/main/java/cc/restfulmc/api/model/MinecraftServer.java
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A model representing a Minecraft server.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Setter @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public class MinecraftServer {
|
||||
/**
|
||||
* The hostname of this server.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private final String hostname;
|
||||
|
||||
/**
|
||||
* The IP address of this server, if resolved.
|
||||
*/
|
||||
private final String ip;
|
||||
|
||||
/**
|
||||
* The port of this server.
|
||||
*/
|
||||
@EqualsAndHashCode.Include private final int port;
|
||||
|
||||
/**
|
||||
* The DNS records resolved for this server, null if none.
|
||||
*/
|
||||
private final DNSRecord[] records;
|
||||
|
||||
/**
|
||||
* The Geo location of this server, null if unknown.
|
||||
*/
|
||||
private GeoLocation geo;
|
||||
|
||||
/**
|
||||
* The player counts of this server.
|
||||
*/
|
||||
@NonNull private final Players players;
|
||||
|
||||
/**
|
||||
* The MOTD of this server.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Players {
|
||||
/**
|
||||
* The online players on this server.
|
||||
*/
|
||||
private final int online;
|
||||
|
||||
/**
|
||||
* The maximum allowed players on this server.
|
||||
*/
|
||||
private final int max;
|
||||
|
||||
/**
|
||||
* A sample of players on this server, null or empty if no sample.
|
||||
*/
|
||||
private final Sample[] sample;
|
||||
|
||||
/**
|
||||
* Create new player count data from a token.
|
||||
*
|
||||
* @param token the token to create from
|
||||
* @return the player count data
|
||||
*/
|
||||
@NonNull
|
||||
public static Players create(@NonNull JavaServerStatusToken.Players token) {
|
||||
List<Sample> samples = null;
|
||||
if (token.getSample() != null) {
|
||||
samples = new ArrayList<>(); // The player samples
|
||||
for (JavaServerStatusToken.Players.Sample sample : token.getSample()) {
|
||||
samples.add(new Sample(sample.getId(), Sample.Name.create(sample.getName())));
|
||||
}
|
||||
}
|
||||
return new Players(token.getOnline(), token.getMax(), samples != null ? samples.toArray(new Sample[0]) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A sample player.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Sample {
|
||||
/**
|
||||
* The unique id of this player.
|
||||
*/
|
||||
@NonNull private final UUID id;
|
||||
|
||||
/**
|
||||
* The name of this player.
|
||||
*/
|
||||
@NonNull private final Name name;
|
||||
|
||||
/**
|
||||
* The name of a sample player.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Name {
|
||||
/**
|
||||
* The raw name.
|
||||
*/
|
||||
@NonNull private final String raw;
|
||||
|
||||
/**
|
||||
* The clean name (no color codes).
|
||||
*/
|
||||
@NonNull private final String clean;
|
||||
|
||||
/**
|
||||
* The HTML name.
|
||||
*/
|
||||
@NonNull private final String html;
|
||||
|
||||
/**
|
||||
* Create a new name from a raw string.
|
||||
*
|
||||
* @param raw the raw name string
|
||||
* @return the new name
|
||||
*/
|
||||
@NonNull
|
||||
public static Name create(@NonNull String raw) {
|
||||
return new Name(raw, ColorUtils.stripColor(raw), ColorUtils.toHTML(raw));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The MOTD for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class MOTD {
|
||||
/**
|
||||
* The raw MOTD lines.
|
||||
*/
|
||||
@NonNull private final String[] raw;
|
||||
|
||||
/**
|
||||
* The clean MOTD lines (no color codes).
|
||||
*/
|
||||
@NonNull private final String[] clean;
|
||||
|
||||
/**
|
||||
* The HTML MOTD lines.
|
||||
*/
|
||||
@NonNull private final String[] html;
|
||||
|
||||
/**
|
||||
* Create a new MOTD from a raw string.
|
||||
*
|
||||
* @param raw the raw motd string
|
||||
* @return the new motd
|
||||
*/
|
||||
@NonNull
|
||||
public static MOTD create(@NonNull String raw) {
|
||||
String[] rawLines = raw.split("\n"); // The raw lines
|
||||
return new MOTD(
|
||||
rawLines,
|
||||
Arrays.stream(rawLines).map(ColorUtils::stripColor).toArray(String[]::new),
|
||||
Arrays.stream(rawLines).map(ColorUtils::toHTML).toArray(String[]::new)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A platform a Minecraft
|
||||
* server can operate on.
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
public enum Platform {
|
||||
/**
|
||||
* The Java edition of Minecraft.
|
||||
*/
|
||||
JAVA(new JavaMinecraftServerPinger(), 25565),
|
||||
|
||||
/**
|
||||
* The Bedrock edition of Minecraft.
|
||||
*/
|
||||
BEDROCK(new BedrockMinecraftServerPinger(), 19132);
|
||||
|
||||
/**
|
||||
* The server pinger for this platform.
|
||||
*/
|
||||
@NonNull private final MinecraftServerPinger<?> pinger;
|
||||
|
||||
/**
|
||||
* The default server port for this platform.
|
||||
*/
|
||||
private final int defaultPort;
|
||||
}
|
||||
}
|
77
API/src/main/java/cc/restfulmc/api/model/Player.java
Normal file
77
API/src/main/java/cc/restfulmc/api/model/Player.java
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cc.restfulmc.api.model.skin.Skin;
|
||||
import cc.restfulmc.api.model.token.MojangProfileToken;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A model representing a player.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public class Player {
|
||||
/**
|
||||
* The unique id of this player.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private final UUID uniqueId;
|
||||
|
||||
/**
|
||||
* The username of this player.
|
||||
*/
|
||||
@NonNull private final String username;
|
||||
|
||||
/**
|
||||
* The skin of this player.
|
||||
*/
|
||||
@NonNull private final Skin skin;
|
||||
|
||||
/**
|
||||
* The cape of this player, null if none.
|
||||
*/
|
||||
private final Cape cape;
|
||||
|
||||
/**
|
||||
* The raw profile properties of this player.
|
||||
*/
|
||||
@NonNull private final MojangProfileToken.ProfileProperty[] properties;
|
||||
|
||||
/**
|
||||
* The profile actions this player has, null if none.
|
||||
*/
|
||||
private final ProfileAction[] profileActions;
|
||||
|
||||
/**
|
||||
* Is this player legacy?
|
||||
* <p>
|
||||
* A "Legacy" player is a player that
|
||||
* has not yet migrated to a Mojang account.
|
||||
* </p>
|
||||
*/
|
||||
private final boolean legacy;
|
||||
}
|
43
API/src/main/java/cc/restfulmc/api/model/ProfileAction.java
Normal file
43
API/src/main/java/cc/restfulmc/api/model/ProfileAction.java
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Profile actions that can
|
||||
* be taken on a {@link Player}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public enum ProfileAction {
|
||||
/**
|
||||
* The player is required to change their
|
||||
* username before accessing Multiplayer.
|
||||
*/
|
||||
FORCED_NAME_CHANGE,
|
||||
|
||||
/**
|
||||
* The player is using a banned skin.
|
||||
*/
|
||||
USING_BANNED_SKIN
|
||||
}
|
56
API/src/main/java/cc/restfulmc/api/model/cache/CachedMinecraftServer.java
vendored
Normal file
56
API/src/main/java/cc/restfulmc/api/model/cache/CachedMinecraftServer.java
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Setter @Getter @ToString
|
||||
@RedisHash(value = "server", timeToLive = 60L) // 1 minute (in seconds)
|
||||
public final class CachedMinecraftServer implements Serializable {
|
||||
/**
|
||||
* The id of this cache element.
|
||||
*/
|
||||
@Id @JsonIgnore @NonNull private final String id;
|
||||
|
||||
/**
|
||||
* The cached server.
|
||||
*/
|
||||
@JsonUnwrapped @NonNull private final MinecraftServer value;
|
||||
|
||||
/**
|
||||
* The unix timestamp of when this
|
||||
* server was cached, -1 if not cached.
|
||||
*/
|
||||
private long cached;
|
||||
}
|
73
API/src/main/java/cc/restfulmc/api/model/cache/CachedPlayer.java
vendored
Normal file
73
API/src/main/java/cc/restfulmc/api/model/cache/CachedPlayer.java
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.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 org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A cacheable {@link Player}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Setter @Getter
|
||||
@ToString(callSuper = true)
|
||||
@RedisHash(value = "player", timeToLive = 60L * 60L) // 1 hour (in seconds)
|
||||
public final class CachedPlayer extends Player implements Serializable {
|
||||
/**
|
||||
* The id of this cache element.
|
||||
* <p>
|
||||
* This ID is in the given format:
|
||||
* player:<uniqueId>-<signed>
|
||||
* </p>
|
||||
*/
|
||||
@Id @NonNull @JsonIgnore private final String cacheId;
|
||||
|
||||
/**
|
||||
* The unix timestamp of when this
|
||||
* player was cached, -1 if not cached.
|
||||
*/
|
||||
private long cached;
|
||||
|
||||
public CachedPlayer(@NonNull String cacheId, @NonNull UUID uniqueId, @NonNull String username, @NonNull Skin skin,
|
||||
Cape cape, @NonNull MojangProfileToken.ProfileProperty[] properties, ProfileAction[] profileActions,
|
||||
boolean legacy, long cached) {
|
||||
super(uniqueId, username, skin, cape, properties, profileActions, legacy);
|
||||
this.cacheId = cacheId;
|
||||
this.cached = cached;
|
||||
}
|
||||
}
|
51
API/src/main/java/cc/restfulmc/api/model/cache/CachedPlayerName.java
vendored
Normal file
51
API/src/main/java/cc/restfulmc/api/model/cache/CachedPlayerName.java
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A cache to easily lookup a
|
||||
* player's UUID by their username.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
@RedisHash(value = "playerName", timeToLive = 60L * 60L) // 1 hour (in seconds)
|
||||
public final class CachedPlayerName {
|
||||
/**
|
||||
* The username of the player.
|
||||
*/
|
||||
@Id @NonNull private String username;
|
||||
|
||||
/**
|
||||
* The unique id of the player.
|
||||
*/
|
||||
@NonNull private UUID uniqueId;
|
||||
}
|
55
API/src/main/java/cc/restfulmc/api/model/cache/CachedSkinPartTexture.java
vendored
Normal file
55
API/src/main/java/cc/restfulmc/api/model/cache/CachedSkinPartTexture.java
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A cache for a skin part texture.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
@RedisHash(value = "skinPart", timeToLive = 15L * 60L) // 15 minutes (in seconds)
|
||||
public final class CachedSkinPartTexture implements Serializable {
|
||||
/**
|
||||
* The id of this cache element.
|
||||
* <p>
|
||||
* This ID is in the given format:
|
||||
* skinPart:<query>-<part>-<renderOverlays>-<size>-<ext>
|
||||
* </p>
|
||||
*/
|
||||
@Id private transient final String id;
|
||||
|
||||
/**
|
||||
* The cached texture;
|
||||
*/
|
||||
private final byte[] texture;
|
||||
}
|
51
API/src/main/java/cc/restfulmc/api/model/dns/DNSRecord.java
Normal file
51
API/src/main/java/cc/restfulmc/api/model/dns/DNSRecord.java
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.dns;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* A representation of a DNS record.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@NoArgsConstructor @AllArgsConstructor @Setter @Getter @ToString
|
||||
public abstract class DNSRecord {
|
||||
/**
|
||||
* The type of this record.
|
||||
*/
|
||||
@NonNull private Type type;
|
||||
|
||||
/**
|
||||
* The TTL (Time To Live) of this record.
|
||||
*/
|
||||
private long ttl;
|
||||
|
||||
/**
|
||||
* Types of a record.
|
||||
*/
|
||||
public enum Type {
|
||||
A, SRV
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.dns.impl;
|
||||
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* An A record implementation.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@NoArgsConstructor @Setter @Getter @ToString(callSuper = true)
|
||||
public final class ARecord extends DNSRecord {
|
||||
/**
|
||||
* The address of this record, null if unresolved.
|
||||
*/
|
||||
private String address;
|
||||
|
||||
public ARecord(@NonNull org.xbill.DNS.ARecord bootstrap) {
|
||||
super(Type.A, bootstrap.getTTL());
|
||||
InetAddress address = bootstrap.getAddress();
|
||||
this.address = address == null ? null : address.getHostAddress();
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.dns.impl;
|
||||
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* An SRV record implementation.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@NoArgsConstructor @Setter @Getter @ToString(callSuper = true)
|
||||
public final class SRVRecord extends DNSRecord {
|
||||
/**
|
||||
* The priority of this record.
|
||||
*/
|
||||
private int priority;
|
||||
|
||||
/**
|
||||
* The weight of this record.
|
||||
*/
|
||||
private int weight;
|
||||
|
||||
/**
|
||||
* The port of this record.
|
||||
*/
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* The target of this record.
|
||||
*/
|
||||
@NonNull private String target;
|
||||
|
||||
public SRVRecord(@NonNull org.xbill.DNS.SRVRecord bootstrap) {
|
||||
super(Type.SRV, bootstrap.getTTL());
|
||||
priority = bootstrap.getPriority();
|
||||
weight = bootstrap.getWeight();
|
||||
port = bootstrap.getPort();
|
||||
target = bootstrap.getTarget().toString().replaceFirst("\\.$", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a socket address from
|
||||
* the target and port.
|
||||
*
|
||||
* @return the socket address
|
||||
*/
|
||||
@NonNull @JsonIgnore
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return new InetSocketAddress(target, port);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A response representing an error.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Getter @ToString
|
||||
public final class ErrorResponse {
|
||||
/**
|
||||
* The status code of this error.
|
||||
*/
|
||||
@NonNull private final HttpStatus status;
|
||||
|
||||
/**
|
||||
* The HTTP code of this error.
|
||||
*/
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* The message of this error.
|
||||
*/
|
||||
@NonNull private final String message;
|
||||
|
||||
/**
|
||||
* The timestamp this error occurred.
|
||||
*/
|
||||
@NonNull private final Date timestamp;
|
||||
|
||||
public ErrorResponse(@NonNull HttpStatus status, @NonNull String message) {
|
||||
this.status = status;
|
||||
code = status.value();
|
||||
this.message = message;
|
||||
timestamp = new Date();
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* A Bedrock edition {@link MinecraftServer}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Getter @ToString(callSuper = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
||||
public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
/**
|
||||
* The ID of this server.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private final String id;
|
||||
|
||||
/**
|
||||
* The edition of this server.
|
||||
*/
|
||||
@NonNull private final Edition edition;
|
||||
|
||||
/**
|
||||
* The version information of this server.
|
||||
*/
|
||||
@NonNull private final Version version;
|
||||
|
||||
/**
|
||||
* The gamemode of this server.
|
||||
*/
|
||||
@NonNull private final GameMode gamemode;
|
||||
|
||||
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;
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Bedrock Minecraft server.
|
||||
*
|
||||
* @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, if any
|
||||
* @param token the status token
|
||||
* @return the Bedrock Minecraft server
|
||||
*/
|
||||
@NonNull
|
||||
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, null, records, edition, version, players, motd, gameMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* The edition of a Bedrock server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
public enum Edition {
|
||||
/**
|
||||
* Minecraft: Pocket Edition.
|
||||
*/
|
||||
MCPE,
|
||||
|
||||
/**
|
||||
* Minecraft: Education Edition.
|
||||
*/
|
||||
MCEE
|
||||
}
|
||||
|
||||
/**
|
||||
* Version information for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Version {
|
||||
/**
|
||||
* The protocol version of the server.
|
||||
*/
|
||||
private final int protocol;
|
||||
|
||||
/**
|
||||
* The version name of the server.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The gamemode of a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class GameMode {
|
||||
/**
|
||||
* The name of this gamemode.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The numeric of this gamemode, -1 if unknown.
|
||||
*/
|
||||
private final int numericId;
|
||||
}
|
||||
}
|
@ -0,0 +1,390 @@
|
||||
/*
|
||||
* 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.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 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}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Setter @Getter @ToString(callSuper = true)
|
||||
public final class JavaMinecraftServer extends MinecraftServer {
|
||||
/**
|
||||
* The version information of this server.
|
||||
*/
|
||||
@NonNull private final Version version;
|
||||
|
||||
/**
|
||||
* The favicon of this server, null if none.
|
||||
*/
|
||||
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>
|
||||
* This is for servers on 1.12 or below.
|
||||
* </p>
|
||||
*/
|
||||
private final ModInfo modInfo;
|
||||
|
||||
/**
|
||||
* The Forge mod information for this server, null if none.
|
||||
* <p>
|
||||
* This is for servers on 1.13 and above.
|
||||
* </p>
|
||||
*/
|
||||
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?
|
||||
*
|
||||
* @see <a href="https://www.minecraft.net/es-mx/article/minecraft-snapshot-22w19a">This for more</a>
|
||||
*/
|
||||
private final boolean previewsChat;
|
||||
|
||||
/**
|
||||
* Does this server enforce secure chat?
|
||||
*/
|
||||
private final boolean enforcesSecureChat;
|
||||
|
||||
/**
|
||||
* Is this server preventing chat reports?
|
||||
*/
|
||||
private final boolean preventsChatReports;
|
||||
|
||||
/**
|
||||
* Is this server on the list
|
||||
* of blocked servers by Mojang?
|
||||
* <p>
|
||||
* This value is later set by the
|
||||
* {@link MojangService} when a server
|
||||
* is requested.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/Mojang_API#Blocked_Servers">Mojang API</a>
|
||||
*/
|
||||
private boolean mojangBanned;
|
||||
|
||||
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;
|
||||
this.mojangBanned = mojangBanned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Java Minecraft server.
|
||||
*
|
||||
* @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, 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, 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(statusToken.getDescription()))).toLegacyText();
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version information for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Version {
|
||||
/**
|
||||
* The version name of the server.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The identified platform of the server, null if unknown.
|
||||
*/
|
||||
private String platform;
|
||||
|
||||
/**
|
||||
* The protocol version of the server.
|
||||
*/
|
||||
private final int protocol;
|
||||
|
||||
/**
|
||||
* A list of versions supported by this server.
|
||||
*/
|
||||
private final int[] supportedVersions;
|
||||
|
||||
/**
|
||||
* The name of the version for the protocol, null if unknown.
|
||||
*/
|
||||
private final String protocolName;
|
||||
|
||||
/**
|
||||
* Create a more detailed
|
||||
* copy of this object.
|
||||
*
|
||||
* @return the detailed copy
|
||||
*/
|
||||
@NonNull
|
||||
public Version detailedCopy() {
|
||||
String platform = null;
|
||||
if (name.contains(" ")) { // Parse the server platform
|
||||
String[] split = name.split(" ");
|
||||
if (split.length == 2) {
|
||||
platform = split[0];
|
||||
}
|
||||
}
|
||||
JavaMinecraftVersion minecraftVersion = JavaMinecraftVersion.byProtocol(protocol);
|
||||
if (minecraftVersion == JavaMinecraftVersion.UNKNOWN) {
|
||||
minecraftVersion = null;
|
||||
}
|
||||
return new Version(name, platform, protocol, supportedVersions, minecraftVersion == null ? null : minecraftVersion.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The favicon for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Favicon {
|
||||
/**
|
||||
* The raw Base64 encoded favicon.
|
||||
*/
|
||||
@NonNull private final String base64;
|
||||
|
||||
/**
|
||||
* The URL to the favicon.
|
||||
*/
|
||||
@NonNull private final String url;
|
||||
|
||||
/**
|
||||
* Create a new favicon for a server.
|
||||
*
|
||||
* @param base64 the Base64 encoded favicon
|
||||
* @param hostname the server hostname
|
||||
* @return the favicon, null if none
|
||||
*/
|
||||
public static Favicon create(String base64, @NonNull String hostname) {
|
||||
if (base64 == null) { // No favicon to create
|
||||
return null;
|
||||
}
|
||||
return new Favicon(
|
||||
base64,
|
||||
AppConfig.INSTANCE.getServerPublicUrl() + "/server/icon/" + hostname
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* This is for servers on 1.12 or below.
|
||||
* </p>
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class ModInfo {
|
||||
/**
|
||||
* The type of modded server this is.
|
||||
*/
|
||||
@NonNull private final String type;
|
||||
|
||||
/**
|
||||
* The list of mods on this server, null or empty if none.
|
||||
*/
|
||||
@SerializedName("modList") private final Mod[] mods;
|
||||
|
||||
/**
|
||||
* A Forge mod for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
private static class Mod {
|
||||
/**
|
||||
* The name of this mod.
|
||||
*/
|
||||
@NonNull @SerializedName("modid") private final String name;
|
||||
|
||||
/**
|
||||
* The version of this mod.
|
||||
*/
|
||||
private final String version;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forge information for a server.
|
||||
* <p>
|
||||
* This is for servers on 1.13 and above.
|
||||
* </p>
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class ForgeData {
|
||||
/**
|
||||
* The list of channels on this server, null or empty if none.
|
||||
*/
|
||||
private final Channel[] channels;
|
||||
|
||||
/**
|
||||
* The list of mods on this server, null or empty if none.
|
||||
*/
|
||||
private final Mod[] mods;
|
||||
|
||||
/**
|
||||
* The version of the FML network.
|
||||
*/
|
||||
private final int fmlNetworkVersion;
|
||||
|
||||
/**
|
||||
* Are the channel and mod lists truncated?
|
||||
* <p>
|
||||
* Legacy versions see truncated lists, modern
|
||||
* versions ignore this truncated flag.
|
||||
* </p>
|
||||
*/
|
||||
private final boolean truncated;
|
||||
|
||||
/**
|
||||
* A Forge channel for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
private static class Channel {
|
||||
/**
|
||||
* The name of this channel.
|
||||
*/
|
||||
@NonNull @SerializedName("res") private final String name;
|
||||
|
||||
/**
|
||||
* The version of this channel.
|
||||
*/
|
||||
@NonNull private final String version;
|
||||
|
||||
/**
|
||||
* Whether this channel is required.
|
||||
*/
|
||||
private final boolean required;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Forge mod for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
private static class Mod {
|
||||
/**
|
||||
* The id of this mod.
|
||||
*/
|
||||
@NonNull @SerializedName("modId") private final String name;
|
||||
|
||||
/**
|
||||
* The marker for this mod.
|
||||
*/
|
||||
@NonNull @SerializedName("modmarker") private final String marker;
|
||||
}
|
||||
}
|
||||
}
|
238
API/src/main/java/cc/restfulmc/api/model/skin/ISkinPart.java
Normal file
238
API/src/main/java/cc/restfulmc/api/model/skin/ISkinPart.java
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.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 java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A part of a {@link Skin}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface ISkinPart {
|
||||
Enum<?>[][] TYPES = { Vanilla.values(), Custom.values() };
|
||||
|
||||
/**
|
||||
* Get the name of this part.
|
||||
*
|
||||
* @return the part name
|
||||
*/
|
||||
@NonNull String name();
|
||||
|
||||
/**
|
||||
* Render a part of a skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@NonNull BufferedImage render(@NonNull Skin skin, boolean overlays, int size);
|
||||
|
||||
/**
|
||||
* Get a skin part by the given name.
|
||||
*
|
||||
* @param name the name of the part
|
||||
* @return the part, null if none
|
||||
*/
|
||||
static ISkinPart getByName(@NonNull String name) {
|
||||
name = name.toUpperCase();
|
||||
for (Enum<?>[] type : TYPES) {
|
||||
for (Enum<?> part : type) {
|
||||
if (!part.name().equals(name)) {
|
||||
continue;
|
||||
}
|
||||
return (ISkinPart) part;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A part of a Vanilla skin texture.
|
||||
*/
|
||||
@RequiredArgsConstructor @Getter @ToString
|
||||
enum Vanilla implements ISkinPart {
|
||||
// Overlays
|
||||
HEAD_OVERLAY_FACE(new Coordinates(40, 8), 8, 8),
|
||||
|
||||
// Head
|
||||
HEAD_TOP(new Coordinates(8, 0), 8, 8),
|
||||
FACE(new Coordinates(8, 8), 8, 8, HEAD_OVERLAY_FACE),
|
||||
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
||||
HEAD_RIGHT(new Coordinates(16, 8), 8, 8),
|
||||
HEAD_BOTTOM(new Coordinates(16, 0), 8, 8),
|
||||
HEAD_BACK(new Coordinates(24, 8), 8, 8),
|
||||
|
||||
// Body
|
||||
BODY_FRONT(new Coordinates(20, 20), 8, 12),
|
||||
|
||||
// Arms
|
||||
LEFT_ARM_TOP(new Coordinates(36, 48), 4, 4),
|
||||
RIGHT_ARM_TOP(new Coordinates(44, 16), 4, 4),
|
||||
|
||||
LEFT_ARM_FRONT(new Coordinates(44, 20), 4, 12),
|
||||
RIGHT_ARM_FRONT(new Coordinates(36, 52), new LegacyCoordinates(44, 20, true), 4, 12),
|
||||
|
||||
// Legs
|
||||
LEFT_LEG_FRONT(new Coordinates(4, 20), 4, 12), // Front
|
||||
RIGHT_LEG_FRONT(new Coordinates(20, 52), new LegacyCoordinates(4, 20, true), 4, 12); // Front
|
||||
|
||||
/**
|
||||
* The coordinates of this part.
|
||||
*/
|
||||
@NonNull private final Coordinates coordinates;
|
||||
|
||||
/**
|
||||
* The legacy coordinates of this part.
|
||||
* <p>
|
||||
* This is for older skin textures
|
||||
* that use different positions.
|
||||
* </p>
|
||||
*/
|
||||
private LegacyCoordinates legacyCoordinates;
|
||||
|
||||
/**
|
||||
* The size of this part.
|
||||
*/
|
||||
private final int width, height;
|
||||
|
||||
/**
|
||||
* The overlay parts this part has.
|
||||
*/
|
||||
private Vanilla[] overlays;
|
||||
|
||||
Vanilla(@NonNull Coordinates coordinates, int width, int height, Vanilla... overlays) {
|
||||
this(coordinates, null, width, height, overlays);
|
||||
}
|
||||
|
||||
Vanilla(@NonNull Coordinates coordinates, LegacyCoordinates legacyCoordinates, int width, int height, Vanilla... overlays) {
|
||||
this.coordinates = coordinates;
|
||||
this.legacyCoordinates = legacyCoordinates;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.overlays = overlays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a part of a skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@Override @NonNull
|
||||
public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) {
|
||||
return VanillaSkinPartRenderer.INSTANCE.render(skin, this, overlays, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this part a front arm?
|
||||
*
|
||||
* @return whether this part is a front arm
|
||||
*/
|
||||
public boolean isFrontArm() {
|
||||
return this == LEFT_ARM_FRONT || this == RIGHT_ARM_FRONT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this part have legacy coordinates?
|
||||
*
|
||||
* @return whether this part has legacy coordinates
|
||||
*/
|
||||
public boolean hasLegacyCoordinates() {
|
||||
return legacyCoordinates != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates of a part of a skin.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Coordinates {
|
||||
/**
|
||||
* The X coordinate.
|
||||
*/
|
||||
private final int x;
|
||||
|
||||
/**
|
||||
* The Y coordinate.
|
||||
*/
|
||||
private final int y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy coordinates of a part of a skin.
|
||||
*/
|
||||
@Getter @ToString
|
||||
public static class LegacyCoordinates extends Coordinates {
|
||||
/**
|
||||
* Whether the part at these coordinates is flipped.
|
||||
*/
|
||||
private final boolean flipped;
|
||||
|
||||
public LegacyCoordinates(int x, int y) {
|
||||
this(x, y, false);
|
||||
}
|
||||
|
||||
public LegacyCoordinates(int x, int y, boolean flipped) {
|
||||
super(x, y);
|
||||
this.flipped = flipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom part of a skin.
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
enum Custom implements ISkinPart {
|
||||
HEAD(IsometricHeadSkinPartRenderer.INSTANCE),
|
||||
BODY_FLAT(BodySkinPartRenderer.INSTANCE);
|
||||
|
||||
/**
|
||||
* The custom renderer to use for this part.
|
||||
*/
|
||||
@NonNull private final SkinRenderer<Custom> renderer;
|
||||
|
||||
/**
|
||||
* Render a part of a skin.
|
||||
*
|
||||
* @param skin the skin to render the part for
|
||||
* @param overlays whether to render overlays
|
||||
* @param size the size to scale the skin part to
|
||||
* @return the rendered skin part
|
||||
*/
|
||||
@Override @NonNull
|
||||
public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) {
|
||||
return renderer.render(skin, this, overlays, size);
|
||||
}
|
||||
}
|
||||
}
|
130
API/src/main/java/cc/restfulmc/api/model/skin/Skin.java
Normal file
130
API/src/main/java/cc/restfulmc/api/model/skin/Skin.java
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.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 javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A skin for a {@link Player}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString
|
||||
public final class Skin {
|
||||
public static final Skin DEFAULT_STEVE = create("http://textures.minecraft.net/texture/60a5bd016b3c9a1b9272e4929e30827a67be4ebb219017adbbc4a4d22ebd5b1", Model.DEFAULT);
|
||||
|
||||
/**
|
||||
* The texture URL of this skin.
|
||||
*/
|
||||
@NonNull private final String url;
|
||||
|
||||
/**
|
||||
* The model of this skin.
|
||||
*/
|
||||
@NonNull private final Model model;
|
||||
|
||||
/**
|
||||
* The image data of this skin.
|
||||
*/
|
||||
@JsonIgnore private final byte[] skinImage;
|
||||
|
||||
/**
|
||||
* Is this skin legacy?
|
||||
*/
|
||||
private final boolean legacy;
|
||||
|
||||
/**
|
||||
* URLs to the parts of this skin.
|
||||
*/
|
||||
@NonNull @JsonProperty("parts") private final Map<String, String> partUrls;
|
||||
|
||||
/**
|
||||
* Populate the part URLs for this skin.
|
||||
*
|
||||
* @param playerUuid the UUID of the player
|
||||
* @return the skin
|
||||
*/
|
||||
@NonNull
|
||||
public Skin populatePartUrls(@NonNull String playerUuid) {
|
||||
for (Enum<?>[] type : ISkinPart.TYPES) {
|
||||
for (Enum<?> part : type) {
|
||||
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a skin from the given Json object.
|
||||
*
|
||||
* @param jsonObject the json object to build from
|
||||
* @return the built skin
|
||||
*/
|
||||
public static Skin fromJsonObject(JsonObject jsonObject) {
|
||||
if (jsonObject == null) { // No object to parse
|
||||
return null;
|
||||
}
|
||||
Model model = Model.DEFAULT; // The skin model
|
||||
|
||||
JsonObject metadataJsonObject = jsonObject.getAsJsonObject("metadata");
|
||||
if (metadataJsonObject != null) { // Parse the skin model
|
||||
model = Model.valueOf(metadataJsonObject.get("model").getAsString().toUpperCase());
|
||||
}
|
||||
return create(jsonObject.get("url").getAsString(), model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a skin from the given URL and model.
|
||||
*
|
||||
* @param url the skin url
|
||||
* @param model the skin model
|
||||
* @return the constructed skin
|
||||
*/
|
||||
@NonNull @SneakyThrows
|
||||
private static Skin create(@NonNull String url, @NonNull Model model) {
|
||||
BufferedImage image = ImageIO.read(new URL(url)); // Get the skin image
|
||||
byte[] bytes = ImageUtils.toByteArray(image); // Convert the image into bytes
|
||||
boolean legacy = image.getWidth() == 64 && image.getHeight() == 32; // Is the skin legacy?
|
||||
return new Skin(url, model, bytes, legacy, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible models for a skin.
|
||||
*/
|
||||
public enum Model {
|
||||
DEFAULT, SLIM
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A token representing the response from
|
||||
* pinging a {@link JavaMinecraftServer}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public final class JavaServerStatusToken {
|
||||
/**
|
||||
* The description (MOTD) of this server.
|
||||
* <p>
|
||||
* Legacy: String, New: JSON Object
|
||||
* </p>
|
||||
*/
|
||||
@NonNull private final Object description;
|
||||
|
||||
/**
|
||||
* The base64 encoded favicon of this server, null if no favicon.
|
||||
*/
|
||||
private final String favicon;
|
||||
|
||||
/**
|
||||
* The version information of this server.
|
||||
*/
|
||||
@NonNull private final JavaMinecraftServer.Version version;
|
||||
|
||||
/**
|
||||
* The player counts of this server.
|
||||
*/
|
||||
@NonNull private final Players players;
|
||||
|
||||
/**
|
||||
* The Forge mod information for this server, null if none.
|
||||
* <p>
|
||||
* This is for servers on 1.12 or below.
|
||||
* </p>
|
||||
*/
|
||||
@SerializedName("modinfo") private final JavaMinecraftServer.ModInfo modInfo;
|
||||
|
||||
/**
|
||||
* The Forge mod information for this server, null if none.
|
||||
* <p>
|
||||
* This is for servers on 1.13 and above.
|
||||
* </p>
|
||||
*/
|
||||
private final JavaMinecraftServer.ForgeData forgeData;
|
||||
|
||||
/**
|
||||
* Does this server preview chat?
|
||||
*
|
||||
* @see <a href="https://www.minecraft.net/es-mx/article/minecraft-snapshot-22w19a">This for more</a>
|
||||
*/
|
||||
private final boolean previewsChat;
|
||||
|
||||
/**
|
||||
* Does this server enforce secure chat?
|
||||
*/
|
||||
private final boolean enforcesSecureChat;
|
||||
|
||||
/**
|
||||
* Is this server preventing chat reports?
|
||||
*/
|
||||
private final boolean preventsChatReports;
|
||||
|
||||
/**
|
||||
* Player count data for a server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Players {
|
||||
/**
|
||||
* The online players on this server.
|
||||
*/
|
||||
private final int online;
|
||||
|
||||
/**
|
||||
* The maximum allowed players on this server.
|
||||
*/
|
||||
private final int max;
|
||||
|
||||
/**
|
||||
* A sample of players on this server, null or empty if no sample.
|
||||
*/
|
||||
private final Sample[] sample;
|
||||
|
||||
/**
|
||||
* A sample player.
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public static class Sample {
|
||||
/**
|
||||
* The unique id of this player.
|
||||
*/
|
||||
@NonNull private final UUID id;
|
||||
|
||||
/**
|
||||
* The name of this player.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.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 java.util.Base64;
|
||||
|
||||
/**
|
||||
* A token representing a Mojang user profile.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape">Mojang API</a>
|
||||
*/
|
||||
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public final class MojangProfileToken {
|
||||
/**
|
||||
* The id of the profile.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private final String id;
|
||||
|
||||
/**
|
||||
* The name of the profile.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The properties of the profile.
|
||||
*/
|
||||
@NonNull private final ProfileProperty[] properties;
|
||||
|
||||
/**
|
||||
* The actions this profile has.
|
||||
*/
|
||||
@NonNull private final ProfileAction[] profileActions;
|
||||
|
||||
/**
|
||||
* Is this profile legacy?
|
||||
* <p>
|
||||
* A "Legacy" profile is a profile that
|
||||
* has not yet migrated to a Mojang account.
|
||||
* </p>
|
||||
*/
|
||||
private final boolean legacy;
|
||||
|
||||
/**
|
||||
* Get the properties of this skin.
|
||||
*
|
||||
* @return the properties
|
||||
*/
|
||||
@NonNull
|
||||
public SkinProperties getSkinProperties() {
|
||||
ProfileProperty textures = getPropertyByName("textures"); // Get the profile textures
|
||||
if (textures == null) { // No profile textures
|
||||
return new SkinProperties();
|
||||
}
|
||||
JsonObject jsonObject = AppConfig.GSON.fromJson(textures.getDecodedValue(), JsonObject.class); // Get the Json object
|
||||
JsonObject texturesJsonObject = jsonObject.getAsJsonObject("textures"); // Get the textures object
|
||||
|
||||
// Return the tuple containing the skin and cape
|
||||
return new SkinProperties(
|
||||
Skin.fromJsonObject(texturesJsonObject.getAsJsonObject("SKIN")).populatePartUrls(id),
|
||||
Cape.fromJsonObject(texturesJsonObject.getAsJsonObject("CAPE"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profile property
|
||||
* with the given name.
|
||||
*
|
||||
* @param name the property name
|
||||
* @return the profile property, null if none
|
||||
*/
|
||||
public ProfileProperty getPropertyByName(@NonNull String name) {
|
||||
for (ProfileProperty property : properties) {
|
||||
if (property.getName().equalsIgnoreCase(name)) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A property of a Mojang profile.
|
||||
*/
|
||||
@NoArgsConstructor @Setter @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public static class ProfileProperty {
|
||||
/**
|
||||
* The name of this property.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private String name;
|
||||
|
||||
/**
|
||||
* The base64 value of this property.
|
||||
*/
|
||||
@NonNull private String value;
|
||||
|
||||
/**
|
||||
* The base64 signature of this property.
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* Get the decoded Base64
|
||||
* value of this property.
|
||||
*
|
||||
* @return the decoded value
|
||||
*/
|
||||
@NonNull @JsonIgnore
|
||||
public String getDecodedValue() {
|
||||
return new String(Base64.getDecoder().decode(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this property signed?
|
||||
*
|
||||
* @return whether this property has a signature
|
||||
*/
|
||||
public boolean isSigned() {
|
||||
return signature != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The properties for a skin.
|
||||
*/
|
||||
@NoArgsConstructor @AllArgsConstructor @Getter @ToString
|
||||
public static class SkinProperties {
|
||||
/**
|
||||
* The skin of the profile.
|
||||
*/
|
||||
private Skin skin;
|
||||
|
||||
/**
|
||||
* The cape of the profile.
|
||||
*/
|
||||
private Cape cape;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* A token representing a Mojang username to UUID response.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://wiki.vg/Mojang_API#Username_to_UUID">Mojang API</a>
|
||||
*/
|
||||
@AllArgsConstructor @Getter @ToString
|
||||
public final class MojangUsernameToUUIDToken {
|
||||
/**
|
||||
* The id of the username.
|
||||
*/
|
||||
@NonNull private final String id;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.repository;
|
||||
|
||||
import cc.restfulmc.api.model.cache.CachedMinecraftServer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* A cache repository for {@link CachedMinecraftServer}'s.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface MinecraftServerCacheRepository extends CrudRepository<CachedMinecraftServer, String> { }
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.repository;
|
||||
|
||||
import cc.restfulmc.api.model.cache.CachedPlayer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* A cache repository for {@link CachedPlayer}'s.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface PlayerCacheRepository extends CrudRepository<CachedPlayer, String> { }
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.repository;
|
||||
|
||||
import cc.restfulmc.api.model.cache.CachedPlayerName;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* A cache repository for player usernames.
|
||||
* <p>
|
||||
* This will allow us to easily lookup a
|
||||
* player's username and get their uuid.
|
||||
* </p>
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface PlayerNameCacheRepository extends CrudRepository<CachedPlayerName, String> { }
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.repository;
|
||||
|
||||
import cc.restfulmc.api.model.cache.CachedSkinPartTexture;
|
||||
import cc.restfulmc.api.model.skin.ISkinPart;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* A cache repository for skin texture parts.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see ISkinPart for skin parts
|
||||
*/
|
||||
public interface SkinPartTextureCacheRepository extends CrudRepository<CachedSkinPartTexture, String> { }
|
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;
|
||||
}
|
||||
}
|
606
API/src/main/java/cc/restfulmc/api/service/MojangService.java
Normal file
606
API/src/main/java/cc/restfulmc/api/service/MojangService.java
Normal file
@ -0,0 +1,606 @@
|
||||
/*
|
||||
* 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 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 net.jodah.expiringmap.ExpirationPolicy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
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;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A service for interacting with the Mojang API.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Service
|
||||
@Log4j2(topic = "Mojang Service")
|
||||
public final class MojangService {
|
||||
private static final String UUID_TO_PROFILE = MojangServer.SESSION.getEndpoint() + "/session/minecraft/profile/%s";
|
||||
private static final String USERNAME_TO_UUID = MojangServer.API.getEndpoint() + "/users/profiles/minecraft/%s";
|
||||
private static final String FETCH_BLOCKED_SERVERS = MojangServer.SESSION.getEndpoint() + "/blockedservers";
|
||||
|
||||
private static final int DEFAULT_PART_TEXTURE_SIZE = 128;
|
||||
private static final int MAX_PART_TEXTURE_SIZE = 512;
|
||||
|
||||
private static final String DEFAULT_SERVER_ICON = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAASFBMVEWwsLBBQUE9PT1JSUlFRUUuLi5MTEyzs7M0NDQ5OTlVVVVQUFAmJia5ubl+fn5zc3PFxcVdXV3AwMCJiYmUlJRmZmbQ0NCjo6OL5p+6AAAFVklEQVRYw+1W67K0KAzkJnIZdRAZ3/9NtzvgXM45dX7st1VbW7XBUVDSdEISRqn/5R+T82/+nsr/XZn/SHm/3x9/ArA/IP8qwPK433d44VubZ/XT6/cJy0L792VZfnDrcRznr86d748u92X5vtaxOe228zcCy+MSMpg/5SwRopsYMv8oigCwngbQhE/rzhwAYMpxnvMvHhgy/8AgByJolzb5pPqEbvtgMBBmtvkbgxKmaaIZ5TyPum6Viue6te241N+s+W6nOlucgjEx6Nay9zZta1XVxejW+Q5ZhhkDS31lgOTegjUBor33CQilbC2GYGy9y9bN8ytevjE4a2stajHDAgAcUkoYwzO6zQi8ZflC+XO0+exiuNa3OQtIJOCk13neUjv7VO7Asu/3LwDFeg37sQtQhy4lAQH6IR9ztca0E3oI5PtDAlJ1tHGplrJ12jjrrXPWYvXsU042Bl/qUr3B9qzPSKaovpvjgglYL2F1x+Zs7gIvpLYuq46wr3H5/RJxyvM6sXOY762oU4YZ3mAz1lpc9O3Y30VJUM/iWhBIib63II/LA4COEMxcSmrH4ddl/wTYe3RIO0vK2VI9wQy6AxRsJpb3AAALvXb6TxvUCYSdOQo5Mh0GySkJc7rB405GUEfzbbl/iFpPoNQVNUQAZG06nkI6RCABRqRA9IimH6Up5Mhybtu2IlewB2Sf6AmQ4ZU9rfBELvyA23Yub6LWWtUBgK3OB79L7FILLDKWd4wpxmMRAMoLQR1ItLoiWUmhFtjptab7LQDgRARliLITLrcBkHNp9VACUH1UDRQEYGuYxzyM9H0mBccQNnCkQ3Q1UHBaO6sNyw0CelEtBGXKSoE+fJWZh5GupyneMIkCOMESAniMAzMreLvuO+pnmBQSp4C+ELCiMSGVLPh7M023SSBAiAA5yPh2m0wigEbWKnw3qDrrscF00cciCATGwNQRAv2YGvyD4Y36QGhqOS4AcABAA88oGvBCRho5H2+UiW6EfyM1L5l8a56rqdvE6lFakc3ScVDOBNBUoFM8c1vgnhAG5VsAqMD6Q9IwwtAkR39iGEQF1ZBxgU+v9UGL6MBQYiTdJllIBtx5y0rixGdAZ1YysbS53TAVy3vf4aabEpt1T0HoB2Eg4Yv5OKNwyHgmNvPKaQAYLG3EIyIqcL6Fj5C2jhXL9EpCdRMROE5nCW3qm1vfR6wYh0HKGG3wY+JgLkUWQ/WMfI8oMvIWMY7aCncNxxpSmHRUCEzDdSR0+dRwIQaMWW1FE0AOGeKkx0OLwYanBK3qfC0BSmIlozkuFcvSkulckoIB2FbHWu0y9gMHsEapMMEoySNUA2RDrduxIqr5POQV2zZ++IBOwVrFO9THrtjU2uWsCMZjxXl88Hmeaz1rPdAqXyJl68F5RTtdvN1aIyYEAMAWJaCMHvon7s23jljlxoKBEgNv6LQ25/rZIQyOdwDO3jLsqE2nbVAil21LxqFpZ2xJ3CFuE33QCo7kfkfO8kpW6gdioxdzZDLOaMMwidzeKD0RxaD7cnHHsu0jVkW5oTwwMGI0lwwA36u2nMY8AKzErLW9JxFiteyzZsAAxY1vPe5Uf68lIDVjV8JZpPfjxbc/QuyRKdAQJaAdIA4tCTht+kQJ1I4nbdjfHxgpTSLyI19pb/iuK7+9YJaZCxEIKj79YZ6uDU8f97878teRN1FzA7OvquSrVKUgk+S6ROpJfA7GpN6RPkx4voshXgu91p7CGHeA+IY8dUUVXwT7PYw12Xsj0Lfh9X4ac9XgKW86cj8bPh8XmyDOD88FLoB+YPXp4YtyB3gBPXu98xeRI2zploVCBQAAAABJRU5ErkJggg==";
|
||||
|
||||
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.
|
||||
*/
|
||||
@NonNull private final PlayerNameCacheRepository playerNameCache;
|
||||
|
||||
/**
|
||||
* The cache repository for {@link Player}'s.
|
||||
*/
|
||||
@NonNull private final PlayerCacheRepository playerCache;
|
||||
|
||||
/**
|
||||
* The cache repository for {@link ISkinPart}'s.
|
||||
*/
|
||||
@NonNull private final SkinPartTextureCacheRepository skinPartTextureCache;
|
||||
|
||||
/**
|
||||
* The cache repository for {@link MinecraftServer}'s.
|
||||
*/
|
||||
@NonNull private final MinecraftServerCacheRepository minecraftServerCache;
|
||||
|
||||
/**
|
||||
* Mapped statuses for {@link MojangServer}'s.
|
||||
*/
|
||||
@Getter private final Map<MojangServer, MojangServer.Status> mojangServerStatuses = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/**
|
||||
* A list of banned server hashes provided by Mojang.
|
||||
* <p>
|
||||
* This is periodically fetched from Mojang, see
|
||||
* {@link #fetchBlockedServers()} for more info.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/Mojang_API#Blocked_Servers">Mojang API</a>
|
||||
*/
|
||||
private List<String> bannedServerHashes;
|
||||
|
||||
/**
|
||||
* A cache of blocked server hostnames.
|
||||
*
|
||||
* @see #isServerHostnameBlocked(String) for more
|
||||
*/
|
||||
private final ExpiringSet<String> blockedServersCache = new ExpiringSet<>(ExpirationPolicy.CREATED, 10L, TimeUnit.MINUTES);
|
||||
|
||||
@Autowired
|
||||
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;
|
||||
this.minecraftServerCache = minecraftServerCache;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void onInitialize() {
|
||||
// Schedule a task to fetch statuses
|
||||
// of Mojang servers every few minutes
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchMojangServerStatuses();
|
||||
}
|
||||
}, 0L, 60L * 3L * 1000L);
|
||||
|
||||
// Schedule a task to fetch blocked
|
||||
// servers from Mojang every hour.
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchBlockedServers();
|
||||
}
|
||||
}, 0L, 60L * 60L * 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the part of a skin texture for
|
||||
* a player by their username or UUID.
|
||||
*
|
||||
* @param partName the part of the player's skin texture to get
|
||||
* @param query the query to search for the player by
|
||||
* @param extension the skin part image extension
|
||||
* @param overlays whether to render overlays
|
||||
* @param sizeString the size of the skin part image
|
||||
* @return the skin part texture
|
||||
* @throws BadRequestException if the extension is invalid
|
||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
||||
*/
|
||||
@SneakyThrows
|
||||
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension,
|
||||
boolean overlays, String sizeString) throws BadRequestException, MojangRateLimitException {
|
||||
log.info("Requesting skin part {} with query {} (ext: {}, overlays: {}, size: {})",
|
||||
partName, query, extension, overlays, sizeString
|
||||
);
|
||||
|
||||
// Get the part from the given name
|
||||
ISkinPart part = ISkinPart.getByName(partName); // The skin part to get
|
||||
if (part == null) { // Default to the face
|
||||
part = ISkinPart.Vanilla.FACE;
|
||||
log.warn("Invalid skin part {}, defaulting to {}", partName, part.name());
|
||||
}
|
||||
|
||||
// Ensure the extension is valid
|
||||
if (extension.isBlank()) {
|
||||
throw new BadRequestException("Invalid extension");
|
||||
}
|
||||
|
||||
// Get the size of the part
|
||||
Integer size = null;
|
||||
if (sizeString != null) { // Attempt to parse the size
|
||||
try {
|
||||
size = Integer.parseInt(sizeString);
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Safely ignore, invalid number provided
|
||||
}
|
||||
}
|
||||
if (size == null || size <= 0) { // Invalid size
|
||||
size = DEFAULT_PART_TEXTURE_SIZE;
|
||||
log.warn("Invalid size {}, defaulting to {}", sizeString, size);
|
||||
}
|
||||
if (size > MAX_PART_TEXTURE_SIZE) { // Limit the size to 512
|
||||
size = MAX_PART_TEXTURE_SIZE;
|
||||
log.warn("Size {} is too large, defaulting to {}", sizeString, MAX_PART_TEXTURE_SIZE);
|
||||
}
|
||||
String id = "%s-%s-%s-%s-%s".formatted(query.toLowerCase(), part.name(), overlays, size, extension); // The id of the skin part
|
||||
|
||||
// In production, check the cache for the
|
||||
// skin part and return it if it's present
|
||||
if (EnvironmentUtils.isProduction()) {
|
||||
Optional<CachedSkinPartTexture> cached = skinPartTextureCache.findById(id);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found skin part {} in cache: {}", part.name(), id);
|
||||
return cached.get().getTexture();
|
||||
}
|
||||
}
|
||||
|
||||
Skin skin = null; // The target skin to get the skin part of
|
||||
long before = System.currentTimeMillis();
|
||||
try {
|
||||
CachedPlayer player = getPlayer(query, false); // Retrieve the player
|
||||
skin = player.getSkin(); // Use the player's skin
|
||||
} catch (Exception ignored) {
|
||||
// Simply ignore, and fallback to the default skin
|
||||
}
|
||||
if (skin == null) { // Fallback to the default skin
|
||||
skin = Skin.DEFAULT_STEVE;
|
||||
log.warn("Failed to get skin for player {}, defaulting to Steve", query);
|
||||
} else {
|
||||
log.info("Got skin for player {} in {}ms", query, System.currentTimeMillis() - before);
|
||||
}
|
||||
before = System.currentTimeMillis();
|
||||
BufferedImage texture = part.render(skin, overlays, size); // Render the skin part
|
||||
log.info("Render of skin part took {}ms: {}", System.currentTimeMillis() - before, id);
|
||||
|
||||
byte[] bytes = ImageUtils.toByteArray(texture); // Convert the image into a byte array
|
||||
skinPartTextureCache.save(new CachedSkinPartTexture(id, bytes)); // Cache the texture
|
||||
log.info("Cached skin part texture: {}", id);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a player by their username or UUID.
|
||||
* <p>
|
||||
* If the player is present within the cache, that will
|
||||
* be returned. If the player is not cached, a request
|
||||
* will be made to retrieve the player from Mojang, cache it
|
||||
* and then return the response.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to search for the player by
|
||||
* @param signed whether the profile is signed
|
||||
* @return the player
|
||||
* @throws BadRequestException if the UUID or username is invalid
|
||||
* @throws ResourceNotFoundException if the player is not found
|
||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
||||
*/
|
||||
@NonNull
|
||||
public CachedPlayer getPlayer(@NonNull String query, boolean signed) throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
|
||||
log.info("Requesting player with query: {}", query);
|
||||
|
||||
UUID uuid; // The player UUID to lookup
|
||||
boolean isFullUuid = query.length() == 36; // Was a UUID provided?
|
||||
if (query.length() == 32 || isFullUuid) { // Parse the query as a UUID
|
||||
try {
|
||||
uuid = isFullUuid ? UUID.fromString(query) : UUIDUtils.addDashes(query);
|
||||
log.info("Parsed {}UUID: {} -> {}", isFullUuid ? "" : "trimmed ", query, uuid);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new BadRequestException("Malformed UUID provided: %s".formatted(query));
|
||||
}
|
||||
} else { // The query is a username, request from Mojang
|
||||
if (!MiscUtils.isUsernameValid(query)) { // Ensure the username is valid
|
||||
throw new BadRequestException("Invalid username provided: %s".formatted(query));
|
||||
}
|
||||
uuid = usernameToUUID(query);
|
||||
log.info("Found UUID for username {}: {}", query, uuid);
|
||||
}
|
||||
String cacheId = "%s-%s".formatted(uuid, signed); // The cache id of the player
|
||||
|
||||
// Check the cache for the player
|
||||
// and return it if it's present
|
||||
Optional<CachedPlayer> cached = playerCache.findById(cacheId);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found player in cache: {}", uuid);
|
||||
return cached.get();
|
||||
}
|
||||
|
||||
// Send a request to Mojang requesting
|
||||
// the player profile by their UUID
|
||||
try {
|
||||
log.info("Retrieving player profile for UUID: {}", uuid);
|
||||
String endpoint = UUID_TO_PROFILE.formatted(uuid) + (signed ? "?unsigned=false" : "");
|
||||
MojangProfileToken token = JsonWebRequest.makeRequest(endpoint, HttpMethod.GET).execute(MojangProfileToken.class);
|
||||
MojangProfileToken.SkinProperties skinProperties = token.getSkinProperties(); // Get the skin and cape
|
||||
ProfileAction[] profileActions = token.getProfileActions();
|
||||
|
||||
// Build our player model, cache it, and then return it
|
||||
CachedPlayer player = new CachedPlayer(cacheId, uuid, token.getName(),
|
||||
skinProperties.getSkin() == null ? Skin.DEFAULT_STEVE : skinProperties.getSkin(),
|
||||
skinProperties.getCape(), token.getProperties(), profileActions.length == 0 ? null : profileActions,
|
||||
token.isLegacy(), System.currentTimeMillis()
|
||||
);
|
||||
// Store in the cache
|
||||
playerCache.save(player);
|
||||
log.info("Cached player: {}", uuid);
|
||||
|
||||
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
|
||||
return player;
|
||||
} catch (JsonWebException ex) {
|
||||
// No profile found, return null
|
||||
if (ex.getStatusCode() == 400) {
|
||||
throw new ResourceNotFoundException("Player not found with query: %s".formatted(query));
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the favicon of a Java
|
||||
* server with the given hostname.
|
||||
* <p>
|
||||
* If the favicon of the server cannot be
|
||||
* retrieved, the default icon will be used.
|
||||
* </p>
|
||||
*
|
||||
* @param hostname the hostname of the server
|
||||
* @return the server favicon
|
||||
* @see #DEFAULT_SERVER_ICON for the default server icon
|
||||
*/
|
||||
public byte[] getServerFavicon(@NonNull String hostname) {
|
||||
String icon = null; // The server base64 icon
|
||||
try {
|
||||
JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getMinecraftServer(MinecraftServer.Platform.JAVA.name(), hostname).getValue()).getFavicon();
|
||||
if (favicon != null) { // Use the server's favicon
|
||||
icon = favicon.getBase64();
|
||||
icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon
|
||||
}
|
||||
} catch (BadRequestException | ResourceNotFoundException ignored) {
|
||||
// Safely ignore these, we will use the 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the server with the
|
||||
* given hostname is blocked by Mojang.
|
||||
*
|
||||
* @param hostname the server hostname to check
|
||||
* @return whether the hostname is blocked
|
||||
*/
|
||||
public boolean isServerBlocked(@NonNull String hostname) {
|
||||
// Remove trailing dots
|
||||
while (hostname.charAt(hostname.length() - 1) == '.') {
|
||||
hostname = hostname.substring(0, hostname.length() - 1);
|
||||
}
|
||||
// Is the hostname banned?
|
||||
if (isServerHostnameBlocked(hostname)) {
|
||||
return true;
|
||||
}
|
||||
List<String> splitDots = Lists.newArrayList(DOT_SPLITTER.split(hostname)); // Split the hostname by dots
|
||||
boolean isIp = splitDots.size() == 4; // Is it an IP address?
|
||||
if (isIp) {
|
||||
for (String element : splitDots) {
|
||||
try {
|
||||
int part = Integer.parseInt(element);
|
||||
if (part >= 0 && part <= 255) { // Ensure the part is within the valid range
|
||||
continue;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Safely ignore, not a number
|
||||
}
|
||||
isIp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the hostname is blocked
|
||||
if (!isIp && isServerHostnameBlocked("*." + hostname)) {
|
||||
return true;
|
||||
}
|
||||
// Additional checks for the hostname
|
||||
while (splitDots.size() > 1) {
|
||||
splitDots.remove(isIp ? splitDots.size() - 1 : 0);
|
||||
String starredPart = isIp ? DOT_JOINER.join(splitDots) + ".*" : "*." + DOT_JOINER.join(splitDots);
|
||||
if (isServerHostnameBlocked(starredPart)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a Minecraft server on the given
|
||||
* platform with the given hostname.
|
||||
*
|
||||
* @param platformName the name of the platform
|
||||
* @param hostname the hostname of the server
|
||||
* @return the resolved Minecraft server
|
||||
* @throws BadRequestException if the hostname, platform, or port is invalid
|
||||
* @throws ResourceNotFoundException if the server isn't found
|
||||
*/
|
||||
@NonNull
|
||||
public CachedMinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname)
|
||||
throws BadRequestException, ResourceNotFoundException {
|
||||
MinecraftServer.Platform platform = EnumUtils.getEnumConstant(MinecraftServer.Platform.class, platformName.toUpperCase());
|
||||
if (platform == null) { // Invalid platform
|
||||
throw new BadRequestException("Invalid platform: %s".formatted(platformName));
|
||||
}
|
||||
String lookupHostname = hostname; // The hostname used to lookup the server
|
||||
|
||||
int port = platform.getDefaultPort(); // Port to ping
|
||||
if (hostname.contains(":")) { // Hostname contains a port
|
||||
String[] split = hostname.split(":");
|
||||
hostname = split[0];
|
||||
try { // Try and parse the port
|
||||
port = Integer.parseInt(split[1]);
|
||||
} catch (NumberFormatException ex) { // Invalid port
|
||||
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(cacheKey);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found server in cache: {}", hostname);
|
||||
return cached.get();
|
||||
}
|
||||
List<DNSRecord> records = new ArrayList<>(); // The resolved DNS records for the server
|
||||
|
||||
SRVRecord srvRecord = platform == MinecraftServer.Platform.JAVA ? DNSUtils.resolveSRV(hostname) : null; // Resolve the SRV record
|
||||
if (srvRecord != null) { // SRV was resolved, use the hostname and port
|
||||
records.add(srvRecord); // Going to need this for later
|
||||
InetSocketAddress socketAddress = srvRecord.getSocketAddress();
|
||||
hostname = socketAddress.getHostName();
|
||||
port = socketAddress.getPort();
|
||||
}
|
||||
|
||||
ARecord aRecord = DNSUtils.resolveA(hostname); // Resolve the A record so we can get the IPv4 address
|
||||
String ip = aRecord == null ? null : aRecord.getAddress(); // Get the IP address
|
||||
if (ip != null) { // Was the IP resolved?
|
||||
records.add(aRecord); // Going to need this for later
|
||||
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(
|
||||
cacheKey, response, System.currentTimeMillis()
|
||||
);
|
||||
|
||||
// Get the blocked status of the Java server
|
||||
if (platform == MinecraftServer.Platform.JAVA) {
|
||||
((JavaMinecraftServer) minecraftServer.getValue()).setMojangBanned(isServerBlocked(hostname));
|
||||
}
|
||||
|
||||
minecraftServerCache.save(minecraftServer);
|
||||
log.info("Cached server: {}", hostname);
|
||||
minecraftServer.setCached(-1L); // Set to -1 to indicate it's not cached in the response
|
||||
return minecraftServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UUID of a player by their username.
|
||||
*
|
||||
* @param username the player's username
|
||||
* @return the player's UUID
|
||||
* @throws ResourceNotFoundException if the player isn't found
|
||||
* @throws MojangRateLimitException if the Mojang rate limit is reached
|
||||
*/
|
||||
@NonNull
|
||||
private UUID usernameToUUID(@NonNull String username) throws ResourceNotFoundException, MojangRateLimitException {
|
||||
String originalUsername = username;
|
||||
username = username.toLowerCase(); // Lowercase the username
|
||||
|
||||
// Check the cache for the player's UUID
|
||||
Optional<CachedPlayerName> cached = playerNameCache.findById(username);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found UUID in cache for username {}: {}", originalUsername, cached.get().getUniqueId());
|
||||
return cached.get().getUniqueId();
|
||||
}
|
||||
|
||||
// Make a request to Mojang requesting the UUID
|
||||
try {
|
||||
MojangUsernameToUUIDToken token = JsonWebRequest.makeRequest(
|
||||
USERNAME_TO_UUID.formatted(username), HttpMethod.GET
|
||||
).execute(MojangUsernameToUUIDToken.class);
|
||||
|
||||
// Cache the UUID and return it
|
||||
UUID uuid = UUIDUtils.addDashes(token.getId());
|
||||
playerNameCache.save(new CachedPlayerName(username, uuid));
|
||||
log.info("Cached UUID for username {}: {}", username, uuid);
|
||||
return uuid;
|
||||
} catch (JsonWebException ex) {
|
||||
if (ex.getStatusCode() == 429) { // Mojang rate limit reached
|
||||
throw new MojangRateLimitException();
|
||||
} else if (ex.getStatusCode() == 404) { // Player not found
|
||||
throw new ResourceNotFoundException("Player not found with username: %s".formatted(originalUsername));
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the statuses of {@link MojangServer}'s.
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void fetchMojangServerStatuses() {
|
||||
log.info("Checking Mojang server statuses...");
|
||||
Arrays.stream(MojangServer.values()).parallel().forEach(server -> {
|
||||
log.info("Pinging {}...", server.getEndpoint());
|
||||
MojangServer.Status status = server.getStatus(); // Retrieve the server status
|
||||
log.info("Retrieved status of {}: {}", server.getEndpoint(), status.name());
|
||||
mojangServerStatuses.put(server, status); // Cache the server status
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a list of blocked servers from Mojang.
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void fetchBlockedServers() {
|
||||
try (
|
||||
InputStream inputStream = new URL(FETCH_BLOCKED_SERVERS).openStream();
|
||||
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8).useDelimiter("\n");
|
||||
) {
|
||||
List<String> hashes = new ArrayList<>();
|
||||
while (scanner.hasNext()) {
|
||||
hashes.add(scanner.next());
|
||||
}
|
||||
bannedServerHashes = Collections.synchronizedList(hashes);
|
||||
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the hash for the given
|
||||
* hostname is in the blocked server list.
|
||||
*
|
||||
* @param hostname the hostname to check
|
||||
* @return whether the hostname is blocked
|
||||
*/
|
||||
private boolean isServerHostnameBlocked(@NonNull String hostname) {
|
||||
// Check the cache first for the hostname
|
||||
if (blockedServersCache.contains(hostname)) {
|
||||
return true;
|
||||
}
|
||||
String hashed = Hashing.sha1().hashBytes(hostname.toLowerCase().getBytes(StandardCharsets.ISO_8859_1)).toString();
|
||||
boolean blocked = bannedServerHashes != null && (bannedServerHashes.contains(hashed)); // Is the hostname blocked?
|
||||
if (blocked) { // Cache the blocked hostname
|
||||
blockedServersCache.add(hostname);
|
||||
}
|
||||
return blocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup when the app is destroyed.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
mojangServerStatuses.clear();
|
||||
bannedServerHashes.clear();
|
||||
blockedServersCache.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cc.restfulmc.api.model.MinecraftServer;
|
||||
import cc.restfulmc.api.model.dns.DNSRecord;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* A {@link MinecraftServerPinger} is
|
||||
* used to ping a {@link MinecraftServer}.
|
||||
*
|
||||
* @param <T> the type of server to ping
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface MinecraftServerPinger<T extends MinecraftServer> {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
T ping(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records);
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.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 java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* The {@link MinecraftServerPinger} for pinging
|
||||
* {@link BedrockMinecraftServer} over UDP.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Log4j2(topic = "Bedrock MC Server Pinger")
|
||||
public final class BedrockMinecraftServerPinger implements MinecraftServerPinger<BedrockMinecraftServer> {
|
||||
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 BedrockMinecraftServer ping(@NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records) {
|
||||
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(TIMEOUT);
|
||||
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);
|
||||
|
||||
// Send the unconnected ping packet
|
||||
new BedrockUnconnectedPingPacket().process(socket);
|
||||
|
||||
// Handle the received unconnected pong packet
|
||||
BedrockUnconnectedPongPacket unconnectedPong = new BedrockUnconnectedPongPacket();
|
||||
unconnectedPong.process(socket);
|
||||
String response = unconnectedPong.getResponse();
|
||||
if (response == null) { // No pong response
|
||||
throw new ResourceNotFoundException("Server didn't respond to ping");
|
||||
}
|
||||
return BedrockMinecraftServer.create(hostname, ip, port, records, response); // Return the server
|
||||
} catch (IOException ex) {
|
||||
if (ex instanceof UnknownHostException) {
|
||||
throw new BadRequestException("Unknown hostname: %s".formatted(hostname));
|
||||
} else if (ex instanceof SocketTimeoutException) {
|
||||
throw new ResourceNotFoundException(ex);
|
||||
}
|
||||
log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -3,24 +3,38 @@ server:
|
||||
address: 0.0.0.0
|
||||
port: 7500
|
||||
publicUrl: "http://localhost:7500" # The publicly accessible URL for this app
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
# Log Configuration
|
||||
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:
|
||||
# Redis - This is used for caching
|
||||
redis:
|
||||
host: "127.0.0.1"
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
database: 0
|
||||
auth: "" # Leave blank for no auth
|
||||
|
||||
# Don't serialize null values by default with Jackson
|
||||
jackson:
|
||||
default-property-inclusion: non_null
|
||||
|
||||
# Ignore
|
||||
application:
|
||||
name: "RESTfulMC"
|
||||
banner:
|
||||
location: "classpath:banner.txt"
|
BIN
API/src/main/resources/public/favicon.ico
Normal file
BIN
API/src/main/resources/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
@ -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>
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.test.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Test configuration for
|
||||
* a mock Redis server.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@TestConfiguration
|
||||
public class TestRedisConfig {
|
||||
@NonNull private final RedisServer server;
|
||||
|
||||
public TestRedisConfig() throws IOException {
|
||||
server = new RedisServer(); // Construct the mock server
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up the mock Redis server.
|
||||
*
|
||||
* @throws IOException if there was an issue starting the server
|
||||
*/
|
||||
@PostConstruct
|
||||
public void onInitialize() throws IOException {
|
||||
server.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the running mock Redis server.
|
||||
*
|
||||
* @throws IOException if there was an issue stopping the server
|
||||
*/
|
||||
@PreDestroy
|
||||
public void housekeeping() throws IOException {
|
||||
server.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.MojangController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for the {@link MojangController}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@SpringBootTest(classes = TestRedisConfig.class)
|
||||
@AutoConfigureMockMvc
|
||||
public final class MojangControllerTests {
|
||||
/**
|
||||
* The {@link MockMvc} instance to use for testing.
|
||||
*/
|
||||
@NonNull private final MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
public MojangControllerTests(@NonNull MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* the status of Mojang servers is
|
||||
* successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureServerStatusCheckSuccess() throws Exception {
|
||||
mockMvc.perform(get("/mojang/status")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andReturn();
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.PlayerController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for the {@link PlayerController}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@SpringBootTest(classes = TestRedisConfig.class)
|
||||
@AutoConfigureMockMvc
|
||||
public final class PlayerControllerTests {
|
||||
/**
|
||||
* The {@link MockMvc} instance to use for testing.
|
||||
*/
|
||||
@NonNull private final MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
public PlayerControllerTests(@NonNull MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* a player's data is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensurePlayerLookupSuccess() throws Exception {
|
||||
mockMvc.perform(get("/player/g")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andExpect(jsonPath("$.username").value("g")) // Expect the player's username
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* invalid player's results in a 404.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensurePlayerNotFound() throws Exception {
|
||||
mockMvc.perform(get("/player/SDFSDFSDFSDFDDDG")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isNotFound()) // Expect 404 (Not Found)
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* player's with invalid usernames
|
||||
* results in a 400.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureUsernameIsInvalid() throws Exception {
|
||||
mockMvc.perform(get("/player/A")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* a player's head texture is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureSkinPartTextureSuccess() throws Exception {
|
||||
mockMvc.perform(get("/player/head/Rainnny.png")
|
||||
.contentType(MediaType.IMAGE_PNG) // Content type is PNG
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andReturn();
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.test.controller;
|
||||
|
||||
import cc.restfulmc.api.controller.ServerController;
|
||||
import cc.restfulmc.api.test.config.TestRedisConfig;
|
||||
import lombok.NonNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for the {@link ServerController}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@SpringBootTest(classes = TestRedisConfig.class)
|
||||
@AutoConfigureMockMvc
|
||||
public final class ServerControllerTests {
|
||||
/**
|
||||
* The {@link MockMvc} instance to use for testing.
|
||||
*/
|
||||
@NonNull private final MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
public ServerControllerTests(@NonNull MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* a Java server's status is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureJavaServerLookupSuccess() throws Exception {
|
||||
mockMvc.perform(get("/server/java/hypixel.net")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andExpect(jsonPath("$.hostname") // Expect the server's resolved hostname
|
||||
.value("mc.hypixel.net")
|
||||
).andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* a Bedrock server's status is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureBedrockServerLookupSuccess() throws Exception {
|
||||
mockMvc.perform(get("/server/bedrock/wildprison.bedrock.minehut.gg")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andExpect(jsonPath("$.hostname") // Expect the server's resolved hostname
|
||||
.value("wildprison.bedrock.minehut.gg")
|
||||
).andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure that requesting
|
||||
* information about a server on an invalid
|
||||
* platform results in a 400.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureUnknownPlatform() throws Exception {
|
||||
mockMvc.perform(get("/server/invalid/hypixel.net")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure looking up
|
||||
* an invalid hostname results in a 400.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureUnknownHostname() throws Exception {
|
||||
mockMvc.perform(get("/server/java/invalid")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure looking up
|
||||
* an invalid port results in a 400.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureUnknownPort() throws Exception {
|
||||
mockMvc.perform(get("/server/java/hypixel.net:A")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure checking if
|
||||
* a server is banned is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureServerBanCheckSuccess() throws Exception{
|
||||
mockMvc.perform(get("/server/blocked/arkhamnetwork.org")
|
||||
.accept(MediaType.APPLICATION_JSON) // Accept JSON
|
||||
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andExpect(jsonPath("$.blocked").value(true)) // Expect block
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test to ensure retrieving
|
||||
* a server's favicon is successful.
|
||||
*
|
||||
* @throws Exception if the test fails
|
||||
*/
|
||||
@Test
|
||||
void ensureServerFaviconSuccess() throws Exception {
|
||||
mockMvc.perform(get("/server/icon/hypixel.net")
|
||||
.contentType(MediaType.IMAGE_PNG) // Content type is PNG
|
||||
).andExpect(status().isOk()) // Expect 200 (OK)
|
||||
.andReturn();
|
||||
}
|
||||
}
|
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();
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user